Merge "Fix typo in comment and logging (dream -> communal)." into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 9e30843..f5bf437 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -13,66 +13,143 @@
// limitations under the License.
aconfig_srcjars = [
- ":android.app.usage.flags-aconfig-java{.generated_srcjars}",
+ // !!! KEEP THIS LIST ALPHABETICAL !!!
+ ":aconfig_mediacodec_flags_java_lib{.generated_srcjars}",
+ ":android.adaptiveauth.flags-aconfig-java{.generated_srcjars}",
+ ":android.app.flags-aconfig-java{.generated_srcjars}",
":android.app.smartspace.flags-aconfig-java{.generated_srcjars}",
+ ":android.app.usage.flags-aconfig-java{.generated_srcjars}",
+ ":android.appwidget.flags-aconfig-java{.generated_srcjars}",
+ ":android.chre.flags-aconfig-java{.generated_srcjars}",
":android.companion.flags-aconfig-java{.generated_srcjars}",
+ ":android.companion.virtual.flags-aconfig-java{.generated_srcjars}",
+ ":android.content.flags-aconfig-java{.generated_srcjars}",
":android.content.pm.flags-aconfig-java{.generated_srcjars}",
":android.content.res.flags-aconfig-java{.generated_srcjars}",
+ ":android.credentials.flags-aconfig-java{.generated_srcjars}",
+ ":android.database.sqlite-aconfig-java{.generated_srcjars}",
+ ":android.hardware.biometrics.flags-aconfig-java{.generated_srcjars}",
":android.hardware.flags-aconfig-java{.generated_srcjars}",
":android.hardware.radio.flags-aconfig-java{.generated_srcjars}",
+ ":android.hardware.usb.flags-aconfig-java{.generated_srcjars}",
":android.location.flags-aconfig-java{.generated_srcjars}",
+ ":android.media.tv.flags-aconfig-java{.generated_srcjars}",
+ ":android.multiuser.flags-aconfig-java{.generated_srcjars}",
":android.net.vcn.flags-aconfig-java{.generated_srcjars}",
":android.nfc.flags-aconfig-java{.generated_srcjars}",
":android.os.flags-aconfig-java{.generated_srcjars}",
":android.os.vibrator.flags-aconfig-java{.generated_srcjars}",
+ ":android.permission.flags-aconfig-java{.generated_srcjars}",
+ ":android.provider.flags-aconfig-java{.generated_srcjars}",
":android.security.flags-aconfig-java{.generated_srcjars}",
":android.server.app.flags-aconfig-java{.generated_srcjars}",
+ ":android.service.autofill.flags-aconfig-java{.generated_srcjars}",
":android.service.chooser.flags-aconfig-java{.generated_srcjars}",
+ ":android.service.controls.flags-aconfig-java{.generated_srcjars}",
":android.service.dreams.flags-aconfig-java{.generated_srcjars}",
":android.service.notification.flags-aconfig-java{.generated_srcjars}",
- ":android.view.flags-aconfig-java{.generated_srcjars}",
+ ":android.service.voice.flags-aconfig-java{.generated_srcjars}",
+ ":android.speech.flags-aconfig-java{.generated_srcjars}",
+ ":android.tracing.flags-aconfig-java{.generated_srcjars}",
":android.view.accessibility.flags-aconfig-java{.generated_srcjars}",
+ ":android.view.contentcapture.flags-aconfig-java{.generated_srcjars}",
+ ":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}",
+ ":android.view.flags-aconfig-java{.generated_srcjars}",
+ ":android.view.inputmethod.flags-aconfig-java{.generated_srcjars}",
+ ":android.webkit.flags-aconfig-java{.generated_srcjars}",
+ ":android.widget.flags-aconfig-java{.generated_srcjars}",
":audio-framework-aconfig",
":camera_platform_flags_core_java_lib{.generated_srcjars}",
- ":com.android.window.flags.window-aconfig-java{.generated_srcjars}",
- ":android.hardware.biometrics.flags-aconfig-java{.generated_srcjars}",
":com.android.hardware.input-aconfig-java{.generated_srcjars}",
":com.android.input.flags-aconfig-java{.generated_srcjars}",
+ ":com.android.internal.foldables.flags-aconfig-java{.generated_srcjars}",
+ ":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}",
+ ":com.android.net.flags-aconfig-java{.generated_srcjars}",
+ ":com.android.server.flags.services-aconfig-java{.generated_srcjars}",
":com.android.text.flags-aconfig-java{.generated_srcjars}",
+ ":com.android.window.flags.window-aconfig-java{.generated_srcjars}",
+ ":device_policy_aconfig_flags_lib{.generated_srcjars}",
+ ":display_flags_lib{.generated_srcjars}",
":framework-jobscheduler-job.flags-aconfig-java{.generated_srcjars}",
+ ":framework_graphics_flags_java_lib{.generated_srcjars}",
+ ":hwui_flags_java_lib{.generated_srcjars}",
+ ":power_flags_lib{.generated_srcjars}",
+ ":sdk_sandbox_flags_lib{.generated_srcjars}",
+ ":surfaceflinger_flags_java_lib{.generated_srcjars}",
":telecom_flags_core_java_lib{.generated_srcjars}",
":telephony_flags_core_java_lib{.generated_srcjars}",
- ":android.companion.virtual.flags-aconfig-java{.generated_srcjars}",
- ":android.view.inputmethod.flags-aconfig-java{.generated_srcjars}",
- ":android.widget.flags-aconfig-java{.generated_srcjars}",
- ":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}",
- ":sdk_sandbox_flags_lib{.generated_srcjars}",
- ":android.permission.flags-aconfig-java{.generated_srcjars}",
- ":android.database.sqlite-aconfig-java{.generated_srcjars}",
- ":hwui_flags_java_lib{.generated_srcjars}",
- ":framework_graphics_flags_java_lib{.generated_srcjars}",
- ":display_flags_lib{.generated_srcjars}",
- ":com.android.internal.foldables.flags-aconfig-java{.generated_srcjars}",
- ":android.multiuser.flags-aconfig-java{.generated_srcjars}",
- ":android.app.flags-aconfig-java{.generated_srcjars}",
- ":android.credentials.flags-aconfig-java{.generated_srcjars}",
- ":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}",
- ":com.android.server.flags.services-aconfig-java{.generated_srcjars}",
- ":android.service.controls.flags-aconfig-java{.generated_srcjars}",
- ":android.service.voice.flags-aconfig-java{.generated_srcjars}",
- ":android.media.tv.flags-aconfig-java{.generated_srcjars}",
- ":android.service.autofill.flags-aconfig-java{.generated_srcjars}",
- ":com.android.net.flags-aconfig-java{.generated_srcjars}",
- ":device_policy_aconfig_flags_lib{.generated_srcjars}",
- ":surfaceflinger_flags_java_lib{.generated_srcjars}",
- ":android.view.contentcapture.flags-aconfig-java{.generated_srcjars}",
- ":android.hardware.usb.flags-aconfig-java{.generated_srcjars}",
- ":android.tracing.flags-aconfig-java{.generated_srcjars}",
- ":android.appwidget.flags-aconfig-java{.generated_srcjars}",
- ":android.webkit.flags-aconfig-java{.generated_srcjars}",
- ":android.provider.flags-aconfig-java{.generated_srcjars}",
+ // !!! KEEP THIS LIST ALPHABETICAL !!!
]
+stubs_defaults {
+ name: "framework-minus-apex-aconfig-declarations",
+ aconfig_declarations: [
+ "android.app.flags-aconfig",
+ "android.app.smartspace.flags-aconfig",
+ "android.app.usage.flags-aconfig",
+ "android.appwidget.flags-aconfig",
+ "android.companion.flags-aconfig",
+ "android.companion.virtual.flags-aconfig",
+ "android.content.pm.flags-aconfig",
+ "android.content.res.flags-aconfig",
+ "android.credentials.flags-aconfig",
+ "android.database.sqlite-aconfig",
+ "android.hardware.biometrics.flags-aconfig",
+ "android.hardware.flags-aconfig",
+ "android.hardware.radio.flags-aconfig",
+ "android.hardware.usb.flags-aconfig",
+ "android.location.flags-aconfig",
+ "android.media.audio-aconfig",
+ "android.media.audiopolicy-aconfig",
+ "android.media.midi-aconfig",
+ "android.media.tv.flags-aconfig",
+ "android.multiuser.flags-aconfig",
+ "android.net.vcn.flags-aconfig",
+ "android.nfc.flags-aconfig",
+ "android.os.flags-aconfig",
+ "android.os.vibrator.flags-aconfig",
+ "android.permission.flags-aconfig",
+ "android.provider.flags-aconfig",
+ "android.security.flags-aconfig",
+ "android.server.app.flags-aconfig",
+ "android.service.autofill.flags-aconfig",
+ "android.service.chooser.flags-aconfig",
+ "android.service.controls.flags-aconfig",
+ "android.service.dreams.flags-aconfig",
+ "android.service.notification.flags-aconfig",
+ "android.service.voice.flags-aconfig",
+ "android.speech.flags-aconfig",
+ "android.tracing.flags-aconfig",
+ "android.view.accessibility.flags-aconfig",
+ "android.view.contentcapture.flags-aconfig",
+ "android.view.contentprotection.flags-aconfig",
+ "android.view.flags-aconfig",
+ "android.view.inputmethod.flags-aconfig",
+ "android.webkit.flags-aconfig",
+ "android.widget.flags-aconfig",
+ "camera_platform_flags",
+ "chre_flags",
+ "com.android.hardware.input.input-aconfig",
+ "com.android.input.flags-aconfig",
+ "com.android.media.flags.bettertogether-aconfig",
+ "com.android.net.flags-aconfig",
+ "com.android.server.flags.services-aconfig",
+ "com.android.text.flags-aconfig",
+ "com.android.window.flags.window-aconfig",
+ "device_policy_aconfig_flags",
+ "display_flags",
+ "fold_lock_setting_flags",
+ "framework-jobscheduler-job.flags-aconfig",
+ "framework_graphics_flags",
+ "hwui_flags",
+ "power_flags",
+ "sdk_sandbox_flags",
+ "surfaceflinger_flags",
+ "telecom_flags",
+ "telephony_flags",
+ ],
+}
+
filegroup {
name: "framework-minus-apex-aconfig-srcjars",
srcs: aconfig_srcjars,
@@ -196,7 +273,7 @@
aconfig_declarations {
name: "android.nfc.flags-aconfig",
package: "android.nfc",
- srcs: ["core/java/android/nfc/*.aconfig"],
+ srcs: ["nfc/java/android/nfc/*.aconfig"],
}
cc_aconfig_library {
@@ -214,7 +291,7 @@
java_aconfig_library {
name: "android.nfc.flags-aconfig-java",
aconfig_declarations: "android.nfc.flags-aconfig",
- min_sdk_version: "VanillaIceCream",
+ min_sdk_version: "34",
apex_available: [
"//apex_available:platform",
"com.android.nfcservices",
@@ -452,6 +529,13 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+java_aconfig_library {
+ name: "com.android.media.flags.bettertogether-aconfig-java-host",
+ aconfig_declarations: "com.android.media.flags.bettertogether-aconfig",
+ host_supported: true,
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Media TV
aconfig_declarations {
name: "android.media.tv.flags-aconfig",
@@ -480,8 +564,8 @@
apex_available: [
"//apex_available:platform",
"com.android.permission",
+ "com.android.nfcservices",
],
-
}
// SQLite
@@ -743,6 +827,11 @@
java_aconfig_library {
name: "android.service.chooser.flags-aconfig-java",
aconfig_declarations: "android.service.chooser.flags-aconfig",
+ min_sdk_version: "34",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.nfcservices",
+ ],
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
@@ -905,3 +994,56 @@
aconfig_declarations: "android.provider.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+
+// ContextHub
+java_aconfig_library {
+ name: "android.chre.flags-aconfig-java",
+ aconfig_declarations: "chre_flags",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+// Speech
+aconfig_declarations {
+ name: "android.speech.flags-aconfig",
+ package: "android.speech.flags",
+ srcs: ["core/java/android/speech/flags/*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.speech.flags-aconfig-java",
+ aconfig_declarations: "android.speech.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+// Power
+java_aconfig_library {
+ name: "power_flags_lib",
+ aconfig_declarations: "power_flags",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+// Content
+aconfig_declarations {
+ name: "android.content.flags-aconfig",
+ package: "android.content.flags",
+ srcs: ["core/java/android/content/flags/flags.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.content.flags-aconfig-java",
+ aconfig_declarations: "android.content.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+// Adaptive Auth
+aconfig_declarations {
+ name: "android.adaptiveauth.flags-aconfig",
+ package: "android.adaptiveauth",
+ srcs: ["core/java/android/adaptiveauth/*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.adaptiveauth.flags-aconfig-java",
+ aconfig_declarations: "android.adaptiveauth.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/Android.bp b/Android.bp
index f3b2ebb..9c56733 100644
--- a/Android.bp
+++ b/Android.bp
@@ -149,6 +149,7 @@
":framework-javastream-protos",
":statslog-framework-java-gen", // FrameworkStatsLog.java
":audio_policy_configuration_V7_0",
+ ":perfetto_trace_javastream_protos",
],
}
@@ -175,9 +176,6 @@
// and remove this line.
"//frameworks/base/tools/hoststubgen:__subpackages__",
],
- lint: {
- baseline_filename: "lint-baseline.xml",
- },
}
// AIDL files under these paths are mixture of public and private ones.
@@ -218,7 +216,6 @@
"apex_aidl_interface-java",
"packagemanager_aidl-java",
"framework-protos",
- "libtombstone_proto_java",
"updatable-driver-protos",
"ota_metadata_proto_java",
"android.hidl.base-V1.0-java",
@@ -270,9 +267,6 @@
],
sdk_version: "core_platform",
installable: false,
- lint: {
- baseline_filename: "lint-baseline.xml",
- },
}
// NOTE: This filegroup is exposed for vendor libraries to depend on and is referenced in
@@ -437,13 +431,9 @@
name: "framework-non-updatable-unbundled-impl-libs",
static_libs: [
"framework-location.impl",
- "framework-nfc.impl",
],
sdk_version: "core_platform",
installable: false,
- lint: {
- baseline_filename: "lint-baseline.xml",
- },
}
// Separated so framework-minus-apex-defaults can be used without the libs dependency
@@ -487,9 +477,6 @@
],
compile_dex: false,
headers_only: true,
- lint: {
- baseline_filename: "lint-baseline.xml",
- },
}
java_library {
@@ -534,7 +521,7 @@
},
lint: {
enabled: false,
- baseline_filename: "lint-baseline.xml",
+
},
}
@@ -559,9 +546,6 @@
],
sdk_version: "core_platform",
apex_available: ["//apex_available:platform"],
- lint: {
- baseline_filename: "lint-baseline.xml",
- },
}
java_library {
@@ -577,9 +561,6 @@
"calendar-provider-compat-config",
"contacts-provider-platform-compat-config",
],
- lint: {
- baseline_filename: "lint-baseline.xml",
- },
}
platform_compat_config {
@@ -634,9 +615,6 @@
"rappor",
],
dxflags: ["--core-library"],
- lint: {
- baseline_filename: "lint-baseline.xml",
- },
}
// utility classes statically linked into framework-wifi and dynamically linked
diff --git a/ProtoLibraries.bp b/ProtoLibraries.bp
index d03bbd2..e7adf20 100644
--- a/ProtoLibraries.bp
+++ b/ProtoLibraries.bp
@@ -34,6 +34,7 @@
":ipconnectivity-proto-src",
":libstats_atom_enum_protos",
":libstats_atom_message_protos",
+ ":libtombstone_proto-src",
"core/proto/**/*.proto",
"libs/incident/**/*.proto",
],
diff --git a/Ravenwood.bp b/Ravenwood.bp
index f330ad1..0877bce 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -75,16 +75,36 @@
],
}
+java_library {
+ name: "mockito-ravenwood-prebuilt",
+ installable: false,
+ static_libs: [
+ "mockito-robolectric-prebuilt",
+ ],
+}
+
+java_library {
+ name: "inline-mockito-ravenwood-prebuilt",
+ installable: false,
+ static_libs: [
+ "inline-mockito-robolectric-prebuilt",
+ ],
+}
+
android_ravenwood_libgroup {
name: "ravenwood-runtime",
libs: [
"framework-minus-apex.ravenwood",
"hoststubgen-helper-runtime.ravenwood",
"hoststubgen-helper-framework-runtime.ravenwood",
+ "core-libart-for-host",
+ "all-updatable-modules-system-stubs",
"junit",
"truth",
"ravenwood-junit-impl",
"android.test.mock.ravenwood",
+ "mockito-ravenwood-prebuilt",
+ "inline-mockito-ravenwood-prebuilt",
],
}
@@ -94,5 +114,7 @@
"junit",
"truth",
"ravenwood-junit",
+ "mockito-ravenwood-prebuilt",
+ "inline-mockito-ravenwood-prebuilt",
],
}
diff --git a/STABILITY_OWNERS b/STABILITY_OWNERS
deleted file mode 100644
index a7ecb4d..0000000
--- a/STABILITY_OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
[email protected]
-
diff --git a/TEST_MAPPING b/TEST_MAPPING
index d59775f..c904eb4 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -138,15 +138,13 @@
}
],
"postsubmit-ravenwood": [
- // TODO(ravenwood) promote it to presubmit
- // TODO: Enable it once the infra knows how to run it.
-// {
-// "name": "CtsUtilTestCasesRavenwood",
-// "file_patterns": [
-// "*Ravenwood*",
-// "*ravenwood*"
-// ]
-// }
+ {
+ "name": "CtsUtilTestCasesRavenwood",
+ "host": true,
+ "file_patterns": [
+ "[Rr]avenwood"
+ ]
+ }
],
"postsubmit-managedprofile-stress": [
{
diff --git a/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java b/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java
index caf7e7f..1fc888b 100644
--- a/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java
@@ -16,6 +16,7 @@
package com.android.server;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.PowerExemptionManager;
import android.os.PowerExemptionManager.ReasonCode;
@@ -77,6 +78,9 @@
int[] getPowerSaveTempWhitelistAppIds();
+ @NonNull
+ String[] getFullPowerWhitelistExceptIdle();
+
/**
* Listener to be notified when DeviceIdleController determines that the device has moved or is
* stationary.
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 6383ed8..31214cb 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -2374,6 +2374,11 @@
return DeviceIdleController.this.isAppOnWhitelistInternal(appid);
}
+ @Override
+ public String[] getFullPowerWhitelistExceptIdle() {
+ return DeviceIdleController.this.getFullPowerWhitelistInternalUnchecked();
+ }
+
/**
* Returns the array of app ids whitelisted by user. Take care not to
* modify this, as it is a reference to the original copy. But the reference
@@ -3100,10 +3105,14 @@
}
private String[] getFullPowerWhitelistInternal(final int callingUid, final int callingUserId) {
- final String[] apps;
+ return ArrayUtils.filter(getFullPowerWhitelistInternalUnchecked(), String[]::new,
+ (pkg) -> !mPackageManagerInternal.filterAppAccess(pkg, callingUid, callingUserId));
+ }
+
+ private String[] getFullPowerWhitelistInternalUnchecked() {
synchronized (this) {
int size = mPowerSaveWhitelistApps.size() + mPowerSaveWhitelistUserApps.size();
- apps = new String[size];
+ final String[] apps = new String[size];
int cur = 0;
for (int i = 0; i < mPowerSaveWhitelistApps.size(); i++) {
apps[cur] = mPowerSaveWhitelistApps.keyAt(i);
@@ -3113,9 +3122,8 @@
apps[cur] = mPowerSaveWhitelistUserApps.keyAt(i);
cur++;
}
+ return apps;
}
- return ArrayUtils.filter(apps, String[]::new,
- (pkg) -> !mPackageManagerInternal.filterAppAccess(pkg, callingUid, callingUserId));
}
public boolean isPowerSaveWhitelistExceptIdleAppInternal(String packageName) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
index e3ba50d..e3ac780 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -318,6 +318,10 @@
try {
final boolean isStopped = mPackageManagerInternal.isPackageStopped(packageName, uid);
+ if (DEBUG) {
+ Slog.d(TAG,
+ "Pulled stopped state of " + packageName + " (" + uid + "): " + isStopped);
+ }
mPackageStoppedState.add(uid, packageName, isStopped);
return isStopped;
} catch (PackageManager.NameNotFoundException e) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index 0e67b9a..44afbe6 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -30,11 +30,15 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.job.JobInfo;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.PowerManager;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.util.ArraySet;
@@ -48,6 +52,8 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.AppSchedulingModuleThread;
+import com.android.server.DeviceIdleInternal;
+import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerService;
import com.android.server.utils.AlarmQueue;
@@ -127,6 +133,19 @@
@GuardedBy("mLock")
private final SparseLongArray mLastSeenConstraintTimesElapsed = new SparseLongArray();
+ private DeviceIdleInternal mDeviceIdleInternal;
+ private final ArraySet<String> mPowerAllowlistedApps = new ArraySet<>();
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ switch (intent.getAction()) {
+ case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED:
+ mHandler.post(FlexibilityController.this::updatePowerAllowlistCache);
+ break;
+ }
+ }
+ };
@VisibleForTesting
@GuardedBy("mLock")
final FlexibilityTracker mFlexibilityTracker;
@@ -180,8 +199,16 @@
}
};
- private static final int MSG_UPDATE_JOBS = 0;
- private static final int MSG_UPDATE_JOB = 1;
+ private static final int MSG_CHECK_ALL_JOBS = 0;
+ /** Check the jobs in {@link #mJobsToCheck} */
+ private static final int MSG_CHECK_JOBS = 1;
+ /** Check the jobs of packages in {@link #mPackagesToCheck} */
+ private static final int MSG_CHECK_PACKAGES = 2;
+
+ @GuardedBy("mLock")
+ private final ArraySet<JobStatus> mJobsToCheck = new ArraySet<>();
+ @GuardedBy("mLock")
+ private final ArraySet<String> mPackagesToCheck = new ArraySet<>();
public FlexibilityController(
JobSchedulerService service, PrefetchController prefetchController) {
@@ -204,6 +231,16 @@
mPercentToDropConstraints =
mFcConfig.DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
mPrefetchController = prefetchController;
+
+ if (mFlexibilityEnabled) {
+ registerBroadcastReceiver();
+ }
+ }
+
+ @Override
+ public void onSystemServicesReady() {
+ mDeviceIdleInternal = LocalServices.getService(DeviceIdleInternal.class);
+ mHandler.post(FlexibilityController.this::updatePowerAllowlistCache);
}
@Override
@@ -241,6 +278,7 @@
mFlexibilityAlarmQueue.removeAlarmForKey(js);
mFlexibilityTracker.remove(js);
}
+ mJobsToCheck.remove(js);
}
@Override
@@ -266,7 +304,14 @@
@GuardedBy("mLock")
boolean isFlexibilitySatisfiedLocked(JobStatus js) {
return !mFlexibilityEnabled
+ // Exclude all jobs of the TOP app
|| mService.getUidBias(js.getSourceUid()) == JobInfo.BIAS_TOP_APP
+ // Only exclude DEFAULT+ priority jobs for BFGS+ apps
+ || (mService.getUidBias(js.getSourceUid()) >= JobInfo.BIAS_BOUND_FOREGROUND_SERVICE
+ && js.getEffectivePriority() >= JobInfo.PRIORITY_DEFAULT)
+ // For apps in the power allowlist, automatically exclude DEFAULT+ priority jobs.
+ || (js.getEffectivePriority() >= JobInfo.PRIORITY_DEFAULT
+ && mPowerAllowlistedApps.contains(js.getSourcePackageName()))
|| hasEnoughSatisfiedConstraintsLocked(js)
|| mService.isCurrentlyRunningLocked(js);
}
@@ -371,7 +416,7 @@
// Push the job update to the handler to avoid blocking other controllers and
// potentially batch back-to-back controller state updates together.
- mHandler.obtainMessage(MSG_UPDATE_JOBS).sendToTarget();
+ mHandler.obtainMessage(MSG_CHECK_ALL_JOBS).sendToTarget();
}
}
}
@@ -485,7 +530,9 @@
@Override
@GuardedBy("mLock")
public void onUidBiasChangedLocked(int uid, int prevBias, int newBias) {
- if (prevBias != JobInfo.BIAS_TOP_APP && newBias != JobInfo.BIAS_TOP_APP) {
+ if (prevBias < JobInfo.BIAS_BOUND_FOREGROUND_SERVICE
+ && newBias < JobInfo.BIAS_BOUND_FOREGROUND_SERVICE) {
+ // All changes are below BFGS. There's no significant change to care about.
return;
}
final long nowElapsed = sElapsedRealtimeClock.millis();
@@ -557,6 +604,39 @@
mFcConfig.processConstantLocked(properties, key);
}
+ private void registerBroadcastReceiver() {
+ IntentFilter filter = new IntentFilter(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
+ mContext.registerReceiver(mBroadcastReceiver, filter);
+ }
+
+ private void unregisterBroadcastReceiver() {
+ mContext.unregisterReceiver(mBroadcastReceiver);
+ }
+
+ private void updatePowerAllowlistCache() {
+ if (mDeviceIdleInternal == null) {
+ return;
+ }
+
+ // Don't call out to DeviceIdleController with the lock held.
+ final String[] allowlistedPkgs = mDeviceIdleInternal.getFullPowerWhitelistExceptIdle();
+ final ArraySet<String> changedPkgs = new ArraySet<>();
+ synchronized (mLock) {
+ changedPkgs.addAll(mPowerAllowlistedApps);
+ mPowerAllowlistedApps.clear();
+ for (final String pkgName : allowlistedPkgs) {
+ mPowerAllowlistedApps.add(pkgName);
+ if (changedPkgs.contains(pkgName)) {
+ changedPkgs.remove(pkgName);
+ } else {
+ changedPkgs.add(pkgName);
+ }
+ }
+ mPackagesToCheck.addAll(changedPkgs);
+ mHandler.sendEmptyMessage(MSG_CHECK_PACKAGES);
+ }
+ }
+
@VisibleForTesting
class FlexibilityTracker {
final ArrayList<ArraySet<JobStatus>> mTrackedJobs;
@@ -710,7 +790,8 @@
}
mFlexibilityTracker.setNumDroppedFlexibleConstraints(js,
js.getNumAppliedFlexibleConstraints());
- mHandler.obtainMessage(MSG_UPDATE_JOB, js).sendToTarget();
+ mJobsToCheck.add(js);
+ mHandler.sendEmptyMessage(MSG_CHECK_JOBS);
return;
}
if (nextTimeElapsed == NO_LIFECYCLE_END) {
@@ -761,10 +842,12 @@
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case MSG_UPDATE_JOBS:
- removeMessages(MSG_UPDATE_JOBS);
+ case MSG_CHECK_ALL_JOBS:
+ removeMessages(MSG_CHECK_ALL_JOBS);
synchronized (mLock) {
+ mJobsToCheck.clear();
+ mPackagesToCheck.clear();
final long nowElapsed = sElapsedRealtimeClock.millis();
final ArraySet<JobStatus> changedJobs = new ArraySet<>();
@@ -790,19 +873,50 @@
}
break;
- case MSG_UPDATE_JOB:
+ case MSG_CHECK_JOBS:
synchronized (mLock) {
- final JobStatus js = (JobStatus) msg.obj;
- if (DEBUG) {
- Slog.d("blah", "Checking on " + js.toShortString());
- }
final long nowElapsed = sElapsedRealtimeClock.millis();
- if (js.setFlexibilityConstraintSatisfied(
- nowElapsed, isFlexibilitySatisfiedLocked(js))) {
- // TODO(141645789): add method that will take a single job
- ArraySet<JobStatus> changedJob = new ArraySet<>();
- changedJob.add(js);
- mStateChangedListener.onControllerStateChanged(changedJob);
+ ArraySet<JobStatus> changedJobs = new ArraySet<>();
+
+ for (int i = mJobsToCheck.size() - 1; i >= 0; --i) {
+ final JobStatus js = mJobsToCheck.valueAt(i);
+ if (DEBUG) {
+ Slog.d(TAG, "Checking on " + js.toShortString());
+ }
+ if (js.setFlexibilityConstraintSatisfied(
+ nowElapsed, isFlexibilitySatisfiedLocked(js))) {
+ changedJobs.add(js);
+ }
+ }
+
+ mJobsToCheck.clear();
+ if (changedJobs.size() > 0) {
+ mStateChangedListener.onControllerStateChanged(changedJobs);
+ }
+ }
+ break;
+
+ case MSG_CHECK_PACKAGES:
+ synchronized (mLock) {
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+ final ArraySet<JobStatus> changedJobs = new ArraySet<>();
+
+ mService.getJobStore().forEachJob(
+ (js) -> mPackagesToCheck.contains(js.getSourcePackageName())
+ || mPackagesToCheck.contains(js.getCallingPackageName()),
+ (js) -> {
+ if (DEBUG) {
+ Slog.d(TAG, "Checking on " + js.toShortString());
+ }
+ if (js.setFlexibilityConstraintSatisfied(
+ nowElapsed, isFlexibilitySatisfiedLocked(js))) {
+ changedJobs.add(js);
+ }
+ });
+
+ mPackagesToCheck.clear();
+ if (changedJobs.size() > 0) {
+ mStateChangedListener.onControllerStateChanged(changedJobs);
}
}
break;
@@ -837,7 +951,7 @@
@VisibleForTesting
static final long DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS;
@VisibleForTesting
- static final long DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS = 72 * HOUR_IN_MILLIS;
+ static final long DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS = 24 * HOUR_IN_MILLIS;
private static final long DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS = MINUTE_IN_MILLIS;
@VisibleForTesting
final int[] DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS = {50, 60, 70, 80};
@@ -882,10 +996,12 @@
mFlexibilityEnabled = true;
mPrefetchController
.registerPrefetchChangedListener(mPrefetchChangedListener);
+ registerBroadcastReceiver();
} else {
mFlexibilityEnabled = false;
mPrefetchController
.unRegisterPrefetchChangedListener(mPrefetchChangedListener);
+ unregisterBroadcastReceiver();
}
}
break;
@@ -985,7 +1101,14 @@
pw.println(":");
pw.increaseIndent();
- pw.print(KEY_APPLIED_CONSTRAINTS, APPLIED_CONSTRAINTS).println();
+ pw.print(KEY_APPLIED_CONSTRAINTS, APPLIED_CONSTRAINTS);
+ pw.print("(");
+ if (APPLIED_CONSTRAINTS != 0) {
+ JobStatus.dumpConstraints(pw, APPLIED_CONSTRAINTS);
+ } else {
+ pw.print("nothing");
+ }
+ pw.println(")");
pw.print(KEY_DEADLINE_PROXIMITY_LIMIT, DEADLINE_PROXIMITY_LIMIT_MS).println();
pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINE, FALLBACK_FLEXIBILITY_DEADLINE_MS).println();
pw.print(KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS,
@@ -1044,6 +1167,10 @@
pw.decreaseIndent();
pw.println();
+ pw.print("Power allowlisted packages: ");
+ pw.println(mPowerAllowlistedApps);
+
+ pw.println();
mFlexibilityTracker.dump(pw, predicate);
pw.println();
mFlexibilityAlarmQueue.dump(pw);
diff --git a/api/Android.bp b/api/Android.bp
index 1686943..b3b18b6 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -60,8 +60,40 @@
metalava_cmd += " -J--add-opens=java.base/java.util=ALL-UNNAMED "
metalava_cmd += " --quiet "
+soong_config_module_type {
+ name: "enable_crashrecovery_module",
+ module_type: "combined_apis_defaults",
+ config_namespace: "ANDROID",
+ bool_variables: ["release_crashrecovery_module"],
+ properties: [
+ "bootclasspath",
+ "system_server_classpath",
+ ],
+}
+
+soong_config_bool_variable {
+ name: "release_crashrecovery_module",
+}
+
+enable_crashrecovery_module {
+ name: "crashrecovery_module_defaults",
+ soong_config_variables: {
+ release_crashrecovery_module: {
+ bootclasspath: [
+ "framework-crashrecovery",
+ ],
+ system_server_classpath: [
+ "service-crashrecovery",
+ ],
+ },
+ },
+}
+
combined_apis {
name: "frameworks-base-api",
+ defaults: [
+ "crashrecovery_module_defaults",
+ ],
bootclasspath: [
"android.net.ipsec.ike",
"art.module.public.api",
@@ -269,6 +301,7 @@
// classpath (or sources) somehow.
stubs_defaults {
name: "android-non-updatable-stubs-defaults",
+ defaults: ["framework-minus-apex-aconfig-declarations"],
srcs: [":android-non-updatable-stub-sources"],
sdk_version: "none",
system_modules: "none",
diff --git a/api/ApiDocs.bp b/api/ApiDocs.bp
index bcfb68f..7ae3224 100644
--- a/api/ApiDocs.bp
+++ b/api/ApiDocs.bp
@@ -63,6 +63,7 @@
":framework-graphics-srcs",
":framework-mediaprovider-sources",
":framework-nearby-sources",
+ ":framework-nfc-updatable-sources",
":framework-ondevicepersonalization-sources",
":framework-permission-sources",
":framework-permission-s-sources",
@@ -183,6 +184,7 @@
"-federationapi AndroidX $(location :current-androidx-api)",
// doclava contains checks for a few issues that are have been migrated to metalava.
// disable them in doclava, to avoid mistriggering or double triggering.
+ "-hide 101", // TODO: turn Lint 101 back into an error again
"-hide 111", // HIDDEN_SUPERCLASS
"-hide 113", // DEPRECATION_MISMATCH
"-hide 125", // REQUIRES_PERMISSION
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index ef1fa609..59c0128 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -239,6 +239,10 @@
name: "android-non-updatable_from_source_defaults",
libs: ["stub-annotations"],
static_libs: ["framework-res-package-jar"], // Export package of framework-res
+}
+
+java_defaults {
+ name: "android-non-updatable_exportable_from_source_defaults",
dist: {
targets: ["sdk"],
tag: ".jar",
@@ -265,6 +269,14 @@
}
java_library {
+ name: "android-non-updatable.stubs.exportable",
+ defaults: ["android-non-updatable_defaults"],
+ static_libs: [
+ "android-non-updatable.stubs.exportable.from-source",
+ ],
+}
+
+java_library {
name: "android-non-updatable.stubs.system",
defaults: ["android-non-updatable_defaults"],
static_libs: [
@@ -283,6 +295,14 @@
}
java_library {
+ name: "android-non-updatable.stubs.exportable.system",
+ defaults: ["android-non-updatable_defaults"],
+ static_libs: [
+ "android-non-updatable.stubs.exportable.system.from-source",
+ ],
+}
+
+java_library {
name: "android-non-updatable.stubs.module_lib",
defaults: ["android-non-updatable_defaults"],
static_libs: [
@@ -301,6 +321,14 @@
}
java_library {
+ name: "android-non-updatable.stubs.exportable.module_lib",
+ defaults: ["android-non-updatable_defaults"],
+ static_libs: [
+ "android-non-updatable.stubs.exportable.module_lib.from-source",
+ ],
+}
+
+java_library {
name: "android-non-updatable.stubs.test",
defaults: ["android-non-updatable_defaults"],
static_libs: [
@@ -319,6 +347,14 @@
}
java_library {
+ name: "android-non-updatable.stubs.exportable.test",
+ defaults: ["android-non-updatable_defaults"],
+ static_libs: [
+ "android-non-updatable.stubs.exportable.test.from-source",
+ ],
+}
+
+java_library {
name: "android-non-updatable.stubs.from-source",
defaults: [
"android-non-updatable_defaults",
@@ -326,6 +362,17 @@
],
srcs: [":api-stubs-docs-non-updatable"],
libs: ["all-modules-public-stubs"],
+}
+
+java_library {
+ name: "android-non-updatable.stubs.exportable.from-source",
+ defaults: [
+ "android-non-updatable_defaults",
+ "android-non-updatable_from_source_defaults",
+ "android-non-updatable_exportable_from_source_defaults",
+ ],
+ srcs: [":api-stubs-docs-non-updatable{.exportable}"],
+ libs: ["all-modules-public-stubs"],
dist: {
dir: "apistubs/android/public",
},
@@ -339,6 +386,17 @@
],
srcs: [":system-api-stubs-docs-non-updatable"],
libs: ["all-modules-system-stubs"],
+}
+
+java_library {
+ name: "android-non-updatable.stubs.exportable.system.from-source",
+ defaults: [
+ "android-non-updatable_defaults",
+ "android-non-updatable_from_source_defaults",
+ "android-non-updatable_exportable_from_source_defaults",
+ ],
+ srcs: [":system-api-stubs-docs-non-updatable{.exportable}"],
+ libs: ["all-modules-system-stubs"],
dist: {
dir: "apistubs/android/system",
},
@@ -352,6 +410,17 @@
],
srcs: [":module-lib-api-stubs-docs-non-updatable"],
libs: non_updatable_api_deps_on_modules,
+}
+
+java_library {
+ name: "android-non-updatable.stubs.exportable.module_lib.from-source",
+ defaults: [
+ "android-non-updatable_defaults",
+ "android-non-updatable_from_source_defaults",
+ "android-non-updatable_exportable_from_source_defaults",
+ ],
+ srcs: [":module-lib-api-stubs-docs-non-updatable{.exportable}"],
+ libs: non_updatable_api_deps_on_modules,
dist: {
dir: "apistubs/android/module-lib",
},
@@ -365,6 +434,17 @@
],
srcs: [":test-api-stubs-docs-non-updatable"],
libs: ["all-modules-system-stubs"],
+}
+
+java_library {
+ name: "android-non-updatable.stubs.exportable.test.from-source",
+ defaults: [
+ "android-non-updatable_defaults",
+ "android-non-updatable_from_source_defaults",
+ "android-non-updatable_exportable_from_source_defaults",
+ ],
+ srcs: [":test-api-stubs-docs-non-updatable{.exportable}"],
+ libs: ["all-modules-system-stubs"],
dist: {
dir: "apistubs/android/test",
},
@@ -462,6 +542,16 @@
}
java_library {
+ name: "android_stubs_current_exportable.from-source",
+ static_libs: [
+ "all-modules-public-stubs-exportable",
+ "android-non-updatable.stubs.exportable",
+ "private-stub-annotations-jar",
+ ],
+ defaults: ["android.jar_defaults"],
+}
+
+java_library {
name: "android_system_stubs_current.from-source",
static_libs: [
"all-modules-system-stubs",
@@ -470,6 +560,19 @@
],
defaults: [
"android.jar_defaults",
+ ],
+ visibility: ["//frameworks/base/services"],
+}
+
+java_library {
+ name: "android_system_stubs_current_exportable.from-source",
+ static_libs: [
+ "all-modules-system-stubs-exportable",
+ "android-non-updatable.stubs.exportable.system",
+ "private-stub-annotations-jar",
+ ],
+ defaults: [
+ "android.jar_defaults",
"android_stubs_dists_default",
],
dist: {
@@ -498,6 +601,23 @@
],
defaults: [
"android.jar_defaults",
+ ],
+ visibility: ["//frameworks/base/services"],
+}
+
+java_library {
+ name: "android_test_stubs_current_exportable.from-source",
+ static_libs: [
+ // Updatable modules do not have test APIs, but we want to include their SystemApis, like we
+ // include the SystemApi of framework-non-updatable-sources.
+ "all-updatable-modules-system-stubs-exportable",
+ // Non-updatable modules on the other hand can have test APIs, so include their test-stubs.
+ "all-non-updatable-modules-test-stubs-exportable",
+ "android-non-updatable.stubs.exportable.test",
+ "private-stub-annotations-jar",
+ ],
+ defaults: [
+ "android.jar_defaults",
"android_stubs_dists_default",
],
dist: {
@@ -505,6 +625,7 @@
},
}
+// This module does not need to be copied to dist
java_library {
name: "android_test_frameworks_core_stubs_current.from-source",
static_libs: [
@@ -513,24 +634,34 @@
],
defaults: [
"android.jar_defaults",
- "android_stubs_dists_default",
],
- dist: {
- dir: "apistubs/android/test-core",
- },
+ visibility: ["//frameworks/base/services"],
}
java_library {
name: "android_module_lib_stubs_current.from-source",
defaults: [
"android.jar_defaults",
- "android_stubs_dists_default",
],
static_libs: [
"android-non-updatable.stubs.module_lib",
"art.module.public.api.stubs.module_lib",
"i18n.module.public.api.stubs",
],
+ visibility: ["//frameworks/base/services"],
+}
+
+java_library {
+ name: "android_module_lib_stubs_current_exportable.from-source",
+ defaults: [
+ "android.jar_defaults",
+ "android_stubs_dists_default",
+ ],
+ static_libs: [
+ "android-non-updatable.stubs.exportable.module_lib",
+ "art.module.public.api.stubs.exportable.module_lib",
+ "i18n.module.public.api.stubs.exportable",
+ ],
dist: {
dir: "apistubs/android/module-lib",
},
@@ -540,13 +671,26 @@
name: "android_system_server_stubs_current.from-source",
defaults: [
"android.jar_defaults",
- "android_stubs_dists_default",
],
srcs: [":services-non-updatable-stubs"],
installable: false,
static_libs: [
"android_module_lib_stubs_current.from-source",
],
+ visibility: ["//frameworks/base/services"],
+}
+
+java_library {
+ name: "android_system_server_stubs_current_exportable.from-source",
+ defaults: [
+ "android.jar_defaults",
+ "android_stubs_dists_default",
+ ],
+ srcs: [":services-non-updatable-stubs{.exportable}"],
+ installable: false,
+ static_libs: [
+ "android_module_lib_stubs_current_exportable.from-source",
+ ],
dist: {
dir: "apistubs/android/system-server",
},
@@ -635,7 +779,6 @@
api_contributions: [
"framework-virtualization.stubs.source.test.api.contribution",
"framework-location.stubs.source.test.api.contribution",
- "framework-nfc.stubs.source.test.api.contribution",
],
}
diff --git a/api/api.go b/api/api.go
index 2668999..fa2be21 100644
--- a/api/api.go
+++ b/api/api.go
@@ -31,7 +31,6 @@
const i18n = "i18n.module.public.api"
const virtualization = "framework-virtualization"
const location = "framework-location"
-const nfc = "framework-nfc"
var core_libraries_modules = []string{art, conscrypt, i18n}
@@ -43,7 +42,7 @@
// APIs.
// In addition, the modules in this list are allowed to contribute to test APIs
// stubs.
-var non_updatable_modules = []string{virtualization, location, nfc}
+var non_updatable_modules = []string{virtualization, location}
// The intention behind this soong plugin is to generate a number of "merged"
// API-related modules that would otherwise require a large amount of very
@@ -64,6 +63,7 @@
type CombinedApis struct {
android.ModuleBase
+ android.DefaultableModuleBase
properties CombinedApisProperties
}
@@ -74,6 +74,7 @@
func registerBuildComponents(ctx android.RegistrationContext) {
ctx.RegisterModuleType("combined_apis", combinedApisModuleFactory)
+ ctx.RegisterModuleType("combined_apis_defaults", CombinedApisModuleDefaultsFactory)
}
var PrepareForCombinedApisTest = android.FixtureRegisterWithContext(registerBuildComponents)
@@ -203,6 +204,15 @@
ctx.CreateModule(java.LibraryFactory, &props)
}
+func createMergedPublicExportableStubs(ctx android.LoadHookContext, modules []string) {
+ props := libraryProps{}
+ props.Name = proptools.StringPtr("all-modules-public-stubs-exportable")
+ props.Static_libs = transformArray(modules, "", ".stubs.exportable")
+ props.Sdk_version = proptools.StringPtr("module_current")
+ props.Visibility = []string{"//frameworks/base"}
+ ctx.CreateModule(java.LibraryFactory, &props)
+}
+
func createMergedSystemStubs(ctx android.LoadHookContext, modules []string) {
// First create the all-updatable-modules-system-stubs
{
@@ -227,6 +237,30 @@
}
}
+func createMergedSystemExportableStubs(ctx android.LoadHookContext, modules []string) {
+ // First create the all-updatable-modules-system-stubs
+ {
+ updatable_modules := removeAll(modules, non_updatable_modules)
+ props := libraryProps{}
+ props.Name = proptools.StringPtr("all-updatable-modules-system-stubs-exportable")
+ props.Static_libs = transformArray(updatable_modules, "", ".stubs.exportable.system")
+ props.Sdk_version = proptools.StringPtr("module_current")
+ props.Visibility = []string{"//frameworks/base"}
+ ctx.CreateModule(java.LibraryFactory, &props)
+ }
+ // Now merge all-updatable-modules-system-stubs and stubs from non-updatable modules
+ // into all-modules-system-stubs.
+ {
+ props := libraryProps{}
+ props.Name = proptools.StringPtr("all-modules-system-stubs-exportable")
+ props.Static_libs = transformArray(non_updatable_modules, "", ".stubs.exportable.system")
+ props.Static_libs = append(props.Static_libs, "all-updatable-modules-system-stubs-exportable")
+ props.Sdk_version = proptools.StringPtr("module_current")
+ props.Visibility = []string{"//frameworks/base"}
+ ctx.CreateModule(java.LibraryFactory, &props)
+ }
+}
+
func createMergedTestStubsForNonUpdatableModules(ctx android.LoadHookContext) {
props := libraryProps{}
props.Name = proptools.StringPtr("all-non-updatable-modules-test-stubs")
@@ -236,6 +270,15 @@
ctx.CreateModule(java.LibraryFactory, &props)
}
+func createMergedTestExportableStubsForNonUpdatableModules(ctx android.LoadHookContext) {
+ props := libraryProps{}
+ props.Name = proptools.StringPtr("all-non-updatable-modules-test-stubs-exportable")
+ props.Static_libs = transformArray(non_updatable_modules, "", ".stubs.exportable.test")
+ props.Sdk_version = proptools.StringPtr("module_current")
+ props.Visibility = []string{"//frameworks/base"}
+ ctx.CreateModule(java.LibraryFactory, &props)
+}
+
func createMergedFrameworkImpl(ctx android.LoadHookContext, modules []string) {
// This module is for the "framework-all" module, which should not include the core libraries.
modules = removeAll(modules, core_libraries_modules)
@@ -266,6 +309,19 @@
}
}
+func createMergedFrameworkModuleLibExportableStubs(ctx android.LoadHookContext, modules []string) {
+ // The user of this module compiles against the "core" SDK and against non-updatable modules,
+ // so remove to avoid dupes.
+ modules = removeAll(modules, core_libraries_modules)
+ modules = removeAll(modules, non_updatable_modules)
+ props := libraryProps{}
+ props.Name = proptools.StringPtr("framework-updatable-stubs-module_libs_api-exportable")
+ props.Static_libs = transformArray(modules, "", ".stubs.exportable.module_lib")
+ props.Sdk_version = proptools.StringPtr("module_current")
+ props.Visibility = []string{"//frameworks/base"}
+ ctx.CreateModule(java.LibraryFactory, &props)
+}
+
func createMergedFrameworkModuleLibStubs(ctx android.LoadHookContext, modules []string) {
// The user of this module compiles against the "core" SDK and against non-updatable modules,
// so remove to avoid dupes.
@@ -381,6 +437,27 @@
}
}
+func createFullExportableApiLibraries(ctx android.LoadHookContext) {
+ javaLibraryNames := []string{
+ "android_stubs_current_exportable",
+ "android_system_stubs_current_exportable",
+ "android_test_stubs_current_exportable",
+ "android_module_lib_stubs_current_exportable",
+ "android_system_server_stubs_current_exportable",
+ }
+
+ for _, libraryName := range javaLibraryNames {
+ props := libraryProps{}
+ props.Name = proptools.StringPtr(libraryName)
+ staticLib := libraryName + ".from-source"
+ props.Static_libs = []string{staticLib}
+ props.Defaults = []string{"android.jar_defaults"}
+ props.Visibility = []string{"//visibility:public"}
+
+ ctx.CreateModule(java.LibraryFactory, &props)
+ }
+}
+
func (a *CombinedApis) createInternalModules(ctx android.LoadHookContext) {
bootclasspath := a.properties.Bootclasspath
system_server_classpath := a.properties.System_server_classpath
@@ -396,6 +473,11 @@
createMergedFrameworkModuleLibStubs(ctx, bootclasspath)
createMergedFrameworkImpl(ctx, bootclasspath)
+ createMergedPublicExportableStubs(ctx, bootclasspath)
+ createMergedSystemExportableStubs(ctx, bootclasspath)
+ createMergedTestExportableStubsForNonUpdatableModules(ctx)
+ createMergedFrameworkModuleLibExportableStubs(ctx, bootclasspath)
+
createMergedAnnotationsFilegroups(ctx, bootclasspath, system_server_classpath)
createPublicStubsSourceFilegroup(ctx, bootclasspath)
@@ -403,12 +485,15 @@
createApiContributionDefaults(ctx, bootclasspath)
createFullApiLibraries(ctx)
+
+ createFullExportableApiLibraries(ctx)
}
func combinedApisModuleFactory() android.Module {
module := &CombinedApis{}
module.AddProperties(&module.properties)
android.InitAndroidModule(module)
+ android.InitDefaultableModule(module)
android.AddLoadHook(module, func(ctx android.LoadHookContext) { module.createInternalModules(ctx) })
return module
}
@@ -445,3 +530,16 @@
}
return s2
}
+
+// Defaults
+type CombinedApisModuleDefaults struct {
+ android.ModuleBase
+ android.DefaultsModuleBase
+}
+
+func CombinedApisModuleDefaultsFactory() android.Module {
+ module := &CombinedApisModuleDefaults{}
+ module.AddProperties(&CombinedApisProperties{})
+ android.InitDefaultsModule(module)
+ return module
+}
diff --git a/boot/Android.bp b/boot/Android.bp
index 8a3d35e..228d060 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -26,9 +26,11 @@
soong_config_module_type {
name: "custom_platform_bootclasspath",
module_type: "platform_bootclasspath",
- config_namespace: "AUTO",
+ config_namespace: "bootclasspath",
bool_variables: [
"car_bootclasspath_fragment",
+ "nfc_apex_bootclasspath_fragment",
+ "release_crashrecovery_module",
],
properties: [
"fragments",
@@ -155,6 +157,24 @@
},
],
},
+ nfc_apex_bootclasspath_fragment: {
+ fragments: [
+ // only used if NFC mainline is enabled.
+ {
+ apex: "com.android.nfcservices",
+ module: "com.android.nfcservices-bootclasspath-fragment",
+ },
+ ],
+ },
+ release_crashrecovery_module: {
+ fragments: [
+ // only used when crashrecovery is enabled
+ {
+ apex: "com.android.crashrecovery",
+ module: "com.android.crashrecovery-bootclasspath-fragment",
+ },
+ ],
+ },
},
// Additional information needed by hidden api processing.
diff --git a/boot/hiddenapi/hiddenapi-max-target-o.txt b/boot/hiddenapi/hiddenapi-max-target-o.txt
index 3c16915..2d417ce 100644
--- a/boot/hiddenapi/hiddenapi-max-target-o.txt
+++ b/boot/hiddenapi/hiddenapi-max-target-o.txt
@@ -33834,649 +33834,6 @@
Landroid/net/WifiLinkQualityInfo;->setTxBad(J)V
Landroid/net/WifiLinkQualityInfo;->setTxGood(J)V
Landroid/net/WifiLinkQualityInfo;->setType(I)V
-Landroid/nfc/ApduList;-><init>()V
-Landroid/nfc/ApduList;-><init>(Landroid/os/Parcel;)V
-Landroid/nfc/ApduList;->add([B)V
-Landroid/nfc/ApduList;->commands:Ljava/util/ArrayList;
-Landroid/nfc/ApduList;->CREATOR:Landroid/os/Parcelable$Creator;
-Landroid/nfc/ApduList;->get()Ljava/util/List;
-Landroid/nfc/BeamShareData;-><init>(Landroid/nfc/NdefMessage;[Landroid/net/Uri;Landroid/os/UserHandle;I)V
-Landroid/nfc/BeamShareData;->CREATOR:Landroid/os/Parcelable$Creator;
-Landroid/nfc/BeamShareData;->flags:I
-Landroid/nfc/BeamShareData;->ndefMessage:Landroid/nfc/NdefMessage;
-Landroid/nfc/BeamShareData;->uris:[Landroid/net/Uri;
-Landroid/nfc/BeamShareData;->userHandle:Landroid/os/UserHandle;
-Landroid/nfc/cardemulation/AidGroup;-><init>(Ljava/util/List;Ljava/lang/String;)V
-Landroid/nfc/cardemulation/AidGroup;->isValidCategory(Ljava/lang/String;)Z
-Landroid/nfc/cardemulation/AidGroup;->MAX_NUM_AIDS:I
-Landroid/nfc/cardemulation/AidGroup;->TAG:Ljava/lang/String;
-Landroid/nfc/cardemulation/ApduServiceInfo;->dump(Ljava/io/FileDescriptor;Ljava/io/PrintWriter;[Ljava/lang/String;)V
-Landroid/nfc/cardemulation/ApduServiceInfo;->getAidGroups()Ljava/util/ArrayList;
-Landroid/nfc/cardemulation/ApduServiceInfo;->getAids()Ljava/util/List;
-Landroid/nfc/cardemulation/ApduServiceInfo;->getCategoryForAid(Ljava/lang/String;)Ljava/lang/String;
-Landroid/nfc/cardemulation/ApduServiceInfo;->getComponent()Landroid/content/ComponentName;
-Landroid/nfc/cardemulation/ApduServiceInfo;->getDynamicAidGroupForCategory(Ljava/lang/String;)Landroid/nfc/cardemulation/AidGroup;
-Landroid/nfc/cardemulation/ApduServiceInfo;->getPrefixAids()Ljava/util/List;
-Landroid/nfc/cardemulation/ApduServiceInfo;->getSubsetAids()Ljava/util/List;
-Landroid/nfc/cardemulation/ApduServiceInfo;->hasCategory(Ljava/lang/String;)Z
-Landroid/nfc/cardemulation/ApduServiceInfo;->loadAppLabel(Landroid/content/pm/PackageManager;)Ljava/lang/CharSequence;
-Landroid/nfc/cardemulation/ApduServiceInfo;->loadIcon(Landroid/content/pm/PackageManager;)Landroid/graphics/drawable/Drawable;
-Landroid/nfc/cardemulation/ApduServiceInfo;->loadLabel(Landroid/content/pm/PackageManager;)Ljava/lang/CharSequence;
-Landroid/nfc/cardemulation/ApduServiceInfo;->mBannerResourceId:I
-Landroid/nfc/cardemulation/ApduServiceInfo;->mDescription:Ljava/lang/String;
-Landroid/nfc/cardemulation/ApduServiceInfo;->mOnHost:Z
-Landroid/nfc/cardemulation/ApduServiceInfo;->mRequiresDeviceUnlock:Z
-Landroid/nfc/cardemulation/ApduServiceInfo;->mSettingsActivityName:Ljava/lang/String;
-Landroid/nfc/cardemulation/ApduServiceInfo;->mUid:I
-Landroid/nfc/cardemulation/ApduServiceInfo;->removeDynamicAidGroupForCategory(Ljava/lang/String;)Z
-Landroid/nfc/cardemulation/ApduServiceInfo;->setOrReplaceDynamicAidGroup(Landroid/nfc/cardemulation/AidGroup;)V
-Landroid/nfc/cardemulation/ApduServiceInfo;->TAG:Ljava/lang/String;
-Landroid/nfc/cardemulation/CardEmulation;-><init>(Landroid/content/Context;Landroid/nfc/INfcCardEmulation;)V
-Landroid/nfc/cardemulation/CardEmulation;->getServices(Ljava/lang/String;)Ljava/util/List;
-Landroid/nfc/cardemulation/CardEmulation;->isValidAid(Ljava/lang/String;)Z
-Landroid/nfc/cardemulation/CardEmulation;->mContext:Landroid/content/Context;
-Landroid/nfc/cardemulation/CardEmulation;->recoverService()V
-Landroid/nfc/cardemulation/CardEmulation;->sCardEmus:Ljava/util/HashMap;
-Landroid/nfc/cardemulation/CardEmulation;->setDefaultForNextTap(Landroid/content/ComponentName;)Z
-Landroid/nfc/cardemulation/CardEmulation;->setDefaultServiceForCategory(Landroid/content/ComponentName;Ljava/lang/String;)Z
-Landroid/nfc/cardemulation/CardEmulation;->sIsInitialized:Z
-Landroid/nfc/cardemulation/CardEmulation;->sService:Landroid/nfc/INfcCardEmulation;
-Landroid/nfc/cardemulation/CardEmulation;->TAG:Ljava/lang/String;
-Landroid/nfc/cardemulation/HostApduService;->KEY_DATA:Ljava/lang/String;
-Landroid/nfc/cardemulation/HostApduService;->mMessenger:Landroid/os/Messenger;
-Landroid/nfc/cardemulation/HostApduService;->mNfcService:Landroid/os/Messenger;
-Landroid/nfc/cardemulation/HostApduService;->MSG_COMMAND_APDU:I
-Landroid/nfc/cardemulation/HostApduService;->MSG_DEACTIVATED:I
-Landroid/nfc/cardemulation/HostApduService;->MSG_RESPONSE_APDU:I
-Landroid/nfc/cardemulation/HostApduService;->MSG_UNHANDLED:I
-Landroid/nfc/cardemulation/HostApduService;->TAG:Ljava/lang/String;
-Landroid/nfc/cardemulation/HostNfcFService;->KEY_DATA:Ljava/lang/String;
-Landroid/nfc/cardemulation/HostNfcFService;->KEY_MESSENGER:Ljava/lang/String;
-Landroid/nfc/cardemulation/HostNfcFService;->mMessenger:Landroid/os/Messenger;
-Landroid/nfc/cardemulation/HostNfcFService;->mNfcService:Landroid/os/Messenger;
-Landroid/nfc/cardemulation/HostNfcFService;->MSG_COMMAND_PACKET:I
-Landroid/nfc/cardemulation/HostNfcFService;->MSG_DEACTIVATED:I
-Landroid/nfc/cardemulation/HostNfcFService;->MSG_RESPONSE_PACKET:I
-Landroid/nfc/cardemulation/HostNfcFService;->TAG:Ljava/lang/String;
-Landroid/nfc/cardemulation/NfcFCardEmulation;-><init>(Landroid/content/Context;Landroid/nfc/INfcFCardEmulation;)V
-Landroid/nfc/cardemulation/NfcFCardEmulation;->getMaxNumOfRegisterableSystemCodes()I
-Landroid/nfc/cardemulation/NfcFCardEmulation;->getNfcFServices()Ljava/util/List;
-Landroid/nfc/cardemulation/NfcFCardEmulation;->isValidNfcid2(Ljava/lang/String;)Z
-Landroid/nfc/cardemulation/NfcFCardEmulation;->isValidSystemCode(Ljava/lang/String;)Z
-Landroid/nfc/cardemulation/NfcFCardEmulation;->mContext:Landroid/content/Context;
-Landroid/nfc/cardemulation/NfcFCardEmulation;->recoverService()V
-Landroid/nfc/cardemulation/NfcFCardEmulation;->sCardEmus:Ljava/util/HashMap;
-Landroid/nfc/cardemulation/NfcFCardEmulation;->sIsInitialized:Z
-Landroid/nfc/cardemulation/NfcFCardEmulation;->sService:Landroid/nfc/INfcFCardEmulation;
-Landroid/nfc/cardemulation/NfcFCardEmulation;->TAG:Ljava/lang/String;
-Landroid/nfc/cardemulation/NfcFServiceInfo;-><init>(Landroid/content/pm/PackageManager;Landroid/content/pm/ResolveInfo;)V
-Landroid/nfc/cardemulation/NfcFServiceInfo;-><init>(Landroid/content/pm/ResolveInfo;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;)V
-Landroid/nfc/cardemulation/NfcFServiceInfo;->CREATOR:Landroid/os/Parcelable$Creator;
-Landroid/nfc/cardemulation/NfcFServiceInfo;->DEFAULT_T3T_PMM:Ljava/lang/String;
-Landroid/nfc/cardemulation/NfcFServiceInfo;->dump(Ljava/io/FileDescriptor;Ljava/io/PrintWriter;[Ljava/lang/String;)V
-Landroid/nfc/cardemulation/NfcFServiceInfo;->getComponent()Landroid/content/ComponentName;
-Landroid/nfc/cardemulation/NfcFServiceInfo;->getDescription()Ljava/lang/String;
-Landroid/nfc/cardemulation/NfcFServiceInfo;->getNfcid2()Ljava/lang/String;
-Landroid/nfc/cardemulation/NfcFServiceInfo;->getSystemCode()Ljava/lang/String;
-Landroid/nfc/cardemulation/NfcFServiceInfo;->getT3tPmm()Ljava/lang/String;
-Landroid/nfc/cardemulation/NfcFServiceInfo;->getUid()I
-Landroid/nfc/cardemulation/NfcFServiceInfo;->loadIcon(Landroid/content/pm/PackageManager;)Landroid/graphics/drawable/Drawable;
-Landroid/nfc/cardemulation/NfcFServiceInfo;->loadLabel(Landroid/content/pm/PackageManager;)Ljava/lang/CharSequence;
-Landroid/nfc/cardemulation/NfcFServiceInfo;->mDescription:Ljava/lang/String;
-Landroid/nfc/cardemulation/NfcFServiceInfo;->mDynamicNfcid2:Ljava/lang/String;
-Landroid/nfc/cardemulation/NfcFServiceInfo;->mDynamicSystemCode:Ljava/lang/String;
-Landroid/nfc/cardemulation/NfcFServiceInfo;->mNfcid2:Ljava/lang/String;
-Landroid/nfc/cardemulation/NfcFServiceInfo;->mService:Landroid/content/pm/ResolveInfo;
-Landroid/nfc/cardemulation/NfcFServiceInfo;->mSystemCode:Ljava/lang/String;
-Landroid/nfc/cardemulation/NfcFServiceInfo;->mT3tPmm:Ljava/lang/String;
-Landroid/nfc/cardemulation/NfcFServiceInfo;->mUid:I
-Landroid/nfc/cardemulation/NfcFServiceInfo;->setOrReplaceDynamicNfcid2(Ljava/lang/String;)V
-Landroid/nfc/cardemulation/NfcFServiceInfo;->setOrReplaceDynamicSystemCode(Ljava/lang/String;)V
-Landroid/nfc/cardemulation/NfcFServiceInfo;->TAG:Ljava/lang/String;
-Landroid/nfc/ErrorCodes;-><init>()V
-Landroid/nfc/ErrorCodes;->asString(I)Ljava/lang/String;
-Landroid/nfc/ErrorCodes;->ERROR_BUFFER_TO_SMALL:I
-Landroid/nfc/ErrorCodes;->ERROR_BUSY:I
-Landroid/nfc/ErrorCodes;->ERROR_CANCELLED:I
-Landroid/nfc/ErrorCodes;->ERROR_CONNECT:I
-Landroid/nfc/ErrorCodes;->ERROR_DISCONNECT:I
-Landroid/nfc/ErrorCodes;->ERROR_INSUFFICIENT_RESOURCES:I
-Landroid/nfc/ErrorCodes;->ERROR_INVALID_PARAM:I
-Landroid/nfc/ErrorCodes;->ERROR_IO:I
-Landroid/nfc/ErrorCodes;->ERROR_NFC_ON:I
-Landroid/nfc/ErrorCodes;->ERROR_NOT_INITIALIZED:I
-Landroid/nfc/ErrorCodes;->ERROR_NOT_SUPPORTED:I
-Landroid/nfc/ErrorCodes;->ERROR_NO_SE_CONNECTED:I
-Landroid/nfc/ErrorCodes;->ERROR_READ:I
-Landroid/nfc/ErrorCodes;->ERROR_SAP_USED:I
-Landroid/nfc/ErrorCodes;->ERROR_SERVICE_NAME_USED:I
-Landroid/nfc/ErrorCodes;->ERROR_SE_ALREADY_SELECTED:I
-Landroid/nfc/ErrorCodes;->ERROR_SE_CONNECTED:I
-Landroid/nfc/ErrorCodes;->ERROR_SOCKET_CREATION:I
-Landroid/nfc/ErrorCodes;->ERROR_SOCKET_NOT_CONNECTED:I
-Landroid/nfc/ErrorCodes;->ERROR_SOCKET_OPTIONS:I
-Landroid/nfc/ErrorCodes;->ERROR_TIMEOUT:I
-Landroid/nfc/ErrorCodes;->ERROR_WRITE:I
-Landroid/nfc/ErrorCodes;->SUCCESS:I
-Landroid/nfc/IAppCallback$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
-Landroid/nfc/IAppCallback$Stub$Proxy;->createBeamShareData(B)Landroid/nfc/BeamShareData;
-Landroid/nfc/IAppCallback$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
-Landroid/nfc/IAppCallback$Stub$Proxy;->mRemote:Landroid/os/IBinder;
-Landroid/nfc/IAppCallback$Stub$Proxy;->onNdefPushComplete(B)V
-Landroid/nfc/IAppCallback$Stub$Proxy;->onTagDiscovered(Landroid/nfc/Tag;)V
-Landroid/nfc/IAppCallback$Stub;-><init>()V
-Landroid/nfc/IAppCallback$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/IAppCallback;
-Landroid/nfc/IAppCallback$Stub;->DESCRIPTOR:Ljava/lang/String;
-Landroid/nfc/IAppCallback$Stub;->TRANSACTION_createBeamShareData:I
-Landroid/nfc/IAppCallback$Stub;->TRANSACTION_onNdefPushComplete:I
-Landroid/nfc/IAppCallback$Stub;->TRANSACTION_onTagDiscovered:I
-Landroid/nfc/IAppCallback;->createBeamShareData(B)Landroid/nfc/BeamShareData;
-Landroid/nfc/IAppCallback;->onNdefPushComplete(B)V
-Landroid/nfc/IAppCallback;->onTagDiscovered(Landroid/nfc/Tag;)V
-Landroid/nfc/INfcAdapter$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
-Landroid/nfc/INfcAdapter$Stub$Proxy;->addNfcUnlockHandler(Landroid/nfc/INfcUnlockHandler;[I)V
-Landroid/nfc/INfcAdapter$Stub$Proxy;->disable(Z)Z
-Landroid/nfc/INfcAdapter$Stub$Proxy;->disableNdefPush()Z
-Landroid/nfc/INfcAdapter$Stub$Proxy;->dispatch(Landroid/nfc/Tag;)V
-Landroid/nfc/INfcAdapter$Stub$Proxy;->enable()Z
-Landroid/nfc/INfcAdapter$Stub$Proxy;->enableNdefPush()Z
-Landroid/nfc/INfcAdapter$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
-Landroid/nfc/INfcAdapter$Stub$Proxy;->getNfcAdapterExtrasInterface(Ljava/lang/String;)Landroid/nfc/INfcAdapterExtras;
-Landroid/nfc/INfcAdapter$Stub$Proxy;->getNfcCardEmulationInterface()Landroid/nfc/INfcCardEmulation;
-Landroid/nfc/INfcAdapter$Stub$Proxy;->getNfcDtaInterface(Ljava/lang/String;)Landroid/nfc/INfcDta;
-Landroid/nfc/INfcAdapter$Stub$Proxy;->getNfcFCardEmulationInterface()Landroid/nfc/INfcFCardEmulation;
-Landroid/nfc/INfcAdapter$Stub$Proxy;->getNfcTagInterface()Landroid/nfc/INfcTag;
-Landroid/nfc/INfcAdapter$Stub$Proxy;->getState()I
-Landroid/nfc/INfcAdapter$Stub$Proxy;->ignore(IILandroid/nfc/ITagRemovedCallback;)Z
-Landroid/nfc/INfcAdapter$Stub$Proxy;->invokeBeam()V
-Landroid/nfc/INfcAdapter$Stub$Proxy;->invokeBeamInternal(Landroid/nfc/BeamShareData;)V
-Landroid/nfc/INfcAdapter$Stub$Proxy;->isNdefPushEnabled()Z
-Landroid/nfc/INfcAdapter$Stub$Proxy;->mRemote:Landroid/os/IBinder;
-Landroid/nfc/INfcAdapter$Stub$Proxy;->pausePolling(I)V
-Landroid/nfc/INfcAdapter$Stub$Proxy;->removeNfcUnlockHandler(Landroid/nfc/INfcUnlockHandler;)V
-Landroid/nfc/INfcAdapter$Stub$Proxy;->resumePolling()V
-Landroid/nfc/INfcAdapter$Stub$Proxy;->setAppCallback(Landroid/nfc/IAppCallback;)V
-Landroid/nfc/INfcAdapter$Stub$Proxy;->setForegroundDispatch(Landroid/app/PendingIntent;[Landroid/content/IntentFilter;Landroid/nfc/TechListParcel;)V
-Landroid/nfc/INfcAdapter$Stub$Proxy;->setP2pModes(II)V
-Landroid/nfc/INfcAdapter$Stub$Proxy;->setReaderMode(Landroid/os/IBinder;Landroid/nfc/IAppCallback;ILandroid/os/Bundle;)V
-Landroid/nfc/INfcAdapter$Stub$Proxy;->verifyNfcPermission()V
-Landroid/nfc/INfcAdapter$Stub;-><init>()V
-Landroid/nfc/INfcAdapter$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/INfcAdapter;
-Landroid/nfc/INfcAdapter$Stub;->DESCRIPTOR:Ljava/lang/String;
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_addNfcUnlockHandler:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_disable:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_disableNdefPush:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_dispatch:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_enableNdefPush:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_getNfcAdapterExtrasInterface:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_getNfcCardEmulationInterface:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_getNfcDtaInterface:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_getNfcFCardEmulationInterface:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_getNfcTagInterface:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_getState:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_ignore:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_invokeBeam:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_invokeBeamInternal:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_isNdefPushEnabled:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_pausePolling:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_removeNfcUnlockHandler:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_resumePolling:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_setAppCallback:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_setForegroundDispatch:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_setP2pModes:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_setReaderMode:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_verifyNfcPermission:I
-Landroid/nfc/INfcAdapter;->addNfcUnlockHandler(Landroid/nfc/INfcUnlockHandler;[I)V
-Landroid/nfc/INfcAdapter;->disable(Z)Z
-Landroid/nfc/INfcAdapter;->disableNdefPush()Z
-Landroid/nfc/INfcAdapter;->dispatch(Landroid/nfc/Tag;)V
-Landroid/nfc/INfcAdapter;->enable()Z
-Landroid/nfc/INfcAdapter;->enableNdefPush()Z
-Landroid/nfc/INfcAdapter;->getNfcAdapterExtrasInterface(Ljava/lang/String;)Landroid/nfc/INfcAdapterExtras;
-Landroid/nfc/INfcAdapter;->getNfcCardEmulationInterface()Landroid/nfc/INfcCardEmulation;
-Landroid/nfc/INfcAdapter;->getNfcDtaInterface(Ljava/lang/String;)Landroid/nfc/INfcDta;
-Landroid/nfc/INfcAdapter;->getNfcFCardEmulationInterface()Landroid/nfc/INfcFCardEmulation;
-Landroid/nfc/INfcAdapter;->getNfcTagInterface()Landroid/nfc/INfcTag;
-Landroid/nfc/INfcAdapter;->getState()I
-Landroid/nfc/INfcAdapter;->ignore(IILandroid/nfc/ITagRemovedCallback;)Z
-Landroid/nfc/INfcAdapter;->invokeBeam()V
-Landroid/nfc/INfcAdapter;->invokeBeamInternal(Landroid/nfc/BeamShareData;)V
-Landroid/nfc/INfcAdapter;->isNdefPushEnabled()Z
-Landroid/nfc/INfcAdapter;->pausePolling(I)V
-Landroid/nfc/INfcAdapter;->removeNfcUnlockHandler(Landroid/nfc/INfcUnlockHandler;)V
-Landroid/nfc/INfcAdapter;->resumePolling()V
-Landroid/nfc/INfcAdapter;->setAppCallback(Landroid/nfc/IAppCallback;)V
-Landroid/nfc/INfcAdapter;->setForegroundDispatch(Landroid/app/PendingIntent;[Landroid/content/IntentFilter;Landroid/nfc/TechListParcel;)V
-Landroid/nfc/INfcAdapter;->setP2pModes(II)V
-Landroid/nfc/INfcAdapter;->setReaderMode(Landroid/os/IBinder;Landroid/nfc/IAppCallback;ILandroid/os/Bundle;)V
-Landroid/nfc/INfcAdapter;->verifyNfcPermission()V
-Landroid/nfc/INfcAdapterExtras$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
-Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->authenticate(Ljava/lang/String;[B)V
-Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->close(Ljava/lang/String;Landroid/os/IBinder;)Landroid/os/Bundle;
-Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->getCardEmulationRoute(Ljava/lang/String;)I
-Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->getDriverName(Ljava/lang/String;)Ljava/lang/String;
-Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
-Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->mRemote:Landroid/os/IBinder;
-Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->open(Ljava/lang/String;Landroid/os/IBinder;)Landroid/os/Bundle;
-Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->setCardEmulationRoute(Ljava/lang/String;I)V
-Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->transceive(Ljava/lang/String;[B)Landroid/os/Bundle;
-Landroid/nfc/INfcAdapterExtras$Stub;-><init>()V
-Landroid/nfc/INfcAdapterExtras$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/INfcAdapterExtras;
-Landroid/nfc/INfcAdapterExtras$Stub;->DESCRIPTOR:Ljava/lang/String;
-Landroid/nfc/INfcAdapterExtras$Stub;->TRANSACTION_authenticate:I
-Landroid/nfc/INfcAdapterExtras$Stub;->TRANSACTION_close:I
-Landroid/nfc/INfcAdapterExtras$Stub;->TRANSACTION_getCardEmulationRoute:I
-Landroid/nfc/INfcAdapterExtras$Stub;->TRANSACTION_getDriverName:I
-Landroid/nfc/INfcAdapterExtras$Stub;->TRANSACTION_open:I
-Landroid/nfc/INfcAdapterExtras$Stub;->TRANSACTION_setCardEmulationRoute:I
-Landroid/nfc/INfcAdapterExtras$Stub;->TRANSACTION_transceive:I
-Landroid/nfc/INfcCardEmulation$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
-Landroid/nfc/INfcCardEmulation$Stub$Proxy;->getAidGroupForService(ILandroid/content/ComponentName;Ljava/lang/String;)Landroid/nfc/cardemulation/AidGroup;
-Landroid/nfc/INfcCardEmulation$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
-Landroid/nfc/INfcCardEmulation$Stub$Proxy;->getServices(ILjava/lang/String;)Ljava/util/List;
-Landroid/nfc/INfcCardEmulation$Stub$Proxy;->isDefaultServiceForAid(ILandroid/content/ComponentName;Ljava/lang/String;)Z
-Landroid/nfc/INfcCardEmulation$Stub$Proxy;->isDefaultServiceForCategory(ILandroid/content/ComponentName;Ljava/lang/String;)Z
-Landroid/nfc/INfcCardEmulation$Stub$Proxy;->mRemote:Landroid/os/IBinder;
-Landroid/nfc/INfcCardEmulation$Stub$Proxy;->registerAidGroupForService(ILandroid/content/ComponentName;Landroid/nfc/cardemulation/AidGroup;)Z
-Landroid/nfc/INfcCardEmulation$Stub$Proxy;->removeAidGroupForService(ILandroid/content/ComponentName;Ljava/lang/String;)Z
-Landroid/nfc/INfcCardEmulation$Stub$Proxy;->setDefaultForNextTap(ILandroid/content/ComponentName;)Z
-Landroid/nfc/INfcCardEmulation$Stub$Proxy;->setDefaultServiceForCategory(ILandroid/content/ComponentName;Ljava/lang/String;)Z
-Landroid/nfc/INfcCardEmulation$Stub$Proxy;->setPreferredService(Landroid/content/ComponentName;)Z
-Landroid/nfc/INfcCardEmulation$Stub$Proxy;->supportsAidPrefixRegistration()Z
-Landroid/nfc/INfcCardEmulation$Stub$Proxy;->unsetPreferredService()Z
-Landroid/nfc/INfcCardEmulation$Stub;-><init>()V
-Landroid/nfc/INfcCardEmulation$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/INfcCardEmulation;
-Landroid/nfc/INfcCardEmulation$Stub;->DESCRIPTOR:Ljava/lang/String;
-Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_getAidGroupForService:I
-Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_getServices:I
-Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_isDefaultServiceForAid:I
-Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_isDefaultServiceForCategory:I
-Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_registerAidGroupForService:I
-Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_removeAidGroupForService:I
-Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_setDefaultForNextTap:I
-Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_setDefaultServiceForCategory:I
-Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_setPreferredService:I
-Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_supportsAidPrefixRegistration:I
-Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_unsetPreferredService:I
-Landroid/nfc/INfcCardEmulation;->getAidGroupForService(ILandroid/content/ComponentName;Ljava/lang/String;)Landroid/nfc/cardemulation/AidGroup;
-Landroid/nfc/INfcCardEmulation;->getServices(ILjava/lang/String;)Ljava/util/List;
-Landroid/nfc/INfcCardEmulation;->isDefaultServiceForAid(ILandroid/content/ComponentName;Ljava/lang/String;)Z
-Landroid/nfc/INfcCardEmulation;->isDefaultServiceForCategory(ILandroid/content/ComponentName;Ljava/lang/String;)Z
-Landroid/nfc/INfcCardEmulation;->registerAidGroupForService(ILandroid/content/ComponentName;Landroid/nfc/cardemulation/AidGroup;)Z
-Landroid/nfc/INfcCardEmulation;->removeAidGroupForService(ILandroid/content/ComponentName;Ljava/lang/String;)Z
-Landroid/nfc/INfcCardEmulation;->setDefaultForNextTap(ILandroid/content/ComponentName;)Z
-Landroid/nfc/INfcCardEmulation;->setDefaultServiceForCategory(ILandroid/content/ComponentName;Ljava/lang/String;)Z
-Landroid/nfc/INfcCardEmulation;->setPreferredService(Landroid/content/ComponentName;)Z
-Landroid/nfc/INfcCardEmulation;->supportsAidPrefixRegistration()Z
-Landroid/nfc/INfcCardEmulation;->unsetPreferredService()Z
-Landroid/nfc/INfcDta$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
-Landroid/nfc/INfcDta$Stub$Proxy;->disableClient()V
-Landroid/nfc/INfcDta$Stub$Proxy;->disableDta()V
-Landroid/nfc/INfcDta$Stub$Proxy;->disableServer()V
-Landroid/nfc/INfcDta$Stub$Proxy;->enableClient(Ljava/lang/String;III)Z
-Landroid/nfc/INfcDta$Stub$Proxy;->enableDta()V
-Landroid/nfc/INfcDta$Stub$Proxy;->enableServer(Ljava/lang/String;IIII)Z
-Landroid/nfc/INfcDta$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
-Landroid/nfc/INfcDta$Stub$Proxy;->mRemote:Landroid/os/IBinder;
-Landroid/nfc/INfcDta$Stub$Proxy;->registerMessageService(Ljava/lang/String;)Z
-Landroid/nfc/INfcDta$Stub;-><init>()V
-Landroid/nfc/INfcDta$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/INfcDta;
-Landroid/nfc/INfcDta$Stub;->DESCRIPTOR:Ljava/lang/String;
-Landroid/nfc/INfcDta$Stub;->TRANSACTION_disableClient:I
-Landroid/nfc/INfcDta$Stub;->TRANSACTION_disableDta:I
-Landroid/nfc/INfcDta$Stub;->TRANSACTION_disableServer:I
-Landroid/nfc/INfcDta$Stub;->TRANSACTION_enableClient:I
-Landroid/nfc/INfcDta$Stub;->TRANSACTION_enableDta:I
-Landroid/nfc/INfcDta$Stub;->TRANSACTION_enableServer:I
-Landroid/nfc/INfcDta$Stub;->TRANSACTION_registerMessageService:I
-Landroid/nfc/INfcDta;->disableClient()V
-Landroid/nfc/INfcDta;->disableDta()V
-Landroid/nfc/INfcDta;->disableServer()V
-Landroid/nfc/INfcDta;->enableClient(Ljava/lang/String;III)Z
-Landroid/nfc/INfcDta;->enableDta()V
-Landroid/nfc/INfcDta;->enableServer(Ljava/lang/String;IIII)Z
-Landroid/nfc/INfcDta;->registerMessageService(Ljava/lang/String;)Z
-Landroid/nfc/INfcFCardEmulation$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
-Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->disableNfcFForegroundService()Z
-Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->enableNfcFForegroundService(Landroid/content/ComponentName;)Z
-Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
-Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->getMaxNumOfRegisterableSystemCodes()I
-Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->getNfcFServices(I)Ljava/util/List;
-Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->getNfcid2ForService(ILandroid/content/ComponentName;)Ljava/lang/String;
-Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->getSystemCodeForService(ILandroid/content/ComponentName;)Ljava/lang/String;
-Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->mRemote:Landroid/os/IBinder;
-Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->registerSystemCodeForService(ILandroid/content/ComponentName;Ljava/lang/String;)Z
-Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->removeSystemCodeForService(ILandroid/content/ComponentName;)Z
-Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->setNfcid2ForService(ILandroid/content/ComponentName;Ljava/lang/String;)Z
-Landroid/nfc/INfcFCardEmulation$Stub;-><init>()V
-Landroid/nfc/INfcFCardEmulation$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/INfcFCardEmulation;
-Landroid/nfc/INfcFCardEmulation$Stub;->DESCRIPTOR:Ljava/lang/String;
-Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_disableNfcFForegroundService:I
-Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_enableNfcFForegroundService:I
-Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_getMaxNumOfRegisterableSystemCodes:I
-Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_getNfcFServices:I
-Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_getNfcid2ForService:I
-Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_getSystemCodeForService:I
-Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_registerSystemCodeForService:I
-Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_removeSystemCodeForService:I
-Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_setNfcid2ForService:I
-Landroid/nfc/INfcFCardEmulation;->disableNfcFForegroundService()Z
-Landroid/nfc/INfcFCardEmulation;->enableNfcFForegroundService(Landroid/content/ComponentName;)Z
-Landroid/nfc/INfcFCardEmulation;->getMaxNumOfRegisterableSystemCodes()I
-Landroid/nfc/INfcFCardEmulation;->getNfcFServices(I)Ljava/util/List;
-Landroid/nfc/INfcFCardEmulation;->getNfcid2ForService(ILandroid/content/ComponentName;)Ljava/lang/String;
-Landroid/nfc/INfcFCardEmulation;->getSystemCodeForService(ILandroid/content/ComponentName;)Ljava/lang/String;
-Landroid/nfc/INfcFCardEmulation;->registerSystemCodeForService(ILandroid/content/ComponentName;Ljava/lang/String;)Z
-Landroid/nfc/INfcFCardEmulation;->removeSystemCodeForService(ILandroid/content/ComponentName;)Z
-Landroid/nfc/INfcFCardEmulation;->setNfcid2ForService(ILandroid/content/ComponentName;Ljava/lang/String;)Z
-Landroid/nfc/INfcTag$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
-Landroid/nfc/INfcTag$Stub$Proxy;->canMakeReadOnly(I)Z
-Landroid/nfc/INfcTag$Stub$Proxy;->connect(II)I
-Landroid/nfc/INfcTag$Stub$Proxy;->formatNdef(I[B)I
-Landroid/nfc/INfcTag$Stub$Proxy;->getExtendedLengthApdusSupported()Z
-Landroid/nfc/INfcTag$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
-Landroid/nfc/INfcTag$Stub$Proxy;->getMaxTransceiveLength(I)I
-Landroid/nfc/INfcTag$Stub$Proxy;->getTechList(I)[I
-Landroid/nfc/INfcTag$Stub$Proxy;->getTimeout(I)I
-Landroid/nfc/INfcTag$Stub$Proxy;->isNdef(I)Z
-Landroid/nfc/INfcTag$Stub$Proxy;->isPresent(I)Z
-Landroid/nfc/INfcTag$Stub$Proxy;->mRemote:Landroid/os/IBinder;
-Landroid/nfc/INfcTag$Stub$Proxy;->ndefIsWritable(I)Z
-Landroid/nfc/INfcTag$Stub$Proxy;->ndefMakeReadOnly(I)I
-Landroid/nfc/INfcTag$Stub$Proxy;->ndefRead(I)Landroid/nfc/NdefMessage;
-Landroid/nfc/INfcTag$Stub$Proxy;->ndefWrite(ILandroid/nfc/NdefMessage;)I
-Landroid/nfc/INfcTag$Stub$Proxy;->reconnect(I)I
-Landroid/nfc/INfcTag$Stub$Proxy;->rediscover(I)Landroid/nfc/Tag;
-Landroid/nfc/INfcTag$Stub$Proxy;->resetTimeouts()V
-Landroid/nfc/INfcTag$Stub$Proxy;->setTimeout(II)I
-Landroid/nfc/INfcTag$Stub$Proxy;->transceive(I[BZ)Landroid/nfc/TransceiveResult;
-Landroid/nfc/INfcTag$Stub;-><init>()V
-Landroid/nfc/INfcTag$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/INfcTag;
-Landroid/nfc/INfcTag$Stub;->DESCRIPTOR:Ljava/lang/String;
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_canMakeReadOnly:I
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_connect:I
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_formatNdef:I
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_getExtendedLengthApdusSupported:I
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_getMaxTransceiveLength:I
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_getTechList:I
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_getTimeout:I
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_isNdef:I
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_isPresent:I
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_ndefIsWritable:I
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_ndefMakeReadOnly:I
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_ndefRead:I
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_ndefWrite:I
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_reconnect:I
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_rediscover:I
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_resetTimeouts:I
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_setTimeout:I
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_transceive:I
-Landroid/nfc/INfcTag;->canMakeReadOnly(I)Z
-Landroid/nfc/INfcTag;->connect(II)I
-Landroid/nfc/INfcTag;->formatNdef(I[B)I
-Landroid/nfc/INfcTag;->getExtendedLengthApdusSupported()Z
-Landroid/nfc/INfcTag;->getMaxTransceiveLength(I)I
-Landroid/nfc/INfcTag;->getTechList(I)[I
-Landroid/nfc/INfcTag;->getTimeout(I)I
-Landroid/nfc/INfcTag;->isNdef(I)Z
-Landroid/nfc/INfcTag;->isPresent(I)Z
-Landroid/nfc/INfcTag;->ndefIsWritable(I)Z
-Landroid/nfc/INfcTag;->ndefMakeReadOnly(I)I
-Landroid/nfc/INfcTag;->ndefRead(I)Landroid/nfc/NdefMessage;
-Landroid/nfc/INfcTag;->ndefWrite(ILandroid/nfc/NdefMessage;)I
-Landroid/nfc/INfcTag;->reconnect(I)I
-Landroid/nfc/INfcTag;->rediscover(I)Landroid/nfc/Tag;
-Landroid/nfc/INfcTag;->resetTimeouts()V
-Landroid/nfc/INfcTag;->setTimeout(II)I
-Landroid/nfc/INfcTag;->transceive(I[BZ)Landroid/nfc/TransceiveResult;
-Landroid/nfc/INfcUnlockHandler$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
-Landroid/nfc/INfcUnlockHandler$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
-Landroid/nfc/INfcUnlockHandler$Stub$Proxy;->mRemote:Landroid/os/IBinder;
-Landroid/nfc/INfcUnlockHandler$Stub$Proxy;->onUnlockAttempted(Landroid/nfc/Tag;)Z
-Landroid/nfc/INfcUnlockHandler$Stub;-><init>()V
-Landroid/nfc/INfcUnlockHandler$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/INfcUnlockHandler;
-Landroid/nfc/INfcUnlockHandler$Stub;->DESCRIPTOR:Ljava/lang/String;
-Landroid/nfc/INfcUnlockHandler$Stub;->TRANSACTION_onUnlockAttempted:I
-Landroid/nfc/INfcUnlockHandler;->onUnlockAttempted(Landroid/nfc/Tag;)Z
-Landroid/nfc/ITagRemovedCallback$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
-Landroid/nfc/ITagRemovedCallback$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
-Landroid/nfc/ITagRemovedCallback$Stub$Proxy;->mRemote:Landroid/os/IBinder;
-Landroid/nfc/ITagRemovedCallback$Stub$Proxy;->onTagRemoved()V
-Landroid/nfc/ITagRemovedCallback$Stub;-><init>()V
-Landroid/nfc/ITagRemovedCallback$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/ITagRemovedCallback;
-Landroid/nfc/ITagRemovedCallback$Stub;->DESCRIPTOR:Ljava/lang/String;
-Landroid/nfc/ITagRemovedCallback$Stub;->TRANSACTION_onTagRemoved:I
-Landroid/nfc/ITagRemovedCallback;->onTagRemoved()V
-Landroid/nfc/NdefMessage;->mRecords:[Landroid/nfc/NdefRecord;
-Landroid/nfc/NdefRecord;->bytesToString([B)Ljava/lang/StringBuilder;
-Landroid/nfc/NdefRecord;->EMPTY_BYTE_ARRAY:[B
-Landroid/nfc/NdefRecord;->ensureSanePayloadSize(J)V
-Landroid/nfc/NdefRecord;->FLAG_CF:B
-Landroid/nfc/NdefRecord;->FLAG_IL:B
-Landroid/nfc/NdefRecord;->FLAG_MB:B
-Landroid/nfc/NdefRecord;->FLAG_ME:B
-Landroid/nfc/NdefRecord;->FLAG_SR:B
-Landroid/nfc/NdefRecord;->getByteLength()I
-Landroid/nfc/NdefRecord;->MAX_PAYLOAD_SIZE:I
-Landroid/nfc/NdefRecord;->mPayload:[B
-Landroid/nfc/NdefRecord;->mTnf:S
-Landroid/nfc/NdefRecord;->mType:[B
-Landroid/nfc/NdefRecord;->parse(Ljava/nio/ByteBuffer;Z)[Landroid/nfc/NdefRecord;
-Landroid/nfc/NdefRecord;->parseWktUri()Landroid/net/Uri;
-Landroid/nfc/NdefRecord;->RTD_ANDROID_APP:[B
-Landroid/nfc/NdefRecord;->TNF_RESERVED:S
-Landroid/nfc/NdefRecord;->toUri(Z)Landroid/net/Uri;
-Landroid/nfc/NdefRecord;->URI_PREFIX_MAP:[Ljava/lang/String;
-Landroid/nfc/NdefRecord;->validateTnf(S[B[B[B)Ljava/lang/String;
-Landroid/nfc/NdefRecord;->writeToByteBuffer(Ljava/nio/ByteBuffer;ZZ)V
-Landroid/nfc/NfcActivityManager$NfcActivityState;->activity:Landroid/app/Activity;
-Landroid/nfc/NfcActivityManager$NfcActivityState;->destroy()V
-Landroid/nfc/NfcActivityManager$NfcActivityState;->flags:I
-Landroid/nfc/NfcActivityManager$NfcActivityState;->ndefMessage:Landroid/nfc/NdefMessage;
-Landroid/nfc/NfcActivityManager$NfcActivityState;->ndefMessageCallback:Landroid/nfc/NfcAdapter$CreateNdefMessageCallback;
-Landroid/nfc/NfcActivityManager$NfcActivityState;->onNdefPushCompleteCallback:Landroid/nfc/NfcAdapter$OnNdefPushCompleteCallback;
-Landroid/nfc/NfcActivityManager$NfcActivityState;->readerCallback:Landroid/nfc/NfcAdapter$ReaderCallback;
-Landroid/nfc/NfcActivityManager$NfcActivityState;->readerModeExtras:Landroid/os/Bundle;
-Landroid/nfc/NfcActivityManager$NfcActivityState;->readerModeFlags:I
-Landroid/nfc/NfcActivityManager$NfcActivityState;->resumed:Z
-Landroid/nfc/NfcActivityManager$NfcActivityState;->token:Landroid/os/Binder;
-Landroid/nfc/NfcActivityManager$NfcActivityState;->uriCallback:Landroid/nfc/NfcAdapter$CreateBeamUrisCallback;
-Landroid/nfc/NfcActivityManager$NfcActivityState;->uris:[Landroid/net/Uri;
-Landroid/nfc/NfcActivityManager$NfcApplicationState;->app:Landroid/app/Application;
-Landroid/nfc/NfcActivityManager$NfcApplicationState;->refCount:I
-Landroid/nfc/NfcActivityManager$NfcApplicationState;->register()V
-Landroid/nfc/NfcActivityManager$NfcApplicationState;->unregister()V
-Landroid/nfc/NfcActivityManager;-><init>(Landroid/nfc/NfcAdapter;)V
-Landroid/nfc/NfcActivityManager;->createBeamShareData(B)Landroid/nfc/BeamShareData;
-Landroid/nfc/NfcActivityManager;->DBG:Ljava/lang/Boolean;
-Landroid/nfc/NfcActivityManager;->destroyActivityState(Landroid/app/Activity;)V
-Landroid/nfc/NfcActivityManager;->disableReaderMode(Landroid/app/Activity;)V
-Landroid/nfc/NfcActivityManager;->enableReaderMode(Landroid/app/Activity;Landroid/nfc/NfcAdapter$ReaderCallback;ILandroid/os/Bundle;)V
-Landroid/nfc/NfcActivityManager;->findActivityState(Landroid/app/Activity;)Landroid/nfc/NfcActivityManager$NfcActivityState;
-Landroid/nfc/NfcActivityManager;->findAppState(Landroid/app/Application;)Landroid/nfc/NfcActivityManager$NfcApplicationState;
-Landroid/nfc/NfcActivityManager;->findResumedActivityState()Landroid/nfc/NfcActivityManager$NfcActivityState;
-Landroid/nfc/NfcActivityManager;->getActivityState(Landroid/app/Activity;)Landroid/nfc/NfcActivityManager$NfcActivityState;
-Landroid/nfc/NfcActivityManager;->mActivities:Ljava/util/List;
-Landroid/nfc/NfcActivityManager;->mApps:Ljava/util/List;
-Landroid/nfc/NfcActivityManager;->onNdefPushComplete(B)V
-Landroid/nfc/NfcActivityManager;->onTagDiscovered(Landroid/nfc/Tag;)V
-Landroid/nfc/NfcActivityManager;->registerApplication(Landroid/app/Application;)V
-Landroid/nfc/NfcActivityManager;->requestNfcServiceCallback()V
-Landroid/nfc/NfcActivityManager;->setNdefPushContentUri(Landroid/app/Activity;[Landroid/net/Uri;)V
-Landroid/nfc/NfcActivityManager;->setNdefPushContentUriCallback(Landroid/app/Activity;Landroid/nfc/NfcAdapter$CreateBeamUrisCallback;)V
-Landroid/nfc/NfcActivityManager;->setNdefPushMessage(Landroid/app/Activity;Landroid/nfc/NdefMessage;I)V
-Landroid/nfc/NfcActivityManager;->setNdefPushMessageCallback(Landroid/app/Activity;Landroid/nfc/NfcAdapter$CreateNdefMessageCallback;I)V
-Landroid/nfc/NfcActivityManager;->setOnNdefPushCompleteCallback(Landroid/app/Activity;Landroid/nfc/NfcAdapter$OnNdefPushCompleteCallback;)V
-Landroid/nfc/NfcActivityManager;->setReaderMode(Landroid/os/Binder;ILandroid/os/Bundle;)V
-Landroid/nfc/NfcActivityManager;->TAG:Ljava/lang/String;
-Landroid/nfc/NfcActivityManager;->unregisterApplication(Landroid/app/Application;)V
-Landroid/nfc/NfcActivityManager;->verifyNfcPermission()V
-Landroid/nfc/NfcAdapter;-><init>(Landroid/content/Context;)V
-Landroid/nfc/NfcAdapter;->ACTION_HANDOVER_TRANSFER_DONE:Ljava/lang/String;
-Landroid/nfc/NfcAdapter;->ACTION_HANDOVER_TRANSFER_STARTED:Ljava/lang/String;
-Landroid/nfc/NfcAdapter;->ACTION_TAG_LEFT_FIELD:Ljava/lang/String;
-Landroid/nfc/NfcAdapter;->disableForegroundDispatchInternal(Landroid/app/Activity;Z)V
-Landroid/nfc/NfcAdapter;->dispatch(Landroid/nfc/Tag;)V
-Landroid/nfc/NfcAdapter;->enforceResumed(Landroid/app/Activity;)V
-Landroid/nfc/NfcAdapter;->EXTRA_HANDOVER_TRANSFER_STATUS:Ljava/lang/String;
-Landroid/nfc/NfcAdapter;->EXTRA_HANDOVER_TRANSFER_URI:Ljava/lang/String;
-Landroid/nfc/NfcAdapter;->getCardEmulationService()Landroid/nfc/INfcCardEmulation;
-Landroid/nfc/NfcAdapter;->getNfcDtaInterface()Landroid/nfc/INfcDta;
-Landroid/nfc/NfcAdapter;->getNfcFCardEmulationService()Landroid/nfc/INfcFCardEmulation;
-Landroid/nfc/NfcAdapter;->getSdkVersion()I
-Landroid/nfc/NfcAdapter;->getServiceInterface()Landroid/nfc/INfcAdapter;
-Landroid/nfc/NfcAdapter;->getTagService()Landroid/nfc/INfcTag;
-Landroid/nfc/NfcAdapter;->HANDOVER_TRANSFER_STATUS_FAILURE:I
-Landroid/nfc/NfcAdapter;->HANDOVER_TRANSFER_STATUS_SUCCESS:I
-Landroid/nfc/NfcAdapter;->hasNfcFeature()Z
-Landroid/nfc/NfcAdapter;->hasNfcHceFeature()Z
-Landroid/nfc/NfcAdapter;->invokeBeam(Landroid/nfc/BeamShareData;)Z
-Landroid/nfc/NfcAdapter;->mContext:Landroid/content/Context;
-Landroid/nfc/NfcAdapter;->mForegroundDispatchListener:Landroid/app/OnActivityPausedListener;
-Landroid/nfc/NfcAdapter;->mLock:Ljava/lang/Object;
-Landroid/nfc/NfcAdapter;->mNfcActivityManager:Landroid/nfc/NfcActivityManager;
-Landroid/nfc/NfcAdapter;->mNfcUnlockHandlers:Ljava/util/HashMap;
-Landroid/nfc/NfcAdapter;->mTagRemovedListener:Landroid/nfc/ITagRemovedCallback;
-Landroid/nfc/NfcAdapter;->pausePolling(I)V
-Landroid/nfc/NfcAdapter;->resumePolling()V
-Landroid/nfc/NfcAdapter;->sCardEmulationService:Landroid/nfc/INfcCardEmulation;
-Landroid/nfc/NfcAdapter;->setP2pModes(II)V
-Landroid/nfc/NfcAdapter;->sHasNfcFeature:Z
-Landroid/nfc/NfcAdapter;->sIsInitialized:Z
-Landroid/nfc/NfcAdapter;->sNfcAdapters:Ljava/util/HashMap;
-Landroid/nfc/NfcAdapter;->sNfcFCardEmulationService:Landroid/nfc/INfcFCardEmulation;
-Landroid/nfc/NfcAdapter;->sNullContextNfcAdapter:Landroid/nfc/NfcAdapter;
-Landroid/nfc/NfcAdapter;->sTagService:Landroid/nfc/INfcTag;
-Landroid/nfc/NfcAdapter;->TAG:Ljava/lang/String;
-Landroid/nfc/NfcEvent;-><init>(Landroid/nfc/NfcAdapter;B)V
-Landroid/nfc/NfcManager;->mAdapter:Landroid/nfc/NfcAdapter;
-Landroid/nfc/Tag;-><init>([B[I[Landroid/os/Bundle;ILandroid/nfc/INfcTag;)V
-Landroid/nfc/Tag;->createMockTag([B[I[Landroid/os/Bundle;)Landroid/nfc/Tag;
-Landroid/nfc/Tag;->generateTechStringList([I)[Ljava/lang/String;
-Landroid/nfc/Tag;->getConnectedTechnology()I
-Landroid/nfc/Tag;->getTechCodeList()[I
-Landroid/nfc/Tag;->getTechCodesFromStrings([Ljava/lang/String;)[I
-Landroid/nfc/Tag;->getTechExtras(I)Landroid/os/Bundle;
-Landroid/nfc/Tag;->getTechStringToCodeMap()Ljava/util/HashMap;
-Landroid/nfc/Tag;->hasTech(I)Z
-Landroid/nfc/Tag;->mConnectedTechnology:I
-Landroid/nfc/Tag;->mServiceHandle:I
-Landroid/nfc/Tag;->mTagService:Landroid/nfc/INfcTag;
-Landroid/nfc/Tag;->mTechExtras:[Landroid/os/Bundle;
-Landroid/nfc/Tag;->mTechList:[I
-Landroid/nfc/Tag;->mTechStringList:[Ljava/lang/String;
-Landroid/nfc/Tag;->readBytesWithNull(Landroid/os/Parcel;)[B
-Landroid/nfc/Tag;->rediscover()Landroid/nfc/Tag;
-Landroid/nfc/Tag;->setConnectedTechnology(I)V
-Landroid/nfc/Tag;->setTechnologyDisconnected()V
-Landroid/nfc/Tag;->writeBytesWithNull(Landroid/os/Parcel;[B)V
-Landroid/nfc/tech/BasicTagTechnology;-><init>(Landroid/nfc/Tag;I)V
-Landroid/nfc/tech/BasicTagTechnology;->checkConnected()V
-Landroid/nfc/tech/BasicTagTechnology;->getMaxTransceiveLengthInternal()I
-Landroid/nfc/tech/BasicTagTechnology;->mIsConnected:Z
-Landroid/nfc/tech/BasicTagTechnology;->mSelectedTechnology:I
-Landroid/nfc/tech/BasicTagTechnology;->mTag:Landroid/nfc/Tag;
-Landroid/nfc/tech/BasicTagTechnology;->reconnect()V
-Landroid/nfc/tech/BasicTagTechnology;->TAG:Ljava/lang/String;
-Landroid/nfc/tech/BasicTagTechnology;->transceive([BZ)[B
-Landroid/nfc/tech/IsoDep;-><init>(Landroid/nfc/Tag;)V
-Landroid/nfc/tech/IsoDep;->EXTRA_HIST_BYTES:Ljava/lang/String;
-Landroid/nfc/tech/IsoDep;->EXTRA_HI_LAYER_RESP:Ljava/lang/String;
-Landroid/nfc/tech/IsoDep;->mHiLayerResponse:[B
-Landroid/nfc/tech/IsoDep;->mHistBytes:[B
-Landroid/nfc/tech/IsoDep;->TAG:Ljava/lang/String;
-Landroid/nfc/tech/MifareClassic;-><init>(Landroid/nfc/Tag;)V
-Landroid/nfc/tech/MifareClassic;->authenticate(I[BZ)Z
-Landroid/nfc/tech/MifareClassic;->isEmulated()Z
-Landroid/nfc/tech/MifareClassic;->MAX_BLOCK_COUNT:I
-Landroid/nfc/tech/MifareClassic;->MAX_SECTOR_COUNT:I
-Landroid/nfc/tech/MifareClassic;->mIsEmulated:Z
-Landroid/nfc/tech/MifareClassic;->mSize:I
-Landroid/nfc/tech/MifareClassic;->mType:I
-Landroid/nfc/tech/MifareClassic;->TAG:Ljava/lang/String;
-Landroid/nfc/tech/MifareClassic;->validateBlock(I)V
-Landroid/nfc/tech/MifareClassic;->validateSector(I)V
-Landroid/nfc/tech/MifareClassic;->validateValueOperand(I)V
-Landroid/nfc/tech/MifareUltralight;-><init>(Landroid/nfc/Tag;)V
-Landroid/nfc/tech/MifareUltralight;->EXTRA_IS_UL_C:Ljava/lang/String;
-Landroid/nfc/tech/MifareUltralight;->MAX_PAGE_COUNT:I
-Landroid/nfc/tech/MifareUltralight;->mType:I
-Landroid/nfc/tech/MifareUltralight;->NXP_MANUFACTURER_ID:I
-Landroid/nfc/tech/MifareUltralight;->TAG:Ljava/lang/String;
-Landroid/nfc/tech/MifareUltralight;->validatePageIndex(I)V
-Landroid/nfc/tech/Ndef;-><init>(Landroid/nfc/Tag;)V
-Landroid/nfc/tech/Ndef;->EXTRA_NDEF_CARDSTATE:Ljava/lang/String;
-Landroid/nfc/tech/Ndef;->EXTRA_NDEF_MAXLENGTH:Ljava/lang/String;
-Landroid/nfc/tech/Ndef;->EXTRA_NDEF_MSG:Ljava/lang/String;
-Landroid/nfc/tech/Ndef;->EXTRA_NDEF_TYPE:Ljava/lang/String;
-Landroid/nfc/tech/Ndef;->ICODE_SLI:Ljava/lang/String;
-Landroid/nfc/tech/Ndef;->mCardState:I
-Landroid/nfc/tech/Ndef;->mMaxNdefSize:I
-Landroid/nfc/tech/Ndef;->mNdefMsg:Landroid/nfc/NdefMessage;
-Landroid/nfc/tech/Ndef;->mNdefType:I
-Landroid/nfc/tech/Ndef;->NDEF_MODE_READ_ONLY:I
-Landroid/nfc/tech/Ndef;->NDEF_MODE_READ_WRITE:I
-Landroid/nfc/tech/Ndef;->NDEF_MODE_UNKNOWN:I
-Landroid/nfc/tech/Ndef;->TAG:Ljava/lang/String;
-Landroid/nfc/tech/Ndef;->TYPE_1:I
-Landroid/nfc/tech/Ndef;->TYPE_2:I
-Landroid/nfc/tech/Ndef;->TYPE_3:I
-Landroid/nfc/tech/Ndef;->TYPE_4:I
-Landroid/nfc/tech/Ndef;->TYPE_ICODE_SLI:I
-Landroid/nfc/tech/Ndef;->TYPE_MIFARE_CLASSIC:I
-Landroid/nfc/tech/Ndef;->TYPE_OTHER:I
-Landroid/nfc/tech/Ndef;->UNKNOWN:Ljava/lang/String;
-Landroid/nfc/tech/NdefFormatable;-><init>(Landroid/nfc/Tag;)V
-Landroid/nfc/tech/NdefFormatable;->format(Landroid/nfc/NdefMessage;Z)V
-Landroid/nfc/tech/NdefFormatable;->TAG:Ljava/lang/String;
-Landroid/nfc/tech/NfcA;-><init>(Landroid/nfc/Tag;)V
-Landroid/nfc/tech/NfcA;->EXTRA_ATQA:Ljava/lang/String;
-Landroid/nfc/tech/NfcA;->EXTRA_SAK:Ljava/lang/String;
-Landroid/nfc/tech/NfcA;->mAtqa:[B
-Landroid/nfc/tech/NfcA;->mSak:S
-Landroid/nfc/tech/NfcA;->TAG:Ljava/lang/String;
-Landroid/nfc/tech/NfcB;-><init>(Landroid/nfc/Tag;)V
-Landroid/nfc/tech/NfcB;->EXTRA_APPDATA:Ljava/lang/String;
-Landroid/nfc/tech/NfcB;->EXTRA_PROTINFO:Ljava/lang/String;
-Landroid/nfc/tech/NfcB;->mAppData:[B
-Landroid/nfc/tech/NfcB;->mProtInfo:[B
-Landroid/nfc/tech/NfcBarcode;-><init>(Landroid/nfc/Tag;)V
-Landroid/nfc/tech/NfcBarcode;->EXTRA_BARCODE_TYPE:Ljava/lang/String;
-Landroid/nfc/tech/NfcBarcode;->mType:I
-Landroid/nfc/tech/NfcF;-><init>(Landroid/nfc/Tag;)V
-Landroid/nfc/tech/NfcF;->EXTRA_PMM:Ljava/lang/String;
-Landroid/nfc/tech/NfcF;->EXTRA_SC:Ljava/lang/String;
-Landroid/nfc/tech/NfcF;->mManufacturer:[B
-Landroid/nfc/tech/NfcF;->mSystemCode:[B
-Landroid/nfc/tech/NfcF;->TAG:Ljava/lang/String;
-Landroid/nfc/tech/NfcV;-><init>(Landroid/nfc/Tag;)V
-Landroid/nfc/tech/NfcV;->EXTRA_DSFID:Ljava/lang/String;
-Landroid/nfc/tech/NfcV;->EXTRA_RESP_FLAGS:Ljava/lang/String;
-Landroid/nfc/tech/NfcV;->mDsfId:B
-Landroid/nfc/tech/NfcV;->mRespFlags:B
-Landroid/nfc/tech/TagTechnology;->ISO_DEP:I
-Landroid/nfc/tech/TagTechnology;->MIFARE_CLASSIC:I
-Landroid/nfc/tech/TagTechnology;->MIFARE_ULTRALIGHT:I
-Landroid/nfc/tech/TagTechnology;->NDEF:I
-Landroid/nfc/tech/TagTechnology;->NDEF_FORMATABLE:I
-Landroid/nfc/tech/TagTechnology;->NFC_A:I
-Landroid/nfc/tech/TagTechnology;->NFC_B:I
-Landroid/nfc/tech/TagTechnology;->NFC_BARCODE:I
-Landroid/nfc/tech/TagTechnology;->NFC_F:I
-Landroid/nfc/tech/TagTechnology;->NFC_V:I
-Landroid/nfc/tech/TagTechnology;->reconnect()V
-Landroid/nfc/TechListParcel;->CREATOR:Landroid/os/Parcelable$Creator;
-Landroid/nfc/TechListParcel;->getTechLists()[[Ljava/lang/String;
-Landroid/nfc/TechListParcel;->mTechLists:[[Ljava/lang/String;
-Landroid/nfc/TransceiveResult;-><init>(I[B)V
-Landroid/nfc/TransceiveResult;->CREATOR:Landroid/os/Parcelable$Creator;
-Landroid/nfc/TransceiveResult;->getResponseOrThrow()[B
-Landroid/nfc/TransceiveResult;->mResponseData:[B
-Landroid/nfc/TransceiveResult;->mResult:I
-Landroid/nfc/TransceiveResult;->RESULT_EXCEEDED_LENGTH:I
-Landroid/nfc/TransceiveResult;->RESULT_FAILURE:I
-Landroid/nfc/TransceiveResult;->RESULT_SUCCESS:I
-Landroid/nfc/TransceiveResult;->RESULT_TAGLOST:I
Landroid/opengl/EGL14;->eglCreatePbufferFromClientBuffer(Landroid/opengl/EGLDisplay;IJLandroid/opengl/EGLConfig;[II)Landroid/opengl/EGLSurface;
Landroid/opengl/EGL14;->_eglCreateWindowSurface(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLConfig;Ljava/lang/Object;[II)Landroid/opengl/EGLSurface;
Landroid/opengl/EGL14;->_eglCreateWindowSurfaceTexture(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLConfig;Ljava/lang/Object;[II)Landroid/opengl/EGLSurface;
diff --git a/boot/hiddenapi/hiddenapi-max-target-r-loprio.txt b/boot/hiddenapi/hiddenapi-max-target-r-loprio.txt
index f5184e7..4df1dca 100644
--- a/boot/hiddenapi/hiddenapi-max-target-r-loprio.txt
+++ b/boot/hiddenapi/hiddenapi-max-target-r-loprio.txt
@@ -19,7 +19,6 @@
Landroid/media/IVolumeController$Stub;->asInterface(Landroid/os/IBinder;)Landroid/media/IVolumeController;
Landroid/net/INetworkPolicyListener$Stub;-><init>()V
Landroid/net/sip/ISipSession$Stub;->asInterface(Landroid/os/IBinder;)Landroid/net/sip/ISipSession;
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_enable:I
Landroid/os/IPowerManager$Stub;->TRANSACTION_acquireWakeLock:I
Landroid/os/IPowerManager$Stub;->TRANSACTION_goToSleep:I
Landroid/service/euicc/IEuiccService$Stub;-><init>()V
diff --git a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
index 16bb896..55ea15d 100644
--- a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
+++ b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
@@ -468,9 +468,9 @@
entry.name().c_str());
const auto& res_value = entry.res_value();
result.pairs.emplace_back(OverlayData::Value{
- name, TargetValueWithConfig{.config = entry.configuration(), .value = TargetValue{
+ name, TargetValueWithConfig{.value = TargetValue{
.data_type = static_cast<uint8_t>(res_value.data_type()),
- .data_value = res_value.data_value()}}});
+ .data_value = res_value.data_value()}, .config = entry.configuration()}});
}
}
}
diff --git a/cmds/idmap2/libidmap2/ResourceContainer.cpp b/cmds/idmap2/libidmap2/ResourceContainer.cpp
index 7869fbd..3c0e118 100644
--- a/cmds/idmap2/libidmap2/ResourceContainer.cpp
+++ b/cmds/idmap2/libidmap2/ResourceContainer.cpp
@@ -227,9 +227,9 @@
} else {
overlay_data.pairs.emplace_back(
OverlayData::Value{*target_resource, TargetValueWithConfig{
- .config = std::string(),
.value = TargetValue{.data_type = overlay_resource->dataType,
- .data_value = overlay_resource->data}}});
+ .data_value = overlay_resource->data},
+ .config = std::string()}});
}
}
@@ -268,10 +268,11 @@
std::unique_ptr<AssetManager2> am;
ZipAssetsProvider* zip_assets;
- static Result<ResState> Initialize(std::unique_ptr<ZipAssetsProvider> zip) {
+ static Result<ResState> Initialize(std::unique_ptr<ZipAssetsProvider> zip,
+ package_property_t flags) {
ResState state;
state.zip_assets = zip.get();
- if ((state.apk_assets = ApkAssets::Load(std::move(zip))) == nullptr) {
+ if ((state.apk_assets = ApkAssets::Load(std::move(zip), flags)) == nullptr) {
return Error("failed to load apk asset");
}
@@ -284,7 +285,7 @@
}
state.am = std::make_unique<AssetManager2>();
- if (!state.am->SetApkAssets({state.apk_assets})) {
+ if (!state.am->SetApkAssets({state.apk_assets}, false)) {
return Error("failed to create asset manager");
}
@@ -343,8 +344,8 @@
return state;
}
- auto state =
- ResState::Initialize(std::move(std::get<std::unique_ptr<ZipAssetsProvider>>(state_)));
+ auto state = ResState::Initialize(std::move(std::get<std::unique_ptr<ZipAssetsProvider>>(state_)),
+ PROPERTY_OPTIMIZE_NAME_LOOKUPS);
if (!state) {
return state.GetError();
}
diff --git a/core/api/current.txt b/core/api/current.txt
index af821b6..40dcc42 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -49,6 +49,7 @@
field public static final String BIND_SCREENING_SERVICE = "android.permission.BIND_SCREENING_SERVICE";
field public static final String BIND_TELECOM_CONNECTION_SERVICE = "android.permission.BIND_TELECOM_CONNECTION_SERVICE";
field public static final String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String BIND_TV_AD_SERVICE = "android.permission.BIND_TV_AD_SERVICE";
field public static final String BIND_TV_INPUT = "android.permission.BIND_TV_INPUT";
field public static final String BIND_TV_INTERACTIVE_APP = "android.permission.BIND_TV_INTERACTIVE_APP";
field public static final String BIND_VISUAL_VOICEMAIL_SERVICE = "android.permission.BIND_VISUAL_VOICEMAIL_SERVICE";
@@ -1601,7 +1602,6 @@
field public static final int switchTextOff = 16843628; // 0x101036c
field public static final int switchTextOn = 16843627; // 0x101036b
field public static final int syncable = 16842777; // 0x1010019
- field @FlaggedApi("android.multiuser.enable_system_user_only_for_services_and_providers") public static final int systemUserOnly;
field public static final int tabStripEnabled = 16843453; // 0x10102bd
field public static final int tabStripLeft = 16843451; // 0x10102bb
field public static final int tabStripRight = 16843452; // 0x10102bc
@@ -4371,7 +4371,7 @@
method public final android.media.session.MediaController getMediaController();
method @NonNull public android.view.MenuInflater getMenuInflater();
method @NonNull public android.window.OnBackInvokedDispatcher getOnBackInvokedDispatcher();
- method @Deprecated public final android.app.Activity getParent();
+ method 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();
@@ -4389,7 +4389,7 @@
method public void invalidateOptionsMenu();
method public boolean isActivityTransitionRunning();
method public boolean isChangingConfigurations();
- method @Deprecated public final boolean isChild();
+ method public final boolean isChild();
method public boolean isDestroyed();
method public boolean isFinishing();
method public boolean isImmersive();
@@ -4624,9 +4624,9 @@
public class ActivityManager {
method public int addAppTask(@NonNull android.app.Activity, @NonNull android.content.Intent, @Nullable android.app.ActivityManager.TaskDescription, @NonNull android.graphics.Bitmap);
+ method @FlaggedApi("android.app.app_start_info") public void addApplicationStartInfoCompletionListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.ApplicationStartInfo>);
method @FlaggedApi("android.app.app_start_info") public void addStartInfoTimestamp(@IntRange(from=android.app.ApplicationStartInfo.START_TIMESTAMP_RESERVED_RANGE_DEVELOPER_START, to=android.app.ApplicationStartInfo.START_TIMESTAMP_RESERVED_RANGE_DEVELOPER) int, long);
method public void appNotResponding(@NonNull String);
- method @FlaggedApi("android.app.app_start_info") public void clearApplicationStartInfoCompletionListener();
method public boolean clearApplicationUserData();
method public void clearWatchHeapLimit();
method @RequiresPermission(android.Manifest.permission.DUMP) public void dumpPackageState(java.io.FileDescriptor, String);
@@ -4660,8 +4660,8 @@
method @RequiresPermission(android.Manifest.permission.KILL_BACKGROUND_PROCESSES) public void killBackgroundProcesses(String);
method @RequiresPermission(android.Manifest.permission.REORDER_TASKS) public void moveTaskToFront(int, int);
method @RequiresPermission(android.Manifest.permission.REORDER_TASKS) public void moveTaskToFront(int, int, android.os.Bundle);
+ method @FlaggedApi("android.app.app_start_info") public void removeApplicationStartInfoCompletionListener(@NonNull java.util.function.Consumer<android.app.ApplicationStartInfo>);
method @Deprecated public void restartPackage(String);
- method @FlaggedApi("android.app.app_start_info") public void setApplicationStartInfoCompletionListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.ApplicationStartInfo>);
method public void setProcessStateSummary(@Nullable byte[]);
method public static void setVrThread(int);
method public void setWatchHeapLimit(long);
@@ -5317,7 +5317,6 @@
ctor @Deprecated public AutomaticZenRule(String, android.content.ComponentName, android.net.Uri, 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 @FlaggedApi("android.app.modes_api") public boolean canUpdate();
method public int describeContents();
method public android.net.Uri getConditionId();
method @Nullable public android.content.ComponentName getConfigurationActivity();
@@ -9380,15 +9379,15 @@
method public int describeContents();
method public long getBeginTimeMillis();
method public long getEndTimeMillis();
- method @NonNull public java.util.Set<java.lang.Integer> getEventTypes();
+ method @NonNull public int[] getEventTypes();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.usage.UsageEventsQuery> CREATOR;
}
public static final class UsageEventsQuery.Builder {
ctor public UsageEventsQuery.Builder(long, long);
- method @NonNull public android.app.usage.UsageEventsQuery.Builder addEventTypes(@NonNull int...);
method @NonNull public android.app.usage.UsageEventsQuery build();
+ method @NonNull public android.app.usage.UsageEventsQuery.Builder setEventTypes(@NonNull int...);
}
public final class UsageStats implements android.os.Parcelable {
@@ -9415,7 +9414,7 @@
method public java.util.List<android.app.usage.ConfigurationStats> queryConfigurations(int, long, long);
method public java.util.List<android.app.usage.EventStats> queryEventStats(int, long, long);
method public android.app.usage.UsageEvents queryEvents(long, long);
- method @FlaggedApi("android.app.usage.filter_based_event_query_api") @NonNull @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public android.app.usage.UsageEvents queryEvents(@NonNull android.app.usage.UsageEventsQuery);
+ method @FlaggedApi("android.app.usage.filter_based_event_query_api") @Nullable @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public android.app.usage.UsageEvents queryEvents(@NonNull android.app.usage.UsageEventsQuery);
method public android.app.usage.UsageEvents queryEventsForSelf(long, long);
method public java.util.List<android.app.usage.UsageStats> queryUsageStats(int, long, long);
field @FlaggedApi("android.app.usage.user_interaction_type_api") public static final String EXTRA_EVENT_ACTION = "android.app.usage.extra.EVENT_ACTION";
@@ -9485,12 +9484,15 @@
method @NonNull public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProvidersForPackage(@NonNull String, @Nullable android.os.UserHandle);
method @NonNull public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProvidersForProfile(@Nullable android.os.UserHandle);
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 @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);
method public boolean requestPinAppWidget(@NonNull android.content.ComponentName, @Nullable android.os.Bundle, @Nullable android.app.PendingIntent);
+ method @FlaggedApi("android.appwidget.flags.generated_previews") public void setWidgetPreview(@NonNull android.content.ComponentName, int, @NonNull android.widget.RemoteViews);
method public void updateAppWidget(int[], android.widget.RemoteViews);
method public void updateAppWidget(int, android.widget.RemoteViews);
method public void updateAppWidget(android.content.ComponentName, android.widget.RemoteViews);
@@ -9564,6 +9566,7 @@
field public int autoAdvanceViewId;
field public android.content.ComponentName configure;
field @IdRes public int descriptionRes;
+ field @FlaggedApi("android.appwidget.flags.generated_previews") public int generatedPreviewCategories;
field public int icon;
field public int initialKeyguardLayout;
field public int initialLayout;
@@ -10382,6 +10385,7 @@
method @CheckResult(suggest="#enforceCallingPermission(String,String)") public abstract int checkCallingPermission(@NonNull String);
method @CheckResult(suggest="#enforceCallingUriPermission(Uri,int,String)") public abstract int checkCallingUriPermission(android.net.Uri, int);
method @NonNull public int[] checkCallingUriPermissions(@NonNull java.util.List<android.net.Uri>, int);
+ method @FlaggedApi("android.security.content_uri_permission_apis") public int checkContentUriPermissionFull(@NonNull android.net.Uri, int, int, int);
method @CheckResult(suggest="#enforcePermission(String,int,int,String)") public abstract int checkPermission(@NonNull String, int, int);
method public abstract int checkSelfPermission(@NonNull String);
method @CheckResult(suggest="#enforceUriPermission(Uri,int,int,String)") public abstract int checkUriPermission(android.net.Uri, int, int, int);
@@ -10540,6 +10544,7 @@
field public static final int BIND_INCLUDE_CAPABILITIES = 4096; // 0x1000
field public static final int BIND_NOT_FOREGROUND = 4; // 0x4
field public static final int BIND_NOT_PERCEPTIBLE = 256; // 0x100
+ field @FlaggedApi("android.content.flags.enable_bind_package_isolated_process") public static final int BIND_PACKAGE_ISOLATED_PROCESS = 16384; // 0x4000
field public static final int BIND_SHARED_ISOLATED_PROCESS = 8192; // 0x2000
field public static final int BIND_WAIVE_PRIORITY = 32; // 0x20
field public static final String BIOMETRIC_SERVICE = "biometric";
@@ -12440,7 +12445,7 @@
method public void registerSessionCallback(@NonNull android.content.pm.PackageInstaller.SessionCallback);
method public void registerSessionCallback(@NonNull android.content.pm.PackageInstaller.SessionCallback, @NonNull android.os.Handler);
method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void reportUnarchivalStatus(int, int, long, @Nullable android.app.PendingIntent) throws android.content.pm.PackageManager.NameNotFoundException;
- method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException;
method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String, @NonNull android.content.IntentSender) throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException;
method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void uninstall(@NonNull String, @NonNull android.content.IntentSender);
method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void uninstall(@NonNull android.content.pm.VersionedPackage, @NonNull android.content.IntentSender);
@@ -12867,7 +12872,6 @@
field public static final int COMPONENT_ENABLED_STATE_DISABLED_USER = 3; // 0x3
field public static final int COMPONENT_ENABLED_STATE_ENABLED = 1; // 0x1
field @FlaggedApi("android.content.pm.archiving") public static final int DELETE_ARCHIVE = 16; // 0x10
- field @FlaggedApi("android.content.pm.archiving") public static final int DELETE_SHOW_DIALOG = 32; // 0x20
field public static final int DONT_KILL_APP = 1; // 0x1
field public static final String EXTRA_VERIFICATION_ID = "android.content.pm.extra.VERIFICATION_ID";
field public static final String EXTRA_VERIFICATION_RESULT = "android.content.pm.extra.VERIFICATION_RESULT";
@@ -13670,11 +13674,8 @@
@FlaggedApi("android.content.res.font_scale_converter_public") public interface FontScaleConverter {
method public float convertDpToSp(float);
method public float convertSpToDp(float);
- }
-
- @FlaggedApi("android.content.res.font_scale_converter_public") public class FontScaleConverterFactory {
- method @FlaggedApi("android.content.res.font_scale_converter_public") @AnyThread @Nullable public static android.content.res.FontScaleConverter forScale(float);
- method @FlaggedApi("android.content.res.font_scale_converter_public") @AnyThread public static boolean isNonLinearFontScalingActive(float);
+ method @AnyThread @Nullable public static android.content.res.FontScaleConverter forScale(float);
+ method @AnyThread public static boolean isNonLinearFontScalingActive(float);
}
public class ObbInfo implements android.os.Parcelable {
@@ -16394,6 +16395,8 @@
field public static final int START_HYPHEN_EDIT_NO_EDIT = 0; // 0x0
field public static final int STRIKE_THRU_TEXT_FLAG = 16; // 0x10
field public static final int SUBPIXEL_TEXT_FLAG = 128; // 0x80
+ field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int TEXT_RUN_FLAG_LEFT_EDGE = 8192; // 0x2000
+ field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int TEXT_RUN_FLAG_RIGHT_EDGE = 16384; // 0x4000
field public static final int UNDERLINE_TEXT_FLAG = 8; // 0x8
}
@@ -18675,7 +18678,10 @@
method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public void authenticate(@NonNull android.hardware.biometrics.BiometricPrompt.CryptoObject, @NonNull android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.biometrics.BiometricPrompt.AuthenticationCallback);
method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public void authenticate(@NonNull android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.biometrics.BiometricPrompt.AuthenticationCallback);
method @Nullable public int getAllowedAuthenticators();
+ method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable public android.hardware.biometrics.PromptContentView getContentView();
method @Nullable public CharSequence getDescription();
+ method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public android.graphics.Bitmap getLogoBitmap();
+ method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @DrawableRes @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public int getLogoRes();
method @Nullable public CharSequence getNegativeButtonText();
method @Nullable public CharSequence getSubtitle();
method @NonNull public CharSequence getTitle();
@@ -18722,8 +18728,11 @@
method @NonNull public android.hardware.biometrics.BiometricPrompt build();
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setAllowedAuthenticators(int);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setConfirmationRequired(boolean);
+ method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setContentView(@NonNull android.hardware.biometrics.PromptContentView);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDescription(@NonNull CharSequence);
method @Deprecated @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDeviceCredentialAllowed(boolean);
+ method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public android.hardware.biometrics.BiometricPrompt.Builder setLogo(@DrawableRes int);
+ method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public android.hardware.biometrics.BiometricPrompt.Builder setLogo(@NonNull android.graphics.Bitmap);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setNegativeButton(@NonNull CharSequence, @NonNull java.util.concurrent.Executor, @NonNull android.content.DialogInterface.OnClickListener);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setSubtitle(@NonNull CharSequence);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setTitle(@NonNull CharSequence);
@@ -18745,6 +18754,44 @@
method @Nullable public java.security.Signature getSignature();
}
+ @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public interface PromptContentItem {
+ }
+
+ @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptContentItemBulletedText implements android.os.Parcelable android.hardware.biometrics.PromptContentItem {
+ ctor public PromptContentItemBulletedText(@NonNull CharSequence);
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.biometrics.PromptContentItemBulletedText> CREATOR;
+ }
+
+ @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptContentItemPlainText implements android.os.Parcelable android.hardware.biometrics.PromptContentItem {
+ ctor public PromptContentItemPlainText(@NonNull CharSequence);
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.biometrics.PromptContentItemPlainText> CREATOR;
+ }
+
+ @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public interface PromptContentView {
+ }
+
+ @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptVerticalListContentView implements android.os.Parcelable android.hardware.biometrics.PromptContentView {
+ method public int describeContents();
+ method @Nullable public CharSequence getDescription();
+ method @NonNull public java.util.List<android.hardware.biometrics.PromptContentItem> getListItems();
+ method public static int getMaxEachItemCharacterNumber();
+ method public static int getMaxItemCount();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.biometrics.PromptVerticalListContentView> CREATOR;
+ }
+
+ public static final class PromptVerticalListContentView.Builder {
+ ctor public PromptVerticalListContentView.Builder();
+ method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder addListItem(@NonNull android.hardware.biometrics.PromptContentItem);
+ method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder addListItem(@NonNull android.hardware.biometrics.PromptContentItem, int);
+ method @NonNull public android.hardware.biometrics.PromptVerticalListContentView build();
+ method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder setDescription(@NonNull CharSequence);
+ }
+
}
package android.hardware.camera2 {
@@ -19734,7 +19781,7 @@
@FlaggedApi("com.android.internal.camera.flags.concert_mode") public final class LensIntrinsicsSample {
ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public LensIntrinsicsSample(long, @NonNull float[]);
method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public float[] getLensIntrinsics();
- method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public long getTimestamp();
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public long getTimestampNanos();
}
public final class LensShadingMap {
@@ -23296,6 +23343,7 @@
field public static final String KEY_HDR10_PLUS_INFO = "hdr10-plus-info";
field public static final String KEY_HDR_STATIC_INFO = "hdr-static-info";
field public static final String KEY_HEIGHT = "height";
+ field @FlaggedApi("com.android.media.codec.flags.codec_importance") public static final String KEY_IMPORTANCE = "importance";
field public static final String KEY_INTRA_REFRESH_PERIOD = "intra-refresh-period";
field public static final String KEY_IS_ADTS = "is-adts";
field public static final String KEY_IS_AUTOSELECT = "is-autoselect";
@@ -24258,7 +24306,7 @@
method public void release();
method public void selectRoute(@NonNull android.media.MediaRoute2Info);
method public void setVolume(int);
- method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public boolean wasTransferRequestedBySelf();
+ method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public boolean wasTransferInitiatedBySelf();
}
public abstract static class MediaRouter2.TransferCallback {
@@ -26624,12 +26672,16 @@
public static final class TvContract.Channels implements android.media.tv.TvContract.BaseTvColumns {
method @Nullable public static String getVideoResolution(String);
+ field @FlaggedApi("android.media.tv.flags.broadcast_visibility_types") public static final int BROADCAST_VISIBILITY_TYPE_INVISIBLE = 2; // 0x2
+ field @FlaggedApi("android.media.tv.flags.broadcast_visibility_types") public static final int BROADCAST_VISIBILITY_TYPE_NUMERIC_SELECTABLE_ONLY = 1; // 0x1
+ field @FlaggedApi("android.media.tv.flags.broadcast_visibility_types") public static final int BROADCAST_VISIBILITY_TYPE_VISIBLE = 0; // 0x0
field public static final String COLUMN_APP_LINK_COLOR = "app_link_color";
field public static final String COLUMN_APP_LINK_ICON_URI = "app_link_icon_uri";
field public static final String COLUMN_APP_LINK_INTENT_URI = "app_link_intent_uri";
field public static final String COLUMN_APP_LINK_POSTER_ART_URI = "app_link_poster_art_uri";
field public static final String COLUMN_APP_LINK_TEXT = "app_link_text";
field public static final String COLUMN_BROADCAST_GENRE = "broadcast_genre";
+ field @FlaggedApi("android.media.tv.flags.broadcast_visibility_types") public static final String COLUMN_BROADCAST_VISIBILITY_TYPE = "broadcast_visibility_type";
field public static final String COLUMN_BROWSABLE = "browsable";
field public static final String COLUMN_CHANNEL_LIST_ID = "channel_list_id";
field public static final String COLUMN_DESCRIPTION = "description";
@@ -28748,460 +28800,6 @@
}
-package android.nfc {
-
- public final class AvailableNfcAntenna implements android.os.Parcelable {
- ctor public AvailableNfcAntenna(int, int);
- method public int describeContents();
- method public int getLocationX();
- method public int getLocationY();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.nfc.AvailableNfcAntenna> CREATOR;
- }
-
- public class FormatException extends java.lang.Exception {
- ctor public FormatException();
- ctor public FormatException(String);
- ctor public FormatException(String, Throwable);
- }
-
- public final class NdefMessage implements android.os.Parcelable {
- ctor public NdefMessage(byte[]) throws android.nfc.FormatException;
- ctor public NdefMessage(android.nfc.NdefRecord, android.nfc.NdefRecord...);
- ctor public NdefMessage(android.nfc.NdefRecord[]);
- method public int describeContents();
- method public int getByteArrayLength();
- method public android.nfc.NdefRecord[] getRecords();
- method public byte[] toByteArray();
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.nfc.NdefMessage> CREATOR;
- }
-
- public final class NdefRecord implements android.os.Parcelable {
- ctor public NdefRecord(short, byte[], byte[], byte[]);
- ctor @Deprecated public NdefRecord(byte[]) throws android.nfc.FormatException;
- method public static android.nfc.NdefRecord createApplicationRecord(String);
- method public static android.nfc.NdefRecord createExternal(String, String, byte[]);
- method public static android.nfc.NdefRecord createMime(String, byte[]);
- method public static android.nfc.NdefRecord createTextRecord(String, String);
- method public static android.nfc.NdefRecord createUri(android.net.Uri);
- method public static android.nfc.NdefRecord createUri(String);
- method public int describeContents();
- method public byte[] getId();
- method public byte[] getPayload();
- method public short getTnf();
- method public byte[] getType();
- method @Deprecated public byte[] toByteArray();
- method public String toMimeType();
- method public android.net.Uri toUri();
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.nfc.NdefRecord> CREATOR;
- field public static final byte[] RTD_ALTERNATIVE_CARRIER;
- field public static final byte[] RTD_HANDOVER_CARRIER;
- field public static final byte[] RTD_HANDOVER_REQUEST;
- field public static final byte[] RTD_HANDOVER_SELECT;
- field public static final byte[] RTD_SMART_POSTER;
- field public static final byte[] RTD_TEXT;
- field public static final byte[] RTD_URI;
- field public static final short TNF_ABSOLUTE_URI = 3; // 0x3
- field public static final short TNF_EMPTY = 0; // 0x0
- field public static final short TNF_EXTERNAL_TYPE = 4; // 0x4
- field public static final short TNF_MIME_MEDIA = 2; // 0x2
- field public static final short TNF_UNCHANGED = 6; // 0x6
- field public static final short TNF_UNKNOWN = 5; // 0x5
- field public static final short TNF_WELL_KNOWN = 1; // 0x1
- }
-
- public final class NfcAdapter {
- method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean allowTransaction();
- method public void disableForegroundDispatch(android.app.Activity);
- method public void disableReaderMode(android.app.Activity);
- method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean disallowTransaction();
- method public void enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]);
- method public void enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle);
- method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context);
- method @Nullable public android.nfc.NfcAntennaInfo getNfcAntennaInfo();
- method @FlaggedApi("android.nfc.enable_nfc_charging") @Nullable public android.nfc.WlcLDeviceInfo getWlcLDeviceInfo();
- method public boolean ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler);
- method public boolean isEnabled();
- method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean isObserveModeSupported();
- method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionEnabled();
- method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionSupported();
- method public boolean isSecureNfcEnabled();
- method public boolean isSecureNfcSupported();
- method @FlaggedApi("android.nfc.enable_nfc_charging") public boolean isWlcEnabled();
- method @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public void resetDiscoveryTechnology(@NonNull android.app.Activity);
- method @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public void setDiscoveryTechnology(@NonNull android.app.Activity, int, int);
- field public static final String ACTION_ADAPTER_STATE_CHANGED = "android.nfc.action.ADAPTER_STATE_CHANGED";
- field public static final String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED";
- field @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static final String ACTION_PREFERRED_PAYMENT_CHANGED = "android.nfc.action.PREFERRED_PAYMENT_CHANGED";
- field public static final String ACTION_TAG_DISCOVERED = "android.nfc.action.TAG_DISCOVERED";
- field public static final String ACTION_TECH_DISCOVERED = "android.nfc.action.TECH_DISCOVERED";
- field @RequiresPermission(android.Manifest.permission.NFC_TRANSACTION_EVENT) public static final String ACTION_TRANSACTION_DETECTED = "android.nfc.action.TRANSACTION_DETECTED";
- field public static final String EXTRA_ADAPTER_STATE = "android.nfc.extra.ADAPTER_STATE";
- field public static final String EXTRA_AID = "android.nfc.extra.AID";
- field public static final String EXTRA_DATA = "android.nfc.extra.DATA";
- field public static final String EXTRA_ID = "android.nfc.extra.ID";
- field public static final String EXTRA_NDEF_MESSAGES = "android.nfc.extra.NDEF_MESSAGES";
- field public static final String EXTRA_PREFERRED_PAYMENT_CHANGED_REASON = "android.nfc.extra.PREFERRED_PAYMENT_CHANGED_REASON";
- field public static final String EXTRA_READER_PRESENCE_CHECK_DELAY = "presence";
- field public static final String EXTRA_SECURE_ELEMENT_NAME = "android.nfc.extra.SECURE_ELEMENT_NAME";
- field public static final String EXTRA_TAG = "android.nfc.extra.TAG";
- field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_DISABLE = 0; // 0x0
- field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_KEEP = -1; // 0xffffffff
- field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_NFC_PASSIVE_A = 1; // 0x1
- field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_NFC_PASSIVE_B = 2; // 0x2
- field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_NFC_PASSIVE_F = 4; // 0x4
- field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_READER_DISABLE = 0; // 0x0
- field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_READER_KEEP = -1; // 0xffffffff
- field public static final int FLAG_READER_NFC_A = 1; // 0x1
- field public static final int FLAG_READER_NFC_B = 2; // 0x2
- field public static final int FLAG_READER_NFC_BARCODE = 16; // 0x10
- field public static final int FLAG_READER_NFC_F = 4; // 0x4
- field public static final int FLAG_READER_NFC_V = 8; // 0x8
- field public static final int FLAG_READER_NO_PLATFORM_SOUNDS = 256; // 0x100
- field public static final int FLAG_READER_SKIP_NDEF_CHECK = 128; // 0x80
- field public static final int PREFERRED_PAYMENT_CHANGED = 2; // 0x2
- field public static final int PREFERRED_PAYMENT_LOADED = 1; // 0x1
- field public static final int PREFERRED_PAYMENT_UPDATED = 3; // 0x3
- field public static final int STATE_OFF = 1; // 0x1
- field public static final int STATE_ON = 3; // 0x3
- field public static final int STATE_TURNING_OFF = 4; // 0x4
- field public static final int STATE_TURNING_ON = 2; // 0x2
- }
-
- @Deprecated public static interface NfcAdapter.CreateBeamUrisCallback {
- method @Deprecated public android.net.Uri[] createBeamUris(android.nfc.NfcEvent);
- }
-
- @Deprecated public static interface NfcAdapter.CreateNdefMessageCallback {
- method @Deprecated public android.nfc.NdefMessage createNdefMessage(android.nfc.NfcEvent);
- }
-
- @Deprecated public static interface NfcAdapter.OnNdefPushCompleteCallback {
- method @Deprecated public void onNdefPushComplete(android.nfc.NfcEvent);
- }
-
- public static interface NfcAdapter.OnTagRemovedListener {
- method public void onTagRemoved();
- }
-
- public static interface NfcAdapter.ReaderCallback {
- method public void onTagDiscovered(android.nfc.Tag);
- }
-
- public final class NfcAntennaInfo implements android.os.Parcelable {
- ctor public NfcAntennaInfo(int, int, boolean, @NonNull java.util.List<android.nfc.AvailableNfcAntenna>);
- method public int describeContents();
- method @NonNull public java.util.List<android.nfc.AvailableNfcAntenna> getAvailableNfcAntennas();
- method public int getDeviceHeight();
- method public int getDeviceWidth();
- method public boolean isDeviceFoldable();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.nfc.NfcAntennaInfo> CREATOR;
- }
-
- public final class NfcEvent {
- field public final android.nfc.NfcAdapter nfcAdapter;
- field public final int peerLlcpMajorVersion;
- field public final int peerLlcpMinorVersion;
- }
-
- public final class NfcManager {
- method public android.nfc.NfcAdapter getDefaultAdapter();
- }
-
- public final class Tag implements android.os.Parcelable {
- method public int describeContents();
- method public byte[] getId();
- method public String[] getTechList();
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.nfc.Tag> CREATOR;
- }
-
- public class TagLostException extends java.io.IOException {
- ctor public TagLostException();
- ctor public TagLostException(String);
- }
-
- @FlaggedApi("android.nfc.enable_nfc_charging") public final class WlcLDeviceInfo implements android.os.Parcelable {
- ctor public WlcLDeviceInfo(double, double, double, int);
- method public int describeContents();
- method public double getBatteryLevel();
- method public double getProductId();
- method public int getState();
- method public double getTemperature();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field public static final int CONNECTED_CHARGING = 2; // 0x2
- field public static final int CONNECTED_DISCHARGING = 3; // 0x3
- field @NonNull public static final android.os.Parcelable.Creator<android.nfc.WlcLDeviceInfo> CREATOR;
- field public static final int DISCONNECTED = 1; // 0x1
- }
-
-}
-
-package android.nfc.cardemulation {
-
- public final class CardEmulation {
- method public boolean categoryAllowsForegroundPreference(String);
- method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public java.util.List<java.lang.String> getAidsForPreferredPaymentService();
- method public java.util.List<java.lang.String> getAidsForService(android.content.ComponentName, String);
- method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public CharSequence getDescriptionForPreferredPaymentService();
- method public static android.nfc.cardemulation.CardEmulation getInstance(android.nfc.NfcAdapter);
- method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public String getRouteDestinationForPreferredPaymentService();
- method public int getSelectionModeForCategory(String);
- method public boolean isDefaultServiceForAid(android.content.ComponentName, String);
- method public boolean isDefaultServiceForCategory(android.content.ComponentName, String);
- method public boolean registerAidsForService(android.content.ComponentName, String, java.util.List<java.lang.String>);
- method public boolean removeAidsForService(android.content.ComponentName, String);
- method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean setOffHostForService(@NonNull android.content.ComponentName, @NonNull String);
- method public boolean setPreferredService(android.app.Activity, android.content.ComponentName);
- method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setServiceObserveModeDefault(@NonNull android.content.ComponentName, boolean);
- method public boolean supportsAidPrefixRegistration();
- method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean unsetOffHostForService(@NonNull android.content.ComponentName);
- method public boolean unsetPreferredService(android.app.Activity);
- field @Deprecated public static final String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT";
- field public static final String CATEGORY_OTHER = "other";
- field public static final String CATEGORY_PAYMENT = "payment";
- field public static final String EXTRA_CATEGORY = "category";
- field public static final String EXTRA_SERVICE_COMPONENT = "component";
- field public static final int SELECTION_MODE_ALWAYS_ASK = 1; // 0x1
- field public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2; // 0x2
- field public static final int SELECTION_MODE_PREFER_DEFAULT = 0; // 0x0
- }
-
- public abstract class HostApduService extends android.app.Service {
- ctor public HostApduService();
- method public final void notifyUnhandled();
- method public final android.os.IBinder onBind(android.content.Intent);
- method public abstract void onDeactivated(int);
- method public abstract byte[] processCommandApdu(byte[], android.os.Bundle);
- method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void processPollingFrames(@NonNull java.util.List<android.os.Bundle>);
- method public final void sendResponseApdu(byte[]);
- field public static final int DEACTIVATION_DESELECTED = 1; // 0x1
- field public static final int DEACTIVATION_LINK_LOSS = 0; // 0x0
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_DATA_KEY = "android.nfc.cardemulation.DATA";
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_GAIN_KEY = "android.nfc.cardemulation.GAIN";
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TIMESTAMP_KEY = "android.nfc.cardemulation.TIMESTAMP";
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_A = 65; // 0x0041 'A'
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_B = 66; // 0x0042 'B'
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_F = 70; // 0x0046 'F'
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TYPE_KEY = "android.nfc.cardemulation.TYPE";
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_OFF = 88; // 0x0058 'X'
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_ON = 79; // 0x004f 'O'
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_UNKNOWN = 85; // 0x0055 'U'
- field public static final String SERVICE_INTERFACE = "android.nfc.cardemulation.action.HOST_APDU_SERVICE";
- field public static final String SERVICE_META_DATA = "android.nfc.cardemulation.host_apdu_service";
- }
-
- public abstract class HostNfcFService extends android.app.Service {
- ctor public HostNfcFService();
- method public final android.os.IBinder onBind(android.content.Intent);
- method public abstract void onDeactivated(int);
- method public abstract byte[] processNfcFPacket(byte[], android.os.Bundle);
- method public final void sendResponsePacket(byte[]);
- field public static final int DEACTIVATION_LINK_LOSS = 0; // 0x0
- field public static final String SERVICE_INTERFACE = "android.nfc.cardemulation.action.HOST_NFCF_SERVICE";
- field public static final String SERVICE_META_DATA = "android.nfc.cardemulation.host_nfcf_service";
- }
-
- public final class NfcFCardEmulation {
- method public boolean disableService(android.app.Activity) throws java.lang.RuntimeException;
- method public boolean enableService(android.app.Activity, android.content.ComponentName) throws java.lang.RuntimeException;
- method public static android.nfc.cardemulation.NfcFCardEmulation getInstance(android.nfc.NfcAdapter);
- method public String getNfcid2ForService(android.content.ComponentName) throws java.lang.RuntimeException;
- method public String getSystemCodeForService(android.content.ComponentName) throws java.lang.RuntimeException;
- method public boolean registerSystemCodeForService(android.content.ComponentName, String) throws java.lang.RuntimeException;
- method public boolean setNfcid2ForService(android.content.ComponentName, String) throws java.lang.RuntimeException;
- method public boolean unregisterSystemCodeForService(android.content.ComponentName) throws java.lang.RuntimeException;
- }
-
- public abstract class OffHostApduService extends android.app.Service {
- ctor public OffHostApduService();
- field public static final String SERVICE_INTERFACE = "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE";
- field public static final String SERVICE_META_DATA = "android.nfc.cardemulation.off_host_apdu_service";
- }
-
-}
-
-package android.nfc.tech {
-
- public final class IsoDep implements android.nfc.tech.TagTechnology {
- method public void close() throws java.io.IOException;
- method public void connect() throws java.io.IOException;
- method public static android.nfc.tech.IsoDep get(android.nfc.Tag);
- method public byte[] getHiLayerResponse();
- method public byte[] getHistoricalBytes();
- method public int getMaxTransceiveLength();
- method public android.nfc.Tag getTag();
- method public int getTimeout();
- method public boolean isConnected();
- method public boolean isExtendedLengthApduSupported();
- method public void setTimeout(int);
- method public byte[] transceive(byte[]) throws java.io.IOException;
- }
-
- public final class MifareClassic implements android.nfc.tech.TagTechnology {
- method public boolean authenticateSectorWithKeyA(int, byte[]) throws java.io.IOException;
- method public boolean authenticateSectorWithKeyB(int, byte[]) throws java.io.IOException;
- method public int blockToSector(int);
- method public void close() throws java.io.IOException;
- method public void connect() throws java.io.IOException;
- method public void decrement(int, int) throws java.io.IOException;
- method public static android.nfc.tech.MifareClassic get(android.nfc.Tag);
- method public int getBlockCount();
- method public int getBlockCountInSector(int);
- method public int getMaxTransceiveLength();
- method public int getSectorCount();
- method public int getSize();
- method public android.nfc.Tag getTag();
- method public int getTimeout();
- method public int getType();
- method public void increment(int, int) throws java.io.IOException;
- method public boolean isConnected();
- method public byte[] readBlock(int) throws java.io.IOException;
- method public void restore(int) throws java.io.IOException;
- method public int sectorToBlock(int);
- method public void setTimeout(int);
- method public byte[] transceive(byte[]) throws java.io.IOException;
- method public void transfer(int) throws java.io.IOException;
- method public void writeBlock(int, byte[]) throws java.io.IOException;
- field public static final int BLOCK_SIZE = 16; // 0x10
- field public static final byte[] KEY_DEFAULT;
- field public static final byte[] KEY_MIFARE_APPLICATION_DIRECTORY;
- field public static final byte[] KEY_NFC_FORUM;
- field public static final int SIZE_1K = 1024; // 0x400
- field public static final int SIZE_2K = 2048; // 0x800
- field public static final int SIZE_4K = 4096; // 0x1000
- field public static final int SIZE_MINI = 320; // 0x140
- field public static final int TYPE_CLASSIC = 0; // 0x0
- field public static final int TYPE_PLUS = 1; // 0x1
- field public static final int TYPE_PRO = 2; // 0x2
- field public static final int TYPE_UNKNOWN = -1; // 0xffffffff
- }
-
- public final class MifareUltralight implements android.nfc.tech.TagTechnology {
- method public void close() throws java.io.IOException;
- method public void connect() throws java.io.IOException;
- method public static android.nfc.tech.MifareUltralight get(android.nfc.Tag);
- method public int getMaxTransceiveLength();
- method public android.nfc.Tag getTag();
- method public int getTimeout();
- method public int getType();
- method public boolean isConnected();
- method public byte[] readPages(int) throws java.io.IOException;
- method public void setTimeout(int);
- method public byte[] transceive(byte[]) throws java.io.IOException;
- method public void writePage(int, byte[]) throws java.io.IOException;
- field public static final int PAGE_SIZE = 4; // 0x4
- field public static final int TYPE_ULTRALIGHT = 1; // 0x1
- field public static final int TYPE_ULTRALIGHT_C = 2; // 0x2
- field public static final int TYPE_UNKNOWN = -1; // 0xffffffff
- }
-
- public final class Ndef implements android.nfc.tech.TagTechnology {
- method public boolean canMakeReadOnly();
- method public void close() throws java.io.IOException;
- method public void connect() throws java.io.IOException;
- method public static android.nfc.tech.Ndef get(android.nfc.Tag);
- method public android.nfc.NdefMessage getCachedNdefMessage();
- method public int getMaxSize();
- method public android.nfc.NdefMessage getNdefMessage() throws android.nfc.FormatException, java.io.IOException;
- method public android.nfc.Tag getTag();
- method public String getType();
- method public boolean isConnected();
- method public boolean isWritable();
- method public boolean makeReadOnly() throws java.io.IOException;
- method public void writeNdefMessage(android.nfc.NdefMessage) throws android.nfc.FormatException, java.io.IOException;
- field public static final String MIFARE_CLASSIC = "com.nxp.ndef.mifareclassic";
- field public static final String NFC_FORUM_TYPE_1 = "org.nfcforum.ndef.type1";
- field public static final String NFC_FORUM_TYPE_2 = "org.nfcforum.ndef.type2";
- field public static final String NFC_FORUM_TYPE_3 = "org.nfcforum.ndef.type3";
- field public static final String NFC_FORUM_TYPE_4 = "org.nfcforum.ndef.type4";
- }
-
- public final class NdefFormatable implements android.nfc.tech.TagTechnology {
- method public void close() throws java.io.IOException;
- method public void connect() throws java.io.IOException;
- method public void format(android.nfc.NdefMessage) throws android.nfc.FormatException, java.io.IOException;
- method public void formatReadOnly(android.nfc.NdefMessage) throws android.nfc.FormatException, java.io.IOException;
- method public static android.nfc.tech.NdefFormatable get(android.nfc.Tag);
- method public android.nfc.Tag getTag();
- method public boolean isConnected();
- }
-
- public final class NfcA implements android.nfc.tech.TagTechnology {
- method public void close() throws java.io.IOException;
- method public void connect() throws java.io.IOException;
- method public static android.nfc.tech.NfcA get(android.nfc.Tag);
- method public byte[] getAtqa();
- method public int getMaxTransceiveLength();
- method public short getSak();
- method public android.nfc.Tag getTag();
- method public int getTimeout();
- method public boolean isConnected();
- method public void setTimeout(int);
- method public byte[] transceive(byte[]) throws java.io.IOException;
- }
-
- public final class NfcB implements android.nfc.tech.TagTechnology {
- method public void close() throws java.io.IOException;
- method public void connect() throws java.io.IOException;
- method public static android.nfc.tech.NfcB get(android.nfc.Tag);
- method public byte[] getApplicationData();
- method public int getMaxTransceiveLength();
- method public byte[] getProtocolInfo();
- method public android.nfc.Tag getTag();
- method public boolean isConnected();
- method public byte[] transceive(byte[]) throws java.io.IOException;
- }
-
- public final class NfcBarcode implements android.nfc.tech.TagTechnology {
- method public void close() throws java.io.IOException;
- method public void connect() throws java.io.IOException;
- method public static android.nfc.tech.NfcBarcode get(android.nfc.Tag);
- method public byte[] getBarcode();
- method public android.nfc.Tag getTag();
- method public int getType();
- method public boolean isConnected();
- field public static final int TYPE_KOVIO = 1; // 0x1
- field public static final int TYPE_UNKNOWN = -1; // 0xffffffff
- }
-
- public final class NfcF implements android.nfc.tech.TagTechnology {
- method public void close() throws java.io.IOException;
- method public void connect() throws java.io.IOException;
- method public static android.nfc.tech.NfcF get(android.nfc.Tag);
- method public byte[] getManufacturer();
- method public int getMaxTransceiveLength();
- method public byte[] getSystemCode();
- method public android.nfc.Tag getTag();
- method public int getTimeout();
- method public boolean isConnected();
- method public void setTimeout(int);
- method public byte[] transceive(byte[]) throws java.io.IOException;
- }
-
- public final class NfcV implements android.nfc.tech.TagTechnology {
- method public void close() throws java.io.IOException;
- method public void connect() throws java.io.IOException;
- method public static android.nfc.tech.NfcV get(android.nfc.Tag);
- method public byte getDsfId();
- method public int getMaxTransceiveLength();
- method public byte getResponseFlags();
- method public android.nfc.Tag getTag();
- method public boolean isConnected();
- method public byte[] transceive(byte[]) throws java.io.IOException;
- }
-
- public interface TagTechnology extends java.io.Closeable {
- method public void connect() throws java.io.IOException;
- method public android.nfc.Tag getTag();
- method public boolean isConnected();
- }
-
-}
-
package android.opengl {
public class EGL14 {
@@ -32254,6 +31852,7 @@
method public long computeChargeTimeRemaining();
method public int getIntProperty(int);
method public long getLongProperty(int);
+ method @FlaggedApi("android.os.battery_part_status_api") @Nullable public String getStringProperty(int);
method public boolean isCharging();
field public static final String ACTION_CHARGING = "android.os.action.CHARGING";
field public static final String ACTION_DISCHARGING = "android.os.action.DISCHARGING";
@@ -33401,7 +33000,7 @@
@FlaggedApi("com.android.server.power.optimization.power_monitor_api") public final class PowerMonitorReadings {
method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public long getConsumedEnergy(@NonNull android.os.PowerMonitor);
- method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public long getTimestamp(@NonNull android.os.PowerMonitor);
+ method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public long getTimestampMillis(@NonNull android.os.PowerMonitor);
field @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public static final int ENERGY_UNAVAILABLE = -1; // 0xffffffff
}
@@ -33913,7 +33512,6 @@
@FlaggedApi("android.os.adpf_gpu_report_actual_work_duration") public final class WorkDuration implements android.os.Parcelable {
ctor public WorkDuration();
- ctor public WorkDuration(long, long, long, long);
method public int describeContents();
method public long getActualCpuDurationNanos();
method public long getActualGpuDurationNanos();
@@ -33996,8 +33594,8 @@
}
public class SystemHealthManager {
- method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public void getPowerMonitorReadings(@NonNull java.util.List<android.os.PowerMonitor>, @Nullable android.os.Handler, @NonNull java.util.function.Consumer<android.os.PowerMonitorReadings>, @NonNull java.util.function.Consumer<java.lang.RuntimeException>);
- method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public void getSupportedPowerMonitors(@Nullable android.os.Handler, @NonNull java.util.function.Consumer<java.util.List<android.os.PowerMonitor>>);
+ method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public void getPowerMonitorReadings(@NonNull java.util.List<android.os.PowerMonitor>, @Nullable java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.os.PowerMonitorReadings,java.lang.RuntimeException>);
+ method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public void getSupportedPowerMonitors(@Nullable java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.os.PowerMonitor>>);
method public android.os.health.HealthStats takeMyUidSnapshot();
method public android.os.health.HealthStats takeUidSnapshot(int);
method public android.os.health.HealthStats[] takeUidSnapshots(int[]);
@@ -40922,7 +40520,6 @@
public final class ZenPolicy implements android.os.Parcelable {
method public int describeContents();
- method @FlaggedApi("android.app.modes_api") public int getAllowedChannels();
method public int getPriorityCallSenders();
method public int getPriorityCategoryAlarms();
method public int getPriorityCategoryCalls();
@@ -40933,6 +40530,7 @@
method public int getPriorityCategoryReminders();
method public int getPriorityCategoryRepeatCallers();
method public int getPriorityCategorySystem();
+ method @FlaggedApi("android.app.modes_api") public int getPriorityChannels();
method public int getPriorityConversationSenders();
method public int getPriorityMessageSenders();
method public int getVisualEffectAmbient();
@@ -40943,9 +40541,6 @@
method public int getVisualEffectPeek();
method public int getVisualEffectStatusBar();
method public void writeToParcel(android.os.Parcel, int);
- field @FlaggedApi("android.app.modes_api") public static final int CHANNEL_TYPE_NONE = 2; // 0x2
- field @FlaggedApi("android.app.modes_api") public static final int CHANNEL_TYPE_PRIORITY = 1; // 0x1
- field @FlaggedApi("android.app.modes_api") public static final int CHANNEL_TYPE_UNSET = 0; // 0x0
field public static final int CONVERSATION_SENDERS_ANYONE = 1; // 0x1
field public static final int CONVERSATION_SENDERS_IMPORTANT = 2; // 0x2
field public static final int CONVERSATION_SENDERS_NONE = 3; // 0x3
@@ -40966,11 +40561,11 @@
method @NonNull public android.service.notification.ZenPolicy.Builder allowAlarms(boolean);
method @NonNull public android.service.notification.ZenPolicy.Builder allowAllSounds();
method @NonNull public android.service.notification.ZenPolicy.Builder allowCalls(int);
- method @FlaggedApi("android.app.modes_api") @NonNull public android.service.notification.ZenPolicy.Builder allowChannels(int);
method @NonNull public android.service.notification.ZenPolicy.Builder allowConversations(int);
method @NonNull public android.service.notification.ZenPolicy.Builder allowEvents(boolean);
method @NonNull public android.service.notification.ZenPolicy.Builder allowMedia(boolean);
method @NonNull public android.service.notification.ZenPolicy.Builder allowMessages(int);
+ method @FlaggedApi("android.app.modes_api") @NonNull public android.service.notification.ZenPolicy.Builder allowPriorityChannels(boolean);
method @NonNull public android.service.notification.ZenPolicy.Builder allowReminders(boolean);
method @NonNull public android.service.notification.ZenPolicy.Builder allowRepeatCallers(boolean);
method @NonNull public android.service.notification.ZenPolicy.Builder allowSystem(boolean);
@@ -41552,6 +41147,8 @@
field public static final String EXTRA_LANGUAGE_MODEL = "android.speech.extra.LANGUAGE_MODEL";
field public static final String EXTRA_LANGUAGE_PREFERENCE = "android.speech.extra.LANGUAGE_PREFERENCE";
field public static final String EXTRA_LANGUAGE_SWITCH_ALLOWED_LANGUAGES = "android.speech.extra.LANGUAGE_SWITCH_ALLOWED_LANGUAGES";
+ field @FlaggedApi("android.speech.flags.multilang_extra_launch") public static final String EXTRA_LANGUAGE_SWITCH_INITIAL_ACTIVE_DURATION_TIME_MILLIS = "android.speech.extra.LANGUAGE_SWITCH_INITIAL_ACTIVE_DURATION_TIME_MILLIS";
+ field @FlaggedApi("android.speech.flags.multilang_extra_launch") public static final String EXTRA_LANGUAGE_SWITCH_MAX_SWITCHES = "android.speech.extra.LANGUAGE_SWITCH_MAX_SWITCHES";
field public static final String EXTRA_MASK_OFFENSIVE_WORDS = "android.speech.extra.MASK_OFFENSIVE_WORDS";
field public static final String EXTRA_MAX_RESULTS = "android.speech.extra.MAX_RESULTS";
field public static final String EXTRA_ONLY_RETURN_LANGUAGE_PREFERENCE = "android.speech.extra.ONLY_RETURN_LANGUAGE_PREFERENCE";
@@ -42064,6 +41661,7 @@
method public void sendEvent(@NonNull String, @NonNull android.os.Bundle);
method public void setActive(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
method public void setInactive(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
+ method @FlaggedApi("com.android.server.telecom.flags.set_mute_state") public void setMuteState(boolean, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
method public void startCallStreaming(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
}
@@ -43482,6 +43080,8 @@
field public static final String KEY_RTT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_VT_CALL_BOOL = "rtt_upgrade_supported_for_downgraded_vt_call";
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ATTACH_SUPPORTED_BOOL = "satellite_attach_supported_bool";
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT = "satellite_connection_hysteresis_sec_int";
+ field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT = "satellite_entitlement_status_refresh_days_int";
+ field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL = "satellite_entitlement_supported_bool";
field public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL = "show_4g_for_3g_data_icon_bool";
field public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL = "show_4g_for_lte_data_icon_bool";
field public static final String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool";
@@ -45343,7 +44943,7 @@
method public void addOnSubscriptionsChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void addSubscriptionsIntoGroup(@NonNull java.util.List<java.lang.Integer>, @NonNull android.os.ParcelUuid);
method public boolean canManageSubscription(android.telephony.SubscriptionInfo);
- method @FlaggedApi("com.android.internal.telephony.flags.work_profile_api_split") @NonNull public android.telephony.SubscriptionManager createForAllUserProfiles();
+ method @FlaggedApi("com.android.internal.telephony.flags.enforce_subscription_user_filter") @NonNull public android.telephony.SubscriptionManager createForAllUserProfiles();
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.os.ParcelUuid createSubscriptionGroup(@NonNull java.util.List<java.lang.Integer>);
method @Deprecated public static android.telephony.SubscriptionManager from(android.content.Context);
method public java.util.List<android.telephony.SubscriptionInfo> getAccessibleSubscriptionInfoList();
@@ -47295,6 +46895,7 @@
method public int getLineForOffset(int);
method public int getLineForVertical(int);
method public float getLineLeft(int);
+ method @FlaggedApi("com.android.text.flags.inter_character_justification") @IntRange(from=0) public int getLineLetterSpacingUnitCount(@IntRange(from=0) int, boolean);
method public float getLineMax(int);
method public float getLineRight(int);
method @FlaggedApi("com.android.text.flags.use_bounds_for_width") public final float getLineSpacingAmount();
@@ -51818,7 +51419,7 @@
method @NonNull public android.view.SurfaceControl.Transaction setCrop(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect);
method @NonNull public android.view.SurfaceControl.Transaction setDamageRegion(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Region);
method @NonNull public android.view.SurfaceControl.Transaction setDataSpace(@NonNull android.view.SurfaceControl, int);
- method @FlaggedApi("com.android.window.flags.sdk_desired_present_time") @NonNull public android.view.SurfaceControl.Transaction setDesiredPresentTime(long);
+ method @FlaggedApi("com.android.window.flags.sdk_desired_present_time") @NonNull public android.view.SurfaceControl.Transaction setDesiredPresentTimeNanos(long);
method @NonNull public android.view.SurfaceControl.Transaction setExtendedRangeBrightness(@NonNull android.view.SurfaceControl, float, float);
method @NonNull public android.view.SurfaceControl.Transaction setFrameRate(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0) float, int);
method @NonNull public android.view.SurfaceControl.Transaction setFrameRate(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0) float, int, int);
@@ -51839,7 +51440,7 @@
}
@FlaggedApi("com.android.window.flags.sdk_desired_present_time") public static final class SurfaceControl.TransactionStats {
- method @FlaggedApi("com.android.window.flags.sdk_desired_present_time") public long getLatchTime();
+ method @FlaggedApi("com.android.window.flags.sdk_desired_present_time") public long getLatchTimeNanos();
method @FlaggedApi("com.android.window.flags.sdk_desired_present_time") @NonNull public android.hardware.SyncFence getPresentFence();
}
@@ -51847,6 +51448,10 @@
ctor public SurfaceControl.TrustedPresentationThresholds(@FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @IntRange(from=1) int);
}
+ @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public interface SurfaceControlInputReceiver {
+ method public boolean onInputEvent(@NonNull android.view.InputEvent);
+ }
+
public class SurfaceControlViewHost {
ctor public SurfaceControlViewHost(@NonNull android.content.Context, @NonNull android.view.Display, @Nullable android.os.IBinder);
method @Nullable public android.view.SurfaceControlViewHost.SurfacePackage getSurfacePackage();
@@ -53970,10 +53575,13 @@
method @Deprecated public android.view.Display getDefaultDisplay();
method @NonNull public default android.view.WindowMetrics getMaximumWindowMetrics();
method public default boolean isCrossWindowBlurEnabled();
+ method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") @NonNull public default android.os.IBinder registerBatchedSurfaceControlInputReceiver(int, @NonNull android.os.IBinder, @NonNull android.view.SurfaceControl, @NonNull android.view.Choreographer, @NonNull android.view.SurfaceControlInputReceiver);
method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void registerTrustedPresentationListener(@NonNull android.os.IBinder, @NonNull android.window.TrustedPresentationThresholds, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") @NonNull public default android.os.IBinder registerUnbatchedSurfaceControlInputReceiver(int, @NonNull android.os.IBinder, @NonNull android.view.SurfaceControl, @NonNull android.os.Looper, @NonNull android.view.SurfaceControlInputReceiver);
method public default void removeCrossWindowBlurEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public default void removeProposedRotationListener(@NonNull java.util.function.IntConsumer);
method public void removeViewImmediate(android.view.View);
+ method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void unregisterSurfaceControlInputReceiver(@NonNull android.os.IBinder);
method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void unregisterTrustedPresentationListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
field public static final String PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED = "android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED";
@@ -53986,6 +53594,8 @@
field public static final String PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE = "android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE";
field @FlaggedApi("com.android.window.flags.app_compat_properties_api") public static final String PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES = "android.window.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES";
field public static final String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS = "android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS";
+ field @FlaggedApi("com.android.window.flags.app_compat_properties_api") public static final String PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE = "android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE";
+ field @FlaggedApi("com.android.window.flags.app_compat_properties_api") public static final String PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE = "android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE";
field public static final String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
field public static final String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION = "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
field @FlaggedApi("com.android.window.flags.supports_multi_instance_system_ui") public static final String PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI = "android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI";
@@ -56500,6 +56110,7 @@
field public static final String TYPE_EMAIL = "email";
field public static final String TYPE_FLIGHT_NUMBER = "flight";
field public static final String TYPE_OTHER = "other";
+ field @FlaggedApi("android.service.notification.redact_sensitive_notifications_from_untrusted_listeners") public static final String TYPE_OTP_CODE = "otp_code";
field public static final String TYPE_PHONE = "phone";
field public static final String TYPE_UNKNOWN = "";
field public static final String TYPE_URL = "url";
diff --git a/core/api/lint-baseline.txt b/core/api/lint-baseline.txt
index f331e7f..162f54c 100644
--- a/core/api/lint-baseline.txt
+++ b/core/api/lint-baseline.txt
@@ -181,12 +181,6 @@
Field 'ACTION_WATCH_NEXT_PROGRAM_BROWSABLE_DISABLED' is missing @BroadcastBehavior
BroadcastBehavior: android.net.Proxy#PROXY_CHANGE_ACTION:
Field 'PROXY_CHANGE_ACTION' is missing @BroadcastBehavior
-BroadcastBehavior: android.nfc.NfcAdapter#ACTION_ADAPTER_STATE_CHANGED:
- Field 'ACTION_ADAPTER_STATE_CHANGED' is missing @BroadcastBehavior
-BroadcastBehavior: android.nfc.NfcAdapter#ACTION_PREFERRED_PAYMENT_CHANGED:
- Field 'ACTION_PREFERRED_PAYMENT_CHANGED' is missing @BroadcastBehavior
-BroadcastBehavior: android.nfc.NfcAdapter#ACTION_TRANSACTION_DETECTED:
- Field 'ACTION_TRANSACTION_DETECTED' is missing @BroadcastBehavior
BroadcastBehavior: android.os.DropBoxManager#ACTION_DROPBOX_ENTRY_ADDED:
Field 'ACTION_DROPBOX_ENTRY_ADDED' is missing @BroadcastBehavior
BroadcastBehavior: android.provider.CalendarContract#ACTION_EVENT_REMINDER:
@@ -715,86 +709,6 @@
Method 'setSpeakerMode' documentation mentions permissions without declaring @RequiresPermission
RequiresPermission: android.net.sip.SipAudioCall#startAudio():
Method 'startAudio' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.NfcAdapter#disableForegroundDispatch(android.app.Activity):
- Method 'disableForegroundDispatch' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.NfcAdapter#enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]):
- Method 'enableForegroundDispatch' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForAid(android.content.ComponentName, String):
- Method 'isDefaultServiceForAid' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForCategory(android.content.ComponentName, String):
- Method 'isDefaultServiceForCategory' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.cardemulation.CardEmulation#setOffHostForService(android.content.ComponentName, String):
- Method 'setOffHostForService' documentation mentions permissions already declared by @RequiresPermission
-RequiresPermission: android.nfc.tech.IsoDep#getTimeout():
- Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.IsoDep#setTimeout(int):
- Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.IsoDep#transceive(byte[]):
- Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyA(int, byte[]):
- Method 'authenticateSectorWithKeyA' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyB(int, byte[]):
- Method 'authenticateSectorWithKeyB' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#decrement(int, int):
- Method 'decrement' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#getTimeout():
- Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#increment(int, int):
- Method 'increment' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#readBlock(int):
- Method 'readBlock' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#restore(int):
- Method 'restore' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#setTimeout(int):
- Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#transceive(byte[]):
- Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#transfer(int):
- Method 'transfer' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#writeBlock(int, byte[]):
- Method 'writeBlock' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareUltralight#getTimeout():
- Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareUltralight#readPages(int):
- Method 'readPages' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareUltralight#setTimeout(int):
- Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareUltralight#transceive(byte[]):
- Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareUltralight#writePage(int, byte[]):
- Method 'writePage' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.Ndef#getNdefMessage():
- Method 'getNdefMessage' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.Ndef#isWritable():
- Method 'isWritable' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.Ndef#makeReadOnly():
- Method 'makeReadOnly' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.Ndef#writeNdefMessage(android.nfc.NdefMessage):
- Method 'writeNdefMessage' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NdefFormatable#format(android.nfc.NdefMessage):
- Method 'format' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NdefFormatable#formatReadOnly(android.nfc.NdefMessage):
- Method 'formatReadOnly' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcA#getTimeout():
- Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcA#setTimeout(int):
- Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcA#transceive(byte[]):
- Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcB#transceive(byte[]):
- Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcF#getTimeout():
- Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcF#setTimeout(int):
- Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcF#transceive(byte[]):
- Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcV#transceive(byte[]):
- Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.TagTechnology#close():
- Method 'close' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.TagTechnology#connect():
- Method 'connect' documentation mentions permissions without declaring @RequiresPermission
RequiresPermission: android.os.BugreportManager#cancelBugreport():
Method 'cancelBugreport' documentation mentions permissions without declaring @RequiresPermission
RequiresPermission: android.os.Build#getSerial():
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index c1b9f64..24b9233 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -319,11 +319,6 @@
package android.nfc {
- public class NfcFrameworkInitializer {
- method public static void registerServiceWrappers();
- method public static void setNfcServiceManager(@NonNull android.nfc.NfcServiceManager);
- }
-
public class NfcServiceManager {
method @NonNull public android.nfc.NfcServiceManager.ServiceRegisterer getNfcManagerServiceRegisterer();
}
diff --git a/core/api/module-lib-lint-baseline.txt b/core/api/module-lib-lint-baseline.txt
index a6a948c..a2179bc 100644
--- a/core/api/module-lib-lint-baseline.txt
+++ b/core/api/module-lib-lint-baseline.txt
@@ -235,14 +235,6 @@
Field 'ACTION_SCORE_NETWORKS' is missing @BroadcastBehavior
BroadcastBehavior: android.net.Proxy#PROXY_CHANGE_ACTION:
Field 'PROXY_CHANGE_ACTION' is missing @BroadcastBehavior
-BroadcastBehavior: android.nfc.NfcAdapter#ACTION_ADAPTER_STATE_CHANGED:
- Field 'ACTION_ADAPTER_STATE_CHANGED' is missing @BroadcastBehavior
-BroadcastBehavior: android.nfc.NfcAdapter#ACTION_PREFERRED_PAYMENT_CHANGED:
- Field 'ACTION_PREFERRED_PAYMENT_CHANGED' is missing @BroadcastBehavior
-BroadcastBehavior: android.nfc.NfcAdapter#ACTION_REQUIRE_UNLOCK_FOR_NFC:
- Field 'ACTION_REQUIRE_UNLOCK_FOR_NFC' is missing @BroadcastBehavior
-BroadcastBehavior: android.nfc.NfcAdapter#ACTION_TRANSACTION_DETECTED:
- Field 'ACTION_TRANSACTION_DETECTED' is missing @BroadcastBehavior
BroadcastBehavior: android.os.DropBoxManager#ACTION_DROPBOX_ENTRY_ADDED:
Field 'ACTION_DROPBOX_ENTRY_ADDED' is missing @BroadcastBehavior
BroadcastBehavior: android.provider.CalendarContract#ACTION_EVENT_REMINDER:
@@ -1009,86 +1001,6 @@
Method 'applyVcnNetworkPolicy' documentation mentions permissions already declared by @RequiresPermission
RequiresPermission: android.net.vcn.VcnManager#removeVcnNetworkPolicyChangeListener(android.net.vcn.VcnManager.VcnNetworkPolicyChangeListener):
Method 'removeVcnNetworkPolicyChangeListener' documentation mentions permissions already declared by @RequiresPermission
-RequiresPermission: android.nfc.NfcAdapter#disableForegroundDispatch(android.app.Activity):
- Method 'disableForegroundDispatch' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.NfcAdapter#enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]):
- Method 'enableForegroundDispatch' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForAid(android.content.ComponentName, String):
- Method 'isDefaultServiceForAid' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForCategory(android.content.ComponentName, String):
- Method 'isDefaultServiceForCategory' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.cardemulation.CardEmulation#setOffHostForService(android.content.ComponentName, String):
- Method 'setOffHostForService' documentation mentions permissions already declared by @RequiresPermission
-RequiresPermission: android.nfc.tech.IsoDep#getTimeout():
- Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.IsoDep#setTimeout(int):
- Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.IsoDep#transceive(byte[]):
- Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyA(int, byte[]):
- Method 'authenticateSectorWithKeyA' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyB(int, byte[]):
- Method 'authenticateSectorWithKeyB' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#decrement(int, int):
- Method 'decrement' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#getTimeout():
- Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#increment(int, int):
- Method 'increment' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#readBlock(int):
- Method 'readBlock' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#restore(int):
- Method 'restore' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#setTimeout(int):
- Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#transceive(byte[]):
- Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#transfer(int):
- Method 'transfer' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#writeBlock(int, byte[]):
- Method 'writeBlock' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareUltralight#getTimeout():
- Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareUltralight#readPages(int):
- Method 'readPages' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareUltralight#setTimeout(int):
- Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareUltralight#transceive(byte[]):
- Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareUltralight#writePage(int, byte[]):
- Method 'writePage' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.Ndef#getNdefMessage():
- Method 'getNdefMessage' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.Ndef#isWritable():
- Method 'isWritable' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.Ndef#makeReadOnly():
- Method 'makeReadOnly' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.Ndef#writeNdefMessage(android.nfc.NdefMessage):
- Method 'writeNdefMessage' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NdefFormatable#format(android.nfc.NdefMessage):
- Method 'format' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NdefFormatable#formatReadOnly(android.nfc.NdefMessage):
- Method 'formatReadOnly' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcA#getTimeout():
- Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcA#setTimeout(int):
- Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcA#transceive(byte[]):
- Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcB#transceive(byte[]):
- Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcF#getTimeout():
- Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcF#setTimeout(int):
- Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcF#transceive(byte[]):
- Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcV#transceive(byte[]):
- Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.TagTechnology#close():
- Method 'close' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.TagTechnology#connect():
- Method 'connect' documentation mentions permissions without declaring @RequiresPermission
RequiresPermission: android.os.BugreportManager#cancelBugreport():
Method 'cancelBugreport' documentation mentions permissions without declaring @RequiresPermission
RequiresPermission: android.os.BugreportManager#preDumpUiData():
@@ -1769,8 +1681,6 @@
Field 'ACTION_USB_PORT_COMPLIANCE_CHANGED' is missing @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
SdkConstant: android.hardware.usb.UsbManager#ACTION_USB_STATE:
Field 'ACTION_USB_STATE' is missing @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
-SdkConstant: android.nfc.NfcAdapter#ACTION_REQUIRE_UNLOCK_FOR_NFC:
- Field 'ACTION_REQUIRE_UNLOCK_FOR_NFC' is missing @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
SdkConstant: android.service.euicc.EuiccService#ACTION_DELETE_SUBSCRIPTION_PRIVILEGED:
Field 'ACTION_DELETE_SUBSCRIPTION_PRIVILEGED' is missing @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
SdkConstant: android.service.euicc.EuiccService#ACTION_RENAME_SUBSCRIPTION_PRIVILEGED:
diff --git a/core/api/removed.txt b/core/api/removed.txt
index b58c822..3c7c0d6 100644
--- a/core/api/removed.txt
+++ b/core/api/removed.txt
@@ -190,22 +190,6 @@
}
-package android.nfc {
-
- public final class NfcAdapter {
- method @Deprecated public void disableForegroundNdefPush(android.app.Activity);
- method @Deprecated public void enableForegroundNdefPush(android.app.Activity, android.nfc.NdefMessage);
- method @Deprecated public boolean invokeBeam(android.app.Activity);
- method @Deprecated public boolean isNdefPushEnabled();
- method @Deprecated public void setBeamPushUris(android.net.Uri[], android.app.Activity);
- method @Deprecated public void setBeamPushUrisCallback(android.nfc.NfcAdapter.CreateBeamUrisCallback, android.app.Activity);
- method @Deprecated public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, android.app.Activity...);
- method @Deprecated public void setNdefPushMessageCallback(android.nfc.NfcAdapter.CreateNdefMessageCallback, android.app.Activity, android.app.Activity...);
- method @Deprecated public void setOnNdefPushCompleteCallback(android.nfc.NfcAdapter.OnNdefPushCompleteCallback, android.app.Activity, android.app.Activity...);
- }
-
-}
-
package android.os {
public class BatteryManager {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 2898705..197e8d7 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -12,6 +12,7 @@
field @Deprecated public static final String ACCESS_FM_RADIO = "android.permission.ACCESS_FM_RADIO";
field public static final String ACCESS_FPS_COUNTER = "android.permission.ACCESS_FPS_COUNTER";
field public static final String ACCESS_INSTANT_APPS = "android.permission.ACCESS_INSTANT_APPS";
+ field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ACCESS_LAST_KNOWN_CELL_ID = "android.permission.ACCESS_LAST_KNOWN_CELL_ID";
field public static final String ACCESS_LOCUS_ID_USAGE_STATS = "android.permission.ACCESS_LOCUS_ID_USAGE_STATS";
field public static final String ACCESS_MOCK_LOCATION = "android.permission.ACCESS_MOCK_LOCATION";
field public static final String ACCESS_MTP = "android.permission.ACCESS_MTP";
@@ -55,6 +56,7 @@
field public static final String BIND_CONTENT_SUGGESTIONS_SERVICE = "android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE";
field public static final String BIND_DIRECTORY_SEARCH = "android.permission.BIND_DIRECTORY_SEARCH";
field public static final String BIND_DISPLAY_HASHING_SERVICE = "android.permission.BIND_DISPLAY_HASHING_SERVICE";
+ field @FlaggedApi("com.android.internal.telephony.flags.ap_domain_selection_enabled") public static final String BIND_DOMAIN_SELECTION_SERVICE = "android.permission.BIND_DOMAIN_SELECTION_SERVICE";
field public static final String BIND_DOMAIN_VERIFICATION_AGENT = "android.permission.BIND_DOMAIN_VERIFICATION_AGENT";
field public static final String BIND_EUICC_SERVICE = "android.permission.BIND_EUICC_SERVICE";
field public static final String BIND_EXTERNAL_STORAGE_SERVICE = "android.permission.BIND_EXTERNAL_STORAGE_SERVICE";
@@ -133,6 +135,7 @@
field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES";
field public static final String GET_APP_METADATA = "android.permission.GET_APP_METADATA";
field public static final String GET_APP_OPS_STATS = "android.permission.GET_APP_OPS_STATS";
+ field @FlaggedApi("android.app.bic_client") public static final String GET_BACKGROUND_INSTALLED_PACKAGES = "android.permission.GET_BACKGROUND_INSTALLED_PACKAGES";
field @FlaggedApi("android.app.get_binding_uid_importance") public static final String GET_BINDING_UID_IMPORTANCE = "android.permission.GET_BINDING_UID_IMPORTANCE";
field public static final String GET_HISTORICAL_APP_OPS_STATS = "android.permission.GET_HISTORICAL_APP_OPS_STATS";
field public static final String GET_PROCESS_STATE_AND_OOM_SCORE = "android.permission.GET_PROCESS_STATE_AND_OOM_SCORE";
@@ -294,6 +297,7 @@
field public static final String READ_RUNTIME_PROFILES = "android.permission.READ_RUNTIME_PROFILES";
field public static final String READ_SAFETY_CENTER_STATUS = "android.permission.READ_SAFETY_CENTER_STATUS";
field public static final String READ_SEARCH_INDEXABLES = "android.permission.READ_SEARCH_INDEXABLES";
+ field @FlaggedApi("android.app.system_terms_of_address_enabled") public static final String READ_SYSTEM_GRAMMATICAL_GENDER = "android.permission.READ_SYSTEM_GRAMMATICAL_GENDER";
field public static final String READ_SYSTEM_UPDATE_INFO = "android.permission.READ_SYSTEM_UPDATE_INFO";
field public static final String READ_WALLPAPER_INTERNAL = "android.permission.READ_WALLPAPER_INTERNAL";
field public static final String READ_WIFI_CREDENTIAL = "android.permission.READ_WIFI_CREDENTIAL";
@@ -419,6 +423,7 @@
public static final class R.attr {
field public static final int allowClearUserDataOnFailedRestore = 16844288; // 0x1010600
+ field @FlaggedApi("android.content.res.manifest_flagging") public static final int featureFlag;
field public static final int gameSessionService = 16844373; // 0x1010655
field public static final int hotwordDetectionService = 16844326; // 0x1010626
field @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") public static final int isVirtualDeviceOnly;
@@ -552,6 +557,7 @@
public class ActivityManager {
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void addOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener, int);
+ method @FlaggedApi("android.app.uid_importance_listener_for_uids") @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void addOnUidImportanceListener(@NonNull android.app.ActivityManager.OnUidImportanceListener, int, @Nullable int[]);
method @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) public void forceStopPackage(String);
method @FlaggedApi("android.app.get_binding_uid_importance") @RequiresPermission(android.Manifest.permission.GET_BINDING_UID_IMPORTANCE) public int getBindingUidImportance(int);
method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", "android.permission.INTERACT_ACROSS_USERS_FULL"}) public static int getCurrentUser();
@@ -622,6 +628,7 @@
field public static final String OPSTR_ACCEPT_HANDOVER = "android:accept_handover";
field public static final String OPSTR_ACCESS_ACCESSIBILITY = "android:access_accessibility";
field public static final String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications";
+ field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String OPSTR_ACCESS_RESTRICTED_SETTINGS = "android:access_restricted_settings";
field public static final String OPSTR_ACTIVATE_PLATFORM_VPN = "android:activate_platform_vpn";
field public static final String OPSTR_ACTIVATE_VPN = "android:activate_vpn";
field public static final String OPSTR_ASSIST_SCREENSHOT = "android:assist_screenshot";
@@ -1596,12 +1603,14 @@
method public int getDensityLevel();
method @NonNull public java.time.Instant getEndTime();
method public int getEventType();
+ method @FlaggedApi("android.app.ambient_heart_rate") @IntRange(from=0xffffffff) public int getRatePerMinute();
method @NonNull public java.time.Instant getStartTime();
method @NonNull public android.os.PersistableBundle getVendorData();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.ambientcontext.AmbientContextEvent> CREATOR;
field public static final int EVENT_BACK_DOUBLE_TAP = 3; // 0x3
field public static final int EVENT_COUGH = 1; // 0x1
+ field @FlaggedApi("android.app.ambient_heart_rate") public static final int EVENT_HEART_RATE = 4; // 0x4
field public static final int EVENT_SNORE = 2; // 0x2
field public static final int EVENT_UNKNOWN = 0; // 0x0
field public static final int EVENT_VENDOR_WEARABLE_START = 100000; // 0x186a0
@@ -1612,6 +1621,7 @@
field public static final int LEVEL_MEDIUM_HIGH = 4; // 0x4
field public static final int LEVEL_MEDIUM_LOW = 2; // 0x2
field public static final int LEVEL_UNKNOWN = 0; // 0x0
+ field @FlaggedApi("android.app.ambient_heart_rate") public static final int RATE_PER_MINUTE_UNKNOWN = -1; // 0xffffffff
}
public static final class AmbientContextEvent.Builder {
@@ -1621,6 +1631,7 @@
method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setDensityLevel(int);
method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setEndTime(@NonNull java.time.Instant);
method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setEventType(int);
+ method @FlaggedApi("android.app.ambient_heart_rate") @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setRatePerMinute(@IntRange(from=0xffffffff) int);
method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setStartTime(@NonNull java.time.Instant);
method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setVendorData(@NonNull android.os.PersistableBundle);
}
@@ -3191,6 +3202,7 @@
public final class VirtualDeviceManager {
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.VirtualDeviceManager.VirtualDevice createVirtualDevice(int, @NonNull android.companion.virtual.VirtualDeviceParams);
+ method @FlaggedApi("android.companion.virtual.flags.persistent_device_id_api") @Nullable public CharSequence getDisplayNameForPersistentDeviceId(@NonNull String);
field public static final int LAUNCH_FAILURE_NO_ACTIVITY = 2; // 0x2
field public static final int LAUNCH_FAILURE_PENDING_INTENT_CANCELED = 1; // 0x1
field public static final int LAUNCH_SUCCESS = 0; // 0x0
@@ -3218,6 +3230,7 @@
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
method @NonNull public android.content.Context createContext();
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.audio.VirtualAudioDevice createVirtualAudioDevice(@NonNull android.hardware.display.VirtualDisplay, @Nullable java.util.concurrent.Executor, @Nullable android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback);
+ method @FlaggedApi("android.companion.virtual.flags.virtual_camera") @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.camera.VirtualCamera createVirtualCamera(@NonNull android.companion.virtual.camera.VirtualCameraConfig);
method @Deprecated @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @Nullable android.view.Surface, int, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.display.VirtualDisplay.Callback);
method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull android.hardware.display.VirtualDisplayConfig, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.display.VirtualDisplay.Callback);
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualDpad createVirtualDpad(@NonNull android.hardware.input.VirtualDpadConfig);
@@ -3226,6 +3239,7 @@
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.input.VirtualMouseConfig);
method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualNavigationTouchpad createVirtualNavigationTouchpad(@NonNull android.hardware.input.VirtualNavigationTouchpadConfig);
+ method @FlaggedApi("android.companion.virtual.flags.virtual_stylus") @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualStylus createVirtualStylus(@NonNull android.hardware.input.VirtualStylusConfig);
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.input.VirtualTouchscreenConfig);
method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
method public int getDeviceId();
@@ -3271,6 +3285,7 @@
field @Deprecated public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1; // 0x1
field @FlaggedApi("android.companion.virtual.flags.dynamic_policy") public static final int POLICY_TYPE_ACTIVITY = 3; // 0x3
field public static final int POLICY_TYPE_AUDIO = 1; // 0x1
+ field @FlaggedApi("android.companion.virtual.flags.virtual_camera") public static final int POLICY_TYPE_CAMERA = 5; // 0x5
field @FlaggedApi("android.companion.virtual.flags.cross_device_clipboard") public static final int POLICY_TYPE_CLIPBOARD = 4; // 0x4
field public static final int POLICY_TYPE_RECENTS = 2; // 0x2
field public static final int POLICY_TYPE_SENSORS = 0; // 0x0
@@ -3353,30 +3368,38 @@
@FlaggedApi("android.companion.virtual.flags.virtual_camera") public interface VirtualCameraCallback {
method public default void onProcessCaptureRequest(int, long);
method public void onStreamClosed(int);
- method public void onStreamConfigured(int, @NonNull android.view.Surface, @NonNull android.companion.virtual.camera.VirtualCameraStreamConfig);
+ method public void onStreamConfigured(int, @NonNull android.view.Surface, @IntRange(from=1) int, @IntRange(from=1) int, int);
}
@FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCameraConfig implements android.os.Parcelable {
method public int describeContents();
+ method public int getLensFacing();
method @NonNull public String getName();
+ method public int getSensorOrientation();
method @NonNull public java.util.Set<android.companion.virtual.camera.VirtualCameraStreamConfig> getStreamConfigs();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.camera.VirtualCameraConfig> CREATOR;
+ field public static final int SENSOR_ORIENTATION_0 = 0; // 0x0
+ field public static final int SENSOR_ORIENTATION_180 = 180; // 0xb4
+ field public static final int SENSOR_ORIENTATION_270 = 270; // 0x10e
+ field public static final int SENSOR_ORIENTATION_90 = 90; // 0x5a
}
@FlaggedApi("android.companion.virtual.flags.virtual_camera") public static final class VirtualCameraConfig.Builder {
ctor public VirtualCameraConfig.Builder();
- method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder addStreamConfig(int, int, int);
+ method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder addStreamConfig(@IntRange(from=1) int, @IntRange(from=1) int, int, @IntRange(from=1) int);
method @NonNull public android.companion.virtual.camera.VirtualCameraConfig build();
+ method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setLensFacing(int);
method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setName(@NonNull String);
+ method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setSensorOrientation(int);
method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setVirtualCameraCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.camera.VirtualCameraCallback);
}
@FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCameraStreamConfig implements android.os.Parcelable {
- ctor public VirtualCameraStreamConfig(@IntRange(from=1) int, @IntRange(from=1) int, int);
method public int describeContents();
method public int getFormat();
method @IntRange(from=1) public int getHeight();
+ method @IntRange(from=1) public int getMaximumFramesPerSecond();
method @IntRange(from=1) public int getWidth();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.camera.VirtualCameraStreamConfig> CREATOR;
@@ -3527,6 +3550,7 @@
field public static final String CLOUDSEARCH_SERVICE = "cloudsearch";
field public static final String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions";
field public static final String CONTEXTHUB_SERVICE = "contexthub";
+ field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String ECM_ENHANCED_CONFIRMATION_SERVICE = "ecm_enhanced_confirmation";
field public static final String ETHERNET_SERVICE = "ethernet";
field public static final String EUICC_CARD_SERVICE = "euicc_card";
field public static final String FONT_SERVICE = "font";
@@ -4205,11 +4229,14 @@
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.UserProperties> CREATOR;
field public static final int CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT = 1; // 0x1
field public static final int CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION = 0; // 0x0
+ field public static final int CROSS_PROFILE_CONTENT_SHARING_UNKNOWN = -1; // 0xffffffff
field public static final int SHOW_IN_QUIET_MODE_DEFAULT = 2; // 0x2
field public static final int SHOW_IN_QUIET_MODE_HIDDEN = 1; // 0x1
field public static final int SHOW_IN_QUIET_MODE_PAUSED = 0; // 0x0
+ field public static final int SHOW_IN_QUIET_MODE_UNKNOWN = -1; // 0xffffffff
field public static final int SHOW_IN_SHARING_SURFACES_NO = 2; // 0x2
field public static final int SHOW_IN_SHARING_SURFACES_SEPARATE = 1; // 0x1
+ field public static final int SHOW_IN_SHARING_SURFACES_UNKNOWN = -1; // 0xffffffff
field public static final int SHOW_IN_SHARING_SURFACES_WITH_PARENT = 0; // 0x0
}
@@ -5301,6 +5328,78 @@
method @NonNull public android.hardware.input.VirtualNavigationTouchpadConfig build();
}
+ @FlaggedApi("android.companion.virtual.flags.virtual_stylus") public class VirtualStylus implements java.io.Closeable {
+ method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
+ method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendButtonEvent(@NonNull android.hardware.input.VirtualStylusButtonEvent);
+ method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendMotionEvent(@NonNull android.hardware.input.VirtualStylusMotionEvent);
+ }
+
+ @FlaggedApi("android.companion.virtual.flags.virtual_stylus") public final class VirtualStylusButtonEvent implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getAction();
+ method public int getButtonCode();
+ method public long getEventTimeNanos();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int ACTION_BUTTON_PRESS = 11; // 0xb
+ field public static final int ACTION_BUTTON_RELEASE = 12; // 0xc
+ field public static final int BUTTON_PRIMARY = 32; // 0x20
+ field public static final int BUTTON_SECONDARY = 64; // 0x40
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualStylusButtonEvent> CREATOR;
+ }
+
+ @FlaggedApi("android.companion.virtual.flags.virtual_stylus") public static final class VirtualStylusButtonEvent.Builder {
+ ctor public VirtualStylusButtonEvent.Builder();
+ method @NonNull public android.hardware.input.VirtualStylusButtonEvent build();
+ method @NonNull public android.hardware.input.VirtualStylusButtonEvent.Builder setAction(int);
+ method @NonNull public android.hardware.input.VirtualStylusButtonEvent.Builder setButtonCode(int);
+ method @NonNull public android.hardware.input.VirtualStylusButtonEvent.Builder setEventTimeNanos(long);
+ }
+
+ @FlaggedApi("android.companion.virtual.flags.virtual_stylus") public final class VirtualStylusConfig extends android.hardware.input.VirtualInputDeviceConfig implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getHeight();
+ method public int getWidth();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualStylusConfig> CREATOR;
+ }
+
+ @FlaggedApi("android.companion.virtual.flags.virtual_stylus") public static final class VirtualStylusConfig.Builder extends android.hardware.input.VirtualInputDeviceConfig.Builder<android.hardware.input.VirtualStylusConfig.Builder> {
+ ctor public VirtualStylusConfig.Builder(@IntRange(from=1) int, @IntRange(from=1) int);
+ method @NonNull public android.hardware.input.VirtualStylusConfig build();
+ }
+
+ @FlaggedApi("android.companion.virtual.flags.virtual_stylus") public final class VirtualStylusMotionEvent implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getAction();
+ method public long getEventTimeNanos();
+ method public int getPressure();
+ method public int getTiltX();
+ method public int getTiltY();
+ method public int getToolType();
+ method public int getX();
+ method public int getY();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int ACTION_DOWN = 0; // 0x0
+ field public static final int ACTION_MOVE = 2; // 0x2
+ field public static final int ACTION_UP = 1; // 0x1
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualStylusMotionEvent> CREATOR;
+ field public static final int TOOL_TYPE_ERASER = 4; // 0x4
+ field public static final int TOOL_TYPE_STYLUS = 2; // 0x2
+ }
+
+ @FlaggedApi("android.companion.virtual.flags.virtual_stylus") public static final class VirtualStylusMotionEvent.Builder {
+ ctor public VirtualStylusMotionEvent.Builder();
+ method @NonNull public android.hardware.input.VirtualStylusMotionEvent build();
+ method @NonNull public android.hardware.input.VirtualStylusMotionEvent.Builder setAction(int);
+ method @NonNull public android.hardware.input.VirtualStylusMotionEvent.Builder setEventTimeNanos(long);
+ method @NonNull public android.hardware.input.VirtualStylusMotionEvent.Builder setPressure(@IntRange(from=0x0, to=0xff) int);
+ method @NonNull public android.hardware.input.VirtualStylusMotionEvent.Builder setTiltX(@IntRange(from=0xffffffa6, to=0x5a) int);
+ method @NonNull public android.hardware.input.VirtualStylusMotionEvent.Builder setTiltY(@IntRange(from=0xffffffa6, to=0x5a) int);
+ method @NonNull public android.hardware.input.VirtualStylusMotionEvent.Builder setToolType(int);
+ method @NonNull public android.hardware.input.VirtualStylusMotionEvent.Builder setX(int);
+ method @NonNull public android.hardware.input.VirtualStylusMotionEvent.Builder setY(int);
+ }
+
public final class VirtualTouchEvent implements android.os.Parcelable {
method public int describeContents();
method public int getAction();
@@ -9876,49 +9975,6 @@
}
-package android.nfc {
-
- public final class NfcAdapter {
- method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean addNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler, String[]);
- method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable();
- method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable(boolean);
- method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable();
- method @FlaggedApi("android.nfc.enable_nfc_reader_option") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableReaderOption(boolean);
- method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableSecureNfc(boolean);
- method @FlaggedApi("android.nfc.enable_nfc_charging") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableWlc(boolean);
- method @FlaggedApi("android.nfc.enable_nfc_mainline") public int getAdapterState();
- method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public java.util.Map<java.lang.String,java.lang.Boolean> getTagIntentAppPreferenceForUser(int);
- method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOn();
- method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOnSupported();
- method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isTagIntentAppPreferenceSupported();
- method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
- method @FlaggedApi("android.nfc.enable_nfc_charging") public void registerWlcStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.WlcStateListener);
- method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler);
- method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean setControllerAlwaysOn(boolean);
- method @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setReaderMode(boolean);
- method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int setTagIntentAppPreferenceForUser(int, @NonNull String, boolean);
- method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
- method @FlaggedApi("android.nfc.enable_nfc_charging") public void unregisterWlcStateListener(@NonNull android.nfc.NfcAdapter.WlcStateListener);
- field @FlaggedApi("android.nfc.enable_nfc_mainline") public static final String ACTION_REQUIRE_UNLOCK_FOR_NFC = "android.nfc.action.REQUIRE_UNLOCK_FOR_NFC";
- field public static final int TAG_INTENT_APP_PREF_RESULT_PACKAGE_NOT_FOUND = -1; // 0xffffffff
- field public static final int TAG_INTENT_APP_PREF_RESULT_SUCCESS = 0; // 0x0
- field public static final int TAG_INTENT_APP_PREF_RESULT_UNAVAILABLE = -2; // 0xfffffffe
- }
-
- public static interface NfcAdapter.ControllerAlwaysOnListener {
- method public void onControllerAlwaysOnChanged(boolean);
- }
-
- public static interface NfcAdapter.NfcUnlockHandler {
- method public boolean onUnlockAttempted(android.nfc.Tag);
- }
-
- @FlaggedApi("android.nfc.enable_nfc_charging") public static interface NfcAdapter.WlcStateListener {
- method public void onWlcStateChanged(@NonNull android.nfc.WlcLDeviceInfo);
- }
-
-}
-
package android.nfc.cardemulation {
@FlaggedApi("android.nfc.enable_nfc_mainline") public final class AidGroup implements android.os.Parcelable {
@@ -9935,6 +9991,7 @@
@FlaggedApi("android.nfc.enable_nfc_mainline") public final class ApduServiceInfo implements android.os.Parcelable {
ctor @FlaggedApi("android.nfc.enable_nfc_mainline") public ApduServiceInfo(@NonNull android.content.pm.PackageManager, @NonNull android.content.pm.ResolveInfo, boolean) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void addPollingLoopFilter(@NonNull String);
method @FlaggedApi("android.nfc.enable_nfc_mainline") public int describeContents();
method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dump(@NonNull android.os.ParcelFileDescriptor, @NonNull java.io.PrintWriter, @NonNull String[]);
method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dumpDebug(@NonNull android.util.proto.ProtoOutputStream);
@@ -9945,6 +10002,7 @@
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getDescription();
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.nfc.cardemulation.AidGroup getDynamicAidGroupForCategory(@NonNull String);
method @FlaggedApi("android.nfc.enable_nfc_mainline") @Nullable public String getOffHostSecureElement();
+ method @FlaggedApi("android.nfc.nfc_read_polling_loop") @NonNull public java.util.List<java.lang.String> getPollingLoopFilters();
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<java.lang.String> getPrefixAids();
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getSettingsActivityName();
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<java.lang.String> getSubsetAids();
@@ -9957,6 +10015,7 @@
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.graphics.drawable.Drawable loadIcon(@NonNull android.content.pm.PackageManager);
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public CharSequence loadLabel(@NonNull android.content.pm.PackageManager);
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public boolean removeDynamicAidGroupForCategory(@NonNull String);
+ method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void removePollingLoopFilter(@NonNull String);
method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean requiresScreenOn();
method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean requiresUnlock();
method @FlaggedApi("android.nfc.enable_nfc_mainline") public void resetOffHostSecureElement();
@@ -9967,11 +10026,6 @@
field @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.ApduServiceInfo> CREATOR;
}
- public final class CardEmulation {
- method @FlaggedApi("android.permission.flags.wallet_role_enabled") @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public android.nfc.cardemulation.ApduServiceInfo getPreferredPaymentService();
- method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<android.nfc.cardemulation.ApduServiceInfo> getServices(@NonNull String, int);
- }
-
@FlaggedApi("android.nfc.enable_nfc_mainline") public final class NfcFServiceInfo implements android.os.Parcelable {
ctor @FlaggedApi("android.nfc.enable_nfc_mainline") public NfcFServiceInfo(@NonNull android.content.pm.PackageManager, @NonNull android.content.pm.ResolveInfo) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
method @FlaggedApi("android.nfc.enable_nfc_mainline") public int describeContents();
@@ -10000,12 +10054,17 @@
field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_CHARGING_POLICY = 9; // 0x9
field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_FIRST_USAGE_DATE = 8; // 0x8
field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_MANUFACTURING_DATE = 7; // 0x7
+ field @FlaggedApi("android.os.battery_part_status_api") @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_PART_STATUS = 12; // 0xc
+ field @FlaggedApi("android.os.battery_part_status_api") @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_SERIAL_NUMBER = 11; // 0xb
field public static final int CHARGING_POLICY_ADAPTIVE_AC = 3; // 0x3
field public static final int CHARGING_POLICY_ADAPTIVE_AON = 2; // 0x2
field public static final int CHARGING_POLICY_ADAPTIVE_LONGLIFE = 4; // 0x4
field public static final int CHARGING_POLICY_DEFAULT = 1; // 0x1
field public static final String EXTRA_EVENTS = "android.os.extra.EVENTS";
field public static final String EXTRA_EVENT_TIMESTAMP = "android.os.extra.EVENT_TIMESTAMP";
+ field @FlaggedApi("android.os.battery_part_status_api") public static final int PART_STATUS_ORIGINAL = 1; // 0x1
+ field @FlaggedApi("android.os.battery_part_status_api") public static final int PART_STATUS_REPLACED = 2; // 0x2
+ field @FlaggedApi("android.os.battery_part_status_api") public static final int PART_STATUS_UNSUPPORTED = 0; // 0x0
}
public final class BatterySaverPolicyConfig implements android.os.Parcelable {
@@ -14516,6 +14575,7 @@
field public static final int EVENT_SERVICE_STATE_CHANGED = 1; // 0x1
field public static final int EVENT_SIGNAL_STRENGTHS_CHANGED = 9; // 0x9
field public static final int EVENT_SIGNAL_STRENGTH_CHANGED = 2; // 0x2
+ field @FlaggedApi("com.android.internal.telephony.flags.simultaneous_calling_indications") @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED = 41; // 0x29
field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_SRVCC_STATE_CHANGED = 16; // 0x10
field public static final int EVENT_USER_MOBILE_DATA_STATE_CHANGED = 20; // 0x14
field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_VOICE_ACTIVATION_STATE_CHANGED = 18; // 0x12
@@ -14562,6 +14622,10 @@
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onRadioPowerStateChanged(int);
}
+ @FlaggedApi("com.android.internal.telephony.flags.simultaneous_calling_indications") public static interface TelephonyCallback.SimultaneousCellularCallingSupportListener {
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onSimultaneousCellularCallingSubscriptionsChanged(@NonNull java.util.Set<java.lang.Integer>);
+ }
+
public static interface TelephonyCallback.SrvccStateListener {
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onSrvccStateChanged(int);
}
@@ -14637,6 +14701,7 @@
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEmergencyNumberDbVersion();
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimDomain();
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimIst();
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @Nullable @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, "com.android.phone.permission.ACCESS_LAST_KNOWN_CELL_ID"}) public android.telephony.CellIdentity getLastKnownCellIdentity();
method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Map<java.lang.Integer,java.lang.Integer> getLogicalToPhysicalSlotMapping();
method public int getMaxNumberOfSimultaneouslyActiveSims();
method public static long getMaxNumberVerificationTimeoutMillis();
@@ -14681,6 +14746,7 @@
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isLteCdmaEvdoGsmWcdmaEnabled();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isMobileDataPolicyEnabled(int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isNrDualConnectivityEnabled();
+ method @FlaggedApi("com.android.internal.telephony.flags.enable_modem_cipher_transparency") @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isNullCipherNotificationsEnabled();
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isOffhook();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isOpportunisticNetworkEnabled();
method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isPotentialEmergencyNumber(@NonNull String);
@@ -14720,6 +14786,7 @@
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(int, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataRoamingEnabled(boolean);
method @FlaggedApi("com.android.internal.telephony.flags.enable_identifier_disclosure_transparency") @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setEnableCellularIdentifierDisclosureNotifications(boolean);
+ method @FlaggedApi("com.android.internal.telephony.flags.enable_modem_cipher_transparency") @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setEnableNullCipherNotifications(boolean);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult setIccLockEnabled(boolean, @NonNull String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMobileDataPolicyEnabled(int, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMultiSimCarrierRestriction(boolean);
@@ -14738,6 +14805,7 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSystemSelectionChannels(@NonNull java.util.List<android.telephony.RadioAccessSpecifier>);
method @Deprecated public void setVisualVoicemailEnabled(android.telecom.PhoneAccountHandle, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setVoiceActivationState(int);
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(android.Manifest.permission.BIND_TELECOM_CONNECTION_SERVICE) public void setVoiceServiceStateOverride(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void shutdownAllRadios();
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult supplyIccLockPin(@NonNull String);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult supplyIccLockPuk(@NonNull String, @NonNull String);
@@ -14877,6 +14945,11 @@
field public static final int ERROR_UNKNOWN = 0; // 0x0
}
+ @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public class TelephonyRegistryManager {
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyCallStateChangedForAllSubscriptions(int, @Nullable String);
+ method public void notifyOutgoingEmergencyCall(int, int, @NonNull android.telephony.emergency.EmergencyNumber);
+ }
+
public final class ThermalMitigationRequest implements android.os.Parcelable {
method public int describeContents();
method @Nullable public android.telephony.DataThrottlingRequest getDataThrottlingRequest();
@@ -15127,7 +15200,7 @@
method public final void notifyDataCallListChanged(java.util.List<android.telephony.data.DataCallResponse>);
method public final void notifyDataProfileUnthrottled(@NonNull android.telephony.data.DataProfile);
method public void requestDataCallList(@NonNull android.telephony.data.DataServiceCallback);
- method @FlaggedApi("com.android.internal.telephony.flags.network_validation") public void requestValidation(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @FlaggedApi("com.android.internal.telephony.flags.network_validation") public void requestNetworkValidation(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method public void setDataProfile(@NonNull java.util.List<android.telephony.data.DataProfile>, boolean, @NonNull android.telephony.data.DataServiceCallback);
method public void setInitialAttachApn(@NonNull android.telephony.data.DataProfile, boolean, @NonNull android.telephony.data.DataServiceCallback);
method public void setupDataCall(int, @NonNull android.telephony.data.DataProfile, boolean, boolean, int, @Nullable android.net.LinkProperties, @NonNull android.telephony.data.DataServiceCallback);
@@ -16334,7 +16407,7 @@
public interface RegistrationManager {
field public static final int SUGGESTED_ACTION_NONE = 0; // 0x0
- field @FlaggedApi("com.android.internal.telephony.flags.add_rat_related_suggested_action_to_ims_registration") public static final int SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCK = 4; // 0x4
+ field @FlaggedApi("com.android.internal.telephony.flags.add_rat_related_suggested_action_to_ims_registration") public static final int SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCKS = 4; // 0x4
field public static final int SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK = 1; // 0x1
field public static final int SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK_WITH_TIMEOUT = 2; // 0x2
field @FlaggedApi("com.android.internal.telephony.flags.add_rat_related_suggested_action_to_ims_registration") public static final int SUGGESTED_ACTION_TRIGGER_RAT_BLOCK = 3; // 0x3
@@ -17032,8 +17105,8 @@
@FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class PointingInfo implements android.os.Parcelable {
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents();
- method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public float getSatelliteAzimuthDegrees();
- method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public float getSatelliteElevationDegrees();
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @FloatRange(from=0xffffff4c, to=180) public float getSatelliteAzimuthDegrees();
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @FloatRange(from=0xffffffa6, to=90) public float getSatelliteElevationDegrees();
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void writeToParcel(@NonNull android.os.Parcel, int);
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.PointingInfo> CREATOR;
}
@@ -17066,13 +17139,14 @@
@FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class SatelliteManager {
method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void addSatelliteAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionSatelliteService(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.List<java.lang.String> getAllSatellitePlmnsForCarrier(int);
method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getSatelliteAttachRestrictionReasonsForCarrier(int);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingSatelliteDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatelliteService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void registerForNtnSignalStrengthChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.NtnSignalStrengthCallback) throws android.telephony.satellite.SatelliteManager.SatelliteException;
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteCapabilitiesChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteCapabilitiesCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteDatagram(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDatagramCallback);
- method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteModemStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteStateCallback);
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteModemStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteModemStateCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteProvisionStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteProvisionStateCallback);
method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void removeSatelliteAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsDemoModeEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
@@ -17093,7 +17167,7 @@
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForNtnSignalStrengthChanged(@NonNull android.telephony.satellite.NtnSignalStrengthCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteCapabilitiesChanged(@NonNull android.telephony.satellite.SatelliteCapabilitiesCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteDatagram(@NonNull android.telephony.satellite.SatelliteDatagramCallback);
- method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteModemStateChanged(@NonNull android.telephony.satellite.SatelliteStateCallback);
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteModemStateChanged(@NonNull android.telephony.satellite.SatelliteModemStateCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteProvisionStateChanged(@NonNull android.telephony.satellite.SatelliteProvisionStateCallback);
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DATAGRAM_TYPE_LOCATION_SHARING = 2; // 0x2
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DATAGRAM_TYPE_SOS_MESSAGE = 1; // 0x1
@@ -17113,6 +17187,7 @@
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_NR_NTN = 2; // 0x2
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_PROPRIETARY = 4; // 0x4
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_UNKNOWN = 0; // 0x0
+ field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT = 2; // 0x2
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1; // 0x1
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE = 0; // 0x0
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED = 7; // 0x7
@@ -17163,12 +17238,12 @@
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int getErrorCode();
}
- @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteProvisionStateCallback {
- method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteProvisionStateChanged(boolean);
+ @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteModemStateCallback {
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteModemStateChanged(int);
}
- @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteStateCallback {
- method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteModemStateChanged(int);
+ @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteProvisionStateCallback {
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteProvisionStateChanged(boolean);
}
@FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteTransmissionUpdateCallback {
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index b2a28b2..6c83fd0 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -239,14 +239,6 @@
Field 'ACTION_SCORE_NETWORKS' is missing @BroadcastBehavior
BroadcastBehavior: android.net.Proxy#PROXY_CHANGE_ACTION:
Field 'PROXY_CHANGE_ACTION' is missing @BroadcastBehavior
-BroadcastBehavior: android.nfc.NfcAdapter#ACTION_ADAPTER_STATE_CHANGED:
- Field 'ACTION_ADAPTER_STATE_CHANGED' is missing @BroadcastBehavior
-BroadcastBehavior: android.nfc.NfcAdapter#ACTION_PREFERRED_PAYMENT_CHANGED:
- Field 'ACTION_PREFERRED_PAYMENT_CHANGED' is missing @BroadcastBehavior
-BroadcastBehavior: android.nfc.NfcAdapter#ACTION_REQUIRE_UNLOCK_FOR_NFC:
- Field 'ACTION_REQUIRE_UNLOCK_FOR_NFC' is missing @BroadcastBehavior
-BroadcastBehavior: android.nfc.NfcAdapter#ACTION_TRANSACTION_DETECTED:
- Field 'ACTION_TRANSACTION_DETECTED' is missing @BroadcastBehavior
BroadcastBehavior: android.os.DropBoxManager#ACTION_DROPBOX_ENTRY_ADDED:
Field 'ACTION_DROPBOX_ENTRY_ADDED' is missing @BroadcastBehavior
BroadcastBehavior: android.provider.CalendarContract#ACTION_EVENT_REMINDER:
@@ -1077,86 +1069,6 @@
Method 'applyVcnNetworkPolicy' documentation mentions permissions already declared by @RequiresPermission
RequiresPermission: android.net.vcn.VcnManager#removeVcnNetworkPolicyChangeListener(android.net.vcn.VcnManager.VcnNetworkPolicyChangeListener):
Method 'removeVcnNetworkPolicyChangeListener' documentation mentions permissions already declared by @RequiresPermission
-RequiresPermission: android.nfc.NfcAdapter#disableForegroundDispatch(android.app.Activity):
- Method 'disableForegroundDispatch' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.NfcAdapter#enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]):
- Method 'enableForegroundDispatch' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForAid(android.content.ComponentName, String):
- Method 'isDefaultServiceForAid' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForCategory(android.content.ComponentName, String):
- Method 'isDefaultServiceForCategory' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.cardemulation.CardEmulation#setOffHostForService(android.content.ComponentName, String):
- Method 'setOffHostForService' documentation mentions permissions already declared by @RequiresPermission
-RequiresPermission: android.nfc.tech.IsoDep#getTimeout():
- Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.IsoDep#setTimeout(int):
- Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.IsoDep#transceive(byte[]):
- Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyA(int, byte[]):
- Method 'authenticateSectorWithKeyA' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyB(int, byte[]):
- Method 'authenticateSectorWithKeyB' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#decrement(int, int):
- Method 'decrement' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#getTimeout():
- Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#increment(int, int):
- Method 'increment' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#readBlock(int):
- Method 'readBlock' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#restore(int):
- Method 'restore' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#setTimeout(int):
- Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#transceive(byte[]):
- Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#transfer(int):
- Method 'transfer' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#writeBlock(int, byte[]):
- Method 'writeBlock' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareUltralight#getTimeout():
- Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareUltralight#readPages(int):
- Method 'readPages' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareUltralight#setTimeout(int):
- Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareUltralight#transceive(byte[]):
- Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareUltralight#writePage(int, byte[]):
- Method 'writePage' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.Ndef#getNdefMessage():
- Method 'getNdefMessage' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.Ndef#isWritable():
- Method 'isWritable' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.Ndef#makeReadOnly():
- Method 'makeReadOnly' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.Ndef#writeNdefMessage(android.nfc.NdefMessage):
- Method 'writeNdefMessage' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NdefFormatable#format(android.nfc.NdefMessage):
- Method 'format' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NdefFormatable#formatReadOnly(android.nfc.NdefMessage):
- Method 'formatReadOnly' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcA#getTimeout():
- Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcA#setTimeout(int):
- Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcA#transceive(byte[]):
- Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcB#transceive(byte[]):
- Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcF#getTimeout():
- Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcF#setTimeout(int):
- Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcF#transceive(byte[]):
- Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcV#transceive(byte[]):
- Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.TagTechnology#close():
- Method 'close' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.TagTechnology#connect():
- Method 'connect' documentation mentions permissions without declaring @RequiresPermission
RequiresPermission: android.os.BugreportManager#cancelBugreport():
Method 'cancelBugreport' documentation mentions permissions without declaring @RequiresPermission
RequiresPermission: android.os.BugreportManager#preDumpUiData():
@@ -1863,10 +1775,6 @@
SAM-compatible parameters (such as parameter 1, "sessionListener", in android.media.session.MediaSessionManager.addOnActiveSessionsChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
SamShouldBeLast: android.media.session.MediaSessionManager#addOnSession2TokensChangedListener(android.media.session.MediaSessionManager.OnSession2TokensChangedListener, android.os.Handler):
SAM-compatible parameters (such as parameter 1, "listener", in android.media.session.MediaSessionManager.addOnSession2TokensChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
-SamShouldBeLast: android.nfc.NfcAdapter#enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle):
- SAM-compatible parameters (such as parameter 2, "callback", in android.nfc.NfcAdapter.enableReaderMode) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
-SamShouldBeLast: android.nfc.NfcAdapter#ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler):
- SAM-compatible parameters (such as parameter 3, "tagRemovedListener", in android.nfc.NfcAdapter.ignore) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
SamShouldBeLast: android.os.Binder#attachInterface(android.os.IInterface, String):
SAM-compatible parameters (such as parameter 1, "owner", in android.os.Binder.attachInterface) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
SamShouldBeLast: android.os.Binder#linkToDeath(android.os.IBinder.DeathRecipient, int):
@@ -1933,8 +1841,6 @@
Field 'ACTION_USB_PORT_COMPLIANCE_CHANGED' is missing @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
SdkConstant: android.hardware.usb.UsbManager#ACTION_USB_STATE:
Field 'ACTION_USB_STATE' is missing @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
-SdkConstant: android.nfc.NfcAdapter#ACTION_REQUIRE_UNLOCK_FOR_NFC:
- Field 'ACTION_REQUIRE_UNLOCK_FOR_NFC' is missing @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
SdkConstant: android.service.euicc.EuiccService#ACTION_DELETE_SUBSCRIPTION_PRIVILEGED:
Field 'ACTION_DELETE_SUBSCRIPTION_PRIVILEGED' is missing @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
SdkConstant: android.service.euicc.EuiccService#ACTION_RENAME_SUBSCRIPTION_PRIVILEGED:
@@ -2359,8 +2265,8 @@
New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteManager.provisionSatelliteService(String,byte[],android.os.CancellationSignal,java.util.concurrent.Executor,java.util.function.Consumer<java.lang.Integer>)
UnflaggedApi: android.telephony.satellite.SatelliteManager#registerForSatelliteDatagram(java.util.concurrent.Executor, android.telephony.satellite.SatelliteDatagramCallback):
New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteManager.registerForSatelliteDatagram(java.util.concurrent.Executor,android.telephony.satellite.SatelliteDatagramCallback)
-UnflaggedApi: android.telephony.satellite.SatelliteManager#registerForSatelliteModemStateChanged(java.util.concurrent.Executor, android.telephony.satellite.SatelliteStateCallback):
- New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteManager.registerForSatelliteModemStateChanged(java.util.concurrent.Executor,android.telephony.satellite.SatelliteStateCallback)
+UnflaggedApi: android.telephony.satellite.SatelliteManager#registerForSatelliteModemStateChanged(java.util.concurrent.Executor, android.telephony.satellite.SatelliteModemStateCallback):
+ New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteManager.registerForSatelliteModemStateChanged(java.util.concurrent.Executor,android.telephony.satellite.SatelliteModemStateCallback)
UnflaggedApi: android.telephony.satellite.SatelliteManager#registerForSatelliteProvisionStateChanged(java.util.concurrent.Executor, android.telephony.satellite.SatelliteProvisionStateCallback):
New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteManager.registerForSatelliteProvisionStateChanged(java.util.concurrent.Executor,android.telephony.satellite.SatelliteProvisionStateCallback)
UnflaggedApi: android.telephony.satellite.SatelliteManager#requestIsDemoModeEnabled(java.util.concurrent.Executor, android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>):
@@ -2389,8 +2295,8 @@
New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteManager.stopSatelliteTransmissionUpdates(android.telephony.satellite.SatelliteTransmissionUpdateCallback,java.util.concurrent.Executor,java.util.function.Consumer<java.lang.Integer>)
UnflaggedApi: android.telephony.satellite.SatelliteManager#unregisterForSatelliteDatagram(android.telephony.satellite.SatelliteDatagramCallback):
New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteManager.unregisterForSatelliteDatagram(android.telephony.satellite.SatelliteDatagramCallback)
-UnflaggedApi: android.telephony.satellite.SatelliteManager#unregisterForSatelliteModemStateChanged(android.telephony.satellite.SatelliteStateCallback):
- New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteManager.unregisterForSatelliteModemStateChanged(android.telephony.satellite.SatelliteStateCallback)
+UnflaggedApi: android.telephony.satellite.SatelliteManager#unregisterForSatelliteModemStateChanged(android.telephony.satellite.SatelliteModemStateCallback):
+ New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteManager.unregisterForSatelliteModemStateChanged(android.telephony.satellite.SatelliteModemStateCallback)
UnflaggedApi: android.telephony.satellite.SatelliteManager#unregisterForSatelliteProvisionStateChanged(android.telephony.satellite.SatelliteProvisionStateCallback):
New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteManager.unregisterForSatelliteProvisionStateChanged(android.telephony.satellite.SatelliteProvisionStateCallback)
UnflaggedApi: android.telephony.satellite.SatelliteManager.SatelliteException:
@@ -2403,10 +2309,10 @@
New API must be flagged with @FlaggedApi: class android.telephony.satellite.SatelliteProvisionStateCallback
UnflaggedApi: android.telephony.satellite.SatelliteProvisionStateCallback#onSatelliteProvisionStateChanged(boolean):
New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteProvisionStateCallback.onSatelliteProvisionStateChanged(boolean)
-UnflaggedApi: android.telephony.satellite.SatelliteStateCallback:
- New API must be flagged with @FlaggedApi: class android.telephony.satellite.SatelliteStateCallback
-UnflaggedApi: android.telephony.satellite.SatelliteStateCallback#onSatelliteModemStateChanged(int):
- New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteStateCallback.onSatelliteModemStateChanged(int)
+UnflaggedApi: android.telephony.satellite.SatelliteModemStateCallback:
+ New API must be flagged with @FlaggedApi: class android.telephony.satellite.SatelliteModemStateCallback
+UnflaggedApi: android.telephony.satellite.SatelliteModemStateCallback#onSatelliteModemStateChanged(int):
+ New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteModemStateCallback.onSatelliteModemStateChanged(int)
UnflaggedApi: android.telephony.satellite.SatelliteTransmissionUpdateCallback:
New API must be flagged with @FlaggedApi: class android.telephony.satellite.SatelliteTransmissionUpdateCallback
UnflaggedApi: android.telephony.satellite.SatelliteTransmissionUpdateCallback#onReceiveDatagramStateChanged(int, int, int):
diff --git a/core/api/system-removed.txt b/core/api/system-removed.txt
index 51b8a11..bbfa0ec 100644
--- a/core/api/system-removed.txt
+++ b/core/api/system-removed.txt
@@ -142,17 +142,6 @@
}
-package android.nfc {
-
- public final class NfcAdapter {
- method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disableNdefPush();
- method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableNdefPush();
- method public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, int);
- field public static final int FLAG_NDEF_PUSH_NO_CONFIRM = 1; // 0x1
- }
-
-}
-
package android.os {
public class Build {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index bbe03a3..a866a34 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -186,6 +186,7 @@
method public void setEligibleForLegacyPermissionPrompt(boolean);
method public static void setExitTransitionTimeout(long);
method public void setLaunchActivityType(int);
+ method public void setLaunchCookie(@NonNull android.app.ActivityOptions.LaunchCookie);
method public void setLaunchTaskDisplayAreaFeatureId(int);
method public void setLaunchWindowingMode(int);
method public void setLaunchedFromBubble(boolean);
@@ -193,6 +194,10 @@
method public void setTaskOverlay(boolean, boolean);
}
+ public static final class ActivityOptions.LaunchCookie {
+ ctor public ActivityOptions.LaunchCookie();
+ }
+
public static interface ActivityOptions.OnAnimationFinishedListener {
method public void onAnimationFinished(long);
}
@@ -284,16 +289,6 @@
method public default void onOpActiveChanged(@NonNull String, int, @NonNull String, @Nullable String, boolean, int, int);
}
- public final class AutomaticZenRule implements android.os.Parcelable {
- method @FlaggedApi("android.app.modes_api") public int getUserModifiedFields();
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_INTERRUPTION_FILTER = 2; // 0x2
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_NAME = 1; // 0x1
- }
-
- @FlaggedApi("android.app.modes_api") public static final class AutomaticZenRule.Builder {
- method @FlaggedApi("android.app.modes_api") @NonNull public android.app.AutomaticZenRule.Builder setUserModifiedFields(int);
- }
-
public class BroadcastOptions extends android.app.ComponentOptions {
ctor public BroadcastOptions();
ctor public BroadcastOptions(@NonNull android.os.Bundle);
@@ -493,10 +488,15 @@
}
public class UiModeManager {
+ method @FlaggedApi("android.app.modes_api") @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) public int getAttentionModeThemeOverlay();
method public boolean isNightModeLocked();
method public boolean isUiModeLocked();
method @RequiresPermission(value=android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION, conditional=true) public boolean releaseProjection(int);
method @RequiresPermission(value=android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION, conditional=true) public boolean requestProjection(int);
+ field @FlaggedApi("android.app.modes_api") public static final int MODE_ATTENTION_THEME_OVERLAY_DAY = 1002; // 0x3ea
+ field @FlaggedApi("android.app.modes_api") public static final int MODE_ATTENTION_THEME_OVERLAY_NIGHT = 1001; // 0x3e9
+ field @FlaggedApi("android.app.modes_api") public static final int MODE_ATTENTION_THEME_OVERLAY_OFF = 1000; // 0x3e8
+ field @FlaggedApi("android.app.modes_api") public static final int MODE_ATTENTION_THEME_OVERLAY_UNKNOWN = -1; // 0xffffffff
field public static final int PROJECTION_TYPE_ALL = -1; // 0xffffffff
field public static final int PROJECTION_TYPE_AUTOMOTIVE = 1; // 0x1
field public static final int PROJECTION_TYPE_NONE = 0; // 0x0
@@ -1177,6 +1177,7 @@
method public int getShowInLauncher();
field public static final int SHOW_IN_LAUNCHER_NO = 2; // 0x2
field public static final int SHOW_IN_LAUNCHER_SEPARATE = 1; // 0x1
+ field public static final int SHOW_IN_LAUNCHER_UNKNOWN = -1; // 0xffffffff
field public static final int SHOW_IN_LAUNCHER_WITH_PARENT = 0; // 0x0
}
@@ -3021,47 +3022,8 @@
method @Deprecated public boolean isBound();
}
- @FlaggedApi("android.app.modes_api") public final class ZenDeviceEffects implements android.os.Parcelable {
- method public int getUserModifiedFields();
- field public static final int FIELD_DIM_WALLPAPER = 4; // 0x4
- field public static final int FIELD_DISABLE_AUTO_BRIGHTNESS = 16; // 0x10
- field public static final int FIELD_DISABLE_TAP_TO_WAKE = 32; // 0x20
- field public static final int FIELD_DISABLE_TILT_TO_WAKE = 64; // 0x40
- field public static final int FIELD_DISABLE_TOUCH = 128; // 0x80
- field public static final int FIELD_GRAYSCALE = 1; // 0x1
- field public static final int FIELD_MAXIMIZE_DOZE = 512; // 0x200
- field public static final int FIELD_MINIMIZE_RADIO_USAGE = 256; // 0x100
- field public static final int FIELD_NIGHT_MODE = 8; // 0x8
- field public static final int FIELD_SUPPRESS_AMBIENT_DISPLAY = 2; // 0x2
- }
-
- @FlaggedApi("android.app.modes_api") public static final class ZenDeviceEffects.Builder {
- method @NonNull public android.service.notification.ZenDeviceEffects.Builder setUserModifiedFields(int);
- }
-
- public final class ZenPolicy implements android.os.Parcelable {
- method @FlaggedApi("android.app.modes_api") public int getUserModifiedFields();
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_ALLOW_CHANNELS = 8; // 0x8
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_CALLS = 2; // 0x2
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_CONVERSATIONS = 4; // 0x4
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_MESSAGES = 1; // 0x1
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_ALARMS = 128; // 0x80
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_EVENTS = 32; // 0x20
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_MEDIA = 256; // 0x100
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_REPEAT_CALLERS = 64; // 0x40
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_SYSTEM = 512; // 0x200
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_AMBIENT = 32768; // 0x8000
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_BADGE = 16384; // 0x4000
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_FULL_SCREEN_INTENT = 1024; // 0x400
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_LIGHTS = 2048; // 0x800
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_NOTIFICATION_LIST = 65536; // 0x10000
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_PEEK = 4096; // 0x1000
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_STATUS_BAR = 8192; // 0x2000
- }
-
public static final class ZenPolicy.Builder {
ctor public ZenPolicy.Builder(@Nullable android.service.notification.ZenPolicy);
- method @FlaggedApi("android.app.modes_api") @NonNull public android.service.notification.ZenPolicy.Builder setUserModifiedFields(int);
}
}
@@ -3292,7 +3254,7 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void refreshUiccProfile();
method @Deprecated public void setCarrierTestOverride(String, String, String, String, String, String, String);
method public void setCarrierTestOverride(String, String, String, String, String, String, String, String, String);
- method @RequiresPermission(android.Manifest.permission.BIND_TELECOM_CONNECTION_SERVICE) public void setVoiceServiceStateOverride(boolean);
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(android.Manifest.permission.BIND_TELECOM_CONNECTION_SERVICE) public void setVoiceServiceStateOverride(boolean);
field public static final int HAL_SERVICE_DATA = 1; // 0x1
field public static final int HAL_SERVICE_IMS = 7; // 0x7
field public static final int HAL_SERVICE_MESSAGING = 2; // 0x2
diff --git a/core/java/Android.bp b/core/java/Android.bp
index fb1e16a..eba500d 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -14,20 +14,12 @@
hdrs: ["android/hardware/HardwareBuffer.aidl"],
}
-// TODO (b/303286040): Remove this once |ENABLE_NFC_MAINLINE_FLAG| is rolled out
-filegroup {
- name: "framework-core-nfc-infcadapter-sources",
- srcs: [
- "android/nfc/INfcAdapter.aidl",
- ],
- visibility: ["//frameworks/base/services/core"],
-}
-
filegroup {
name: "framework-core-sources",
srcs: [
"**/*.java",
"**/*.aidl",
+ ":framework-nfc-non-updatable-sources",
],
visibility: ["//frameworks/base"],
}
diff --git a/core/java/android/adaptiveauth/OWNERS b/core/java/android/adaptiveauth/OWNERS
new file mode 100644
index 0000000..0218a78
--- /dev/null
+++ b/core/java/android/adaptiveauth/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/adaptiveauth/OWNERS
\ No newline at end of file
diff --git a/core/java/android/adaptiveauth/flags.aconfig b/core/java/android/adaptiveauth/flags.aconfig
new file mode 100644
index 0000000..39e46bb
--- /dev/null
+++ b/core/java/android/adaptiveauth/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.adaptiveauth"
+
+flag {
+ name: "report_biometric_auth_attempts"
+ namespace: "biometrics"
+ description: "Control the usage of the biometric auth signal in adaptive auth"
+ bug: "285053096"
+}
\ No newline at end of file
diff --git a/core/java/android/animation/ObjectAnimator.java b/core/java/android/animation/ObjectAnimator.java
index 1e1f155..5840f02 100644
--- a/core/java/android/animation/ObjectAnimator.java
+++ b/core/java/android/animation/ObjectAnimator.java
@@ -25,8 +25,6 @@
import android.util.Property;
import android.view.animation.AccelerateDecelerateInterpolator;
-import java.lang.ref.WeakReference;
-
/**
* This subclass of {@link ValueAnimator} provides support for animating properties on target objects.
* The constructors of this class take parameters to define the target object that will be animated
@@ -73,11 +71,7 @@
private static final boolean DBG = false;
- /**
- * A weak reference to the target object on which the property exists, set
- * in the constructor. We'll cancel the animation if this goes away.
- */
- private WeakReference<Object> mTarget;
+ private Object mTarget;
private String mPropertyName;
@@ -919,7 +913,7 @@
*/
@Nullable
public Object getTarget() {
- return mTarget == null ? null : mTarget.get();
+ return mTarget;
}
@Override
@@ -929,7 +923,7 @@
if (isStarted()) {
cancel();
}
- mTarget = target == null ? null : new WeakReference<Object>(target);
+ mTarget = target;
// New target should cause re-initialization prior to starting
mInitialized = false;
}
@@ -977,13 +971,6 @@
@Override
void animateValue(float fraction) {
final Object target = getTarget();
- if (mTarget != null && target == null) {
- // We lost the target reference, cancel and clean up. Note: we allow null target if the
- /// target has never been set.
- cancel();
- return;
- }
-
super.animateValue(fraction);
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index be2582f..2103055 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1177,23 +1177,12 @@
return mApplication;
}
- /**
- * Whether this is a child {@link Activity} of an {@link ActivityGroup}.
- *
- * @deprecated {@link ActivityGroup} is deprecated.
- */
- @Deprecated
+ /** Is this activity embedded inside of another activity? */
public final boolean isChild() {
return mParent != null;
}
- /**
- * Returns the parent {@link Activity} if this is a child {@link Activity} of an
- * {@link ActivityGroup}.
- *
- * @deprecated {@link ActivityGroup} is deprecated.
- */
- @Deprecated
+ /** Return the parent activity if this view is an embedded child. */
public final Activity getParent() {
return mParent;
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 6b7f4880..b2c6475 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -3574,7 +3574,7 @@
* foreground. This may be running a window that is behind the current
* foreground (so paused and with its state saved, not interacting with
* the user, but visible to them to some degree); it may also be running
- * other services under the system's control that it inconsiders important.
+ * other services under the system's control that it considers important.
*/
public static final int IMPORTANCE_VISIBLE = 200;
@@ -3646,9 +3646,9 @@
public static final int IMPORTANCE_CANT_SAVE_STATE = 350;
/**
- * Constant for {@link #importance}: This process process contains
- * cached code that is expendable, not actively running any app components
- * we care about.
+ * Constant for {@link #importance}: This process contains cached code
+ * that is expendable, not actively running any app components we care
+ * about.
*/
public static final int IMPORTANCE_CACHED = 400;
@@ -4052,10 +4052,28 @@
}
}
+ private final ArrayList<AppStartInfoCallbackWrapper> mAppStartInfoCallbacks =
+ new ArrayList<>();
+ @Nullable
+ private IApplicationStartInfoCompleteListener mAppStartInfoCompleteListener = null;
+
+ private static final class AppStartInfoCallbackWrapper {
+ @NonNull final Executor mExecutor;
+ @NonNull final Consumer<ApplicationStartInfo> mListener;
+
+ AppStartInfoCallbackWrapper(@NonNull final Executor executor,
+ @NonNull final Consumer<ApplicationStartInfo> listener) {
+ mExecutor = executor;
+ mListener = listener;
+ }
+ }
+
/**
- * Sets a callback to be notified when the {@link ApplicationStartInfo} records of this startup
+ * Adds a callback to be notified when the {@link ApplicationStartInfo} records of this startup
* are complete.
*
+ * <p class="note"> Note: callback will be removed automatically after being triggered.</p>
+ *
* <p class="note"> Note: callback will not wait for {@link Activity#reportFullyDrawn} to occur.
* Timestamp for fully drawn may be added after callback occurs. Set callback after invoking
* {@link Activity#reportFullyDrawn} if timestamp for fully drawn is required.</p>
@@ -4073,33 +4091,77 @@
* @throws IllegalArgumentException if executor or listener are null.
*/
@FlaggedApi(Flags.FLAG_APP_START_INFO)
- public void setApplicationStartInfoCompletionListener(@NonNull final Executor executor,
+ public void addApplicationStartInfoCompletionListener(@NonNull final Executor executor,
@NonNull final Consumer<ApplicationStartInfo> listener) {
Preconditions.checkNotNull(executor, "executor cannot be null");
Preconditions.checkNotNull(listener, "listener cannot be null");
- IApplicationStartInfoCompleteListener callback =
- new IApplicationStartInfoCompleteListener.Stub() {
- @Override
- public void onApplicationStartInfoComplete(ApplicationStartInfo applicationStartInfo) {
- executor.execute(() -> listener.accept(applicationStartInfo));
+ synchronized (mAppStartInfoCallbacks) {
+ for (int i = 0; i < mAppStartInfoCallbacks.size(); i++) {
+ if (listener.equals(mAppStartInfoCallbacks.get(i).mListener)) {
+ return;
+ }
}
- };
- try {
- getService().setApplicationStartInfoCompleteListener(callback, mContext.getUserId());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ if (mAppStartInfoCompleteListener == null) {
+ mAppStartInfoCompleteListener = new IApplicationStartInfoCompleteListener.Stub() {
+ @Override
+ public void onApplicationStartInfoComplete(
+ ApplicationStartInfo applicationStartInfo) {
+ synchronized (mAppStartInfoCallbacks) {
+ for (int i = 0; i < mAppStartInfoCallbacks.size(); i++) {
+ final AppStartInfoCallbackWrapper callback =
+ mAppStartInfoCallbacks.get(i);
+ callback.mExecutor.execute(() -> callback.mListener.accept(
+ applicationStartInfo));
+ }
+ mAppStartInfoCallbacks.clear();
+ mAppStartInfoCompleteListener = null;
+ }
+ }
+ };
+ boolean succeeded = false;
+ try {
+ getService().addApplicationStartInfoCompleteListener(
+ mAppStartInfoCompleteListener, mContext.getUserId());
+ succeeded = true;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ if (succeeded) {
+ mAppStartInfoCallbacks.add(new AppStartInfoCallbackWrapper(executor, listener));
+ } else {
+ mAppStartInfoCompleteListener = null;
+ mAppStartInfoCallbacks.clear();
+ }
+ } else {
+ mAppStartInfoCallbacks.add(new AppStartInfoCallbackWrapper(executor, listener));
+ }
}
}
/**
- * Removes the callback set by {@link #setApplicationStartInfoCompletionListener} if there is one.
+ * Removes the provided callback set by {@link #addApplicationStartInfoCompletionListener}.
*/
@FlaggedApi(Flags.FLAG_APP_START_INFO)
- public void clearApplicationStartInfoCompletionListener() {
- try {
- getService().clearApplicationStartInfoCompleteListener(mContext.getUserId());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ public void removeApplicationStartInfoCompletionListener(
+ @NonNull final Consumer<ApplicationStartInfo> listener) {
+ Preconditions.checkNotNull(listener, "listener cannot be null");
+ synchronized (mAppStartInfoCallbacks) {
+ for (int i = 0; i < mAppStartInfoCallbacks.size(); i++) {
+ final AppStartInfoCallbackWrapper callback = mAppStartInfoCallbacks.get(i);
+ if (listener.equals(callback.mListener)) {
+ mAppStartInfoCallbacks.remove(i);
+ break;
+ }
+ }
+ if (mAppStartInfoCompleteListener != null && mAppStartInfoCallbacks.isEmpty()) {
+ try {
+ getService().removeApplicationStartInfoCompleteListener(
+ mAppStartInfoCompleteListener, mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ mAppStartInfoCompleteListener = null;
+ }
}
}
@@ -4340,7 +4402,7 @@
}
/**
- * Start monitoring changes to the importance of uids running in the system.
+ * Start monitoring changes to the importance of all uids running in the system.
* @param listener The listener callback that will receive change reports.
* @param importanceCutpoint The level of importance in which the caller is interested
* in differences. For example, if {@link RunningAppProcessInfo#IMPORTANCE_PERCEPTIBLE}
@@ -4351,6 +4413,10 @@
* <p>The caller must hold the {@link android.Manifest.permission#PACKAGE_USAGE_STATS}
* permission to use this feature.</p>
*
+ * <p>Calling this API with the same instance of {@code listener} without
+ * unregistering with {@link #removeOnUidImportanceListener} before it will result in
+ * an {@link IllegalArgumentException}.</p>
+ *
* @throws IllegalArgumentException If the listener is already registered.
* @throws SecurityException If the caller does not hold
* {@link android.Manifest.permission#PACKAGE_USAGE_STATS}.
@@ -4360,17 +4426,52 @@
@RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
public void addOnUidImportanceListener(OnUidImportanceListener listener,
@RunningAppProcessInfo.Importance int importanceCutpoint) {
- synchronized (this) {
+ addOnUidImportanceListenerInternal(listener, importanceCutpoint, null /* uids */);
+ }
+
+ /**
+ * Start monitoring changes to the importance of given uids running in the system.
+ *
+ * @param listener The listener callback that will receive change reports.
+ * @param importanceCutpoint The level of importance in which the caller is interested
+ * in differences. For example, if {@link RunningAppProcessInfo#IMPORTANCE_PERCEPTIBLE}
+ * is used here, you will receive a call each time a uids importance transitions between
+ * being <= {@link RunningAppProcessInfo#IMPORTANCE_PERCEPTIBLE} and
+ * > {@link RunningAppProcessInfo#IMPORTANCE_PERCEPTIBLE}.
+ * @param uids The UIDs that this listener is interested with. A {@code null} value means
+ * all UIDs will be monitored by this listener, this will be equivalent to the
+ * {@link #addOnUidImportanceListener(OnUidImportanceListener, int)} in this case.
+ *
+ * <p>Calling this API with the same instance of {@code listener} without
+ * unregistering with {@link #removeOnUidImportanceListener} before it will result in
+ * an {@link IllegalArgumentException}.</p>
+ *
+ * @throws IllegalArgumentException If the listener is already registered.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_UID_IMPORTANCE_LISTENER_FOR_UIDS)
+ @SystemApi
+ @SuppressLint("SamShouldBeLast")
+ @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
+ public void addOnUidImportanceListener(@NonNull OnUidImportanceListener listener,
+ @RunningAppProcessInfo.Importance int importanceCutpoint, @Nullable int[] uids) {
+ addOnUidImportanceListenerInternal(listener, importanceCutpoint, uids);
+ }
+
+ @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
+ private void addOnUidImportanceListenerInternal(@NonNull OnUidImportanceListener listener,
+ @RunningAppProcessInfo.Importance int importanceCutpoint, @Nullable int[] uids) {
+ synchronized (mImportanceListeners) {
if (mImportanceListeners.containsKey(listener)) {
throw new IllegalArgumentException("Listener already registered: " + listener);
}
// TODO: implement the cut point in the system process to avoid IPCs.
MyUidObserver observer = new MyUidObserver(listener, mContext);
try {
- getService().registerUidObserver(observer,
+ getService().registerUidObserverForUids(observer,
UID_OBSERVER_PROCSTATE | UID_OBSERVER_GONE,
RunningAppProcessInfo.importanceToProcState(importanceCutpoint),
- mContext.getOpPackageName());
+ mContext.getOpPackageName(), uids);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -4388,7 +4489,7 @@
@SystemApi
@RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
public void removeOnUidImportanceListener(OnUidImportanceListener listener) {
- synchronized (this) {
+ synchronized (mImportanceListeners) {
MyUidObserver observer = mImportanceListeners.remove(listener);
if (observer == null) {
throw new IllegalArgumentException("Listener not registered: " + listener);
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 8af7ed1..57fca74 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -29,6 +29,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.ExitTransitionCoordinator.ActivityExitTransitionCallbacks;
@@ -41,6 +42,7 @@
import android.graphics.Bitmap.Config;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
+import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -1921,6 +1923,38 @@
}
/**
+ * An opaque token to use with {@link #setLaunchCookie(LaunchCookie)}.
+ *
+ * @hide
+ */
+ @SuppressLint("UnflaggedApi")
+ @TestApi
+ public static final class LaunchCookie {
+ /** @hide */
+ public final IBinder binder = new Binder();
+
+ /** @hide */
+ @SuppressLint("UnflaggedApi")
+ @TestApi
+ public LaunchCookie() {}
+ }
+
+ /**
+ * Sets a launch cookie that can be used to track the {@link Activity} and task that are
+ * launched as a result of this option. If the launched activity is a trampoline that starts
+ * another activity immediately, the cookie will be transferred to the next activity.
+ *
+ * @param launchCookie a developer specified identifier for a specific task.
+ *
+ * @hide
+ */
+ @SuppressLint("UnflaggedApi")
+ @TestApi
+ public void setLaunchCookie(@NonNull LaunchCookie launchCookie) {
+ setLaunchCookie(launchCookie.binder);
+ }
+
+ /**
* Sets a launch cookie that can be used to track the activity and task that are launch as a
* result of this option. If the launched activity is a trampoline that starts another activity
* immediately, the cookie will be transferred to the next activity.
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 1db1caf..4b2e93f 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1540,9 +1540,16 @@
public static final int OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER =
AppProtoEnums.APP_OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER;
+ /**
+ * See {@link #OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER}.
+ * @hide
+ */
+ public static final int OP_READ_SYSTEM_GRAMMATICAL_GENDER =
+ AppProtoEnums.APP_OP_READ_SYSTEM_GRAMMATICAL_GENDER;
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 143;
+ public static final int _NUM_OP = 144;
/**
* All app ops represented as strings.
@@ -2180,6 +2187,8 @@
*
* @hide
*/
+ @FlaggedApi(android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ @SystemApi
public static final String OPSTR_ACCESS_RESTRICTED_SETTINGS =
"android:access_restricted_settings";
@@ -2373,6 +2382,14 @@
public static final String OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER =
"android:rapid_clear_notifications_by_listener";
+ /**
+ * Allows an application to read the system grammatical gender.
+ *
+ * @hide
+ */
+ public static final String OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER =
+ "android:read_system_grammatical_gender";
+
/** {@link #sAppOpsToNote} not initialized yet for this op */
private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
/** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -2484,6 +2501,7 @@
OP_RECEIVE_SANDBOX_TRIGGER_AUDIO,
OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
OP_MEDIA_ROUTING_CONTROL,
+ OP_READ_SYSTEM_GRAMMATICAL_GENDER,
};
static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
@@ -2936,6 +2954,10 @@
OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER,
"RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER")
.setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_READ_SYSTEM_GRAMMATICAL_GENDER,
+ OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER, "READ_SYSTEM_GRAMMATICAL_GENDER")
+ .setPermission(Manifest.permission.READ_SYSTEM_GRAMMATICAL_GENDER)
+ .build(),
};
// The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java
index 656feb0..afa513d 100644
--- a/core/java/android/app/ApplicationStartInfo.java
+++ b/core/java/android/app/ApplicationStartInfo.java
@@ -26,11 +26,19 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.Xml;
import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
import android.util.proto.WireTypeMismatchException;
+import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -39,12 +47,18 @@
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Iterator;
import java.util.Map;
-import java.util.Set;
+import java.util.Objects;
/**
- * Provide information related to a processes startup.
+ * Describes information related to an application process's startup.
+ *
+ * <p>
+ * Many aspects concerning why and how an applications process was started are valuable for apps
+ * both for logging and for potential behavior changes. Reason for process start, start type,
+ * start times, throttling, and other useful diagnostic data can be obtained from
+ * {@link ApplicationStartInfo} records.
+ * </p>
*/
@FlaggedApi(Flags.FLAG_APP_START_INFO)
public final class ApplicationStartInfo implements Parcelable {
@@ -210,6 +224,11 @@
private int mDefiningUid;
/**
+ * @see #getPackageName
+ */
+ private String mPackageName;
+
+ /**
* @see #getProcessName
*/
private String mProcessName;
@@ -344,6 +363,14 @@
}
/**
+ * @see #getPackageName
+ * @hide
+ */
+ public void setPackageName(final String packageName) {
+ mPackageName = intern(packageName);
+ }
+
+ /**
* @see #getProcessName
* @hide
*/
@@ -456,6 +483,15 @@
}
/**
+ * Name of first package running in this process;
+ *
+ * @hide
+ */
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
* The actual process name it was running with.
*
* <p class="note"> Note: field will be set for any {@link #getStartupState} value.</p>
@@ -550,15 +586,15 @@
dest.writeInt(mRealUid);
dest.writeInt(mPackageUid);
dest.writeInt(mDefiningUid);
+ dest.writeString(mPackageName);
dest.writeString(mProcessName);
dest.writeInt(mReason);
- dest.writeInt(mStartupTimestampsNs.size());
- Set<Map.Entry<Integer, Long>> timestampEntrySet = mStartupTimestampsNs.entrySet();
- Iterator<Map.Entry<Integer, Long>> iter = timestampEntrySet.iterator();
- while (iter.hasNext()) {
- Map.Entry<Integer, Long> entry = iter.next();
- dest.writeInt(entry.getKey());
- dest.writeLong(entry.getValue());
+ dest.writeInt(mStartupTimestampsNs == null ? 0 : mStartupTimestampsNs.size());
+ if (mStartupTimestampsNs != null) {
+ for (int i = 0; i < mStartupTimestampsNs.size(); i++) {
+ dest.writeInt(mStartupTimestampsNs.keyAt(i));
+ dest.writeLong(mStartupTimestampsNs.valueAt(i));
+ }
}
dest.writeInt(mStartType);
dest.writeParcelable(mStartIntent, flags);
@@ -575,6 +611,7 @@
mRealUid = other.mRealUid;
mPackageUid = other.mPackageUid;
mDefiningUid = other.mDefiningUid;
+ mPackageName = other.mPackageName;
mProcessName = other.mProcessName;
mReason = other.mReason;
mStartupTimestampsNs = other.mStartupTimestampsNs;
@@ -589,6 +626,7 @@
mRealUid = in.readInt();
mPackageUid = in.readInt();
mDefiningUid = in.readInt();
+ mPackageName = intern(in.readString());
mProcessName = intern(in.readString());
mReason = in.readInt();
int starupTimestampCount = in.readInt();
@@ -620,6 +658,11 @@
}
};
+ private static final String PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMPS = "timestamps";
+ private static final String PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMP = "timestamp";
+ private static final String PROTO_SERIALIZER_ATTRIBUTE_KEY = "key";
+ private static final String PROTO_SERIALIZER_ATTRIBUTE_TS = "ts";
+
/**
* Write to a protocol buffer output stream. Protocol buffer message definition at {@link
* android.app.ApplicationStartInfoProto}
@@ -640,9 +683,22 @@
if (mStartupTimestampsNs != null && mStartupTimestampsNs.size() > 0) {
ByteArrayOutputStream timestampsBytes = new ByteArrayOutputStream();
ObjectOutputStream timestampsOut = new ObjectOutputStream(timestampsBytes);
- timestampsOut.writeObject(mStartupTimestampsNs);
+ TypedXmlSerializer serializer = Xml.resolveSerializer(timestampsOut);
+ serializer.startDocument(null, true);
+ serializer.startTag(null, PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMPS);
+ for (int i = 0; i < mStartupTimestampsNs.size(); i++) {
+ serializer.startTag(null, PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMP);
+ serializer.attributeInt(null, PROTO_SERIALIZER_ATTRIBUTE_KEY,
+ mStartupTimestampsNs.keyAt(i));
+ serializer.attributeLong(null, PROTO_SERIALIZER_ATTRIBUTE_TS,
+ mStartupTimestampsNs.valueAt(i));
+ serializer.endTag(null, PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMP);
+ }
+ serializer.endTag(null, PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMPS);
+ serializer.endDocument();
proto.write(ApplicationStartInfoProto.STARTUP_TIMESTAMPS,
timestampsBytes.toByteArray());
+ timestampsOut.close();
}
proto.write(ApplicationStartInfoProto.START_TYPE, mStartType);
if (mStartIntent != null) {
@@ -693,7 +749,24 @@
ByteArrayInputStream timestampsBytes = new ByteArrayInputStream(proto.readBytes(
ApplicationStartInfoProto.STARTUP_TIMESTAMPS));
ObjectInputStream timestampsIn = new ObjectInputStream(timestampsBytes);
- mStartupTimestampsNs = (ArrayMap<Integer, Long>) timestampsIn.readObject();
+ mStartupTimestampsNs = new ArrayMap<Integer, Long>();
+ try {
+ TypedXmlPullParser parser = Xml.resolvePullParser(timestampsIn);
+ XmlUtils.beginDocument(parser, PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMPS);
+ int depth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, depth)) {
+ if (PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMP.equals(parser.getName())) {
+ int key = parser.getAttributeInt(null,
+ PROTO_SERIALIZER_ATTRIBUTE_KEY);
+ long ts = parser.getAttributeLong(null,
+ PROTO_SERIALIZER_ATTRIBUTE_TS);
+ mStartupTimestampsNs.put(key, ts);
+ }
+ }
+ } catch (XmlPullParserException e) {
+ // Timestamps lost
+ }
+ timestampsIn.close();
break;
case (int) ApplicationStartInfoProto.START_TYPE:
mStartType = proto.readInt(ApplicationStartInfoProto.START_TYPE);
@@ -730,6 +803,7 @@
.append(" definingUid=").append(mDefiningUid)
.append(" user=").append(UserHandle.getUserId(mPackageUid))
.append('\n')
+ .append(" package=").append(mPackageName)
.append(" process=").append(mProcessName)
.append(" startupState=").append(mStartupState)
.append(" reason=").append(reasonToString(mReason))
@@ -740,13 +814,11 @@
sb.append(" intent=").append(mStartIntent.toString())
.append('\n');
}
- if (mStartupTimestampsNs.size() > 0) {
+ if (mStartupTimestampsNs != null && mStartupTimestampsNs.size() > 0) {
sb.append(" timestamps: ");
- Set<Map.Entry<Integer, Long>> timestampEntrySet = mStartupTimestampsNs.entrySet();
- Iterator<Map.Entry<Integer, Long>> iter = timestampEntrySet.iterator();
- while (iter.hasNext()) {
- Map.Entry<Integer, Long> entry = iter.next();
- sb.append(entry.getKey()).append("=").append(entry.getValue()).append(" ");
+ for (int i = 0; i < mStartupTimestampsNs.size(); i++) {
+ sb.append(mStartupTimestampsNs.keyAt(i)).append("=").append(mStartupTimestampsNs
+ .valueAt(i)).append(" ");
}
sb.append('\n');
}
@@ -780,4 +852,35 @@
default -> "";
};
}
+
+ /** @hide */
+ @Override
+ public boolean equals(@Nullable Object other) {
+ if (other == null || !(other instanceof ApplicationStartInfo)) {
+ return false;
+ }
+ final ApplicationStartInfo o = (ApplicationStartInfo) other;
+ return mPid == o.mPid && mRealUid == o.mRealUid && mPackageUid == o.mPackageUid
+ && mDefiningUid == o.mDefiningUid && mReason == o.mReason
+ && mStartupState == o.mStartupState && mStartType == o.mStartType
+ && mLaunchMode == o.mLaunchMode && TextUtils.equals(mProcessName, o.mProcessName)
+ && timestampsEquals(o);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPid, mRealUid, mPackageUid, mDefiningUid, mReason, mStartupState,
+ mStartType, mLaunchMode, mProcessName,
+ mStartupTimestampsNs);
+ }
+
+ private boolean timestampsEquals(@NonNull ApplicationStartInfo other) {
+ if (mStartupTimestampsNs == null && other.mStartupTimestampsNs == null) {
+ return true;
+ }
+ if (mStartupTimestampsNs == null || other.mStartupTimestampsNs == null) {
+ return false;
+ }
+ return mStartupTimestampsNs.equals(other.mStartupTimestampsNs);
+ }
}
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index 5b354fc..d57a4e5 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -23,7 +23,6 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.TestApi;
import android.app.NotificationManager.InterruptionFilter;
import android.content.ComponentName;
import android.net.Uri;
@@ -113,8 +112,8 @@
@Retention(RetentionPolicy.SOURCE)
public @interface Type {}
- /** Used to track which rule variables have been modified by the user.
- * Should be checked against the bitmask {@link #getUserModifiedFields()}.
+ /**
+ * Enum for the user-modifiable fields in this object.
* @hide
*/
@IntDef(flag = true, prefix = { "FIELD_" }, value = {
@@ -128,13 +127,11 @@
* @hide
*/
@FlaggedApi(Flags.FLAG_MODES_API)
- @TestApi
public static final int FIELD_NAME = 1 << 0;
/**
* @hide
*/
@FlaggedApi(Flags.FLAG_MODES_API)
- @TestApi
public static final int FIELD_INTERRUPTION_FILTER = 1 << 1;
private boolean enabled;
@@ -153,7 +150,6 @@
private int mIconResId;
private String mTriggerDescription;
private boolean mAllowManualInvocation;
- private @ModifiableField int mUserModifiedFields; // Bitwise representation
/**
* The maximum string length for any string contained in this automatic zen rule. This pertains
@@ -256,7 +252,6 @@
mIconResId = source.readInt();
mTriggerDescription = getTrimmedString(source.readString(), MAX_DESC_LENGTH);
mType = source.readInt();
- mUserModifiedFields = source.readInt();
}
}
@@ -307,8 +302,7 @@
* Returns whether this rule's name has been modified by the user.
* @hide
*/
- // TODO: b/310620812 - Replace with mUserModifiedFields & FIELD_NAME once
- // FLAG_MODES_API is inlined.
+ // TODO: b/310620812 - Consider removing completely. Seems not be used anywhere except tests.
public boolean isModified() {
return mModified;
}
@@ -506,32 +500,6 @@
return type;
}
- /**
- * Gets the bitmask representing which fields are user modified. Bits are set using
- * {@link ModifiableField}.
- * @hide
- */
- @FlaggedApi(Flags.FLAG_MODES_API)
- @TestApi
- public @ModifiableField int getUserModifiedFields() {
- return mUserModifiedFields;
- }
-
- /**
- * Returns {@code true} if the {@link AutomaticZenRule} can be updated.
- * When this returns {@code false}, calls to
- * {@link NotificationManager#updateAutomaticZenRule(String, AutomaticZenRule)}) with this rule
- * will ignore changes to user-configurable fields.
- */
- @FlaggedApi(Flags.FLAG_MODES_API)
- public boolean canUpdate() {
- // The rule is considered updateable if its bitmask has no user modifications, and
- // the bitmasks of the policy and device effects have no modification.
- return mUserModifiedFields == 0
- && (mZenPolicy == null || mZenPolicy.getUserModifiedFields() == 0)
- && (mDeviceEffects == null || mDeviceEffects.getUserModifiedFields() == 0);
- }
-
@Override
public int describeContents() {
return 0;
@@ -560,7 +528,6 @@
dest.writeInt(mIconResId);
dest.writeString(mTriggerDescription);
dest.writeInt(mType);
- dest.writeInt(mUserModifiedFields);
}
}
@@ -582,16 +549,14 @@
.append(",allowManualInvocation=").append(mAllowManualInvocation)
.append(",iconResId=").append(mIconResId)
.append(",triggerDescription=").append(mTriggerDescription)
- .append(",type=").append(mType)
- .append(",userModifiedFields=")
- .append(modifiedFieldsToString(mUserModifiedFields));
+ .append(",type=").append(mType);
}
return sb.append(']').toString();
}
- @FlaggedApi(Flags.FLAG_MODES_API)
- private String modifiedFieldsToString(int bitmask) {
+ /** @hide */
+ public static String fieldsToString(@ModifiableField int bitmask) {
ArrayList<String> modified = new ArrayList<>();
if ((bitmask & FIELD_NAME) != 0) {
modified.add("FIELD_NAME");
@@ -623,8 +588,7 @@
&& other.mAllowManualInvocation == mAllowManualInvocation
&& other.mIconResId == mIconResId
&& Objects.equals(other.mTriggerDescription, mTriggerDescription)
- && other.mType == mType
- && other.mUserModifiedFields == mUserModifiedFields;
+ && other.mType == mType;
}
return finalEquals;
}
@@ -634,8 +598,7 @@
if (Flags.modesApi()) {
return Objects.hash(enabled, name, interruptionFilter, conditionId, owner,
configurationActivity, mZenPolicy, mDeviceEffects, mModified, creationTime,
- mPkg, mAllowManualInvocation, mIconResId, mTriggerDescription, mType,
- mUserModifiedFields);
+ mPkg, mAllowManualInvocation, mIconResId, mTriggerDescription, mType);
}
return Objects.hash(enabled, name, interruptionFilter, conditionId, owner,
configurationActivity, mZenPolicy, mModified, creationTime, mPkg);
@@ -704,7 +667,6 @@
private boolean mAllowManualInvocation;
private long mCreationTime;
private String mPkg;
- private @ModifiableField int mUserModifiedFields;
public Builder(@NonNull AutomaticZenRule rule) {
mName = rule.getName();
@@ -721,7 +683,6 @@
mAllowManualInvocation = rule.isManualInvocationAllowed();
mCreationTime = rule.getCreationTime();
mPkg = rule.getPackageName();
- mUserModifiedFields = rule.mUserModifiedFields;
}
public Builder(@NonNull String name, @NonNull Uri conditionId) {
@@ -848,19 +809,6 @@
return this;
}
- /**
- * Sets the bitmask representing which fields have been user-modified.
- * This method should not be used outside of tests. The value of userModifiedFields
- * should be set based on what values are changed when a rule is populated or updated..
- * @hide
- */
- @FlaggedApi(Flags.FLAG_MODES_API)
- @TestApi
- public @NonNull Builder setUserModifiedFields(@ModifiableField int userModifiedFields) {
- mUserModifiedFields = userModifiedFields;
- return this;
- }
-
public @NonNull AutomaticZenRule build() {
AutomaticZenRule rule = new AutomaticZenRule(mName, mOwner, mConfigurationActivity,
mConditionId, mPolicy, mInterruptionFilter, mEnabled);
@@ -871,7 +819,6 @@
rule.mIconResId = mIconResId;
rule.mAllowManualInvocation = mAllowManualInvocation;
rule.setPackageName(mPkg);
- rule.mUserModifiedFields = mUserModifiedFields;
return rule;
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index edeec77..ed00d9c 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2409,6 +2409,17 @@
}
}
+ @Override
+ public int checkContentUriPermissionFull(Uri uri, int pid, int uid, int modeFlags) {
+ try {
+ return ActivityManager.getService().checkContentUriPermissionFull(
+ ContentProvider.getUriWithoutUserId(uri), pid, uid, modeFlags,
+ resolveUserId(uri));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
@NonNull
@Override
public int[] checkUriPermissions(@NonNull List<Uri> uris, int pid, int uid,
diff --git a/core/java/android/app/GrammaticalInflectionManager.java b/core/java/android/app/GrammaticalInflectionManager.java
index a55121a..483a6e1 100644
--- a/core/java/android/app/GrammaticalInflectionManager.java
+++ b/core/java/android/app/GrammaticalInflectionManager.java
@@ -114,7 +114,7 @@
}
try {
- mService.setSystemWideGrammaticalGender(mContext.getUserId(), grammaticalGender);
+ mService.setSystemWideGrammaticalGender(grammaticalGender, mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -131,7 +131,8 @@
@Configuration.GrammaticalGender
public int getSystemGrammaticalGender() {
try {
- return mService.getSystemGrammaticalGender(mContext.getUserId());
+ return mService.getSystemGrammaticalGender(mContext.getAttributionSource(),
+ mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 260e985..47403d2 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -116,6 +116,7 @@
* @throws RemoteException
* @return Returns A binder token identifying the UidObserver registration.
*/
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)")
IBinder registerUidObserverForUids(in IUidObserver observer, int which, int cutpoint,
String callingPackage, in int[] uids);
@@ -274,6 +275,7 @@
int getProcessLimit();
int checkUriPermission(in Uri uri, int pid, int uid, int mode, int userId,
in IBinder callerToken);
+ int checkContentUriPermissionFull(in Uri uri, int pid, int uid, int mode, int userId);
int[] checkUriPermissions(in List<Uri> uris, int pid, int uid, int mode, int userId,
in IBinder callerToken);
void grantUriPermission(in IApplicationThread caller, in String targetPkg, in Uri uri,
@@ -715,7 +717,7 @@
* @param listener A listener to for the callback upon completion of startup data collection.
* @param userId The userId in the multi-user environment.
*/
- void setApplicationStartInfoCompleteListener(IApplicationStartInfoCompleteListener listener,
+ void addApplicationStartInfoCompleteListener(IApplicationStartInfoCompleteListener listener,
int userId);
@@ -724,7 +726,8 @@
*
* @param userId The userId in the multi-user environment.
*/
- void clearApplicationStartInfoCompleteListener(int userId);
+ void removeApplicationStartInfoCompleteListener(IApplicationStartInfoCompleteListener listener,
+ int userId);
/**
diff --git a/core/java/android/app/IGrammaticalInflectionManager.aidl b/core/java/android/app/IGrammaticalInflectionManager.aidl
index 48a4841..86f2e91 100644
--- a/core/java/android/app/IGrammaticalInflectionManager.aidl
+++ b/core/java/android/app/IGrammaticalInflectionManager.aidl
@@ -1,5 +1,22 @@
+/**
+ * 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;
+import android.content.AttributionSource;
/**
* Internal interface used to control app-specific gender.
@@ -20,10 +37,10 @@
/**
* Sets the grammatical gender to system.
*/
- void setSystemWideGrammaticalGender(int userId, int gender);
+ void setSystemWideGrammaticalGender(int gender, int userId);
/**
* Gets the grammatical gender from system.
*/
- int getSystemGrammaticalGender(int userId);
+ int getSystemGrammaticalGender(in AttributionSource attributionSource, int userId);
}
diff --git a/core/java/android/app/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl
index 60b34cd..3b83024 100644
--- a/core/java/android/app/IUiModeManager.aidl
+++ b/core/java/android/app/IUiModeManager.aidl
@@ -95,6 +95,34 @@
int getNightModeCustomType();
/**
+ * Overlays current Night Mode value.
+ * {@code attentionModeThemeOverlayType}.
+ *
+ * @param attentionModeThemeOverlayType
+ * @hide
+ */
+ @EnforcePermission("MODIFY_DAY_NIGHT_MODE")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)")
+ void setAttentionModeThemeOverlay(int attentionModeThemeOverlayType);
+
+
+ /**
+ * Returns current Attention Mode overlay type.
+ * <p>
+ * returns
+ * <ul>
+ * <li>{@link #MODE_ATTENTION_OFF}</li>
+ * <li>{@link #MODE_ATTENTION_NIGHT}</li>
+ * <li>{@link #MODE_ATTENTION_DAY}</li>
+ * </ul>
+ * </p>
+ * @hide
+ */
+ @EnforcePermission("MODIFY_DAY_NIGHT_MODE")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)")
+ int getAttentionModeThemeOverlay();
+
+ /**
* Sets the dark mode for the given application. This setting is persisted and will override the
* system configuration for this application.
* 1 - notnight mode
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index d7d6546..5acb9b5 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -16,6 +16,7 @@
package android.app;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Bundle;
@@ -26,6 +27,8 @@
import android.content.ComponentName;
import android.app.WallpaperColors;
+import java.util.List;
+
/** @hide */
interface IWallpaperManager {
@@ -39,15 +42,21 @@
* FLAG_SET_SYSTEM
* FLAG_SET_LOCK
*
- * A 'null' cropHint rectangle is explicitly permitted as a sentinel for "whatever
- * the source image's bounding rect is."
+ * 'screenOrientations' and 'crops' define how the wallpaper will be positioned for
+ * different screen orientations. If some screen orientations are missing, crops for these
+ * orientations will be added by the system.
+ *
+ * If 'screenOrientations' is null, 'crops' can be null or a singleton list. The system will
+ * fit the provided crop (or the whole image, if 'crops' is 'null') for the current device
+ * orientation, and add crops for the missing orientations.
*
* The completion callback's "onWallpaperChanged()" method is invoked when the
* new wallpaper content is ready to display.
*/
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.SET_WALLPAPER)")
ParcelFileDescriptor setWallpaper(String name, in String callingPackage,
- in Rect cropHint, boolean allowBackup, out Bundle extras, int which,
- IWallpaperManagerCallback completion, int userId);
+ in int[] screenOrientations, in List<Rect> crops, boolean allowBackup,
+ out Bundle extras, int which, IWallpaperManagerCallback completion, int userId);
/**
* Set the live wallpaper.
@@ -78,6 +87,30 @@
boolean getCropped);
/**
+ * For a given user and a list of display sizes, get a list of Rect representing the
+ * area of the current wallpaper that is displayed for each display size.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL)")
+ @SuppressWarnings(value={"untyped-collection"})
+ List getBitmapCrops(in List<Point> displaySizes, int which, boolean originalBitmap, int userId);
+
+ /**
+ * Return how a bitmap of a given size would be cropped for a given list of display sizes when
+ * set with the given suggested crops.
+ * @hide
+ */
+ @SuppressWarnings(value={"untyped-collection"})
+ List getFutureBitmapCrops(in Point bitmapSize, in List<Point> displaySizes,
+ in int[] screenOrientations, in List<Rect> crops);
+
+ /**
+ * Return how a bitmap of a given size would be cropped when set with the given suggested crops.
+ * @hide
+ */
+ @SuppressWarnings(value={"untyped-collection"})
+ Rect getBitmapCrop(in Point bitmapSize, in int[] screenOrientations, in List<Rect> crops);
+
+ /**
* Retrieve the given user's current wallpaper ID of the given kind.
*/
int getWallpaperIdForUser(int which, int userId);
@@ -245,11 +278,4 @@
* @hide
*/
boolean isStaticWallpaper(int which);
-
- /**
- * Temporary method for project b/270726737.
- * Return true if the wallpaper supports different crops for different display dimensions.
- * @hide
- */
- boolean isMultiCropEnabled();
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 476232c..ed0cfbe 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5652,7 +5652,7 @@
pillColor = Colors.flattenAlpha(
getColors(p).getTertiaryFixedDimAccentColor(), bgColor);
textColor = Colors.flattenAlpha(
- getColors(p).getOnTertiaryAccentTextColor(), pillColor);
+ getColors(p).getOnTertiaryFixedAccentTextColor(), pillColor);
}
contentView.setInt(R.id.expand_button, "setHighlightTextColor", textColor);
contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor);
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 9cf732a..d755413 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -31,6 +31,7 @@
import android.app.blob.BlobStoreManagerFrameworkInitializer;
import android.app.contentsuggestions.ContentSuggestionsManager;
import android.app.contentsuggestions.IContentSuggestionsManager;
+import android.app.ecm.EnhancedConfirmationFrameworkInitializer;
import android.app.job.JobSchedulerFrameworkInitializer;
import android.app.people.PeopleManager;
import android.app.prediction.AppPredictionManager;
@@ -1631,6 +1632,9 @@
OnDevicePersonalizationFrameworkInitializer.registerServiceWrappers();
DeviceLockFrameworkInitializer.registerServiceWrappers();
VirtualizationFrameworkInitializer.registerServiceWrappers();
+ if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()) {
+ EnhancedConfirmationFrameworkInitializer.registerServiceWrappers();
+ }
} finally {
// If any of the above code throws, we're in a pretty bad shape and the process
// will likely crash, but we'll reset it just in case there's an exception handler...
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index 0ccb9cd..a271328 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -17,6 +17,7 @@
package android.app;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.IntRange;
@@ -265,6 +266,60 @@
*/
public static final int MODE_NIGHT_YES = 2;
+ /** @hide */
+ @IntDef(prefix = { "MODE_ATTENTION_THEME_OVERLAY_" }, value = {
+ MODE_ATTENTION_THEME_OVERLAY_OFF,
+ MODE_ATTENTION_THEME_OVERLAY_NIGHT,
+ MODE_ATTENTION_THEME_OVERLAY_DAY
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AttentionModeThemeOverlayType {}
+
+ /** @hide */
+ @IntDef(prefix = { "MODE_ATTENTION_THEME_OVERLAY_" }, value = {
+ MODE_ATTENTION_THEME_OVERLAY_OFF,
+ MODE_ATTENTION_THEME_OVERLAY_NIGHT,
+ MODE_ATTENTION_THEME_OVERLAY_DAY,
+ MODE_ATTENTION_THEME_OVERLAY_UNKNOWN
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AttentionModeThemeOverlayReturnType {}
+
+ /**
+ * Constant for {@link #setAttentionModeThemeOverlay(int)} (int)} and {@link
+ * #getAttentionModeThemeOverlay()}: Keeps night mode as set by {@link #setNightMode(int)}.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ @TestApi
+ public static final int MODE_ATTENTION_THEME_OVERLAY_OFF = 1000;
+
+ /**
+ * Constant for {@link #setAttentionModeThemeOverlay(int)} (int)} and {@link
+ * #getAttentionModeThemeOverlay()}: Maintains night mode always on.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ @TestApi
+ public static final int MODE_ATTENTION_THEME_OVERLAY_NIGHT = 1001;
+
+ /**
+ * Constant for {@link #setAttentionModeThemeOverlay(int)} (int)} and {@link
+ * #getAttentionModeThemeOverlay()}: Maintains night mode always off (Light).
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ @TestApi
+ public static final int MODE_ATTENTION_THEME_OVERLAY_DAY = 1002;
+
+ /**
+ * Constant for {@link #getAttentionModeThemeOverlay()}: Error communication with server.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ @TestApi
+ public static final int MODE_ATTENTION_THEME_OVERLAY_UNKNOWN = -1;
+
/**
* Granular types for {@link #setNightModeCustomType(int)}
* @hide
@@ -733,6 +788,55 @@
}
/**
+ * Overlays current Attention mode Night Mode overlay.
+ * {@code attentionModeThemeOverlayType}.
+ *
+ * @throws IllegalArgumentException if passed an unsupported type to
+ * {@code AttentionModeThemeOverlayType}.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
+ public void setAttentionModeThemeOverlay(
+ @AttentionModeThemeOverlayType int attentionModeThemeOverlayType) {
+ if (sGlobals != null) {
+ try {
+ sGlobals.mService.setAttentionModeThemeOverlay(attentionModeThemeOverlayType);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Returns the currently configured Attention Mode theme overlay.
+ * <p>
+ * May be one of:
+ * <ul>
+ * <li>{@link #MODE_ATTENTION_THEME_OVERLAY_OFF}</li>
+ * <li>{@link #MODE_ATTENTION_THEME_OVERLAY_NIGHT}</li>
+ * <li>{@link #MODE_ATTENTION_THEME_OVERLAY_DAY}</li>
+ * <li>{@link #MODE_ATTENTION_THEME_OVERLAY_UNKNOWN}</li>
+ * </ul>
+ * </p>
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
+ public @AttentionModeThemeOverlayReturnType int getAttentionModeThemeOverlay() {
+ if (sGlobals != null) {
+ try {
+ return sGlobals.mService.getAttentionModeThemeOverlay();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return MODE_ATTENTION_THEME_OVERLAY_UNKNOWN;
+ }
+
+ /**
* Sets and persist the night mode for this application.
* <p>
* The mode can be one of:
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 62db90f..63f37f1 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -18,11 +18,14 @@
import static android.Manifest.permission.MANAGE_EXTERNAL_STORAGE;
import static android.Manifest.permission.READ_WALLPAPER_INTERNAL;
+import static android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
+import static com.android.window.flags.Flags.FLAG_MULTI_CROP;
import static com.android.window.flags.Flags.multiCrop;
+import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -58,6 +61,7 @@
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PixelFormat;
+import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
@@ -84,6 +88,7 @@
import android.util.Log;
import android.util.MathUtils;
import android.util.Pair;
+import android.util.SparseArray;
import android.view.Display;
import android.view.WindowManagerGlobal;
@@ -104,6 +109,7 @@
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -289,6 +295,79 @@
public static final String EXTRA_FROM_FOREGROUND_APP =
"android.service.wallpaper.extra.FROM_FOREGROUND_APP";
+ /**
+ * The different screen orientations. {@link #getOrientation} provides their exact definition.
+ * This is only used internally by the framework and the WallpaperBackupAgent.
+ * @hide
+ */
+ @IntDef(value = {
+ ORIENTATION_UNKNOWN,
+ PORTRAIT,
+ LANDSCAPE,
+ SQUARE_PORTRAIT,
+ SQUARE_LANDSCAPE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ScreenOrientation {}
+
+ /**
+ * @hide
+ */
+ public static final int ORIENTATION_UNKNOWN = -1;
+
+ /**
+ * Portrait orientation of most screens
+ * @hide
+ */
+ public static final int PORTRAIT = 0;
+
+ /**
+ * Landscape orientation of most screens
+ * @hide
+ */
+ public static final int LANDSCAPE = 1;
+
+ /**
+ * Portrait orientation with similar width and height (e.g. the inner screen of a foldable)
+ * @hide
+ */
+ public static final int SQUARE_PORTRAIT = 2;
+
+ /**
+ * Landscape orientation with similar width and height (e.g. the inner screen of a foldable)
+ * @hide
+ */
+ public static final int SQUARE_LANDSCAPE = 3;
+
+ /**
+ * Converts a (width, height) screen size to a {@link ScreenOrientation}.
+ * @param screenSize the dimensions of a screen
+ * @return the corresponding {@link ScreenOrientation}.
+ * @hide
+ */
+ public static @ScreenOrientation int getOrientation(Point screenSize) {
+ float ratio = ((float) screenSize.x) / screenSize.y;
+ // ratios between 3/4 and 4/3 are considered square
+ return ratio >= 4 / 3f ? LANDSCAPE
+ : ratio > 1f ? SQUARE_LANDSCAPE
+ : ratio > 3 / 4f ? SQUARE_PORTRAIT
+ : PORTRAIT;
+ }
+
+ /**
+ * Get the 90° rotation of a given orientation
+ * @hide
+ */
+ public static @ScreenOrientation int getRotatedOrientation(@ScreenOrientation int orientation) {
+ switch (orientation) {
+ case PORTRAIT: return LANDSCAPE;
+ case LANDSCAPE: return PORTRAIT;
+ case SQUARE_PORTRAIT: return SQUARE_LANDSCAPE;
+ case SQUARE_LANDSCAPE: return SQUARE_PORTRAIT;
+ default: return ORIENTATION_UNKNOWN;
+ }
+ }
+
// flags for which kind of wallpaper to act on
/** @hide */
@@ -867,15 +946,8 @@
* @hide
*/
public static boolean isMultiCropEnabled() {
- if (sGlobals == null) {
- sIsMultiCropEnabled = multiCrop();
- }
if (sIsMultiCropEnabled == null) {
- try {
- sIsMultiCropEnabled = sGlobals.mService.isMultiCropEnabled();
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
+ sIsMultiCropEnabled = multiCrop();
}
return sIsMultiCropEnabled;
}
@@ -1502,6 +1574,99 @@
}
/**
+ * For the current user, given a list of display sizes, return a list of rectangles representing
+ * the area of the current wallpaper that would be shown for each of these sizes.
+ *
+ * @param displaySizes the display sizes.
+ * @param which wallpaper type. Must be either {@link #FLAG_SYSTEM} or {@link #FLAG_LOCK}.
+ * @param originalBitmap If true, return areas relative to the original bitmap.
+ * If false, return areas relative to the cropped bitmap.
+ * @return A List of Rect where the Rect is within the cropped/original bitmap, and corresponds
+ * to what is displayed. The Rect may have a larger width/height ratio than the screen
+ * due to parallax. Return {@code null} if the wallpaper is not an ImageWallpaper.
+ * Also return {@code null} when called with which={@link #FLAG_LOCK} if there is a
+ * shared home + lock wallpaper.
+ * @hide
+ */
+ @FlaggedApi(FLAG_MULTI_CROP)
+ @RequiresPermission(READ_WALLPAPER_INTERNAL)
+ @Nullable
+ public List<Rect> getBitmapCrops(@NonNull List<Point> displaySizes,
+ @SetWallpaperFlags int which, boolean originalBitmap) {
+ checkExactlyOneWallpaperFlagSet(which);
+ try {
+ return sGlobals.mService.getBitmapCrops(displaySizes, which, originalBitmap,
+ mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * For preview purposes.
+ * Return how a bitmap of a given size would be cropped for a given list of display sizes, if
+ * it was set as wallpaper via {@link #setBitmapWithCrops(Bitmap, Map, boolean, int)} or
+ * {@link #setStreamWithCrops(InputStream, Map, boolean, int)}.
+ *
+ * @return A List of Rect where the Rect is within the bitmap, and corresponds to what is
+ * displayed for each display size. The Rect may have a larger width/height ratio than
+ * the display due to parallax.
+ * @hide
+ */
+ @FlaggedApi(FLAG_MULTI_CROP)
+ @Nullable
+ public List<Rect> getBitmapCrops(@NonNull Point bitmapSize, @NonNull List<Point> displaySizes,
+ @Nullable Map<Point, Rect> cropHints) {
+ try {
+ if (cropHints == null) cropHints = Map.of();
+ Set<Map.Entry<Point, Rect>> entries = cropHints.entrySet();
+ int[] screenOrientations = entries.stream().mapToInt(entry ->
+ getOrientation(entry.getKey())).toArray();
+ List<Rect> crops = entries.stream().map(Map.Entry::getValue).toList();
+ return sGlobals.mService.getFutureBitmapCrops(bitmapSize, displaySizes,
+ screenOrientations, crops);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * For preview purposes.
+ * Compute the wallpaper colors of the given bitmap, if it was set as wallpaper via
+ * {@link #setBitmapWithCrops(Bitmap, Map, boolean, int)} or
+ * {@link #setStreamWithCrops(InputStream, Map, boolean, int)}.
+ * Return {@code null} if an error occurred and the colors could not be computed.
+ *
+ * @hide
+ */
+ @FlaggedApi(FLAG_MULTI_CROP)
+ @RequiresPermission(SET_WALLPAPER_DIM_AMOUNT)
+ @Nullable
+ public WallpaperColors getWallpaperColors(@NonNull Bitmap bitmap,
+ @Nullable Map<Point, Rect> cropHints) {
+ if (sGlobals.mService == null) {
+ Log.w(TAG, "WallpaperService not running");
+ throw new RuntimeException(new DeadSystemException());
+ }
+ try {
+ if (cropHints == null) cropHints = Map.of();
+ Set<Map.Entry<Point, Rect>> entries = cropHints.entrySet();
+ int[] screenOrientations = entries.stream().mapToInt(entry ->
+ getOrientation(entry.getKey())).toArray();
+ List<Rect> crops = entries.stream().map(Map.Entry::getValue).toList();
+ Point bitmapSize = new Point(bitmap.getWidth(), bitmap.getHeight());
+ Rect crop = sGlobals.mService.getBitmapCrop(bitmapSize, screenOrientations, crops);
+ float dimAmount = getWallpaperDimAmount();
+ Bitmap croppedBitmap = Bitmap.createBitmap(
+ bitmap, crop.left, crop.top, crop.width(), crop.height());
+ WallpaperColors result = WallpaperColors.fromBitmap(croppedBitmap, dimAmount);
+ return result;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* <strong> Important note: </strong>
* <ul>
* <li>Up to version S, this method requires the
@@ -1971,7 +2136,7 @@
/* Set the wallpaper to the default values */
ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(
"res:" + resources.getResourceName(resid),
- mContext.getOpPackageName(), null, false, result, which, completion,
+ mContext.getOpPackageName(), null, null, false, result, which, completion,
mContext.getUserId());
if (fd != null) {
FileOutputStream fos = null;
@@ -2089,6 +2254,11 @@
public int setBitmap(Bitmap fullImage, Rect visibleCropHint,
boolean allowBackup, @SetWallpaperFlags int which, int userId)
throws IOException {
+ if (multiCrop()) {
+ SparseArray<Rect> cropMap = new SparseArray<>();
+ if (visibleCropHint != null) cropMap.put(ORIENTATION_UNKNOWN, visibleCropHint);
+ return setBitmapWithCrops(fullImage, cropMap, allowBackup, which, userId);
+ }
validateRect(visibleCropHint);
if (sGlobals.mService == null) {
Log.w(TAG, "WallpaperService not running");
@@ -2096,9 +2266,69 @@
}
final Bundle result = new Bundle();
final WallpaperSetCompletion completion = new WallpaperSetCompletion();
+ final List<Rect> crops = visibleCropHint == null ? null : List.of(visibleCropHint);
try {
ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,
- mContext.getOpPackageName(), visibleCropHint, allowBackup,
+ mContext.getOpPackageName(), null, crops, allowBackup, result, which,
+ completion, userId);
+ if (fd != null) {
+ FileOutputStream fos = null;
+ try {
+ fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
+ fullImage.compress(Bitmap.CompressFormat.PNG, 90, fos);
+ fos.close();
+ completion.waitForCompletion();
+ } finally {
+ IoUtils.closeQuietly(fos);
+ }
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return result.getInt(EXTRA_NEW_WALLPAPER_ID, 0);
+ }
+
+ /**
+ * Version of setBitmap that defines how the wallpaper will be positioned for different
+ * display sizes.
+ * Requires permission {@link android.Manifest.permission#SET_WALLPAPER}.
+ * @param cropHints map from screen dimensions to a sub-region of the image to display for those
+ * dimensions. The {@code Rect} sub-region may have a larger width/height ratio
+ * than the screen dimensions to apply a horizontal parallax effect. If the
+ * map is empty or some entries are missing, the system will apply a default
+ * strategy to position the wallpaper for any unspecified screen dimensions.
+ * @hide
+ */
+ @FlaggedApi(FLAG_MULTI_CROP)
+ @RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
+ public int setBitmapWithCrops(@Nullable Bitmap fullImage, @NonNull Map<Point, Rect> cropHints,
+ boolean allowBackup, @SetWallpaperFlags int which) throws IOException {
+ SparseArray<Rect> crops = new SparseArray<>();
+ cropHints.forEach((k, v) -> crops.put(getOrientation(k), v));
+ return setBitmapWithCrops(fullImage, crops, allowBackup, which, mContext.getUserId());
+ }
+
+ @RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
+ private int setBitmapWithCrops(@Nullable Bitmap fullImage, @NonNull SparseArray<Rect> cropHints,
+ boolean allowBackup, @SetWallpaperFlags int which, int userId) throws IOException {
+ if (sGlobals.mService == null) {
+ Log.w(TAG, "WallpaperService not running");
+ throw new RuntimeException(new DeadSystemException());
+ }
+ int size = cropHints.size();
+ int[] screenOrientations = new int[size];
+ List<Rect> crops = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
+ screenOrientations[i] = cropHints.keyAt(i);
+ Rect cropHint = cropHints.valueAt(i);
+ validateRect(cropHint);
+ crops.add(cropHint);
+ }
+ final Bundle result = new Bundle();
+ final WallpaperSetCompletion completion = new WallpaperSetCompletion();
+ try {
+ ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,
+ mContext.getOpPackageName(), screenOrientations, crops, allowBackup,
result, which, completion, userId);
if (fd != null) {
FileOutputStream fos = null;
@@ -2214,6 +2444,11 @@
public int setStream(InputStream bitmapData, Rect visibleCropHint,
boolean allowBackup, @SetWallpaperFlags int which)
throws IOException {
+ if (multiCrop()) {
+ SparseArray<Rect> cropMap = new SparseArray<>();
+ if (visibleCropHint != null) cropMap.put(ORIENTATION_UNKNOWN, visibleCropHint);
+ return setStreamWithCrops(bitmapData, cropMap, allowBackup, which);
+ }
validateRect(visibleCropHint);
if (sGlobals.mService == null) {
Log.w(TAG, "WallpaperService not running");
@@ -2221,10 +2456,11 @@
}
final Bundle result = new Bundle();
final WallpaperSetCompletion completion = new WallpaperSetCompletion();
+ final List<Rect> crops = visibleCropHint == null ? null : List.of(visibleCropHint);
try {
ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,
- mContext.getOpPackageName(), visibleCropHint, allowBackup,
- result, which, completion, mContext.getUserId());
+ mContext.getOpPackageName(), null, crops, allowBackup, result, which,
+ completion, mContext.getUserId());
if (fd != null) {
FileOutputStream fos = null;
try {
@@ -2244,6 +2480,75 @@
}
/**
+ * Version of setStream that defines how the wallpaper will be positioned for different
+ * display sizes.
+ * Requires permission {@link android.Manifest.permission#SET_WALLPAPER}.
+ * @param cropHints map from screen dimensions to a sub-region of the image to display for those
+ * dimensions. The {@code Rect} sub-region may have a larger width/height ratio
+ * than the screen dimensions to apply a horizontal parallax effect. If the
+ * map is empty or some entries are missing, the system will apply a default
+ * strategy to position the wallpaper for any unspecified screen dimensions.
+ * @hide
+ */
+ @FlaggedApi(FLAG_MULTI_CROP)
+ @RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
+ public int setStreamWithCrops(InputStream bitmapData, @NonNull Map<Point, Rect> cropHints,
+ boolean allowBackup, @SetWallpaperFlags int which) throws IOException {
+ SparseArray<Rect> crops = new SparseArray<>();
+ cropHints.forEach((k, v) -> crops.put(getOrientation(k), v));
+ return setStreamWithCrops(bitmapData, crops, allowBackup, which);
+ }
+
+ /**
+ * Similar to {@link #setStreamWithCrops(InputStream, Map, boolean, int)}, but using
+ * {@link ScreenOrientation} as keys of the cropHints map. Used for backup & restore, since
+ * WallpaperBackupAgent stores orientations rather than the exact display size.
+ * Requires permission {@link android.Manifest.permission#SET_WALLPAPER}.
+ * @param cropHints map from {@link ScreenOrientation} to a sub-region of the image to display
+ * for that screen orientation.
+ * @hide
+ */
+ @FlaggedApi(FLAG_MULTI_CROP)
+ @RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
+ public int setStreamWithCrops(InputStream bitmapData, @NonNull SparseArray<Rect> cropHints,
+ boolean allowBackup, @SetWallpaperFlags int which) throws IOException {
+ if (sGlobals.mService == null) {
+ Log.w(TAG, "WallpaperService not running");
+ throw new RuntimeException(new DeadSystemException());
+ }
+ int size = cropHints.size();
+ int[] screenOrientations = new int[size];
+ List<Rect> crops = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
+ screenOrientations[i] = cropHints.keyAt(i);
+ Rect cropHint = cropHints.valueAt(i);
+ validateRect(cropHint);
+ crops.add(cropHint);
+ }
+ final Bundle result = new Bundle();
+ final WallpaperSetCompletion completion = new WallpaperSetCompletion();
+ try {
+ ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,
+ mContext.getOpPackageName(), screenOrientations, crops, allowBackup,
+ result, which, completion, mContext.getUserId());
+ if (fd != null) {
+ FileOutputStream fos = null;
+ try {
+ fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
+ copyStreamToWallpaperFile(bitmapData, fos);
+ fos.close();
+ completion.waitForCompletion();
+ } finally {
+ IoUtils.closeQuietly(fos);
+ }
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return result.getInt(EXTRA_NEW_WALLPAPER_ID, 0);
+ }
+
+ /**
* Return whether any users are currently set to use the wallpaper
* with the given resource ID. That is, their wallpaper has been
* set through {@link #setResource(int)} with the same resource id.
@@ -2499,7 +2804,7 @@
* @hide
*/
@SystemApi
- @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT)
+ @RequiresPermission(SET_WALLPAPER_DIM_AMOUNT)
public void setWallpaperDimAmount(@FloatRange (from = 0f, to = 1f) float dimAmount) {
if (sGlobals.mService == null) {
Log.w(TAG, "WallpaperService not running");
@@ -2519,7 +2824,7 @@
* @hide
*/
@SystemApi
- @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT)
+ @RequiresPermission(SET_WALLPAPER_DIM_AMOUNT)
public @FloatRange (from = 0f, to = 1f) float getWallpaperDimAmount() {
if (sGlobals.mService == null) {
Log.w(TAG, "WallpaperService not running");
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index b303ea6..c0b299b 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -13,3 +13,17 @@
description: "API to get importance of UID that's binding to the caller"
bug: "292533010"
}
+
+flag {
+ namespace: "backstage_power"
+ name: "app_restrictions_api"
+ description: "API to track and query restrictions applied to apps"
+ bug: "320150834"
+}
+
+flag {
+ namespace: "backstage_power"
+ name: "uid_importance_listener_for_uids"
+ description: "API to add OnUidImportanceListener with targetted UIDs"
+ bug: "286258140"
+}
diff --git a/core/java/android/app/ambient_context.aconfig b/core/java/android/app/ambient_context.aconfig
new file mode 100644
index 0000000..3f73da2
--- /dev/null
+++ b/core/java/android/app/ambient_context.aconfig
@@ -0,0 +1,8 @@
+package: "android.app"
+
+flag {
+ namespace: "biometrics_integration"
+ name: "ambient_heart_rate"
+ description: "Feature flag for adding heart rate api to ambient context."
+ bug: "318309481"
+}
diff --git a/core/java/android/app/ambientcontext/AmbientContextEvent.java b/core/java/android/app/ambientcontext/AmbientContextEvent.java
index b5c66ff..5ab7991 100644
--- a/core/java/android/app/ambientcontext/AmbientContextEvent.java
+++ b/core/java/android/app/ambientcontext/AmbientContextEvent.java
@@ -16,7 +16,9 @@
package android.app.ambientcontext;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcelable;
@@ -68,6 +70,14 @@
public static final int EVENT_BACK_DOUBLE_TAP = 3;
/**
+ * The integer indicating a heart rate measurement was done.
+ *
+ * @see #getRatePerMinute
+ */
+ @Event @FlaggedApi(android.app.Flags.FLAG_AMBIENT_HEART_RATE)
+ public static final int EVENT_HEART_RATE = 4;
+
+ /**
* Integer indicating the start of wearable vendor defined events that can be detected.
* These depend on the vendor implementation.
*/
@@ -79,12 +89,19 @@
*/
public static final String KEY_VENDOR_WEARABLE_EVENT_NAME = "wearable_event_name";
+ /**
+ * Default value for {@link #getRatePerMinute}. Indicates that the rate of the event is unknown.
+ */
+ @FlaggedApi(android.app.Flags.FLAG_AMBIENT_HEART_RATE)
+ public static final int RATE_PER_MINUTE_UNKNOWN = -1;
+
/** @hide */
@IntDef(prefix = { "EVENT_" }, value = {
EVENT_UNKNOWN,
EVENT_COUGH,
EVENT_SNORE,
EVENT_BACK_DOUBLE_TAP,
+ EVENT_HEART_RATE,
EVENT_VENDOR_WEARABLE_START,
})
@Retention(RetentionPolicy.SOURCE)
@@ -170,6 +187,16 @@
return new PersistableBundle();
}
+ /**
+ * Rate per minute of the event during the start to end time.
+ *
+ * @return the rate per minute, or {@link #RATE_PER_MINUTE_UNKNOWN} if the rate is unknown.
+ */
+ private final @IntRange(from = -1) int mRatePerMinute;
+ private static int defaultRatePerMinute() {
+ return RATE_PER_MINUTE_UNKNOWN;
+ }
+
// Code below generated by codegen v1.0.23.
@@ -179,6 +206,8 @@
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/app/ambientcontext/AmbientContextEvent.java
+ // then manually add @FlaggedApi(android.app.Flags.FLAG_AMBIENT_HEART_RATE) back to flagged
+ // APIs.
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
@@ -191,6 +220,7 @@
EVENT_COUGH,
EVENT_SNORE,
EVENT_BACK_DOUBLE_TAP,
+ EVENT_HEART_RATE,
EVENT_VENDOR_WEARABLE_START
})
@Retention(RetentionPolicy.SOURCE)
@@ -209,6 +239,8 @@
return "EVENT_SNORE";
case EVENT_BACK_DOUBLE_TAP:
return "EVENT_BACK_DOUBLE_TAP";
+ case EVENT_HEART_RATE:
+ return "EVENT_HEART_RATE";
case EVENT_VENDOR_WEARABLE_START:
return "EVENT_VENDOR_WEARABLE_START";
default: return Integer.toHexString(value);
@@ -255,7 +287,8 @@
@NonNull Instant endTime,
@LevelValue int confidenceLevel,
@LevelValue int densityLevel,
- @NonNull PersistableBundle vendorData) {
+ @NonNull PersistableBundle vendorData,
+ @IntRange(from = -1) int ratePerMinute) {
this.mEventType = eventType;
com.android.internal.util.AnnotationValidations.validate(
EventCode.class, null, mEventType);
@@ -274,6 +307,10 @@
this.mVendorData = vendorData;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mVendorData);
+ this.mRatePerMinute = ratePerMinute;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mRatePerMinute,
+ "from", -1);
// onConstructed(); // You can define this method to get a callback
}
@@ -330,6 +367,17 @@
return mVendorData;
}
+ /**
+ * Rate per minute of the event during the start to end time.
+ *
+ * @return the rate per minute, or {@link #RATE_PER_MINUTE_UNKNOWN} if the rate is unknown.
+ */
+ @DataClass.Generated.Member
+ @FlaggedApi(android.app.Flags.FLAG_AMBIENT_HEART_RATE)
+ public @IntRange(from = -1) int getRatePerMinute() {
+ return mRatePerMinute;
+ }
+
@Override
@DataClass.Generated.Member
public String toString() {
@@ -342,7 +390,8 @@
"endTime = " + mEndTime + ", " +
"confidenceLevel = " + mConfidenceLevel + ", " +
"densityLevel = " + mDensityLevel + ", " +
- "vendorData = " + mVendorData +
+ "vendorData = " + mVendorData + ", " +
+ "ratePerMinute = " + mRatePerMinute +
" }";
}
@@ -380,6 +429,7 @@
dest.writeInt(mConfidenceLevel);
dest.writeInt(mDensityLevel);
dest.writeTypedObject(mVendorData, flags);
+ dest.writeInt(mRatePerMinute);
}
@Override
@@ -399,6 +449,7 @@
int confidenceLevel = in.readInt();
int densityLevel = in.readInt();
PersistableBundle vendorData = (PersistableBundle) in.readTypedObject(PersistableBundle.CREATOR);
+ int ratePerMinute = in.readInt();
this.mEventType = eventType;
com.android.internal.util.AnnotationValidations.validate(
@@ -418,6 +469,10 @@
this.mVendorData = vendorData;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mVendorData);
+ this.mRatePerMinute = ratePerMinute;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mRatePerMinute,
+ "from", -1);
// onConstructed(); // You can define this method to get a callback
}
@@ -449,6 +504,7 @@
private @LevelValue int mConfidenceLevel;
private @LevelValue int mDensityLevel;
private @NonNull PersistableBundle mVendorData;
+ private @IntRange(from = -1) int mRatePerMinute;
private long mBuilderFieldsSet = 0L;
@@ -525,10 +581,22 @@
return this;
}
+ /**
+ * Rate per minute of the event during the start to end time.
+ */
+ @DataClass.Generated.Member
+ @FlaggedApi(android.app.Flags.FLAG_AMBIENT_HEART_RATE)
+ public @NonNull Builder setRatePerMinute(@IntRange(from = -1) int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x40;
+ mRatePerMinute = value;
+ return this;
+ }
+
/** Builds the instance. This builder should not be touched after calling this! */
public @NonNull AmbientContextEvent build() {
checkNotUsed();
- mBuilderFieldsSet |= 0x40; // Mark builder used
+ mBuilderFieldsSet |= 0x80; // Mark builder used
if ((mBuilderFieldsSet & 0x1) == 0) {
mEventType = defaultEventType();
@@ -548,18 +616,22 @@
if ((mBuilderFieldsSet & 0x20) == 0) {
mVendorData = defaultVendorData();
}
+ if ((mBuilderFieldsSet & 0x40) == 0) {
+ mRatePerMinute = defaultRatePerMinute();
+ }
AmbientContextEvent o = new AmbientContextEvent(
mEventType,
mStartTime,
mEndTime,
mConfidenceLevel,
mDensityLevel,
- mVendorData);
+ mVendorData,
+ mRatePerMinute);
return o;
}
private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x40) != 0) {
+ if ((mBuilderFieldsSet & 0x80) != 0) {
throw new IllegalStateException(
"This Builder should not be reused. Use a new Builder instance instead");
}
@@ -567,10 +639,10 @@
}
@DataClass.Generated(
- time = 1671217108067L,
+ time = 1705575046107L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/app/ambientcontext/AmbientContextEvent.java",
- inputSignatures = "public static final int EVENT_UNKNOWN\npublic static final int EVENT_COUGH\npublic static final int EVENT_SNORE\npublic static final int EVENT_BACK_DOUBLE_TAP\npublic static final int EVENT_VENDOR_WEARABLE_START\npublic static final java.lang.String KEY_VENDOR_WEARABLE_EVENT_NAME\npublic static final int LEVEL_UNKNOWN\npublic static final int LEVEL_LOW\npublic static final int LEVEL_MEDIUM_LOW\npublic static final int LEVEL_MEDIUM\npublic static final int LEVEL_MEDIUM_HIGH\npublic static final int LEVEL_HIGH\nprivate final @android.app.ambientcontext.AmbientContextEvent.EventCode int mEventType\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mStartTime\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mEndTime\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mConfidenceLevel\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mDensityLevel\nprivate final @android.annotation.NonNull android.os.PersistableBundle mVendorData\nprivate static int defaultEventType()\nprivate static @android.annotation.NonNull java.time.Instant defaultStartTime()\nprivate static @android.annotation.NonNull java.time.Instant defaultEndTime()\nprivate static int defaultConfidenceLevel()\nprivate static int defaultDensityLevel()\nprivate static android.os.PersistableBundle defaultVendorData()\nclass AmbientContextEvent extends java.lang.Object implements [android.os.Parcelable]\[email protected](genBuilder=true, genConstructor=false, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
+ inputSignatures = "public static final int EVENT_UNKNOWN\npublic static final int EVENT_COUGH\npublic static final int EVENT_SNORE\npublic static final int EVENT_BACK_DOUBLE_TAP\npublic static final @android.app.ambientcontext.AmbientContextEvent.Event @android.annotation.FlaggedApi int EVENT_HEART_RATE\npublic static final int EVENT_VENDOR_WEARABLE_START\npublic static final java.lang.String KEY_VENDOR_WEARABLE_EVENT_NAME\npublic static final @android.annotation.FlaggedApi int RATE_PER_MINUTE_UNKNOWN\npublic static final int LEVEL_UNKNOWN\npublic static final int LEVEL_LOW\npublic static final int LEVEL_MEDIUM_LOW\npublic static final int LEVEL_MEDIUM\npublic static final int LEVEL_MEDIUM_HIGH\npublic static final int LEVEL_HIGH\nprivate final @android.app.ambientcontext.AmbientContextEvent.EventCode int mEventType\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mStartTime\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mEndTime\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mConfidenceLevel\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mDensityLevel\nprivate final @android.annotation.NonNull android.os.PersistableBundle mVendorData\nprivate final @android.annotation.IntRange int mRatePerMinute\nprivate static int defaultEventType()\nprivate static @android.annotation.NonNull java.time.Instant defaultStartTime()\nprivate static @android.annotation.NonNull java.time.Instant defaultEndTime()\nprivate static int defaultConfidenceLevel()\nprivate static int defaultDensityLevel()\nprivate static android.os.PersistableBundle defaultVendorData()\nprivate static int defaultRatePerMinute()\nclass AmbientContextEvent extends java.lang.Object implements [android.os.Parcelable]\[email protected](genBuilder=true, genConstructor=false, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java
index 6371871..a41cb1f 100644
--- a/core/java/android/app/backup/FullBackup.java
+++ b/core/java/android/app/backup/FullBackup.java
@@ -239,7 +239,7 @@
Log.e(TAG, "Unable to create/open file " + outFile.getPath(), e);
}
- byte[] buffer = new byte[32 * 1024];
+ byte[] buffer = new byte[64 * 1024];
final long origSize = size;
FileInputStream in = new FileInputStream(data.getFileDescriptor());
while (size > 0) {
diff --git a/core/java/android/app/grammatical_inflection_manager.aconfig b/core/java/android/app/grammatical_inflection_manager.aconfig
index 989ce61..68d12ba 100644
--- a/core/java/android/app/grammatical_inflection_manager.aconfig
+++ b/core/java/android/app/grammatical_inflection_manager.aconfig
@@ -2,7 +2,7 @@
flag {
name: "system_terms_of_address_enabled"
- namespace: "grammatical_gender"
+ namespace: "globalintl"
description: "Feature flag for System Terms of Address"
bug: "297798866"
}
diff --git a/core/java/android/app/usage/UsageEventsQuery.java b/core/java/android/app/usage/UsageEventsQuery.java
index 3cd2923..df6324f 100644
--- a/core/java/android/app/usage/UsageEventsQuery.java
+++ b/core/java/android/app/usage/UsageEventsQuery.java
@@ -29,9 +29,6 @@
import com.android.internal.util.ArrayUtils;
import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
/**
* An Object-Oriented representation for a {@link UsageEvents} query.
@@ -77,19 +74,17 @@
}
/**
- * Returns the set of usage event types for the query.
- * <em>Note: An empty set indicates query for all usage events. </em>
+ * Retrieves the usage event types for the query.
+ * <p>Note that an empty array indicates querying all usage event types, and it may
+ * cause additional system overhead when calling
+ * {@link UsageStatsManager#queryEvents(UsageEventsQuery)}. Apps are encouraged to
+ * provide a list of event types via {@link Builder#setEventTypes(int...)}</p>
+ *
+ * @return an array contains the usage event types that was previously set using
+ * {@link Builder#setEventTypes(int...)} or an empty array if no value has been set.
*/
- public @NonNull Set<Integer> getEventTypes() {
- if (ArrayUtils.isEmpty(mEventTypes)) {
- return Collections.emptySet();
- }
-
- HashSet<Integer> eventTypeSet = new HashSet<>();
- for (int eventType : mEventTypes) {
- eventTypeSet.add(eventType);
- }
- return eventTypeSet;
+ public @NonNull @Event.EventType int[] getEventTypes() {
+ return Arrays.copyOf(mEventTypes, mEventTypes.length);
}
/** @hide */
@@ -125,11 +120,6 @@
}
};
- /** @hide */
- public int[] getEventTypeFilter() {
- return Arrays.copyOf(mEventTypes, mEventTypes.length);
- }
-
/**
* Builder for UsageEventsQuery.
*/
@@ -166,12 +156,25 @@
}
/**
- * Specifies the list of usage event types to be included in the query.
- * @param eventTypes List of the usage event types. See {@link UsageEvents.Event}
+ * Sets the list of usage event types to be included in the query.
*
- * @throws llegalArgumentException if the event type is not valid.
+ * <p>Note: </p> An empty array will be returned by
+ * {@link UsageEventsQuery#getEventTypes()} without calling this method, which indicates
+ * querying for all event types. Apps are encouraged to provide a list of event types.
+ * Only the matching types supplied will be used to query.
+ *
+ * @param eventTypes the array of the usage event types. See {@link UsageEvents.Event}.
+ * @throws NullPointerException if {@code eventTypes} is {@code null} or empty.
+ * @throws IllegalArgumentException if any of event types are invalid.
+ * @see UsageEventsQuery#getEventTypes()
+ * @see UsageStatsManager#queryEvents(UsageEventsQuery)
*/
- public @NonNull Builder addEventTypes(@NonNull @Event.EventType int... eventTypes) {
+ public @NonNull Builder setEventTypes(@NonNull @Event.EventType int... eventTypes) {
+ if (eventTypes == null || eventTypes.length == 0) {
+ throw new NullPointerException("eventTypes is null or empty");
+ }
+
+ mEventTypes.clear();
for (int i = 0; i < eventTypes.length; i++) {
final int eventType = eventTypes[i];
if (eventType < Event.NONE || eventType > Event.MAX_EVENT_TYPE) {
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index 85d223d..8df913a 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -602,14 +602,15 @@
/**
* Query for events with specific UsageEventsQuery object.
+ *
* <em>Note: if the user's device is not in an unlocked state (as defined by
* {@link UserManager#isUserUnlocked()}), then {@code null} will be returned.</em>
*
* @param query The query object used to specify the query parameters.
- * @return A {@link UsageEvents}.
+ * @return A {@link UsageEvents} which contains the events matching the query parameters.
*/
@FlaggedApi(Flags.FLAG_FILTER_BASED_EVENT_QUERY_API)
- @NonNull
+ @Nullable
@RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)
public UsageEvents queryEvents(@NonNull UsageEventsQuery query) {
try {
diff --git a/core/java/android/app/wearable/OWNERS b/core/java/android/app/wearable/OWNERS
index 073e2d7..497eaf0 100644
--- a/core/java/android/app/wearable/OWNERS
+++ b/core/java/android/app/wearable/OWNERS
@@ -1,3 +1,5 @@
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
\ No newline at end of file
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 4cf9fca..6204edc 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -19,6 +19,7 @@
import static android.appwidget.flags.Flags.remoteAdapterConversion;
import android.annotation.BroadcastBehavior;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresFeature;
@@ -30,6 +31,7 @@
import android.annotation.UserIdInt;
import android.app.IServiceConnection;
import android.app.PendingIntent;
+import android.appwidget.flags.Flags;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
@@ -1415,6 +1417,89 @@
}
}
+ /**
+ * Set a preview for this widget. This preview will be used instead of the provider's {@link
+ * AppWidgetProviderInfo#previewLayout previewLayout} or {@link
+ * AppWidgetProviderInfo#previewImage previewImage} for previewing the widget in the widget
+ * picker and pin app widget flow.
+ *
+ * @param provider The {@link ComponentName} for the {@link android.content.BroadcastReceiver
+ * BroadcastReceiver} provider for the AppWidget you intend to provide a preview for.
+ * @param widgetCategories The categories that this preview should be used for. This can be a
+ * single category or combination of categories. If multiple categories are specified,
+ * then this preview will be used for each of those categories. For example, if you
+ * set a preview for WIDGET_CATEGORY_HOME_SCREEN | WIDGET_CATEGORY_KEYGUARD, the preview will
+ * be used when picking widgets for the home screen and keyguard.
+ *
+ * <p>Note: You should only use the widget categories that the provider supports, as defined
+ * in {@link AppWidgetProviderInfo#widgetCategory}.
+ * @param preview This preview will be used for previewing the provider when picking widgets for
+ * the selected categories.
+ *
+ * @see AppWidgetProviderInfo#WIDGET_CATEGORY_HOME_SCREEN
+ * @see AppWidgetProviderInfo#WIDGET_CATEGORY_KEYGUARD
+ * @see AppWidgetProviderInfo#WIDGET_CATEGORY_SEARCHBOX
+ */
+ @FlaggedApi(Flags.FLAG_GENERATED_PREVIEWS)
+ public void setWidgetPreview(@NonNull ComponentName provider,
+ @AppWidgetProviderInfo.CategoryFlags int widgetCategories,
+ @NonNull RemoteViews preview) {
+ try {
+ mService.setWidgetPreview(provider, widgetCategories, preview);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get the RemoteViews previews for this widget.
+ *
+ * @param provider The {@link ComponentName} for the {@link android.content.BroadcastReceiver
+ * BroadcastReceiver} provider for the AppWidget you intend to get a preview for.
+ * @param profile The profile in which the provider resides. Passing null is equivalent
+ * to querying for only the calling user.
+ * @param widgetCategory The widget category for which you want to display previews. This should
+ * be a single category. If a combination of categories is provided, this function will
+ * return a preview that matches at least one of the categories.
+ *
+ * @return The widget preview for the selected category, if available.
+ * @see AppWidgetProviderInfo#generatedPreviewCategories
+ */
+ @Nullable
+ @FlaggedApi(Flags.FLAG_GENERATED_PREVIEWS)
+ public RemoteViews getWidgetPreview(@NonNull ComponentName provider,
+ @Nullable UserHandle profile, @AppWidgetProviderInfo.CategoryFlags int widgetCategory) {
+ try {
+ if (profile == null) {
+ profile = mContext.getUser();
+ }
+ return mService.getWidgetPreview(mPackageName, provider, profile.getIdentifier(),
+ widgetCategory);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Remove this provider's preview for the specified widget categories. If the provider does not
+ * have a preview for the specified widget category, this is a no-op.
+ *
+ * @param provider The AppWidgetProvider to remove previews for.
+ * @param widgetCategories The categories of the preview to remove. For example, removing the
+ * preview for WIDGET_CATEGORY_HOME_SCREEN | WIDGET_CATEGORY_KEYGUARD will remove the
+ * previews for both categories.
+ */
+ @FlaggedApi(Flags.FLAG_GENERATED_PREVIEWS)
+ public void removeWidgetPreview(@NonNull ComponentName provider,
+ @AppWidgetProviderInfo.CategoryFlags int widgetCategories) {
+ try {
+ mService.removeWidgetPreview(provider, widgetCategories);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+
@UiThread
private static @NonNull Executor createUpdateExecutorIfNull() {
if (sUpdateExecutor == null) {
diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java
index e56e53a..1a80cac2 100644
--- a/core/java/android/appwidget/AppWidgetProviderInfo.java
+++ b/core/java/android/appwidget/AppWidgetProviderInfo.java
@@ -16,6 +16,10 @@
package android.appwidget;
+import static android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS;
+import static android.appwidget.flags.Flags.generatedPreviews;
+
+import android.annotation.FlaggedApi;
import android.annotation.IdRes;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -358,6 +362,20 @@
/** @hide */
public boolean isExtendedFromAppWidgetProvider;
+ /**
+ * Flags indicating the widget categories for which generated previews are available.
+ * These correspond to the previews set by this provider with
+ * {@link AppWidgetManager#setWidgetPreview}.
+ *
+ * @see #WIDGET_CATEGORY_HOME_SCREEN
+ * @see #WIDGET_CATEGORY_KEYGUARD
+ * @see #WIDGET_CATEGORY_SEARCHBOX
+ * @see AppWidgetManager#getWidgetPreview
+ */
+ @FlaggedApi(FLAG_GENERATED_PREVIEWS)
+ @SuppressLint("MutableBareField")
+ public int generatedPreviewCategories;
+
public AppWidgetProviderInfo() {
}
@@ -391,6 +409,9 @@
this.widgetFeatures = in.readInt();
this.descriptionRes = in.readInt();
this.isExtendedFromAppWidgetProvider = in.readBoolean();
+ if (generatedPreviews()) {
+ generatedPreviewCategories = in.readInt();
+ }
}
/**
@@ -515,6 +536,9 @@
out.writeInt(this.widgetFeatures);
out.writeInt(this.descriptionRes);
out.writeBoolean(this.isExtendedFromAppWidgetProvider);
+ if (generatedPreviews()) {
+ out.writeInt(this.generatedPreviewCategories);
+ }
}
@Override
@@ -545,6 +569,9 @@
that.widgetFeatures = this.widgetFeatures;
that.descriptionRes = this.descriptionRes;
that.isExtendedFromAppWidgetProvider = this.isExtendedFromAppWidgetProvider;
+ if (generatedPreviews()) {
+ that.generatedPreviewCategories = this.generatedPreviewCategories;
+ }
return that;
}
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 879656a..672e3439 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -76,6 +76,7 @@
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
@@ -909,34 +910,20 @@
}
/**
- * Listener for any changes to the list of attached transports.
- *
- * @see com.android.server.companion.transport.Transport
- *
- * @hide
- */
- public interface OnTransportsChangedListener {
- /**
- * Invoked when a transport is attached or detached.
- *
- * @param associations all the associations which have connected transports.
- */
- void onTransportsChanged(@NonNull List<AssociationInfo> associations);
- }
-
- /**
* Adds a listener for any changes to the list of attached transports.
- * {@link OnTransportsChangedListener#onTransportsChanged(List)} will be triggered with a list
- * of existing transports when a transport is detached or a new transport is attached.
+ * Registered listener will be triggered with a list of existing transports when a transport
+ * is detached or a new transport is attached.
*
+ * @param executor The executor which will be used to invoke the listener.
+ * @param listener Called when a transport is attached or detached. Contains the updated list of
+ * associations which have connected transports.
* @see com.android.server.companion.transport.Transport
- *
* @hide
*/
@RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
public void addOnTransportsChangedListener(
@NonNull @CallbackExecutor Executor executor,
- @NonNull OnTransportsChangedListener listener) {
+ @NonNull Consumer<List<AssociationInfo>> listener) {
final OnTransportsChangedListenerProxy proxy = new OnTransportsChangedListenerProxy(
executor, listener);
try {
@@ -955,7 +942,7 @@
*/
@RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
public void removeOnTransportsChangedListener(
- @NonNull OnTransportsChangedListener listener) {
+ @NonNull Consumer<List<AssociationInfo>> listener) {
final OnTransportsChangedListenerProxy proxy = new OnTransportsChangedListenerProxy(
null, listener);
try {
@@ -983,28 +970,18 @@
}
/**
- * Listener that triggers a callback when a message is received through a connected transport.
+ * Adds a listener that triggers when messages of given type are received.
*
- * @see #addOnMessageReceivedListener(Executor, int, OnMessageReceivedListener)
- *
- * @hide
- */
- public interface OnMessageReceivedListener {
- /**
- * Called when a message is received.
- */
- void onMessageReceived(int associationId, @NonNull byte[] data);
- }
-
- /**
- * Adds a listener to trigger callbacks when messages of given type are received.
- *
+ * @param executor The executor which will be used to invoke the listener.
+ * @param messageType Message type to be subscribed to.
+ * @param listener Called when a message is received. Contains the association ID of the message
+ * sender and the message payload as a byte array.
* @hide
*/
@RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
public void addOnMessageReceivedListener(
@NonNull @CallbackExecutor Executor executor, int messageType,
- @NonNull OnMessageReceivedListener listener) {
+ @NonNull BiConsumer<Integer, byte[]> listener) {
final OnMessageReceivedListenerProxy proxy = new OnMessageReceivedListenerProxy(
executor, listener);
try {
@@ -1021,7 +998,7 @@
*/
@RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
public void removeOnMessageReceivedListener(int messageType,
- @NonNull OnMessageReceivedListener listener) {
+ @NonNull BiConsumer<Integer, byte[]> listener) {
final OnMessageReceivedListenerProxy proxy = new OnMessageReceivedListenerProxy(
null, listener);
try {
@@ -1596,34 +1573,34 @@
private static class OnTransportsChangedListenerProxy
extends IOnTransportsChangedListener.Stub {
private final Executor mExecutor;
- private final OnTransportsChangedListener mListener;
+ private final Consumer<List<AssociationInfo>> mListener;
private OnTransportsChangedListenerProxy(Executor executor,
- OnTransportsChangedListener listener) {
+ Consumer<List<AssociationInfo>> listener) {
mExecutor = executor;
mListener = listener;
}
@Override
public void onTransportsChanged(@NonNull List<AssociationInfo> associations) {
- mExecutor.execute(() -> mListener.onTransportsChanged(associations));
+ mExecutor.execute(() -> mListener.accept(associations));
}
}
private static class OnMessageReceivedListenerProxy
extends IOnMessageReceivedListener.Stub {
private final Executor mExecutor;
- private final OnMessageReceivedListener mListener;
+ private final BiConsumer<Integer, byte[]> mListener;
private OnMessageReceivedListenerProxy(Executor executor,
- OnMessageReceivedListener listener) {
+ BiConsumer<Integer, byte[]> listener) {
mExecutor = executor;
mListener = listener;
}
@Override
public void onMessageReceived(int associationId, byte[] data) {
- mExecutor.execute(() -> mListener.onMessageReceived(associationId, data));
+ mExecutor.execute(() -> mListener.accept(associationId, data));
}
}
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 12229b1..6eab363 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -35,6 +35,9 @@
import android.hardware.input.VirtualMouseConfig;
import android.hardware.input.VirtualMouseRelativeEvent;
import android.hardware.input.VirtualMouseScrollEvent;
+import android.hardware.input.VirtualStylusButtonEvent;
+import android.hardware.input.VirtualStylusConfig;
+import android.hardware.input.VirtualStylusMotionEvent;
import android.hardware.input.VirtualTouchEvent;
import android.hardware.input.VirtualTouchscreenConfig;
import android.hardware.input.VirtualNavigationTouchpadConfig;
@@ -144,6 +147,12 @@
void createVirtualNavigationTouchpad(in VirtualNavigationTouchpadConfig config, IBinder token);
/**
+ * Creates a new stylus and registers it with the input framework with the given token.
+ */
+ @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+ void createVirtualStylus(in VirtualStylusConfig config, IBinder token);
+
+ /**
* Removes the input device corresponding to the given token from the framework.
*/
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
@@ -156,32 +165,32 @@
int getInputDeviceId(IBinder token);
/**
- * Injects a key event to the virtual dpad corresponding to the given token.
- */
+ * Injects a key event to the virtual dpad corresponding to the given token.
+ */
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
boolean sendDpadKeyEvent(IBinder token, in VirtualKeyEvent event);
/**
- * Injects a key event to the virtual keyboard corresponding to the given token.
- */
+ * Injects a key event to the virtual keyboard corresponding to the given token.
+ */
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
boolean sendKeyEvent(IBinder token, in VirtualKeyEvent event);
/**
- * Injects a button event to the virtual mouse corresponding to the given token.
- */
+ * Injects a button event to the virtual mouse corresponding to the given token.
+ */
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
boolean sendButtonEvent(IBinder token, in VirtualMouseButtonEvent event);
/**
- * Injects a relative event to the virtual mouse corresponding to the given token.
- */
+ * Injects a relative event to the virtual mouse corresponding to the given token.
+ */
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
boolean sendRelativeEvent(IBinder token, in VirtualMouseRelativeEvent event);
/**
- * Injects a scroll event to the virtual mouse corresponding to the given token.
- */
+ * Injects a scroll event to the virtual mouse corresponding to the given token.
+ */
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
boolean sendScrollEvent(IBinder token, in VirtualMouseScrollEvent event);
@@ -192,6 +201,18 @@
boolean sendTouchEvent(IBinder token, in VirtualTouchEvent event);
/**
+ * Injects a motion event from the virtual stylus input device corresponding to the given token.
+ */
+ @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+ boolean sendStylusMotionEvent(IBinder token, in VirtualStylusMotionEvent event);
+
+ /**
+ * Injects a button event from the virtual stylus input device corresponding to the given token.
+ */
+ @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+ boolean sendStylusButtonEvent(IBinder token, in VirtualStylusButtonEvent event);
+
+ /**
* Returns all virtual sensors created for this device.
*/
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
index 0493312..325aa28f 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
@@ -78,6 +78,11 @@
int getDeviceIdForDisplayId(int displayId);
/**
+ * Returns the display name corresponding to the given persistent device ID, if any.
+ */
+ CharSequence getDisplayNameForPersistentDeviceId(in String persistentDeviceId);
+
+ /**
* Checks whether the passed {@code deviceId} is a valid virtual device ID or not.
* {@link VirtualDeviceManager#DEVICE_ID_DEFAULT} is not valid as it is the ID of the default
* device which is not a virtual device. {@code deviceId} must correspond to a virtual device
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index 2abeeee..c1e443d 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -42,6 +42,8 @@
import android.hardware.input.VirtualMouseConfig;
import android.hardware.input.VirtualNavigationTouchpad;
import android.hardware.input.VirtualNavigationTouchpadConfig;
+import android.hardware.input.VirtualStylus;
+import android.hardware.input.VirtualStylusConfig;
import android.hardware.input.VirtualTouchscreen;
import android.hardware.input.VirtualTouchscreenConfig;
import android.media.AudioManager;
@@ -316,6 +318,19 @@
}
}
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ @NonNull
+ VirtualStylus createVirtualStylus(@NonNull VirtualStylusConfig config) {
+ try {
+ final IBinder token = new Binder(
+ "android.hardware.input.VirtualStylus:" + config.getInputDeviceName());
+ mVirtualDevice.createVirtualStylus(config, token);
+ return new VirtualStylus(config, mVirtualDevice, token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
@NonNull
VirtualNavigationTouchpad createVirtualNavigationTouchpad(
@NonNull VirtualNavigationTouchpadConfig config) {
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index eef60f1..90d251b 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -55,6 +55,8 @@
import android.hardware.input.VirtualMouseConfig;
import android.hardware.input.VirtualNavigationTouchpad;
import android.hardware.input.VirtualNavigationTouchpadConfig;
+import android.hardware.input.VirtualStylus;
+import android.hardware.input.VirtualStylusConfig;
import android.hardware.input.VirtualTouchscreen;
import android.hardware.input.VirtualTouchscreenConfig;
import android.media.AudioManager;
@@ -359,6 +361,34 @@
}
/**
+ * Get the display name for a given persistent device ID.
+ *
+ * <p>This will work even if currently there is no valid virtual device with the given
+ * persistent ID, as long as such a device has been created or can be created.</p>
+ *
+ * @return the display name associated with the given persistent device ID, or {@code null} if
+ * the persistent ID is invalid or does not correspond to a virtual device.
+ *
+ * @hide
+ */
+ // TODO(b/315481938): Link @see VirtualDevice#getPersistentDeviceId()
+ @FlaggedApi(Flags.FLAG_PERSISTENT_DEVICE_ID_API)
+ @SystemApi
+ @Nullable
+ public CharSequence getDisplayNameForPersistentDeviceId(@NonNull String persistentDeviceId) {
+ if (mService == null) {
+ Log.w(TAG, "Failed to retrieve virtual devices; no virtual device manager service.");
+ return null;
+ }
+ try {
+ return mService.getDisplayNameForPersistentDeviceId(
+ Objects.requireNonNull(persistentDeviceId));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Checks whether the passed {@code deviceId} is a valid virtual device ID or not.
* {@link Context#DEVICE_ID_DEFAULT} is not valid as it is the ID of the default
* device which is not a virtual device. {@code deviceId} must correspond to a virtual device
@@ -859,6 +889,19 @@
}
/**
+ * Creates a virtual stylus.
+ *
+ * @param config the touchscreen configurations for the virtual stylus.
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ @NonNull
+ @FlaggedApi(Flags.FLAG_VIRTUAL_STYLUS)
+ public VirtualStylus createVirtualStylus(
+ @NonNull VirtualStylusConfig config) {
+ return mVirtualDeviceInternal.createVirtualStylus(config);
+ }
+
+ /**
* Creates a VirtualAudioDevice, capable of recording audio emanating from this device,
* or injecting audio from another device.
*
@@ -886,11 +929,14 @@
}
/**
- * Creates a new virtual camera. If a virtual camera was already created, it will be closed.
+ * Creates a new virtual camera with the given {@link VirtualCameraConfig}. A virtual device
+ * can create a virtual camera only if it has
+ * {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM} as its
+ * {@link VirtualDeviceParams#POLICY_TYPE_CAMERA}.
*
- * @param config camera config.
- * @return newly created camera;
- * @hide
+ * @param config camera configuration.
+ * @return newly created camera.
+ * @see VirtualDeviceParams#POLICY_TYPE_CAMERA
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@NonNull
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 0253ddd..f9a9da1 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -159,7 +159,7 @@
* @hide
*/
@IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_SENSORS, POLICY_TYPE_AUDIO,
- POLICY_TYPE_RECENTS, POLICY_TYPE_ACTIVITY})
+ POLICY_TYPE_RECENTS, POLICY_TYPE_ACTIVITY, POLICY_TYPE_CAMERA})
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
public @interface PolicyType {}
@@ -246,6 +246,23 @@
@FlaggedApi(Flags.FLAG_CROSS_DEVICE_CLIPBOARD)
public static final int POLICY_TYPE_CLIPBOARD = 4;
+ /**
+ * Tells the camera framework how to handle camera requests for the front and back cameras from
+ * contexts associated with this virtual device.
+ *
+ * <ul>
+ * <li>{@link #DEVICE_POLICY_DEFAULT}: Returns the front and back cameras of the default
+ * device.
+ * <li>{@link #DEVICE_POLICY_CUSTOM}: Returns the front and back cameras cameras of the
+ * virtual device. Note that if the virtual device did not create any virtual cameras,
+ * then no front and back cameras will be available.
+ * </ul>
+ *
+ * @see Context#getDeviceId
+ */
+ @FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
+ public static final int POLICY_TYPE_CAMERA = 5;
+
private final int mLockState;
@NonNull private final ArraySet<UserHandle> mUsersWithMatchingAccounts;
@NavigationPolicy
@@ -1153,6 +1170,10 @@
mDevicePolicies.delete(POLICY_TYPE_CLIPBOARD);
}
+ if (!Flags.virtualCamera()) {
+ mDevicePolicies.delete(POLICY_TYPE_CAMERA);
+ }
+
if ((mAudioPlaybackSessionId != AUDIO_SESSION_ID_GENERATE
|| mAudioRecordingSessionId != AUDIO_SESSION_ID_GENERATE)
&& mDevicePolicies.get(POLICY_TYPE_AUDIO, DEVICE_POLICY_DEFAULT)
diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl b/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl
index 44942d6..f4f69b5 100644
--- a/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl
+++ b/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl
@@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package android.companion.virtual.camera;
-import android.companion.virtual.camera.VirtualCameraStreamConfig;
import android.view.Surface;
/**
@@ -30,38 +30,36 @@
* Called when one of the requested stream has been configured by the virtual camera service and
* is ready to receive data onto its {@link Surface}
*
- * @param streamId The id of the configured stream
- * @param surface The surface to write data into for this stream
- * @param streamConfig The image data configuration for this stream
+ * @param streamId The id of the configured stream
+ * @param surface The surface to write data into for this stream
+ * @param width The width of the surface
+ * @param height The height of the surface
+ * @param format The pixel format of the surface
*/
- oneway void onStreamConfigured(
- int streamId,
- in Surface surface,
- in VirtualCameraStreamConfig streamConfig);
+ oneway void onStreamConfigured(int streamId, in Surface surface, int width, int height,
+ int format);
/**
* The client application is requesting a camera frame for the given streamId and frameId.
*
* <p>The virtual camera needs to write the frame data in the {@link Surface} corresponding to
- * this stream that was provided during the {@link #onStreamConfigured(int, Surface,
- * VirtualCameraStreamConfig)} call.
+ * this stream that was provided during the
+ * {@link #onStreamConfigured(int, Surface, int, int, int)} call.
*
* @param streamId The streamId for which the frame is requested. This corresponds to the
- * streamId that was given in {@link #onStreamConfigured(int, Surface,
- * VirtualCameraStreamConfig)}
+ * streamId that was given in {@link #onStreamConfigured(int, Surface, int, int, int)}
* @param frameId The frameId that is being requested. Each request will have a different
* frameId, that will be increasing for each call with a particular streamId.
*/
oneway void onProcessCaptureRequest(int streamId, long frameId);
/**
- * The stream previously configured when {@link #onStreamConfigured(int, Surface,
- * VirtualCameraStreamConfig)} was called is now being closed and associated resources can be
- * freed. The Surface was disposed on the client side and should not be used anymore by the
- * virtual camera owner.
+ * The stream previously configured when
+ * {@link #onStreamConfigured(int, Surface, int, int, int)} was called is now being closed and
+ * associated resources can be freed. The Surface was disposed on the client side and should not
+ * be used anymore by the virtual camera owner.
*
* @param streamId The id of the stream that was closed.
*/
oneway void onStreamClosed(int streamId);
-
}
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraCallback.java b/core/java/android/companion/virtual/camera/VirtualCameraCallback.java
index 5b658b8..c894de4 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraCallback.java
+++ b/core/java/android/companion/virtual/camera/VirtualCameraCallback.java
@@ -17,9 +17,11 @@
package android.companion.virtual.camera;
import android.annotation.FlaggedApi;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.companion.virtual.flags.Flags;
+import android.graphics.ImageFormat;
import android.view.Surface;
import java.util.concurrent.Executor;
@@ -41,33 +43,33 @@
*
* @param streamId The id of the configured stream
* @param surface The surface to write data into for this stream
- * @param streamConfig The image data configuration for this stream
+ * @param width The width of the surface
+ * @param height The height of the surface
+ * @param format The {@link ImageFormat} of the surface
*/
- void onStreamConfigured(
- int streamId,
- @NonNull Surface surface,
- @NonNull VirtualCameraStreamConfig streamConfig);
+ void onStreamConfigured(int streamId, @NonNull Surface surface,
+ @IntRange(from = 1) int width, @IntRange(from = 1) int height,
+ @ImageFormat.Format int format);
/**
* The client application is requesting a camera frame for the given streamId and frameId.
*
* <p>The virtual camera needs to write the frame data in the {@link Surface} corresponding to
- * this stream that was provided during the {@link #onStreamConfigured(int, Surface,
- * VirtualCameraStreamConfig)} call.
+ * this stream that was provided during the
+ * {@link #onStreamConfigured(int, Surface, int, int, int)} call.
*
* @param streamId The streamId for which the frame is requested. This corresponds to the
- * streamId that was given in {@link #onStreamConfigured(int, Surface,
- * VirtualCameraStreamConfig)}
+ * streamId that was given in {@link #onStreamConfigured(int, Surface, int, int, int)}
* @param frameId The frameId that is being requested. Each request will have a different
* frameId, that will be increasing for each call with a particular streamId.
*/
default void onProcessCaptureRequest(int streamId, long frameId) {}
/**
- * The stream previously configured when {@link #onStreamConfigured(int, Surface,
- * VirtualCameraStreamConfig)} was called is now being closed and associated resources can be
- * freed. The Surface corresponding to that streamId was disposed on the client side and should
- * not be used anymore by the virtual camera owner
+ * The stream previously configured when
+ * {@link #onStreamConfigured(int, Surface, int, int, int)} was called is now being closed and
+ * associated resources can be freed. The Surface corresponding to that streamId was disposed on
+ * the client side and should not be used anymore by the virtual camera owner.
*
* @param streamId The id of the stream that was closed.
*/
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraConfig.aidl b/core/java/android/companion/virtual/camera/VirtualCameraConfig.aidl
index 88c27a5..5ceb4d1 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraConfig.aidl
+++ b/core/java/android/companion/virtual/camera/VirtualCameraConfig.aidl
@@ -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.
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package android.companion.virtual.camera;
/** @hide */
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
index 59fe9a1..350cf3d 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
+++ b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
@@ -19,16 +19,23 @@
import static java.util.Objects.requireNonNull;
import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
+import android.companion.virtual.VirtualDevice;
import android.companion.virtual.flags.Flags;
import android.graphics.ImageFormat;
+import android.graphics.PixelFormat;
+import android.hardware.camera2.CameraMetadata;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArraySet;
import android.view.Surface;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Set;
import java.util.concurrent.Executor;
@@ -43,16 +50,57 @@
@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
public final class VirtualCameraConfig implements Parcelable {
+ private static final int LENS_FACING_UNKNOWN = -1;
+
+ /**
+ * Sensor orientation of {@code 0} degrees.
+ * @see #getSensorOrientation
+ */
+ public static final int SENSOR_ORIENTATION_0 = 0;
+ /**
+ * Sensor orientation of {@code 90} degrees.
+ * @see #getSensorOrientation
+ */
+ public static final int SENSOR_ORIENTATION_90 = 90;
+ /**
+ * Sensor orientation of {@code 180} degrees.
+ * @see #getSensorOrientation
+ */
+ public static final int SENSOR_ORIENTATION_180 = 180;
+ /**
+ * Sensor orientation of {@code 270} degrees.
+ * @see #getSensorOrientation
+ */
+ public static final int SENSOR_ORIENTATION_270 = 270;
+ /** @hide */
+ @IntDef(prefix = {"SENSOR_ORIENTATION_"}, value = {
+ SENSOR_ORIENTATION_0,
+ SENSOR_ORIENTATION_90,
+ SENSOR_ORIENTATION_180,
+ SENSOR_ORIENTATION_270
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SensorOrientation {}
+
private final String mName;
private final Set<VirtualCameraStreamConfig> mStreamConfigurations;
private final IVirtualCameraCallback mCallback;
+ @SensorOrientation
+ private final int mSensorOrientation;
+ private final int mLensFacing;
private VirtualCameraConfig(
@NonNull String name,
@NonNull Set<VirtualCameraStreamConfig> streamConfigurations,
@NonNull Executor executor,
- @NonNull VirtualCameraCallback callback) {
+ @NonNull VirtualCameraCallback callback,
+ @SensorOrientation int sensorOrientation,
+ int lensFacing) {
mName = requireNonNull(name, "Missing name");
+ if (lensFacing == LENS_FACING_UNKNOWN) {
+ throw new IllegalArgumentException("Lens facing must be set");
+ }
+ mLensFacing = lensFacing;
mStreamConfigurations =
Set.copyOf(requireNonNull(streamConfigurations, "Missing stream configurations"));
if (mStreamConfigurations.isEmpty()) {
@@ -63,6 +111,7 @@
new VirtualCameraCallbackInternal(
requireNonNull(callback, "Missing callback"),
requireNonNull(executor, "Missing callback executor"));
+ mSensorOrientation = sensorOrientation;
}
private VirtualCameraConfig(@NonNull Parcel in) {
@@ -73,6 +122,8 @@
in.readParcelableArray(
VirtualCameraStreamConfig.class.getClassLoader(),
VirtualCameraStreamConfig.class));
+ mSensorOrientation = in.readInt();
+ mLensFacing = in.readInt();
}
@Override
@@ -86,6 +137,8 @@
dest.writeStrongInterface(mCallback);
dest.writeParcelableArray(
mStreamConfigurations.toArray(new VirtualCameraStreamConfig[0]), flags);
+ dest.writeInt(mSensorOrientation);
+ dest.writeInt(mLensFacing);
}
/**
@@ -100,7 +153,7 @@
* Returns an unmodifiable set of the stream configurations added to this {@link
* VirtualCameraConfig}.
*
- * @see VirtualCameraConfig.Builder#addStreamConfig(int, int, int)
+ * @see VirtualCameraConfig.Builder#addStreamConfig(int, int, int, int)
*/
@NonNull
public Set<VirtualCameraStreamConfig> getStreamConfigs() {
@@ -118,13 +171,33 @@
}
/**
+ * Returns the sensor orientation of this stream, which represents the clockwise angle (in
+ * degrees) through which the output image needs to be rotated to be upright on the device
+ * screen in its native orientation. Returns {@link #SENSOR_ORIENTATION_0} if omitted.
+ */
+ @SensorOrientation
+ public int getSensorOrientation() {
+ return mSensorOrientation;
+ }
+
+ /**
+ * Returns the direction that the virtual camera faces relative to the virtual device's screen.
+ *
+ * @see Builder#setLensFacing(int)
+ */
+ public int getLensFacing() {
+ return mLensFacing;
+ }
+
+ /**
* Builder for {@link VirtualCameraConfig}.
*
* <p>To build an instance of {@link VirtualCameraConfig} the following conditions must be met:
- * <li>At least one stream must be added with {@link #addStreamConfig(int, int, int)}.
+ * <li>At least one stream must be added with {@link #addStreamConfig(int, int, int, int)}.
* <li>A callback must be set with {@link #setVirtualCameraCallback(Executor,
* VirtualCameraCallback)}
* <li>A camera name must be set with {@link #setName(String)}
+ * <li>A lens facing must be set with {@link #setLensFacing(int)}
*/
@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
public static final class Builder {
@@ -133,9 +206,11 @@
private final ArraySet<VirtualCameraStreamConfig> mStreamConfigurations = new ArraySet<>();
private Executor mCallbackExecutor;
private VirtualCameraCallback mCallback;
+ private int mSensorOrientation = SENSOR_ORIENTATION_0;
+ private int mLensFacing = LENS_FACING_UNKNOWN;
/**
- * Set the name of the virtual camera instance.
+ * Sets the name of the virtual camera instance.
*/
@NonNull
public Builder setName(@NonNull String name) {
@@ -144,25 +219,94 @@
}
/**
- * Add an available stream configuration fot this {@link VirtualCamera}.
+ * Adds a supported input stream configuration for this {@link VirtualCamera}.
*
* <p>At least one {@link VirtualCameraStreamConfig} must be added.
*
* @param width The width of the stream.
* @param height The height of the stream.
- * @param format The {@link ImageFormat} of the stream.
+ * @param format The input format of the stream. Supported formats are
+ * {@link ImageFormat#YUV_420_888} and {@link PixelFormat#RGBA_8888}.
+ * @param maximumFramesPerSecond The maximum frame rate (in frames per second) for the
+ * stream.
*
- * @throws IllegalArgumentException if invalid format or dimensions are passed.
+ * @throws IllegalArgumentException if invalid dimensions, format or frame rate are passed.
*/
@NonNull
- public Builder addStreamConfig(int width, int height, @ImageFormat.Format int format) {
- if (width <= 0 || height <= 0) {
- throw new IllegalArgumentException("Invalid dimensions passed for stream config");
+ public Builder addStreamConfig(
+ @IntRange(from = 1) int width,
+ @IntRange(from = 1) int height,
+ @ImageFormat.Format int format,
+ @IntRange(from = 1) int maximumFramesPerSecond) {
+ // TODO(b/310857519): Check dimension upper limits based on the maximum texture size
+ // supported by the current device, instead of hardcoded limits.
+ if (width <= 0 || width > VirtualCameraStreamConfig.DIMENSION_UPPER_LIMIT) {
+ throw new IllegalArgumentException(
+ "Invalid width passed for stream config: " + width
+ + ", must be between 1 and "
+ + VirtualCameraStreamConfig.DIMENSION_UPPER_LIMIT);
}
- if (!ImageFormat.isPublicFormat(format)) {
- throw new IllegalArgumentException("Invalid format passed for stream config");
+ if (height <= 0 || height > VirtualCameraStreamConfig.DIMENSION_UPPER_LIMIT) {
+ throw new IllegalArgumentException(
+ "Invalid height passed for stream config: " + height
+ + ", must be between 1 and "
+ + VirtualCameraStreamConfig.DIMENSION_UPPER_LIMIT);
}
- mStreamConfigurations.add(new VirtualCameraStreamConfig(width, height, format));
+ if (!isFormatSupported(format)) {
+ throw new IllegalArgumentException(
+ "Invalid format passed for stream config: " + format);
+ }
+ if (maximumFramesPerSecond <= 0
+ || maximumFramesPerSecond > VirtualCameraStreamConfig.MAX_FPS_UPPER_LIMIT) {
+ throw new IllegalArgumentException(
+ "Invalid maximumFramesPerSecond, must be greater than 0 and less than "
+ + VirtualCameraStreamConfig.MAX_FPS_UPPER_LIMIT);
+ }
+ mStreamConfigurations.add(new VirtualCameraStreamConfig(width, height, format,
+ maximumFramesPerSecond));
+ return this;
+ }
+
+ /**
+ * Sets the sensor orientation of the virtual camera. This field is optional and can be
+ * omitted (defaults to {@link #SENSOR_ORIENTATION_0}).
+ *
+ * @param sensorOrientation The sensor orientation of the camera, which represents the
+ * clockwise angle (in degrees) through which the output image
+ * needs to be rotated to be upright on the device screen in its
+ * native orientation.
+ */
+ @NonNull
+ public Builder setSensorOrientation(@SensorOrientation int sensorOrientation) {
+ if (sensorOrientation != SENSOR_ORIENTATION_0
+ && sensorOrientation != SENSOR_ORIENTATION_90
+ && sensorOrientation != SENSOR_ORIENTATION_180
+ && sensorOrientation != SENSOR_ORIENTATION_270) {
+ throw new IllegalArgumentException(
+ "Invalid sensor orientation: " + sensorOrientation);
+ }
+ mSensorOrientation = sensorOrientation;
+ return this;
+ }
+
+ /**
+ * Sets the lens facing direction of the virtual camera, can be either
+ * {@link CameraMetadata#LENS_FACING_FRONT} or {@link CameraMetadata#LENS_FACING_BACK}.
+ *
+ * <p>A {@link VirtualDevice} can have at most one {@link VirtualCamera} with
+ * {@link CameraMetadata#LENS_FACING_FRONT} and at most one {@link VirtualCamera} with
+ * {@link CameraMetadata#LENS_FACING_BACK}.
+ *
+ * @param lensFacing The direction that the virtual camera faces relative to the device's
+ * screen.
+ */
+ @NonNull
+ public Builder setLensFacing(int lensFacing) {
+ if (lensFacing != CameraMetadata.LENS_FACING_BACK
+ && lensFacing != CameraMetadata.LENS_FACING_FRONT) {
+ throw new IllegalArgumentException("Unsupported lens facing: " + lensFacing);
+ }
+ mLensFacing = lensFacing;
return this;
}
@@ -189,11 +333,13 @@
* Builds a new instance of {@link VirtualCameraConfig}
*
* @throws NullPointerException if some required parameters are missing.
+ * @throws IllegalArgumentException if any parameter is invalid.
*/
@NonNull
public VirtualCameraConfig build() {
return new VirtualCameraConfig(
- mName, mStreamConfigurations, mCallbackExecutor, mCallback);
+ mName, mStreamConfigurations, mCallbackExecutor, mCallback, mSensorOrientation,
+ mLensFacing);
}
}
@@ -208,9 +354,10 @@
}
@Override
- public void onStreamConfigured(
- int streamId, Surface surface, VirtualCameraStreamConfig streamConfig) {
- mExecutor.execute(() -> mCallback.onStreamConfigured(streamId, surface, streamConfig));
+ public void onStreamConfigured(int streamId, Surface surface, int width, int height,
+ int format) {
+ mExecutor.execute(() -> mCallback.onStreamConfigured(streamId, surface, width, height,
+ format));
}
@Override
@@ -237,4 +384,11 @@
return new VirtualCameraConfig[size];
}
};
+
+ private static boolean isFormatSupported(@ImageFormat.Format int format) {
+ return switch (format) {
+ case ImageFormat.YUV_420_888, PixelFormat.RGBA_8888 -> true;
+ default -> false;
+ };
+ }
}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.java b/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.java
index 1272f16..00a814e 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.java
+++ b/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.java
@@ -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.
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package android.companion.virtual.camera;
import android.annotation.FlaggedApi;
@@ -24,6 +25,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.Objects;
/**
@@ -34,32 +37,47 @@
@SystemApi
@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
public final class VirtualCameraStreamConfig implements Parcelable {
+ // TODO(b/310857519): Check if we should increase the fps upper limit in future.
+ static final int MAX_FPS_UPPER_LIMIT = 60;
+ // This is the minimum guaranteed upper bound of texture size supported by all devices.
+ // Keep this in sync with kMaxTextureSize from services/camera/virtualcamera/util/Util.cc
+ // TODO(b/310857519): Remove this once we add support for fetching the maximum texture size
+ // supported by the current device.
+ static final int DIMENSION_UPPER_LIMIT = 2048;
private final int mWidth;
private final int mHeight;
private final int mFormat;
+ private final int mMaxFps;
/**
* Construct a new instance of {@link VirtualCameraStreamConfig} initialized with the provided
- * width, height and {@link ImageFormat}
+ * width, height and {@link ImageFormat}.
*
* @param width The width of the stream.
* @param height The height of the stream.
* @param format The {@link ImageFormat} of the stream.
+ * @param maxFps The maximum frame rate (in frames per second) for the stream.
+ *
+ * @hide
*/
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public VirtualCameraStreamConfig(
@IntRange(from = 1) int width,
@IntRange(from = 1) int height,
- @ImageFormat.Format int format) {
+ @ImageFormat.Format int format,
+ @IntRange(from = 1) int maxFps) {
this.mWidth = width;
this.mHeight = height;
this.mFormat = format;
+ this.mMaxFps = maxFps;
}
private VirtualCameraStreamConfig(@NonNull Parcel in) {
mWidth = in.readInt();
mHeight = in.readInt();
mFormat = in.readInt();
+ mMaxFps = in.readInt();
}
@Override
@@ -72,6 +90,7 @@
dest.writeInt(mWidth);
dest.writeInt(mHeight);
dest.writeInt(mFormat);
+ dest.writeInt(mMaxFps);
}
@NonNull
@@ -105,12 +124,13 @@
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
VirtualCameraStreamConfig that = (VirtualCameraStreamConfig) o;
- return mWidth == that.mWidth && mHeight == that.mHeight && mFormat == that.mFormat;
+ return mWidth == that.mWidth && mHeight == that.mHeight && mFormat == that.mFormat
+ && mMaxFps == that.mMaxFps;
}
@Override
public int hashCode() {
- return Objects.hash(mWidth, mHeight, mFormat);
+ return Objects.hash(mWidth, mHeight, mFormat, mMaxFps);
}
/** Returns the {@link ImageFormat} of this stream. */
@@ -118,4 +138,10 @@
public int getFormat() {
return mFormat;
}
+
+ /** Returns the maximum frame rate (in frames per second) of this stream. */
+ @IntRange(from = 1)
+ public int getMaximumFramesPerSecond() {
+ return mMaxFps;
+ }
}
diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig
index ce2490b..588e4fc 100644
--- a/core/java/android/companion/virtual/flags.aconfig
+++ b/core/java/android/companion/virtual/flags.aconfig
@@ -100,3 +100,10 @@
description: "Enable interactive screen mirroring using Virtual Devices"
bug: "292212199"
}
+
+flag {
+ name: "virtual_stylus"
+ namespace: "virtual_devices"
+ description: "Enable virtual stylus input"
+ bug: "304829446"
+}
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index a1357c9..bde562d 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -231,7 +231,7 @@
}
/** @hide */
- public AttributionSource withToken(@NonNull Binder token) {
+ public AttributionSource withToken(@NonNull IBinder token) {
return new AttributionSource(getUid(), getPid(), getPackageName(), getAttributionTag(),
token, mAttributionSourceState.renouncedPermissions, getDeviceId(), getNext());
}
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index e9b94c9..c7a75ed 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -41,7 +41,6 @@
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.SQLException;
-import android.multiuser.Flags;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Binder;
@@ -147,7 +146,6 @@
private boolean mExported;
private boolean mNoPerms;
private boolean mSingleUser;
- private boolean mSystemUserOnly;
private SparseBooleanArray mUsersRedirectedToOwnerForMedia = new SparseBooleanArray();
private ThreadLocal<AttributionSource> mCallingAttributionSource;
@@ -379,9 +377,7 @@
!= PermissionChecker.PERMISSION_GRANTED
&& getContext().checkUriPermission(userUri, Binder.getCallingPid(),
callingUid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
- != PackageManager.PERMISSION_GRANTED
- && !deniedAccessSystemUserOnlyProvider(callingUserId,
- mSystemUserOnly)) {
+ != PackageManager.PERMISSION_GRANTED) {
FrameworkStatsLog.write(GET_TYPE_ACCESSED_WITHOUT_PERMISSION,
enumCheckUriPermission,
callingUid, uri.getAuthority(), type);
@@ -869,10 +865,6 @@
boolean checkUser(int pid, int uid, Context context) {
final int callingUserId = UserHandle.getUserId(uid);
- if (deniedAccessSystemUserOnlyProvider(callingUserId, mSystemUserOnly)) {
- return false;
- }
-
if (callingUserId == context.getUserId() || mSingleUser) {
return true;
}
@@ -995,9 +987,6 @@
// last chance, check against any uri grants
final int callingUserId = UserHandle.getUserId(uid);
- if (deniedAccessSystemUserOnlyProvider(callingUserId, mSystemUserOnly)) {
- return PermissionChecker.PERMISSION_HARD_DENIED;
- }
final Uri userUri = (mSingleUser && !UserHandle.isSameUser(mMyUid, uid))
? maybeAddUserId(uri, callingUserId) : uri;
if (context.checkUriPermission(userUri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
@@ -2634,7 +2623,6 @@
setPathPermissions(info.pathPermissions);
mExported = info.exported;
mSingleUser = (info.flags & ProviderInfo.FLAG_SINGLE_USER) != 0;
- mSystemUserOnly = (info.flags & ProviderInfo.FLAG_SYSTEM_USER_ONLY) != 0;
setAuthorities(info.authority);
}
if (Build.IS_DEBUGGABLE) {
@@ -2768,11 +2756,6 @@
String auth = uri.getAuthority();
if (!mSingleUser) {
int userId = getUserIdFromAuthority(auth, UserHandle.USER_CURRENT);
- if (deniedAccessSystemUserOnlyProvider(mContext.getUserId(),
- mSystemUserOnly)) {
- throw new SecurityException("Trying to query a SYSTEM user only content"
- + " provider from user:" + mContext.getUserId());
- }
if (userId != UserHandle.USER_CURRENT
&& userId != mContext.getUserId()
// Since userId specified in content uri, the provider userId would be
@@ -2946,16 +2929,4 @@
Trace.traceBegin(traceTag, methodName + subInfo);
}
}
- /**
- * Return true if access to content provider is denied because it's a SYSTEM user only
- * provider and the calling user is not the SYSTEM user.
- *
- * @param callingUserId UserId of the caller accessing the content provider.
- * @param systemUserOnly true when the content provider is only available for the SYSTEM user.
- */
- private static boolean deniedAccessSystemUserOnlyProvider(int callingUserId,
- boolean systemUserOnly) {
- return Flags.enableSystemUserOnlyForServicesAndProviders()
- && (callingUserId != UserHandle.USER_SYSTEM && systemUserOnly);
- }
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index fa76e39..249c0e43 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -16,6 +16,8 @@
package android.content;
+import static android.content.flags.Flags.FLAG_ENABLE_BIND_PACKAGE_ISOLATED_PROCESS;
+
import android.annotation.AttrRes;
import android.annotation.CallbackExecutor;
import android.annotation.CheckResult;
@@ -296,6 +298,7 @@
BIND_ALLOW_ACTIVITY_STARTS,
BIND_INCLUDE_CAPABILITIES,
BIND_SHARED_ISOLATED_PROCESS,
+ BIND_PACKAGE_ISOLATED_PROCESS,
BIND_EXTERNAL_SERVICE
})
@Retention(RetentionPolicy.SOURCE)
@@ -318,6 +321,7 @@
BIND_ALLOW_ACTIVITY_STARTS,
BIND_INCLUDE_CAPABILITIES,
BIND_SHARED_ISOLATED_PROCESS,
+ BIND_PACKAGE_ISOLATED_PROCESS,
// Intentionally not include BIND_EXTERNAL_SERVICE, because it'd cause sign-extension.
// This would allow Android Studio to show a warning, if someone tries to use
// BIND_EXTERNAL_SERVICE BindServiceFlags.
@@ -511,6 +515,26 @@
*/
public static final int BIND_SHARED_ISOLATED_PROCESS = 0x00002000;
+ /**
+ * Flag for {@link #bindIsolatedService}: Bind the service into a shared isolated process,
+ * but only with other isolated services from the same package that declare the same process
+ * name.
+ *
+ * <p>Specifying this flag allows multiple isolated services defined in the same package to be
+ * running in a single shared isolated process. This shared isolated process must be specified
+ * since this flag will not work with the default application process.
+ *
+ * <p>This flag is different from {@link #BIND_SHARED_ISOLATED_PROCESS} since it only
+ * allows binding services from the same package in the same shared isolated process. This also
+ * means the shared package isolated process is global, and not scoped to each potential
+ * calling app.
+ *
+ * <p>The shared isolated process instance is identified by the "android:process" attribute
+ * defined by the service. This flag cannot be used without this attribute set.
+ */
+ @FlaggedApi(FLAG_ENABLE_BIND_PACKAGE_ISOLATED_PROCESS)
+ public static final int BIND_PACKAGE_ISOLATED_PROCESS = 1 << 14;
+
/*********** Public flags above this line ***********/
/*********** Hidden flags below this line ***********/
@@ -4218,6 +4242,7 @@
VIRTUALIZATION_SERVICE,
GRAMMATICAL_INFLECTION_SERVICE,
SECURITY_STATE_SERVICE,
+ //@hide: ECM_ENHANCED_CONFIRMATION_SERVICE,
})
@Retention(RetentionPolicy.SOURCE)
@@ -6503,6 +6528,18 @@
public static final String SECURITY_STATE_SERVICE = "security_state";
/**
+ * Use with {@link #getSystemService(String)} to retrieve an
+ * {@link android.app.ecm.EnhancedConfirmationManager}.
+ *
+ * @see #getSystemService(String)
+ * @see android.app.ecm.EnhancedConfirmationManager
+ * @hide
+ */
+ @FlaggedApi(android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ @SystemApi
+ public static final String ECM_ENHANCED_CONFIRMATION_SERVICE = "ecm_enhanced_confirmation";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
@@ -6734,7 +6771,7 @@
@Intent.AccessUriMode int modeFlags);
/**
- * Determine whether a particular process and user ID has been granted
+ * Determine whether a particular process and uid has been granted
* permission to access a specific URI. This only checks for permissions
* that have been explicitly granted -- if the given process/uid has
* more general access to the URI's content provider then this check will
@@ -6758,7 +6795,38 @@
@Intent.AccessUriMode int modeFlags);
/**
- * Determine whether a particular process and user ID has been granted
+ * Determine whether a particular process and uid has been granted
+ * permission to access a specific content URI.
+ *
+ * <p>Unlike {@link #checkUriPermission(Uri, int, int, int)}, this method
+ * checks for general access to the URI's content provider, as well as
+ * explicitly granted permissions.</p>
+ *
+ * <p>Note, this check will throw an {@link IllegalArgumentException}
+ * for non-content URIs.</p>
+ *
+ * @param uri The content uri that is being checked.
+ * @param pid (Optional) The process ID being checked against. If the
+ * pid is unknown, pass -1.
+ * @param uid The UID being checked against. A uid of 0 is the root
+ * user, which will pass every permission check.
+ * @param modeFlags The access modes to check.
+ *
+ * @return {@link PackageManager#PERMISSION_GRANTED} if the given
+ * pid/uid is allowed to access that uri, or
+ * {@link PackageManager#PERMISSION_DENIED} if it is not.
+ *
+ * @see #checkUriPermission(Uri, int, int, int)
+ */
+ @FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS)
+ @PackageManager.PermissionResult
+ public int checkContentUriPermissionFull(@NonNull Uri uri, int pid, int uid,
+ @Intent.AccessUriMode int modeFlags) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * Determine whether a particular process and uid has been granted
* permission to access a list of URIs. This only checks for permissions
* that have been explicitly granted -- if the given process/uid has
* more general access to the URI's content provider then this check will
@@ -6794,7 +6862,7 @@
@Intent.AccessUriMode int modeFlags, IBinder callerToken);
/**
- * Determine whether the calling process and user ID has been
+ * Determine whether the calling process and uid has been
* granted permission to access a specific URI. This is basically
* the same as calling {@link #checkUriPermission(Uri, int, int,
* int)} with the pid and uid returned by {@link
@@ -6817,7 +6885,7 @@
public abstract int checkCallingUriPermission(Uri uri, @Intent.AccessUriMode int modeFlags);
/**
- * Determine whether the calling process and user ID has been
+ * Determine whether the calling process and uid has been
* granted permission to access a list of URIs. This is basically
* the same as calling {@link #checkUriPermissions(List, int, int, int)}
* with the pid and uid returned by {@link
@@ -6911,7 +6979,7 @@
@Intent.AccessUriMode int modeFlags);
/**
- * If a particular process and user ID has not been granted
+ * If a particular process and uid has not been granted
* permission to access a specific URI, throw {@link
* SecurityException}. This only checks for permissions that have
* been explicitly granted -- if the given process/uid has more
@@ -6931,7 +6999,7 @@
Uri uri, int pid, int uid, @Intent.AccessUriMode int modeFlags, String message);
/**
- * If the calling process and user ID has not been granted
+ * If the calling process and uid has not been granted
* permission to access a specific URI, throw {@link
* SecurityException}. This is basically the same as calling
* {@link #enforceUriPermission(Uri, int, int, int, String)} with
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 0a8029c..e0cf0a5 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -17,6 +17,7 @@
package android.content;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -1003,6 +1004,12 @@
return mBase.checkUriPermission(uri, pid, uid, modeFlags);
}
+ @FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS)
+ @Override
+ public int checkContentUriPermissionFull(@NonNull Uri uri, int pid, int uid, int modeFlags) {
+ return mBase.checkContentUriPermissionFull(uri, pid, uid, modeFlags);
+ }
+
@NonNull
@Override
public int[] checkUriPermissions(@NonNull List<Uri> uris, int pid, int uid,
diff --git a/core/java/android/content/OWNERS b/core/java/android/content/OWNERS
index 90c3d04..a37408b 100644
--- a/core/java/android/content/OWNERS
+++ b/core/java/android/content/OWNERS
@@ -4,6 +4,7 @@
per-file *Content* = file:/services/core/java/com/android/server/am/OWNERS
per-file *Sync* = file:/services/core/java/com/android/server/am/OWNERS
per-file IntentFilter.java = file:/PACKAGE_MANAGER_OWNERS
+per-file UriRelativeFilter* = file:/PACKAGE_MANAGER_OWNERS
per-file IntentFilter.java = file:/services/core/java/com/android/server/am/OWNERS
per-file Intent.java = file:/INTENT_OWNERS
per-file AutofillOptions* = file:/core/java/android/service/autofill/OWNERS
diff --git a/core/java/android/content/flags/flags.aconfig b/core/java/android/content/flags/flags.aconfig
new file mode 100644
index 0000000..3445fb5
--- /dev/null
+++ b/core/java/android/content/flags/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.content.flags"
+
+flag {
+ name: "enable_bind_package_isolated_process"
+ namespace: "machine_learning"
+ description: "This flag enables the newly added flag for binding package-private isolated processes."
+ bug: "312706530"
+}
\ No newline at end of file
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 32ecb58..1f25fd0 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -80,7 +80,7 @@
long timeout);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES,android.Manifest.permission.REQUEST_DELETE_PACKAGES})")
- void requestArchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle, int flags);
+ void requestArchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})")
void requestUnarchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle);
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 43322641..f0efed9 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -2352,7 +2352,6 @@
* communicated.
*
* @param statusReceiver Callback used to notify when the operation is completed.
- * @param flags Flags for archiving. Can be 0 or {@link PackageManager#DELETE_SHOW_DIALOG}.
* @throws PackageManager.NameNotFoundException If {@code packageName} isn't found or not
* available to the caller or isn't archived.
*/
@@ -2360,12 +2359,11 @@
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.REQUEST_DELETE_PACKAGES})
@FlaggedApi(Flags.FLAG_ARCHIVING)
- public void requestArchive(@NonNull String packageName, @NonNull IntentSender statusReceiver,
- @DeleteFlags int flags)
+ public void requestArchive(@NonNull String packageName, @NonNull IntentSender statusReceiver)
throws PackageManager.NameNotFoundException {
try {
mInstaller.requestArchive(packageName, mInstallerPackageName, statusReceiver,
- new UserHandle(mUserId), flags);
+ new UserHandle(mUserId));
} catch (ParcelableException e) {
e.maybeRethrow(PackageManager.NameNotFoundException.class);
} catch (RemoteException e) {
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 8e5e825..aabbe69 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2596,7 +2596,6 @@
DELETE_SYSTEM_APP,
DELETE_DONT_KILL_APP,
DELETE_CHATTY,
- DELETE_SHOW_DIALOG,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DeleteFlags {}
@@ -2649,12 +2648,6 @@
public static final int DELETE_ARCHIVE = 0x00000010;
/**
- * Show a confirmation dialog to the user when app is being deleted.
- */
- @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
- public static final int DELETE_SHOW_DIALOG = 0x00000020;
-
- /**
* Flag parameter for {@link #deletePackage} to indicate that package deletion
* should be chatty.
*
diff --git a/core/java/android/content/pm/ProviderInfo.java b/core/java/android/content/pm/ProviderInfo.java
index de33fa8..9e553db 100644
--- a/core/java/android/content/pm/ProviderInfo.java
+++ b/core/java/android/content/pm/ProviderInfo.java
@@ -89,15 +89,6 @@
public static final int FLAG_VISIBLE_TO_INSTANT_APP = 0x100000;
/**
- * Bit in {@link #flags}: If set, this provider will only be available
- * for the system user.
- * Set from the android.R.attr#systemUserOnly attribute.
- * In Sync with {@link ActivityInfo#FLAG_SYSTEM_USER_ONLY}
- * @hide
- */
- public static final int FLAG_SYSTEM_USER_ONLY = ActivityInfo.FLAG_SYSTEM_USER_ONLY;
-
- /**
* Bit in {@link #flags}: If set, a single instance of the provider will
* run for all users on the device. Set from the
* {@link android.R.attr#singleUser} attribute.
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 2b378b1..ae46c027 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -101,14 +101,6 @@
public static final int FLAG_VISIBLE_TO_INSTANT_APP = 0x100000;
/**
- * @hide Bit in {@link #flags}: If set, this service will only be available
- * for the system user.
- * Set from the android.R.attr#systemUserOnly attribute.
- * In Sync with {@link ActivityInfo#FLAG_SYSTEM_USER_ONLY}
- */
- public static final int FLAG_SYSTEM_USER_ONLY = ActivityInfo.FLAG_SYSTEM_USER_ONLY;
-
- /**
* Bit in {@link #flags}: If set, a single instance of the service will
* run for all users on the device. Set from the
* {@link android.R.attr#singleUser} attribute.
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index 57749d4..269c6c2 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -123,6 +123,7 @@
* @hide
*/
@IntDef(prefix = "SHOW_IN_LAUNCHER_", value = {
+ SHOW_IN_LAUNCHER_UNKNOWN,
SHOW_IN_LAUNCHER_WITH_PARENT,
SHOW_IN_LAUNCHER_SEPARATE,
SHOW_IN_LAUNCHER_NO,
@@ -131,6 +132,13 @@
public @interface ShowInLauncher {
}
/**
+ * Indicates that the show in launcher value for this profile is unknown or unsupported.
+ * @hide
+ */
+ @TestApi
+ @SuppressLint("UnflaggedApi") // b/306636213
+ public static final int SHOW_IN_LAUNCHER_UNKNOWN = -1;
+ /**
* Suggests that the launcher should show this user's apps in the main tab.
* That is, either this user is a full user, so its apps should be presented accordingly, or, if
* this user is a profile, then its apps should be shown alongside its parent's apps.
@@ -157,6 +165,7 @@
* @hide
*/
@IntDef(prefix = "SHOW_IN_SETTINGS_", value = {
+ SHOW_IN_SETTINGS_UNKNOWN,
SHOW_IN_SETTINGS_WITH_PARENT,
SHOW_IN_SETTINGS_SEPARATE,
SHOW_IN_SETTINGS_NO,
@@ -165,6 +174,12 @@
public @interface ShowInSettings {
}
/**
+ * Indicates that the show in settings value for this profile is unknown or unsupported.
+ * @hide
+ */
+ @SuppressLint("UnflaggedApi") // b/306636213
+ public static final int SHOW_IN_SETTINGS_UNKNOWN = -1;
+ /**
* Suggests that the Settings app should show this user's apps in the main tab.
* That is, either this user is a full user, so its apps should be presented accordingly, or, if
* this user is a profile, then its apps should be shown alongside its parent's apps.
@@ -309,6 +324,7 @@
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = "SHOW_IN_QUIET_MODE_",
value = {
+ SHOW_IN_QUIET_MODE_UNKNOWN,
SHOW_IN_QUIET_MODE_PAUSED,
SHOW_IN_QUIET_MODE_HIDDEN,
SHOW_IN_QUIET_MODE_DEFAULT,
@@ -318,6 +334,12 @@
}
/**
+ * Indicates that the show in quiet mode value for this profile is unknown.
+ */
+ @SuppressLint("UnflaggedApi") // b/306636213
+ public static final int SHOW_IN_QUIET_MODE_UNKNOWN = -1;
+
+ /**
* Indicates that the profile should still be visible in quiet mode but should be shown as
* paused (e.g. by greying out its icons).
*/
@@ -347,6 +369,7 @@
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = "SHOW_IN_SHARING_SURFACES_",
value = {
+ SHOW_IN_SHARING_SURFACES_UNKNOWN,
SHOW_IN_SHARING_SURFACES_SEPARATE,
SHOW_IN_SHARING_SURFACES_WITH_PARENT,
SHOW_IN_SHARING_SURFACES_NO,
@@ -356,6 +379,12 @@
}
/**
+ * Indicates that the show in launcher value for this profile is unknown or unsupported.
+ */
+ @SuppressLint("UnflaggedApi") // b/306636213
+ public static final int SHOW_IN_SHARING_SURFACES_UNKNOWN = SHOW_IN_LAUNCHER_UNKNOWN;
+
+ /**
* Indicates that the profile data and apps should be shown in sharing surfaces intermixed with
* parent user's data and apps.
*/
@@ -379,7 +408,8 @@
*
* @hide
*/
- @IntDef(prefix = {"CROSS_PROFILE_CONTENT_SHARING_STRATEGY_"}, value = {
+ @IntDef(prefix = {"CROSS_PROFILE_CONTENT_SHARING_"}, value = {
+ CROSS_PROFILE_CONTENT_SHARING_UNKNOWN,
CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION,
CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT
})
@@ -388,6 +418,13 @@
}
/**
+ * Signifies that cross-profile content sharing strategy, both to and from this profile, is
+ * unknown/unsupported.
+ */
+ @SuppressLint("UnflaggedApi") // b/306636213
+ public static final int CROSS_PROFILE_CONTENT_SHARING_UNKNOWN = -1;
+
+ /**
* Signifies that cross-profile content sharing strategy, both to and from this profile, should
* not be delegated to any other user/profile.
* For ex:
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 1036865..7c5d305 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -71,10 +71,10 @@
description: "Add support for Private Space in resolver sheet"
bug: "307515485"
}
+
flag {
- name: "enable_system_user_only_for_services_and_providers"
- namespace: "multiuser"
- description: "Enable systemUserOnly manifest attribute for services and providers."
- bug: "302354856"
- is_fixed_read_only: true
-}
+ name: "move_quiet_mode_operations_to_separate_thread"
+ namespace: "profile_experiences"
+ description: "Move the quiet mode operations, happening on a background thread today, to a separate thread."
+ bug: "320483504"
+}
\ No newline at end of file
diff --git a/core/java/android/content/res/FontScaleConverter.java b/core/java/android/content/res/FontScaleConverter.java
index 088949e..f4312a9 100644
--- a/core/java/android/content/res/FontScaleConverter.java
+++ b/core/java/android/content/res/FontScaleConverter.java
@@ -17,7 +17,9 @@
package android.content.res;
+import android.annotation.AnyThread;
import android.annotation.FlaggedApi;
+import android.annotation.Nullable;
/**
* A converter for non-linear font scaling. Converts font sizes given in "sp" dimensions to a
@@ -40,4 +42,35 @@
* Converts a dimension in "dp" back to "sp".
*/
float convertDpToSp(float dp);
+
+ /**
+ * Returns true if non-linear font scaling curves would be in effect for the given scale, false
+ * if the scaling would follow a linear curve or for no scaling.
+ *
+ * <p>Example usage: {@code
+ * isNonLinearFontScalingActive(getResources().getConfiguration().fontScale)}
+ */
+ @AnyThread
+ static boolean isNonLinearFontScalingActive(float fontScale) {
+ return FontScaleConverterFactory.isNonLinearFontScalingActive(fontScale);
+ }
+
+ /**
+ * Finds a matching FontScaleConverter for the given fontScale factor.
+ *
+ * Generally you shouldn't need this; you can use {@link
+ * android.util.TypedValue#applyDimension(int, float, DisplayMetrics)} directly and it will do
+ * the scaling conversion for you. Dimens and resources loaded from XML will also be
+ * automatically converted. But for UI frameworks or other situations where you need to do the
+ * conversion without an Android Context, you can use this method.
+ *
+ * @param fontScale the scale factor, usually from {@link Configuration#fontScale}.
+ *
+ * @return a converter for the given scale, or null if non-linear scaling should not be used.
+ */
+ @Nullable
+ @AnyThread
+ static FontScaleConverter forScale(float fontScale) {
+ return FontScaleConverterFactory.forScale(fontScale);
+ }
}
diff --git a/core/java/android/content/res/FontScaleConverterFactory.java b/core/java/android/content/res/FontScaleConverterFactory.java
index 5d31cc0..cbe4c62 100644
--- a/core/java/android/content/res/FontScaleConverterFactory.java
+++ b/core/java/android/content/res/FontScaleConverterFactory.java
@@ -17,7 +17,6 @@
package android.content.res;
import android.annotation.AnyThread;
-import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.MathUtils;
@@ -32,8 +31,9 @@
* android.util.TypedValue#applyDimension(int, float, DisplayMetrics)} directly and it will do the
* scaling conversion for you. But for UI frameworks or other situations where you need to do the
* conversion without an Android Context, you can use this class.
+ *
+ * @hide
*/
-@FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC)
public class FontScaleConverterFactory {
private static final float SCALE_KEY_MULTIPLIER = 100f;
@@ -124,7 +124,6 @@
* <p>Example usage:
* <code>isNonLinearFontScalingActive(getResources().getConfiguration().fontScale)</code>
*/
- @FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC)
@AnyThread
public static boolean isNonLinearFontScalingActive(float fontScale) {
return fontScale >= sMinScaleBeforeCurvesApplied;
@@ -137,7 +136,6 @@
*
* @return a converter for the given scale, or null if non-linear scaling should not be used.
*/
- @FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC)
@Nullable
@AnyThread
public static FontScaleConverter forScale(float fontScale) {
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 47ee76e..796a57b 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -33,12 +33,12 @@
import android.content.IntentSender;
import android.os.Binder;
import android.os.CancellationSignal;
+import android.os.IBinder;
import android.os.ICancellationSignal;
import android.os.OutcomeReceiver;
import android.os.RemoteException;
import android.provider.DeviceConfig;
import android.util.Log;
-import android.view.autofill.IAutoFillManagerClient;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -138,7 +138,7 @@
@CallbackExecutor @NonNull Executor executor,
@NonNull OutcomeReceiver<GetCandidateCredentialsResponse,
GetCandidateCredentialsException> callback,
- @NonNull IAutoFillManagerClient clientCallback
+ @NonNull IBinder clientCallback
) {
requireNonNull(request, "request must not be null");
requireNonNull(callingPackage, "callingPackage must not be null");
diff --git a/core/java/android/credentials/GetCandidateCredentialsException.java b/core/java/android/credentials/GetCandidateCredentialsException.java
index 40650d0..0ac5f6c 100644
--- a/core/java/android/credentials/GetCandidateCredentialsException.java
+++ b/core/java/android/credentials/GetCandidateCredentialsException.java
@@ -46,6 +46,17 @@
"android.credentials.GetCandidateCredentialsException.TYPE_NO_CREDENTIAL";
@NonNull
+ public static final String TYPE_USER_CANCELED =
+ "android.credentials.GetCredentialException.TYPE_USER_CANCELED";
+ /**
+ * The error type value for when the given operation failed due to internal interruption.
+ * Retrying the same operation should fix the error.
+ */
+ @NonNull
+ public static final String TYPE_INTERRUPTED =
+ "android.credentials.GetCredentialException.TYPE_INTERRUPTED";
+
+ @NonNull
private final String mType;
/** Returns the specific exception type. */
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index 726bc97..d4a2d97 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -22,7 +22,6 @@
import android.credentials.ClearCredentialStateRequest;
import android.credentials.CreateCredentialRequest;
import android.credentials.GetCandidateCredentialsRequest;
-import android.view.autofill.IAutoFillManagerClient;
import android.credentials.GetCredentialRequest;
import android.credentials.RegisterCredentialDescriptionRequest;
import android.credentials.UnregisterCredentialDescriptionRequest;
@@ -33,6 +32,7 @@
import android.credentials.IPrepareGetCredentialCallback;
import android.credentials.ISetEnabledProvidersCallback;
import android.content.ComponentName;
+import android.os.IBinder;
import android.os.ICancellationSignal;
/**
@@ -48,7 +48,7 @@
@nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage);
- @nullable ICancellationSignal getCandidateCredentials(in GetCredentialRequest request, in IGetCandidateCredentialsCallback callback, in IAutoFillManagerClient clientCallback, String callingPackage);
+ @nullable ICancellationSignal getCandidateCredentials(in GetCredentialRequest request, in IGetCandidateCredentialsCallback callback, in IBinder clientCallback, String callingPackage);
@nullable ICancellationSignal clearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback, String callingPackage);
diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig
index f876eeb..90cd471 100644
--- a/core/java/android/credentials/flags.aconfig
+++ b/core/java/android/credentials/flags.aconfig
@@ -33,4 +33,18 @@
name: "new_settings_ui"
description: "Enables new settings UI for VIC"
bug: "315209085"
+}
+
+flag {
+ namespace: "credential_manager"
+ name: "selector_ui_improvements_enabled"
+ description: "Enables Credential Selector UI improvements for VIC"
+ bug: "319448437"
+}
+
+flag {
+ namespace: "credential_manager"
+ name: "configurable_selector_ui_enabled"
+ description: "Enables OEM configurable Credential Selector UI"
+ bug: "319448437"
}
\ No newline at end of file
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 8c1ea5f..c0424db 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -16,14 +16,17 @@
package android.hardware.biometrics;
+import static android.Manifest.permission.MANAGE_BIOMETRIC_DIALOG;
import static android.Manifest.permission.TEST_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.hardware.biometrics.BiometricManager.Authenticators;
import static android.hardware.biometrics.Flags.FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT;
import static android.hardware.biometrics.Flags.FLAG_GET_OP_ID_CRYPTO_OBJECT;
+import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
import android.annotation.CallbackExecutor;
+import android.annotation.DrawableRes;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -32,6 +35,7 @@
import android.annotation.TestApi;
import android.content.Context;
import android.content.DialogInterface;
+import android.graphics.Bitmap;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Binder;
@@ -159,6 +163,45 @@
}
/**
+ * Optional: Sets the drawable resource of the logo that will be shown on the prompt.
+ *
+ * <p> Note that using this method is not recommended in most scenarios because the calling
+ * application's icon will be used by default. Setting the logo is intended for large
+ * bundled applications that perform a wide range of functions and need to show distinct
+ * icons for each function.
+ *
+ * @param logoRes A drawable resource of the logo that will be shown on the prompt.
+ * @return This builder.
+ */
+ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ @RequiresPermission(MANAGE_BIOMETRIC_DIALOG)
+ @NonNull
+ public BiometricPrompt.Builder setLogo(@DrawableRes int logoRes) {
+ mPromptInfo.setLogoRes(logoRes);
+ return this;
+ }
+
+ /**
+ * Optional: Sets the bitmap drawable of the logo that will be shown on the prompt.
+ *
+ * <p> Note that using this method is not recommended in most scenarios because the calling
+ * application's icon will be used by default. Setting the logo is intended for large
+ * bundled applications that perform a wide range of functions and need to show distinct
+ * icons for each function.
+ *
+ * @param logoBitmap A bitmap drawable of the logo that will be shown on the prompt.
+ * @return This builder.
+ */
+ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ @RequiresPermission(MANAGE_BIOMETRIC_DIALOG)
+ @NonNull
+ public BiometricPrompt.Builder setLogo(@NonNull Bitmap logoBitmap) {
+ mPromptInfo.setLogoBitmap(logoBitmap);
+ return this;
+ }
+
+
+ /**
* Required: Sets the title that will be shown on the prompt.
* @param title The title to display.
* @return This builder.
@@ -208,6 +251,12 @@
/**
* Optional: Sets a description that will be shown on the prompt.
+ *
+ * <p> Note that the description set by {@link Builder#setDescription(CharSequence)} will be
+ * overridden by {@link Builder#setContentView(PromptContentView)}. The view provided to
+ * {@link Builder#setContentView(PromptContentView)} will be used if both methods are
+ * called.
+ *
* @param description The description to display.
* @return This builder.
*/
@@ -218,6 +267,24 @@
}
/**
+ * Optional: Sets application customized content view that will be shown on the prompt.
+ *
+ * <p> Note that the description set by {@link Builder#setDescription(CharSequence)} will be
+ * overridden by {@link Builder#setContentView(PromptContentView)}. The view provided to
+ * {@link Builder#setContentView(PromptContentView)} will be used if both methods are
+ * called.
+ *
+ * @param view The customized view information.
+ * @return This builder.re
+ */
+ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ @NonNull
+ public BiometricPrompt.Builder setContentView(@NonNull PromptContentView view) {
+ mPromptInfo.setContentView(view);
+ return this;
+ }
+
+ /**
* @param service
* @return This builder.
* @hide
@@ -651,6 +718,34 @@
}
/**
+ * Gets the drawable resource of the logo for the prompt, as set by
+ * {@link Builder#setLogo(int)}. Currently for system applications use only.
+ *
+ * @return The drawable resource of the logo, or -1 if the prompt has no logo resource set.
+ */
+ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ @RequiresPermission(MANAGE_BIOMETRIC_DIALOG)
+ @DrawableRes
+ public int getLogoRes() {
+ return mPromptInfo.getLogoRes();
+ }
+
+ /**
+ * Gets the logo bitmap for the prompt, as set by {@link Builder#setLogo(Bitmap)}. Currently for
+ * system applications use only.
+ *
+ * @return The logo bitmap of the prompt, or null if the prompt has no logo bitmap set.
+ */
+ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ @RequiresPermission(MANAGE_BIOMETRIC_DIALOG)
+ @Nullable
+ public Bitmap getLogoBitmap() {
+ return mPromptInfo.getLogoBitmap();
+ }
+
+
+
+ /**
* Gets the title for the prompt, as set by {@link Builder#setTitle(CharSequence)}.
* @return The title of the prompt, which is guaranteed to be non-null.
*/
@@ -698,6 +793,18 @@
}
/**
+ * Gets the content view for the prompt, as set by
+ * {@link Builder#setContentView(PromptContentView)}.
+ *
+ * @return The content view for the prompt, or null if the prompt has no content view.
+ */
+ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ @Nullable
+ public PromptContentView getContentView() {
+ return mPromptInfo.getContentView();
+ }
+
+ /**
* Gets the negative button text for the prompt, as set by
* {@link Builder#setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}.
* @return The negative button text for the prompt, or null if no negative button text was set.
diff --git a/core/java/android/hardware/biometrics/IBiometricContextListener.aidl b/core/java/android/hardware/biometrics/IBiometricContextListener.aidl
index 6ac6581..2f58f51 100644
--- a/core/java/android/hardware/biometrics/IBiometricContextListener.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricContextListener.aidl
@@ -38,4 +38,8 @@
// Called when the display state of the device changes.
// Where `displayState` is defined in AuthenticateOptions.DisplayState
void onDisplayStateChanged(int displayState);
+
+ // Called when the HAL ignoring touches state changes.
+ // When true, the HAL ignores touches on the sensor.
+ void onHardwareIgnoreTouchesChanged(boolean shouldIgnore);
}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl b/core/java/android/hardware/biometrics/PromptContentItem.java
similarity index 68%
copy from core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
copy to core/java/android/hardware/biometrics/PromptContentItem.java
index ce92b6d..c47b37a 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
+++ b/core/java/android/hardware/biometrics/PromptContentItem.java
@@ -13,10 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.companion.virtual.camera;
+
+package android.hardware.biometrics;
+
+import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
+
+import android.annotation.FlaggedApi;
/**
- * The configuration of a single virtual camera stream.
- * @hide
+ * An item shown on {@link PromptContentView}.
*/
-parcelable VirtualCameraStreamConfig;
\ No newline at end of file
+@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+public interface PromptContentItem {
+}
+
diff --git a/core/java/android/hardware/biometrics/PromptContentItemBulletedText.java b/core/java/android/hardware/biometrics/PromptContentItemBulletedText.java
new file mode 100644
index 0000000..c5e5a80
--- /dev/null
+++ b/core/java/android/hardware/biometrics/PromptContentItemBulletedText.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.biometrics;
+
+import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A list item with bulleted text shown on {@link PromptVerticalListContentView}.
+ */
+@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+public final class PromptContentItemBulletedText implements PromptContentItemParcelable {
+ private final CharSequence mText;
+
+ /**
+ * A list item with bulleted text shown on {@link PromptVerticalListContentView}.
+ *
+ * @param text The text of this list item.
+ */
+ public PromptContentItemBulletedText(@NonNull CharSequence text) {
+ mText = text;
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public CharSequence getText() {
+ return mText;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeCharSequence(mText);
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ @NonNull
+ public static final Creator<PromptContentItemBulletedText> CREATOR = new Creator<>() {
+ @Override
+ public PromptContentItemBulletedText createFromParcel(Parcel in) {
+ return new PromptContentItemBulletedText(in.readCharSequence());
+ }
+
+ @Override
+ public PromptContentItemBulletedText[] newArray(int size) {
+ return new PromptContentItemBulletedText[size];
+ }
+ };
+}
diff --git a/core/java/android/hardware/biometrics/PromptContentItemParcelable.java b/core/java/android/hardware/biometrics/PromptContentItemParcelable.java
new file mode 100644
index 0000000..668912cf
--- /dev/null
+++ b/core/java/android/hardware/biometrics/PromptContentItemParcelable.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.biometrics;
+
+import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
+
+import android.annotation.FlaggedApi;
+import android.os.Parcelable;
+
+/**
+ * A parcelable {@link PromptContentItem}.
+ */
+@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+sealed interface PromptContentItemParcelable extends PromptContentItem, Parcelable
+ permits PromptContentItemPlainText, PromptContentItemBulletedText {
+}
diff --git a/core/java/android/hardware/biometrics/PromptContentItemPlainText.java b/core/java/android/hardware/biometrics/PromptContentItemPlainText.java
new file mode 100644
index 0000000..6434c59
--- /dev/null
+++ b/core/java/android/hardware/biometrics/PromptContentItemPlainText.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.biometrics;
+
+import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A list item with plain text shown on {@link PromptVerticalListContentView}.
+ */
+@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+public final class PromptContentItemPlainText implements PromptContentItemParcelable {
+ private final CharSequence mText;
+
+ /**
+ * A list item with plain text shown on {@link PromptVerticalListContentView}.
+ *
+ * @param text The text of this list item.
+ */
+ public PromptContentItemPlainText(@NonNull CharSequence text) {
+ mText = text;
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public CharSequence getText() {
+ return mText;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeCharSequence(mText);
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ @NonNull
+ public static final Creator<PromptContentItemPlainText> CREATOR = new Creator<>() {
+ @Override
+ public PromptContentItemPlainText createFromParcel(Parcel in) {
+ return new PromptContentItemPlainText(in.readCharSequence());
+ }
+
+ @Override
+ public PromptContentItemPlainText[] newArray(int size) {
+ return new PromptContentItemPlainText[size];
+ }
+ };
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryKosmos.kt b/core/java/android/hardware/biometrics/PromptContentView.java
similarity index 65%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryKosmos.kt
copy to core/java/android/hardware/biometrics/PromptContentView.java
index 7b9634a..ff9313e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryKosmos.kt
+++ b/core/java/android/hardware/biometrics/PromptContentView.java
@@ -14,9 +14,15 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.mobile.data.repository
+package android.hardware.biometrics;
-import com.android.systemui.kosmos.Kosmos
+import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
-var Kosmos.userSetupRepository: UserSetupRepository by Kosmos.Fixture { fakeUserSetupRepository }
-val Kosmos.fakeUserSetupRepository by Kosmos.Fixture { FakeUserSetupRepository() }
+import android.annotation.FlaggedApi;
+
+/**
+ * Contains the information of the template of content view for Biometric Prompt.
+ */
+@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+public interface PromptContentView {
+}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl b/core/java/android/hardware/biometrics/PromptContentViewParcelable.java
similarity index 60%
copy from core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
copy to core/java/android/hardware/biometrics/PromptContentViewParcelable.java
index ce92b6d..43b965b 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
+++ b/core/java/android/hardware/biometrics/PromptContentViewParcelable.java
@@ -13,10 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.companion.virtual.camera;
+
+package android.hardware.biometrics;
+
+import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
+
+import android.annotation.FlaggedApi;
+import android.os.Parcelable;
/**
- * The configuration of a single virtual camera stream.
- * @hide
+ * A parcelable {@link PromptContentView}.
*/
-parcelable VirtualCameraStreamConfig;
\ No newline at end of file
+@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+sealed interface PromptContentViewParcelable extends PromptContentView, Parcelable
+ permits PromptVerticalListContentView {
+}
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index 24cfd164..d788b37 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -16,8 +16,10 @@
package android.hardware.biometrics;
+import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.graphics.Bitmap;
import android.os.Parcel;
import android.os.Parcelable;
@@ -30,11 +32,14 @@
*/
public class PromptInfo implements Parcelable {
+ @DrawableRes private int mLogoRes = -1;
+ @Nullable private Bitmap mLogoBitmap;
@NonNull private CharSequence mTitle;
private boolean mUseDefaultTitle;
@Nullable private CharSequence mSubtitle;
private boolean mUseDefaultSubtitle;
@Nullable private CharSequence mDescription;
+ @Nullable private PromptContentViewParcelable mContentView;
@Nullable private CharSequence mDeviceCredentialTitle;
@Nullable private CharSequence mDeviceCredentialSubtitle;
@Nullable private CharSequence mDeviceCredentialDescription;
@@ -55,11 +60,15 @@
}
PromptInfo(Parcel in) {
+ mLogoRes = in.readInt();
+ mLogoBitmap = in.readTypedObject(Bitmap.CREATOR);
mTitle = in.readCharSequence();
mUseDefaultTitle = in.readBoolean();
mSubtitle = in.readCharSequence();
mUseDefaultSubtitle = in.readBoolean();
mDescription = in.readCharSequence();
+ mContentView = in.readParcelable(PromptContentViewParcelable.class.getClassLoader(),
+ PromptContentViewParcelable.class);
mDeviceCredentialTitle = in.readCharSequence();
mDeviceCredentialSubtitle = in.readCharSequence();
mDeviceCredentialDescription = in.readCharSequence();
@@ -95,11 +104,14 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mLogoRes);
+ dest.writeTypedObject(mLogoBitmap, 0);
dest.writeCharSequence(mTitle);
dest.writeBoolean(mUseDefaultTitle);
dest.writeCharSequence(mSubtitle);
dest.writeBoolean(mUseDefaultSubtitle);
dest.writeCharSequence(mDescription);
+ dest.writeParcelable(mContentView, 0);
dest.writeCharSequence(mDeviceCredentialTitle);
dest.writeCharSequence(mDeviceCredentialSubtitle);
dest.writeCharSequence(mDeviceCredentialDescription);
@@ -152,9 +164,30 @@
}
return false;
}
+
+ /**
+ * Returns whether MANAGE_BIOMETRIC_DIALOG is contained.
+ */
+ public boolean containsManageBioApiConfigurations() {
+ if (mLogoRes != -1) {
+ return true;
+ } else if (mLogoBitmap != null) {
+ return true;
+ }
+ return false;
+ }
// LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/BiometricPrompt.java)
// Setters
+ public void setLogoRes(@DrawableRes int logoRes) {
+ mLogoRes = logoRes;
+ checkOnlyOneLogoSet();
+ }
+
+ public void setLogoBitmap(@NonNull Bitmap logoBitmap) {
+ mLogoBitmap = logoBitmap;
+ checkOnlyOneLogoSet();
+ }
public void setTitle(CharSequence title) {
mTitle = title;
@@ -176,6 +209,10 @@
mDescription = description;
}
+ public void setContentView(PromptContentView view) {
+ mContentView = (PromptContentViewParcelable) view;
+ }
+
public void setDeviceCredentialTitle(CharSequence deviceCredentialTitle) {
mDeviceCredentialTitle = deviceCredentialTitle;
}
@@ -236,6 +273,14 @@
}
// Getters
+ @DrawableRes
+ public int getLogoRes() {
+ return mLogoRes;
+ }
+
+ public Bitmap getLogoBitmap() {
+ return mLogoBitmap;
+ }
public CharSequence getTitle() {
return mTitle;
@@ -257,6 +302,15 @@
return mDescription;
}
+ /**
+ * Gets the content view for the prompt.
+ *
+ * @return The content view for the prompt, or null if the prompt has no content view.
+ */
+ public PromptContentView getContentView() {
+ return mContentView;
+ }
+
public CharSequence getDeviceCredentialTitle() {
return mDeviceCredentialTitle;
}
@@ -320,4 +374,11 @@
public boolean isShowEmergencyCallButton() {
return mShowEmergencyCallButton;
}
+
+ private void checkOnlyOneLogoSet() {
+ if (mLogoRes != -1 && mLogoBitmap != null) {
+ throw new IllegalStateException(
+ "Exclusively one of logo resource or logo bitmap can be set");
+ }
+ }
}
diff --git a/core/java/android/hardware/biometrics/PromptVerticalListContentView.java b/core/java/android/hardware/biometrics/PromptVerticalListContentView.java
new file mode 100644
index 0000000..f3e6290
--- /dev/null
+++ b/core/java/android/hardware/biometrics/PromptVerticalListContentView.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.biometrics;
+
+import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * Contains the information of the template of vertical list content view for Biometric Prompt. Note
+ * that there are limits on the item count and the number of characters allowed for each item's
+ * text.
+ * <p>
+ * Here's how you'd set a <code>PromptVerticalListContentView</code> on a Biometric Prompt:
+ * <pre class="prettyprint">
+ * BiometricPrompt biometricPrompt = new BiometricPrompt.Builder(...)
+ * .setTitle(...)
+ * .setSubTitle(...)
+ * .setContentView(new PromptVerticalListContentView.Builder()
+ * .setDescription("test description")
+ * .addListItem(new PromptContentItemPlainText("test item 1"))
+ * .addListItem(new PromptContentItemPlainText("test item 2"))
+ * .addListItem(new PromptContentItemBulletedText("test item 3"))
+ * .build())
+ * .build();
+ * </pre>
+ */
+@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+public final class PromptVerticalListContentView implements PromptContentViewParcelable {
+ private static final int MAX_ITEM_NUMBER = 20;
+ private static final int MAX_EACH_ITEM_CHARACTER_NUMBER = 640;
+ private final List<PromptContentItemParcelable> mContentList;
+ private final CharSequence mDescription;
+
+ private PromptVerticalListContentView(
+ @NonNull List<PromptContentItemParcelable> contentList,
+ @NonNull CharSequence description) {
+ mContentList = contentList;
+ mDescription = description;
+ }
+
+ private PromptVerticalListContentView(Parcel in) {
+ mContentList = in.readArrayList(
+ PromptContentItemParcelable.class.getClassLoader(),
+ PromptContentItemParcelable.class);
+ mDescription = in.readCharSequence();
+ }
+
+ /**
+ * Returns the maximum count of the list items.
+ */
+ public static int getMaxItemCount() {
+ return MAX_ITEM_NUMBER;
+ }
+
+ /**
+ * Returns the maximum number of characters allowed for each item's text.
+ */
+ public static int getMaxEachItemCharacterNumber() {
+ return MAX_EACH_ITEM_CHARACTER_NUMBER;
+ }
+
+ /**
+ * Gets the description for the content view, as set by
+ * {@link PromptVerticalListContentView.Builder#setDescription(CharSequence)}.
+ *
+ * @return The description for the content view, or null if the content view has no description.
+ */
+ @Nullable
+ public CharSequence getDescription() {
+ return mDescription;
+ }
+
+ /**
+ * Gets the list of items on the content view, as set by
+ * {@link PromptVerticalListContentView.Builder#addListItem(PromptContentItem)}.
+ *
+ * @return The item list on the content view.
+ */
+ @NonNull
+ public List<PromptContentItem> getListItems() {
+ return new ArrayList<>(mContentList);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void writeToParcel(@androidx.annotation.NonNull Parcel dest, int flags) {
+ dest.writeList(mContentList);
+ dest.writeCharSequence(mDescription);
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ @NonNull
+ public static final Creator<PromptVerticalListContentView> CREATOR = new Creator<>() {
+ @Override
+ public PromptVerticalListContentView createFromParcel(Parcel in) {
+ return new PromptVerticalListContentView(in);
+ }
+
+ @Override
+ public PromptVerticalListContentView[] newArray(int size) {
+ return new PromptVerticalListContentView[size];
+ }
+ };
+
+
+ /**
+ * A builder that collects arguments to be shown on the vertical list view.
+ */
+ public static final class Builder {
+ private final List<PromptContentItemParcelable> mContentList = new ArrayList<>();
+ private CharSequence mDescription;
+
+ /**
+ * Optional: Sets a description that will be shown on the content view.
+ *
+ * @param description The description to display.
+ * @return This builder.
+ */
+ @NonNull
+ public Builder setDescription(@NonNull CharSequence description) {
+ mDescription = description;
+ return this;
+ }
+
+ /**
+ * Optional: Adds a list item in the current row. Maximum {@value MAX_ITEM_NUMBER} items in
+ * total. The maximum length for each item is {@value MAX_EACH_ITEM_CHARACTER_NUMBER}
+ * characters.
+ *
+ * @param listItem The list item view to display
+ * @return This builder.
+ */
+ @NonNull
+ public Builder addListItem(@NonNull PromptContentItem listItem) {
+ if (doesListItemExceedsCharLimit(listItem)) {
+ throw new IllegalStateException(
+ "The character number of list item exceeds "
+ + MAX_EACH_ITEM_CHARACTER_NUMBER);
+ }
+ mContentList.add((PromptContentItemParcelable) listItem);
+ return this;
+ }
+
+
+ /**
+ * Optional: Adds a list item in the current row. Maximum {@value MAX_ITEM_NUMBER} items in
+ * total. The maximum length for each item is {@value MAX_EACH_ITEM_CHARACTER_NUMBER}
+ * characters.
+ *
+ * @param listItem The list item view to display
+ * @param index The position at which to add the item
+ * @return This builder.
+ */
+ @NonNull
+ public Builder addListItem(@NonNull PromptContentItem listItem, int index) {
+ if (doesListItemExceedsCharLimit(listItem)) {
+ throw new IllegalStateException(
+ "The character number of list item exceeds "
+ + MAX_EACH_ITEM_CHARACTER_NUMBER);
+ }
+ mContentList.add(index, (PromptContentItemParcelable) listItem);
+ return this;
+ }
+
+ private boolean doesListItemExceedsCharLimit(PromptContentItem listItem) {
+ if (listItem instanceof PromptContentItemPlainText) {
+ return ((PromptContentItemPlainText) listItem).getText().length()
+ > MAX_EACH_ITEM_CHARACTER_NUMBER;
+ } else if (listItem instanceof PromptContentItemBulletedText) {
+ return ((PromptContentItemBulletedText) listItem).getText().length()
+ > MAX_EACH_ITEM_CHARACTER_NUMBER;
+ } else {
+ return false;
+ }
+ }
+
+
+ /**
+ * Creates a {@link PromptVerticalListContentView}.
+ *
+ * @return An instance of {@link PromptVerticalListContentView}.
+ */
+ @NonNull
+ public PromptVerticalListContentView build() {
+ if (mContentList.size() > MAX_ITEM_NUMBER) {
+ throw new IllegalStateException(
+ "The number of list items exceeds " + MAX_ITEM_NUMBER);
+ }
+ return new PromptVerticalListContentView(mContentList, mDescription);
+ }
+ }
+}
diff --git a/core/java/android/hardware/biometrics/flags.aconfig b/core/java/android/hardware/biometrics/flags.aconfig
index 375fdb5..3ba8be4 100644
--- a/core/java/android/hardware/biometrics/flags.aconfig
+++ b/core/java/android/hardware/biometrics/flags.aconfig
@@ -21,3 +21,10 @@
bug: "307601768"
}
+flag {
+ name: "custom_biometric_prompt"
+ namespace: "biometrics_framework"
+ description: "Feature flag for adding a custom content view API to BiometricPrompt.Builder."
+ bug: "302735104"
+}
+
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 3affb73..0cd1c8c 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -79,6 +79,8 @@
import android.util.Range;
import android.util.Size;
+import com.android.internal.camera.flags.Flags;
+
import dalvik.annotation.optimization.FastNative;
import dalvik.system.VMRuntime;
@@ -1795,49 +1797,57 @@
return false;
}
- long[] tsArray = new long[samples.length];
- float[] intrinsicsArray = new float[samples.length * 5];
- for (int i = 0; i < samples.length; i++) {
- tsArray[i] = samples[i].getTimestamp();
- System.arraycopy(samples[i].getLensIntrinsics(), 0, intrinsicsArray, 5*i, 5);
+ if (Flags.concertMode()) {
+ long[] tsArray = new long[samples.length];
+ float[] intrinsicsArray = new float[samples.length * 5];
+ for (int i = 0; i < samples.length; i++) {
+ tsArray[i] = samples[i].getTimestampNanos();
+ System.arraycopy(samples[i].getLensIntrinsics(), 0, intrinsicsArray, 5 * i, 5);
+ }
+ setBase(CaptureResult.STATISTICS_LENS_INTRINSIC_SAMPLES, intrinsicsArray);
+ setBase(CaptureResult.STATISTICS_LENS_INTRINSIC_TIMESTAMPS, tsArray);
+
+ return true;
+ } else {
+ return false;
}
- setBase(CaptureResult.STATISTICS_LENS_INTRINSIC_SAMPLES, intrinsicsArray);
- setBase(CaptureResult.STATISTICS_LENS_INTRINSIC_TIMESTAMPS, tsArray);
-
- return true;
}
private LensIntrinsicsSample[] getLensIntrinsicSamples() {
- long[] timestamps = getBase(CaptureResult.STATISTICS_LENS_INTRINSIC_TIMESTAMPS);
- float[] intrinsics = getBase(CaptureResult.STATISTICS_LENS_INTRINSIC_SAMPLES);
+ if (Flags.concertMode()) {
+ long[] timestamps = getBase(CaptureResult.STATISTICS_LENS_INTRINSIC_TIMESTAMPS);
+ float[] intrinsics = getBase(CaptureResult.STATISTICS_LENS_INTRINSIC_SAMPLES);
- if (timestamps == null) {
- if (intrinsics != null) {
- throw new AssertionError("timestamps is null but intrinsics is not");
+ if (timestamps == null) {
+ if (intrinsics != null) {
+ throw new AssertionError("timestamps is null but intrinsics is not");
+ }
+
+ return null;
}
+ if (intrinsics == null) {
+ throw new AssertionError("timestamps is not null but intrinsics is");
+ } else if ((intrinsics.length % 5) != 0) {
+ throw new AssertionError("intrinsics are not multiple of 5");
+ }
+
+ if ((intrinsics.length / 5) != timestamps.length) {
+ throw new AssertionError(String.format(
+ "timestamps has %d entries but intrinsics has %d", timestamps.length,
+ intrinsics.length / 5));
+ }
+
+ LensIntrinsicsSample[] samples = new LensIntrinsicsSample[timestamps.length];
+ for (int i = 0; i < timestamps.length; i++) {
+ float[] currentIntrinsic = Arrays.copyOfRange(intrinsics, 5 * i, 5 * i + 5);
+ samples[i] = new LensIntrinsicsSample(timestamps[i], currentIntrinsic);
+ }
+ return samples;
+ } else {
return null;
}
-
- if (intrinsics == null) {
- throw new AssertionError("timestamps is not null but intrinsics is");
- } else if((intrinsics.length % 5) != 0) {
- throw new AssertionError("intrinsics are not multiple of 5");
- }
-
- if ((intrinsics.length / 5) != timestamps.length) {
- throw new AssertionError(String.format(
- "timestamps has %d entries but intrinsics has %d", timestamps.length,
- intrinsics.length / 5));
- }
-
- LensIntrinsicsSample[] samples = new LensIntrinsicsSample[timestamps.length];
- for (int i = 0; i < timestamps.length; i++) {
- float[] currentIntrinsic = Arrays.copyOfRange(intrinsics, 5*i, 5*i + 5);
- samples[i] = new LensIntrinsicsSample(timestamps[i], currentIntrinsic);
- }
- return samples;
}
private Capability[] getExtendedSceneModeCapabilities() {
diff --git a/core/java/android/hardware/camera2/params/LensIntrinsicsSample.java b/core/java/android/hardware/camera2/params/LensIntrinsicsSample.java
index 575cbfa..9a4ec5c 100644
--- a/core/java/android/hardware/camera2/params/LensIntrinsicsSample.java
+++ b/core/java/android/hardware/camera2/params/LensIntrinsicsSample.java
@@ -37,16 +37,18 @@
* Create a new {@link LensIntrinsicsSample}.
*
* <p>{@link LensIntrinsicsSample} contains the timestamp and the
- * {@link CaptureResult#LENS_INTRINSIC_CALIBRATION} sample.
+ * {@link CaptureResult#LENS_INTRINSIC_CALIBRATION} sample.</p>
*
- * @param timestamp timestamp of the lens intrinsics sample.
- * @param lensIntrinsics the lens intrinsic calibration for the sample.
+ * @param timestampNs timestamp in nanoseconds of the lens intrinsics sample. This uses the
+ * same time basis as {@link CaptureResult#SENSOR_TIMESTAMP}.
+ * @param lensIntrinsics the lens {@link CaptureResult#LENS_INTRINSIC_CALIBRATION intrinsic}
+ * calibration for the sample.
*
* @throws IllegalArgumentException if lensIntrinsics length is different from 5
*/
@FlaggedApi(Flags.FLAG_CONCERT_MODE)
- public LensIntrinsicsSample(final long timestamp, @NonNull final float[] lensIntrinsics) {
- mTimestampNs = timestamp;
+ public LensIntrinsicsSample(final long timestampNs, @NonNull final float[] lensIntrinsics) {
+ mTimestampNs = timestampNs;
Preconditions.checkArgument(lensIntrinsics.length == 5);
mLensIntrinsics = lensIntrinsics;
}
@@ -54,18 +56,18 @@
/**
* Get the timestamp in nanoseconds.
*
- *<p>The timestamps are in the same timebase as and comparable to
+ *<p>The timestamps are in the same time basis as and comparable to
*{@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp}.</p>
*
* @return a long value (guaranteed to be finite)
*/
@FlaggedApi(Flags.FLAG_CONCERT_MODE)
- public long getTimestamp() {
+ public long getTimestampNanos() {
return mTimestampNs;
}
/**
- * Get the lens intrinsics calibration
+ * Get the lens {@link CaptureResult#LENS_INTRINSIC_CALIBRATION intrinsics} calibration
*
* @return a floating point value (guaranteed to be finite)
* @see CaptureResult#LENS_INTRINSIC_CALIBRATION
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 134a510..8f0e0c9 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -1831,15 +1831,6 @@
String KEY_POWER_THROTTLING_DATA = "power_throttling_data";
/**
- * Key for new power controller feature flag. If enabled new DisplayPowerController will
- * be used.
- * Read value via {@link android.provider.DeviceConfig#getBoolean(String, String, boolean)}
- * with {@link android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER} as the namespace.
- * @hide
- */
- String KEY_NEW_POWER_CONTROLLER = "use_newly_structured_display_power_controller";
-
- /**
* Key for normal brightness mode controller feature flag.
* It enables NormalBrightnessModeController.
* Read value via {@link android.provider.DeviceConfig#getBoolean(String, String, boolean)}
diff --git a/core/java/android/hardware/input/VirtualStylus.java b/core/java/android/hardware/input/VirtualStylus.java
new file mode 100644
index 0000000..c763f740
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualStylus.java
@@ -0,0 +1,80 @@
+/*
+ * 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 android.hardware.input;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.companion.virtual.IVirtualDevice;
+import android.companion.virtual.flags.Flags;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * A virtual stylus which can be used to inject input into the framework that represents a stylus
+ * on a remote device.
+ *
+ * This registers an {@link android.view.InputDevice} that is interpreted like a
+ * physically-connected device and dispatches received events to it.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_VIRTUAL_STYLUS)
+@SystemApi
+public class VirtualStylus extends VirtualInputDevice {
+ /** @hide */
+ public VirtualStylus(VirtualStylusConfig config, IVirtualDevice virtualDevice,
+ IBinder token) {
+ super(config, virtualDevice, token);
+ }
+
+ /**
+ * Sends a motion event to the system.
+ *
+ * @param event the event to send
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void sendMotionEvent(@NonNull VirtualStylusMotionEvent event) {
+ try {
+ if (!mVirtualDevice.sendStylusMotionEvent(mToken, event)) {
+ Log.w(TAG, "Failed to send motion event from virtual stylus "
+ + mConfig.getInputDeviceName());
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sends a button event to the system.
+ *
+ * @param event the event to send
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void sendButtonEvent(@NonNull VirtualStylusButtonEvent event) {
+ try {
+ if (!mVirtualDevice.sendStylusButtonEvent(mToken, event)) {
+ Log.w(TAG, "Failed to send button event from virtual stylus "
+ + mConfig.getInputDeviceName());
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl b/core/java/android/hardware/input/VirtualStylusButtonEvent.aidl
similarity index 72%
copy from core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
copy to core/java/android/hardware/input/VirtualStylusButtonEvent.aidl
index ce92b6d..7de32cc 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
+++ b/core/java/android/hardware/input/VirtualStylusButtonEvent.aidl
@@ -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.
@@ -13,10 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.companion.virtual.camera;
-/**
- * The configuration of a single virtual camera stream.
- * @hide
- */
-parcelable VirtualCameraStreamConfig;
\ No newline at end of file
+package android.hardware.input;
+
+parcelable VirtualStylusButtonEvent;
diff --git a/core/java/android/hardware/input/VirtualStylusButtonEvent.java b/core/java/android/hardware/input/VirtualStylusButtonEvent.java
new file mode 100644
index 0000000..97a4cd0
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualStylusButtonEvent.java
@@ -0,0 +1,215 @@
+/*
+ * 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 android.hardware.input;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.companion.virtual.flags.Flags;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.view.InputEvent;
+import android.view.MotionEvent;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * An event describing a stylus button click interaction originating from a remote device.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_VIRTUAL_STYLUS)
+@SystemApi
+public final class VirtualStylusButtonEvent implements Parcelable {
+ /** @hide */
+ public static final int ACTION_UNKNOWN = -1;
+ /** Action indicating the stylus button has been pressed. */
+ public static final int ACTION_BUTTON_PRESS = MotionEvent.ACTION_BUTTON_PRESS;
+ /** Action indicating the stylus button has been released. */
+ public static final int ACTION_BUTTON_RELEASE = MotionEvent.ACTION_BUTTON_RELEASE;
+ /** @hide */
+ @IntDef(prefix = {"ACTION_"}, value = {
+ ACTION_UNKNOWN,
+ ACTION_BUTTON_PRESS,
+ ACTION_BUTTON_RELEASE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Action {}
+
+ /** @hide */
+ public static final int BUTTON_UNKNOWN = -1;
+ /** Action indicating the stylus button involved in this event is primary. */
+ public static final int BUTTON_PRIMARY = MotionEvent.BUTTON_STYLUS_PRIMARY;
+ /** Action indicating the stylus button involved in this event is secondary. */
+ public static final int BUTTON_SECONDARY = MotionEvent.BUTTON_STYLUS_SECONDARY;
+ /** @hide */
+ @IntDef(prefix = {"BUTTON_"}, value = {
+ BUTTON_UNKNOWN,
+ BUTTON_PRIMARY,
+ BUTTON_SECONDARY,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Button {}
+
+ @Action
+ private final int mAction;
+ @Button
+ private final int mButtonCode;
+ private final long mEventTimeNanos;
+
+ private VirtualStylusButtonEvent(@Action int action, @Button int buttonCode,
+ long eventTimeNanos) {
+ mAction = action;
+ mButtonCode = buttonCode;
+ mEventTimeNanos = eventTimeNanos;
+ }
+
+ private VirtualStylusButtonEvent(@NonNull Parcel parcel) {
+ mAction = parcel.readInt();
+ mButtonCode = parcel.readInt();
+ mEventTimeNanos = parcel.readLong();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int parcelableFlags) {
+ parcel.writeInt(mAction);
+ parcel.writeInt(mButtonCode);
+ parcel.writeLong(mEventTimeNanos);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Returns the button code associated with this event.
+ */
+ @Button
+ public int getButtonCode() {
+ return mButtonCode;
+ }
+
+ /**
+ * Returns the action associated with this event.
+ */
+ @Action
+ public int getAction() {
+ return mAction;
+ }
+
+ /**
+ * Returns the time this event occurred, in the {@link SystemClock#uptimeMillis()} time base but
+ * with nanosecond (instead of millisecond) precision.
+ *
+ * @see InputEvent#getEventTime()
+ */
+ public long getEventTimeNanos() {
+ return mEventTimeNanos;
+ }
+
+ /**
+ * Builder for {@link VirtualStylusButtonEvent}.
+ */
+ @FlaggedApi(Flags.FLAG_VIRTUAL_STYLUS)
+ public static final class Builder {
+
+ @Action
+ private int mAction = ACTION_UNKNOWN;
+ @Button
+ private int mButtonCode = BUTTON_UNKNOWN;
+ private long mEventTimeNanos = 0L;
+
+ /**
+ * Creates a {@link VirtualStylusButtonEvent} object with the current builder configuration.
+ */
+ @NonNull
+ public VirtualStylusButtonEvent build() {
+ if (mAction == ACTION_UNKNOWN) {
+ throw new IllegalArgumentException(
+ "Cannot build stylus button event with unset action");
+ }
+ if (mButtonCode == BUTTON_UNKNOWN) {
+ throw new IllegalArgumentException(
+ "Cannot build stylus button event with unset button code");
+ }
+ return new VirtualStylusButtonEvent(mAction, mButtonCode, mEventTimeNanos);
+ }
+
+ /**
+ * Sets the button code of the event.
+ *
+ * @return this builder, to allow for chaining of calls
+ */
+ @NonNull
+ public Builder setButtonCode(@Button int buttonCode) {
+ if (buttonCode != BUTTON_PRIMARY && buttonCode != BUTTON_SECONDARY) {
+ throw new IllegalArgumentException(
+ "Unsupported stylus button code : " + buttonCode);
+ }
+ mButtonCode = buttonCode;
+ return this;
+ }
+
+ /**
+ * Sets the action of the event.
+ *
+ * @return this builder, to allow for chaining of calls
+ */
+ @NonNull
+ public Builder setAction(@Action int action) {
+ if (action != ACTION_BUTTON_PRESS && action != ACTION_BUTTON_RELEASE) {
+ throw new IllegalArgumentException("Unsupported stylus button action : " + action);
+ }
+ mAction = action;
+ return this;
+ }
+
+ /**
+ * Sets the time (in nanoseconds) when this specific event was generated. This may be
+ * obtained from {@link SystemClock#uptimeMillis()} (with nanosecond precision instead of
+ * millisecond), but can be different depending on the use case.
+ * This field is optional and can be omitted.
+ *
+ * @return this builder, to allow for chaining of calls
+ * @see InputEvent#getEventTime()
+ */
+ @NonNull
+ public Builder setEventTimeNanos(long eventTimeNanos) {
+ if (eventTimeNanos < 0L) {
+ throw new IllegalArgumentException("Event time cannot be negative");
+ }
+ this.mEventTimeNanos = eventTimeNanos;
+ return this;
+ }
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<VirtualStylusButtonEvent> CREATOR =
+ new Parcelable.Creator<>() {
+ public VirtualStylusButtonEvent createFromParcel(Parcel source) {
+ return new VirtualStylusButtonEvent(source);
+ }
+
+ public VirtualStylusButtonEvent[] newArray(int size) {
+ return new VirtualStylusButtonEvent[size];
+ }
+ };
+}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl b/core/java/android/hardware/input/VirtualStylusConfig.aidl
similarity index 72%
copy from core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
copy to core/java/android/hardware/input/VirtualStylusConfig.aidl
index ce92b6d..a13eec2 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
+++ b/core/java/android/hardware/input/VirtualStylusConfig.aidl
@@ -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.
@@ -13,10 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.companion.virtual.camera;
-/**
- * The configuration of a single virtual camera stream.
- * @hide
- */
-parcelable VirtualCameraStreamConfig;
\ No newline at end of file
+package android.hardware.input;
+
+parcelable VirtualStylusConfig;
diff --git a/core/java/android/hardware/input/VirtualStylusConfig.java b/core/java/android/hardware/input/VirtualStylusConfig.java
new file mode 100644
index 0000000..64cf1f5
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualStylusConfig.java
@@ -0,0 +1,98 @@
+/*
+ * 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 android.hardware.input;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.companion.virtual.flags.Flags;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Configurations to create a virtual stylus.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_VIRTUAL_STYLUS)
+@SystemApi
+public final class VirtualStylusConfig extends VirtualTouchDeviceConfig implements Parcelable {
+
+ private VirtualStylusConfig(@NonNull Builder builder) {
+ super(builder);
+ }
+
+ private VirtualStylusConfig(@NonNull Parcel in) {
+ super(in);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ }
+
+ @NonNull
+ public static final Creator<VirtualStylusConfig> CREATOR =
+ new Creator<>() {
+ @Override
+ public VirtualStylusConfig createFromParcel(Parcel in) {
+ return new VirtualStylusConfig(in);
+ }
+
+ @Override
+ public VirtualStylusConfig[] newArray(int size) {
+ return new VirtualStylusConfig[size];
+ }
+ };
+
+ /**
+ * Builder for creating a {@link VirtualStylusConfig}.
+ */
+ @FlaggedApi(Flags.FLAG_VIRTUAL_STYLUS)
+ public static final class Builder extends VirtualTouchDeviceConfig.Builder<Builder> {
+
+ /**
+ * Creates a new instance for the given dimensions of the screen targeted by the
+ * {@link VirtualStylus}.
+ *
+ * <p>The dimensions are not pixels but in the screen's raw coordinate space. They do
+ * not necessarily have to correspond to the display size or aspect ratio. In this case the
+ * framework will handle the scaling appropriately.
+ *
+ * @param screenWidth The width of the targeted screen.
+ * @param screenHeight The height of the targeted screen.
+ */
+ public Builder(@IntRange(from = 1) int screenWidth,
+ @IntRange(from = 1) int screenHeight) {
+ super(screenWidth, screenHeight);
+ }
+
+ /**
+ * Builds the {@link VirtualStylusConfig} instance.
+ */
+ @NonNull
+ public VirtualStylusConfig build() {
+ return new VirtualStylusConfig(this);
+ }
+ }
+}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl b/core/java/android/hardware/input/VirtualStylusMotionEvent.aidl
similarity index 72%
copy from core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
copy to core/java/android/hardware/input/VirtualStylusMotionEvent.aidl
index ce92b6d..42d14ab 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
+++ b/core/java/android/hardware/input/VirtualStylusMotionEvent.aidl
@@ -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.
@@ -13,10 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.companion.virtual.camera;
-/**
- * The configuration of a single virtual camera stream.
- * @hide
- */
-parcelable VirtualCameraStreamConfig;
\ No newline at end of file
+package android.hardware.input;
+
+parcelable VirtualStylusMotionEvent;
diff --git a/core/java/android/hardware/input/VirtualStylusMotionEvent.java b/core/java/android/hardware/input/VirtualStylusMotionEvent.java
new file mode 100644
index 0000000..2ab76ae
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualStylusMotionEvent.java
@@ -0,0 +1,411 @@
+/*
+ * 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 android.hardware.input;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.companion.virtual.flags.Flags;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.view.InputEvent;
+import android.view.MotionEvent;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * An event describing a stylus interaction originating from a remote device.
+ *
+ * The tool type, location and action are required; tilts and pressure are optional.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_VIRTUAL_STYLUS)
+@SystemApi
+public final class VirtualStylusMotionEvent implements Parcelable {
+ private static final int TILT_MIN = -90;
+ private static final int TILT_MAX = 90;
+ private static final int PRESSURE_MIN = 0;
+ private static final int PRESSURE_MAX = 255;
+
+ /** @hide */
+ public static final int TOOL_TYPE_UNKNOWN = MotionEvent.TOOL_TYPE_UNKNOWN;
+ /** Tool type indicating that a stylus is the origin of the event. */
+ public static final int TOOL_TYPE_STYLUS = MotionEvent.TOOL_TYPE_STYLUS;
+ /** Tool type indicating that an eraser is the origin of the event. */
+ public static final int TOOL_TYPE_ERASER = MotionEvent.TOOL_TYPE_ERASER;
+ /** @hide */
+ @IntDef(prefix = { "TOOL_TYPE_" }, value = {
+ TOOL_TYPE_UNKNOWN,
+ TOOL_TYPE_STYLUS,
+ TOOL_TYPE_ERASER,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ToolType {}
+
+ /** @hide */
+ public static final int ACTION_UNKNOWN = -1;
+ /**
+ * Action indicating the stylus has been pressed down to the screen. ACTION_DOWN with pressure
+ * {@code 0} indicates that the stylus is hovering over the screen, and non-zero pressure
+ * indicates that the stylus is touching the screen.
+ */
+ public static final int ACTION_DOWN = MotionEvent.ACTION_DOWN;
+ /** Action indicating the stylus has been lifted from the screen. */
+ public static final int ACTION_UP = MotionEvent.ACTION_UP;
+ /** Action indicating the stylus has been moved along the screen. */
+ public static final int ACTION_MOVE = MotionEvent.ACTION_MOVE;
+ /** @hide */
+ @IntDef(prefix = { "ACTION_" }, value = {
+ ACTION_UNKNOWN,
+ ACTION_DOWN,
+ ACTION_UP,
+ ACTION_MOVE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Action {}
+
+ @ToolType
+ private final int mToolType;
+ @Action
+ private final int mAction;
+ private final int mX;
+ private final int mY;
+ private final int mPressure;
+ private final int mTiltX;
+ private final int mTiltY;
+ private final long mEventTimeNanos;
+
+ private VirtualStylusMotionEvent(@ToolType int toolType, @Action int action, int x, int y,
+ int pressure, int tiltX, int tiltY, long eventTimeNanos) {
+ mToolType = toolType;
+ mAction = action;
+ mX = x;
+ mY = y;
+ mPressure = pressure;
+ mTiltX = tiltX;
+ mTiltY = tiltY;
+ mEventTimeNanos = eventTimeNanos;
+ }
+
+ private VirtualStylusMotionEvent(@NonNull Parcel parcel) {
+ mToolType = parcel.readInt();
+ mAction = parcel.readInt();
+ mX = parcel.readInt();
+ mY = parcel.readInt();
+ mPressure = parcel.readInt();
+ mTiltX = parcel.readInt();
+ mTiltY = parcel.readInt();
+ mEventTimeNanos = parcel.readLong();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mToolType);
+ dest.writeInt(mAction);
+ dest.writeInt(mX);
+ dest.writeInt(mY);
+ dest.writeInt(mPressure);
+ dest.writeInt(mTiltX);
+ dest.writeInt(mTiltY);
+ dest.writeLong(mEventTimeNanos);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Returns the tool type associated with this event.
+ */
+ @ToolType
+ public int getToolType() {
+ return mToolType;
+ }
+
+ /**
+ * Returns the action associated with this event.
+ */
+ @Action
+ public int getAction() {
+ return mAction;
+ }
+
+ /**
+ * Returns the x-axis location associated with this event.
+ */
+ public int getX() {
+ return mX;
+ }
+
+ /**
+ * Returns the y-axis location associated with this event.
+ */
+ public int getY() {
+ return mY;
+ }
+
+ /**
+ * Returns the pressure associated with this event. {@code 0} pressure indicates that the stylus
+ * is hovering, otherwise the stylus is touching the screen. Returns {@code 255} if omitted.
+ */
+ public int getPressure() {
+ return mPressure;
+ }
+
+ /**
+ * Returns the plane angle (in degrees, in the range of [{@code -90}, {@code 90}]) between the
+ * y-z plane and the plane containing both the stylus axis and the y axis. A positive tiltX is
+ * to the right, in the direction of increasing x values. {@code 0} tilt indicates that the
+ * stylus is perpendicular to the x-axis. Returns {@code 0} if omitted.
+ *
+ * @see Builder#setTiltX
+ */
+ public int getTiltX() {
+ return mTiltX;
+ }
+
+ /**
+ * Returns the plane angle (in degrees, in the range of [{@code -90}, {@code 90}]) between the
+ * x-z plane and the plane containing both the stylus axis and the x axis. A positive tiltY is
+ * towards the user, in the direction of increasing y values. {@code 0} tilt indicates that the
+ * stylus is perpendicular to the y-axis. Returns {@code 0} if omitted.
+ *
+ * @see Builder#setTiltY
+ */
+ public int getTiltY() {
+ return mTiltY;
+ }
+
+ /**
+ * Returns the time this event occurred, in the {@link SystemClock#uptimeMillis()} time base but
+ * with nanosecond (instead of millisecond) precision.
+ *
+ * @see InputEvent#getEventTime()
+ */
+ public long getEventTimeNanos() {
+ return mEventTimeNanos;
+ }
+
+ /**
+ * Builder for {@link VirtualStylusMotionEvent}.
+ */
+ @FlaggedApi(Flags.FLAG_VIRTUAL_STYLUS)
+ public static final class Builder {
+
+ @ToolType
+ private int mToolType = TOOL_TYPE_UNKNOWN;
+ @Action
+ private int mAction = ACTION_UNKNOWN;
+ private int mX = 0;
+ private int mY = 0;
+ private boolean mIsXSet = false;
+ private boolean mIsYSet = false;
+ private int mPressure = PRESSURE_MAX;
+ private int mTiltX = 0;
+ private int mTiltY = 0;
+ private long mEventTimeNanos = 0L;
+
+ /**
+ * Creates a {@link VirtualStylusMotionEvent} object with the current builder configuration.
+ *
+ * @throws IllegalArgumentException if one of the required arguments (action, tool type,
+ * x-axis location and y-axis location) is missing.
+ * {@link VirtualStylusMotionEvent} for a detailed explanation.
+ */
+ @NonNull
+ public VirtualStylusMotionEvent build() {
+ if (mToolType == TOOL_TYPE_UNKNOWN) {
+ throw new IllegalArgumentException(
+ "Cannot build stylus motion event with unset tool type");
+ }
+ if (mAction == ACTION_UNKNOWN) {
+ throw new IllegalArgumentException(
+ "Cannot build stylus motion event with unset action");
+ }
+ if (!mIsXSet) {
+ throw new IllegalArgumentException(
+ "Cannot build stylus motion event with unset x-axis location");
+ }
+ if (!mIsYSet) {
+ throw new IllegalArgumentException(
+ "Cannot build stylus motion event with unset y-axis location");
+ }
+ return new VirtualStylusMotionEvent(mToolType, mAction, mX, mY, mPressure, mTiltX,
+ mTiltY, mEventTimeNanos);
+ }
+
+ /**
+ * Sets the tool type of the event.
+ *
+ * @return this builder, to allow for chaining of calls
+ */
+ @NonNull
+ public Builder setToolType(@ToolType int toolType) {
+ if (toolType != TOOL_TYPE_STYLUS && toolType != TOOL_TYPE_ERASER) {
+ throw new IllegalArgumentException("Unsupported stylus tool type: " + toolType);
+ }
+ mToolType = toolType;
+ return this;
+ }
+
+ /**
+ * Sets the action of the event.
+ *
+ * @return this builder, to allow for chaining of calls
+ */
+ @NonNull
+ public Builder setAction(@Action int action) {
+ if (action != ACTION_DOWN && action != ACTION_UP && action != ACTION_MOVE) {
+ throw new IllegalArgumentException("Unsupported stylus action : " + action);
+ }
+ mAction = action;
+ return this;
+ }
+
+ /**
+ * Sets the x-axis location of the event.
+ *
+ * @return this builder, to allow for chaining of calls
+ */
+ @NonNull
+ public Builder setX(int absX) {
+ mX = absX;
+ mIsXSet = true;
+ return this;
+ }
+
+ /**
+ * Sets the y-axis location of the event.
+ *
+ * @return this builder, to allow for chaining of calls
+ */
+ @NonNull
+ public Builder setY(int absY) {
+ mY = absY;
+ mIsYSet = true;
+ return this;
+ }
+
+ /**
+ * Sets the pressure of the event. {@code 0} pressure indicates that the stylus is hovering,
+ * otherwise the stylus is touching the screen. This field is optional and can be omitted
+ * (defaults to {@code 255}).
+ *
+ * @param pressure The pressure of the stylus.
+ *
+ * @throws IllegalArgumentException if the pressure is smaller than 0 or greater than 255.
+ *
+ * @return this builder, to allow for chaining of calls
+ */
+ @NonNull
+ public Builder setPressure(
+ @IntRange(from = PRESSURE_MIN, to = PRESSURE_MAX) int pressure) {
+ if (pressure < PRESSURE_MIN || pressure > PRESSURE_MAX) {
+ throw new IllegalArgumentException(
+ "Pressure should be between " + PRESSURE_MIN + " and " + PRESSURE_MAX);
+ }
+ mPressure = pressure;
+ return this;
+ }
+
+ /**
+ * Sets the x-axis tilt of the event in degrees. {@code 0} tilt indicates that the stylus is
+ * perpendicular to the x-axis. This field is optional and can be omitted (defaults to
+ * {@code 0}). Both x-axis tilt and y-axis tilt are used to derive the tilt and orientation
+ * of the stylus, given by {@link MotionEvent#AXIS_TILT} and
+ * {@link MotionEvent#AXIS_ORIENTATION} respectively.
+ *
+ * @throws IllegalArgumentException if the tilt is smaller than -90 or greater than 90.
+ *
+ * @return this builder, to allow for chaining of calls
+ *
+ * @see VirtualStylusMotionEvent#getTiltX
+ * @see <a href="https://source.android.com/docs/core/interaction/input/touch-devices#orientation-and-tilt-fields">
+ * Stylus tilt and orientation</a>
+ */
+ @NonNull
+ public Builder setTiltX(@IntRange(from = TILT_MIN, to = TILT_MAX) int tiltX) {
+ validateTilt(tiltX);
+ mTiltX = tiltX;
+ return this;
+ }
+
+ /**
+ * Sets the y-axis tilt of the event in degrees. {@code 0} tilt indicates that the stylus is
+ * perpendicular to the y-axis. This field is optional and can be omitted (defaults to
+ * {@code 0}). Both x-axis tilt and y-axis tilt are used to derive the tilt and orientation
+ * of the stylus, given by {@link MotionEvent#AXIS_TILT} and
+ * {@link MotionEvent#AXIS_ORIENTATION} respectively.
+ *
+ * @throws IllegalArgumentException if the tilt is smaller than -90 or greater than 90.
+ *
+ * @return this builder, to allow for chaining of calls
+ *
+ * @see VirtualStylusMotionEvent#getTiltY
+ * @see <a href="https://source.android.com/docs/core/interaction/input/touch-devices#orientation-and-tilt-fields">
+ * Stylus tilt and orientation</a>
+ */
+ @NonNull
+ public Builder setTiltY(@IntRange(from = TILT_MIN, to = TILT_MAX) int tiltY) {
+ validateTilt(tiltY);
+ mTiltY = tiltY;
+ return this;
+ }
+
+ /**
+ * Sets the time (in nanoseconds) when this specific event was generated. This may be
+ * obtained from {@link SystemClock#uptimeMillis()} (with nanosecond precision instead of
+ * millisecond), but can be different depending on the use case.
+ * This field is optional and can be omitted.
+ *
+ * @return this builder, to allow for chaining of calls
+ * @see InputEvent#getEventTime()
+ */
+ @NonNull
+ public Builder setEventTimeNanos(long eventTimeNanos) {
+ if (eventTimeNanos < 0L) {
+ throw new IllegalArgumentException("Event time cannot be negative");
+ }
+ mEventTimeNanos = eventTimeNanos;
+ return this;
+ }
+
+ private void validateTilt(int tilt) {
+ if (tilt < TILT_MIN || tilt > TILT_MAX) {
+ throw new IllegalArgumentException(
+ "Tilt must be between " + TILT_MIN + " and " + TILT_MAX);
+ }
+ }
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<VirtualStylusMotionEvent> CREATOR =
+ new Parcelable.Creator<>() {
+ public VirtualStylusMotionEvent createFromParcel(Parcel source) {
+ return new VirtualStylusMotionEvent(source);
+ }
+ public VirtualStylusMotionEvent[] newArray(int size) {
+ return new VirtualStylusMotionEvent[size];
+ }
+ };
+}
diff --git a/core/java/android/hardware/input/VirtualTouchDeviceConfig.java b/core/java/android/hardware/input/VirtualTouchDeviceConfig.java
new file mode 100644
index 0000000..2e2cfab
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualTouchDeviceConfig.java
@@ -0,0 +1,103 @@
+/*
+ * 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 android.hardware.input;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.os.Parcel;
+
+/**
+ * Configurations to create a virtual touch-based device.
+ *
+ * @hide
+ */
+abstract class VirtualTouchDeviceConfig extends VirtualInputDeviceConfig {
+
+ /** The touch device width. */
+ private final int mWidth;
+ /** The touch device height. */
+ private final int mHeight;
+
+ VirtualTouchDeviceConfig(@NonNull Builder<? extends Builder<?>> builder) {
+ super(builder);
+ mWidth = builder.mWidth;
+ mHeight = builder.mHeight;
+ }
+
+ VirtualTouchDeviceConfig(@NonNull Parcel in) {
+ super(in);
+ mWidth = in.readInt();
+ mHeight = in.readInt();
+ }
+
+ /** Returns the touch device width. */
+ public int getWidth() {
+ return mWidth;
+ }
+
+ /** Returns the touch device height. */
+ public int getHeight() {
+ return mHeight;
+ }
+
+ @Override
+ void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(mWidth);
+ dest.writeInt(mHeight);
+ }
+
+ @Override
+ @NonNull
+ String additionalFieldsToString() {
+ return " width=" + mWidth + " height=" + mHeight;
+ }
+
+ /**
+ * Builder for creating a {@link VirtualTouchDeviceConfig}.
+ *
+ * @param <T> The subclass to be built.
+ */
+ abstract static class Builder<T extends Builder<T>>
+ extends VirtualInputDeviceConfig.Builder<T> {
+
+ private final int mWidth;
+ private final int mHeight;
+
+ /**
+ * Creates a new instance for the given dimensions of the touch device.
+ *
+ * <p>The dimensions are not pixels but in the screen's raw coordinate space. They do
+ * not necessarily have to correspond to the display size or aspect ratio. In this case the
+ * framework will handle the scaling appropriately.
+ *
+ * @param touchDeviceWidth The width of the touch device.
+ * @param touchDeviceHeight The height of the touch device.
+ */
+ Builder(@IntRange(from = 1) int touchDeviceWidth,
+ @IntRange(from = 1) int touchDeviceHeight) {
+ if (touchDeviceHeight <= 0 || touchDeviceWidth <= 0) {
+ throw new IllegalArgumentException(
+ "Cannot create a virtual touch-based device, dimensions must be "
+ + "positive. Got: (" + touchDeviceHeight + ", "
+ + touchDeviceWidth + ")");
+ }
+ mHeight = touchDeviceHeight;
+ mWidth = touchDeviceWidth;
+ }
+ }
+}
diff --git a/core/java/android/hardware/input/VirtualTouchscreenConfig.java b/core/java/android/hardware/input/VirtualTouchscreenConfig.java
index 6308459..851cee6 100644
--- a/core/java/android/hardware/input/VirtualTouchscreenConfig.java
+++ b/core/java/android/hardware/input/VirtualTouchscreenConfig.java
@@ -23,38 +23,19 @@
import android.os.Parcelable;
/**
- * Configurations to create virtual touchscreen.
+ * Configurations to create a virtual touchscreen.
*
* @hide
*/
@SystemApi
-public final class VirtualTouchscreenConfig extends VirtualInputDeviceConfig implements Parcelable {
-
- /** The touchscreen width. */
- private final int mWidth;
- /** The touchscreen height. */
- private final int mHeight;
+public final class VirtualTouchscreenConfig extends VirtualTouchDeviceConfig implements Parcelable {
private VirtualTouchscreenConfig(@NonNull Builder builder) {
super(builder);
- mWidth = builder.mWidth;
- mHeight = builder.mHeight;
}
private VirtualTouchscreenConfig(@NonNull Parcel in) {
super(in);
- mWidth = in.readInt();
- mHeight = in.readInt();
- }
-
- /** Returns the touchscreen width. */
- public int getWidth() {
- return mWidth;
- }
-
- /** Returns the touchscreen height. */
- public int getHeight() {
- return mHeight;
}
@Override
@@ -65,19 +46,11 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
- dest.writeInt(mWidth);
- dest.writeInt(mHeight);
- }
-
- @Override
- @NonNull
- String additionalFieldsToString() {
- return " width=" + mWidth + " height=" + mHeight;
}
@NonNull
public static final Creator<VirtualTouchscreenConfig> CREATOR =
- new Creator<VirtualTouchscreenConfig>() {
+ new Creator<>() {
@Override
public VirtualTouchscreenConfig createFromParcel(Parcel in) {
return new VirtualTouchscreenConfig(in);
@@ -92,9 +65,7 @@
/**
* Builder for creating a {@link VirtualTouchscreenConfig}.
*/
- public static final class Builder extends VirtualInputDeviceConfig.Builder<Builder> {
- private int mWidth;
- private int mHeight;
+ public static final class Builder extends VirtualTouchDeviceConfig.Builder<Builder> {
/**
* Creates a new instance for the given dimensions of the {@link VirtualTouchscreen}.
@@ -108,14 +79,7 @@
*/
public Builder(@IntRange(from = 1) int touchscreenWidth,
@IntRange(from = 1) int touchscreenHeight) {
- if (touchscreenHeight <= 0 || touchscreenWidth <= 0) {
- throw new IllegalArgumentException(
- "Cannot create a virtual touchscreen, touchscreen dimensions must be "
- + "positive. Got: (" + touchscreenHeight + ", "
- + touchscreenWidth + ")");
- }
- mHeight = touchscreenHeight;
- mWidth = touchscreenWidth;
+ super(touchscreenWidth, touchscreenHeight);
}
/**
diff --git a/core/java/android/hardware/radio/TEST_MAPPING b/core/java/android/hardware/radio/TEST_MAPPING
new file mode 100644
index 0000000..ee4eeb6
--- /dev/null
+++ b/core/java/android/hardware/radio/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "imports": [
+ {
+ "path": "frameworks/base/core/tests/BroadcastRadioTests"
+ }
+ ]
+}
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
index 70cf973..83b7eda 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -80,8 +80,6 @@
* <p>The VCN will only migrate to a Carrier WiFi network that has a signal strength greater
* than, or equal to this threshold.
*
- * <p>WARNING: The VCN does not listen for changes to this key made after VCN startup.
- *
* @hide
*/
@NonNull
@@ -94,14 +92,39 @@
* <p>If the VCN's selected Carrier WiFi network has a signal strength less than this threshold,
* the VCN will attempt to migrate away from the Carrier WiFi network.
*
- * <p>WARNING: The VCN does not listen for changes to this key made after VCN startup.
- *
* @hide
*/
@NonNull
public static final String VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY =
"vcn_network_selection_wifi_exit_rssi_threshold";
+ /**
+ * Key for the interval to poll IpSecTransformState for packet loss monitoring
+ *
+ * @hide
+ */
+ @NonNull
+ public static final String VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY =
+ "vcn_network_selection_poll_ipsec_state_interval_seconds";
+
+ /**
+ * Key for the threshold of IPSec packet loss rate
+ *
+ * @hide
+ */
+ @NonNull
+ public static final String VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY =
+ "vcn_network_selection_ipsec_packet_loss_percent_threshold";
+
+ /**
+ * Key for the list of timeouts in minute to stop penalizing an underlying network candidate
+ *
+ * @hide
+ */
+ @NonNull
+ public static final String VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY =
+ "vcn_network_selection_penalty_timeout_minutes_list";
+
// TODO: Add separate signal strength thresholds for 2.4 GHz and 5GHz
/**
@@ -115,6 +138,20 @@
"vcn_restricted_transports";
/**
+ * Key for number of seconds to wait before entering safe mode
+ *
+ * <p>A VcnGatewayConnection will enter safe mode when it takes over the configured timeout to
+ * enter {@link ConnectedState}.
+ *
+ * <p>Defaults to 30, unless overridden by carrier config
+ *
+ * @hide
+ */
+ @NonNull
+ public static final String VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY =
+ "vcn_safe_mode_timeout_seconds_key";
+
+ /**
* Key for maximum number of parallel SAs for tunnel aggregation
*
* <p>If set to a value > 1, multiple tunnels will be set up, and inbound traffic will be
@@ -134,7 +171,11 @@
new String[] {
VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY,
VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY,
+ VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY,
+ VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY,
+ VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY,
VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY,
+ VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY,
VCN_TUNNEL_AGGREGATION_SA_COUNT_MAX_KEY,
};
diff --git a/core/java/android/net/vcn/flags.aconfig b/core/java/android/net/vcn/flags.aconfig
index 6956916..7afd721 100644
--- a/core/java/android/net/vcn/flags.aconfig
+++ b/core/java/android/net/vcn/flags.aconfig
@@ -5,4 +5,18 @@
namespace: "vcn"
description: "Feature flag for safe mode configurability"
bug: "276358140"
+}
+
+flag {
+ name: "safe_mode_timeout_config"
+ namespace: "vcn"
+ description: "Feature flag for adjustable safe mode timeout"
+ bug: "317406085"
+}
+
+flag{
+ name: "network_metric_monitor"
+ namespace: "vcn"
+ description: "Feature flag for enabling network metric monitor"
+ bug: "282996138"
}
\ No newline at end of file
diff --git a/core/java/android/nfc/OWNERS b/core/java/android/nfc/OWNERS
deleted file mode 100644
index 35e9713..0000000
--- a/core/java/android/nfc/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# Bug component: 48448
-include platform/packages/apps/Nfc:/OWNERS
diff --git a/core/java/android/nfc/TEST_MAPPING b/core/java/android/nfc/TEST_MAPPING
deleted file mode 100644
index 5b5ea37..0000000
--- a/core/java/android/nfc/TEST_MAPPING
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "presubmit": [
- {
- "name": "NfcManagerTests"
- },
- {
- "name": "CtsNfcTestCases"
- }
- ]
-}
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index 25fba60..b9bb059 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -17,9 +17,11 @@
package android.os;
import static android.os.Flags.FLAG_STATE_OF_HEALTH_PUBLIC;
+import static android.os.Flags.FLAG_BATTERY_PART_STATUS_API;
import android.Manifest.permission;
import android.annotation.FlaggedApi;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
@@ -236,6 +238,31 @@
public static final int CHARGING_POLICY_ADAPTIVE_LONGLIFE =
OsProtoEnums.CHARGING_POLICY_ADAPTIVE_LONGLIFE; // = 4
+ // values for "battery part status" property
+ /**
+ * Battery part status is not supported.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public static final int PART_STATUS_UNSUPPORTED = 0;
+
+ /**
+ * Battery is the original device battery.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public static final int PART_STATUS_ORIGINAL = 1;
+
+ /**
+ * Battery has been replaced.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public static final int PART_STATUS_REPLACED = 2;
+
/** @hide */
@SuppressLint("UnflaggedApi") // TestApi without associated feature.
@TestApi
@@ -366,6 +393,32 @@
@FlaggedApi(FLAG_STATE_OF_HEALTH_PUBLIC)
public static final int BATTERY_PROPERTY_STATE_OF_HEALTH = 10;
+ /**
+ * Battery part serial number.
+ *
+ * <p class="note">
+ * The sender must hold the {@link android.Manifest.permission#BATTERY_STATS} permission.
+ *
+ * @hide
+ */
+ @RequiresPermission(permission.BATTERY_STATS)
+ @SystemApi
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public static final int BATTERY_PROPERTY_SERIAL_NUMBER = 11;
+
+ /**
+ * Battery part status from a BATTERY_PART_STATUS_* value.
+ *
+ * <p class="note">
+ * The sender must hold the {@link android.Manifest.permission#BATTERY_STATS} permission.
+ *
+ * @hide
+ */
+ @RequiresPermission(permission.BATTERY_STATS)
+ @SystemApi
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public static final int BATTERY_PROPERTY_PART_STATUS = 12;
+
private final Context mContext;
private final IBatteryStats mBatteryStats;
private final IBatteryPropertiesRegistrar mBatteryPropertiesRegistrar;
@@ -431,6 +484,25 @@
}
/**
+ * Same as queryProperty, but for strings.
+ */
+ private String queryStringProperty(int id) {
+ if (mBatteryPropertiesRegistrar == null) {
+ return null;
+ }
+
+ try {
+ BatteryProperty prop = new BatteryProperty();
+ if (mBatteryPropertiesRegistrar.getProperty(id, prop) == 0) {
+ return prop.getString();
+ }
+ return null;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Return the value of a battery property of integer type.
*
* @param id identifier of the requested property
@@ -464,6 +536,21 @@
}
/**
+ * Return the value of a battery property of String type. If the
+ * platform does not provide the property queried, this value will
+ * be null.
+ *
+ * @param id identifier of the requested property.
+ *
+ * @return the property value, or null if not supported.
+ */
+ @Nullable
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public String getStringProperty(int id) {
+ return queryStringProperty(id);
+ }
+
+ /**
* Return true if the plugType given is wired
* @param plugType {@link #BATTERY_PLUGGED_AC}, {@link #BATTERY_PLUGGED_USB},
* or {@link #BATTERY_PLUGGED_WIRELESS}
diff --git a/core/java/android/os/BatteryProperty.java b/core/java/android/os/BatteryProperty.java
index b40988a..464577f 100644
--- a/core/java/android/os/BatteryProperty.java
+++ b/core/java/android/os/BatteryProperty.java
@@ -28,12 +28,14 @@
*/
public class BatteryProperty implements Parcelable {
private long mValueLong;
+ private String mValueString;
/**
* @hide
*/
public BatteryProperty() {
mValueLong = Long.MIN_VALUE;
+ mValueString = null;
}
/**
@@ -46,14 +48,23 @@
/**
* @hide
*/
+ public String getString() {
+ return mValueString;
+ }
+
+ /**
+ * @hide
+ */
public void setLong(long val) {
mValueLong = val;
}
- /*
- * Parcel read/write code must be kept in sync with
- * frameworks/native/services/batteryservice/BatteryProperty.cpp
+ /**
+ * @hide
*/
+ public void setString(String val) {
+ mValueString = val;
+ }
private BatteryProperty(Parcel p) {
readFromParcel(p);
@@ -61,10 +72,12 @@
public void readFromParcel(Parcel p) {
mValueLong = p.readLong();
+ mValueString = p.readString8();
}
public void writeToParcel(Parcel p, int flags) {
p.writeLong(mValueLong);
+ p.writeString8(mValueString);
}
public static final @android.annotation.NonNull Parcelable.Creator<BatteryProperty> CREATOR
diff --git a/core/java/android/os/BugreportParams.java b/core/java/android/os/BugreportParams.java
index 8510084..f2ef185 100644
--- a/core/java/android/os/BugreportParams.java
+++ b/core/java/android/os/BugreportParams.java
@@ -21,6 +21,7 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.admin.flags.Flags;
+import android.compat.annotation.UnsupportedAppUsage;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -131,6 +132,7 @@
*/
@TestApi
@FlaggedApi(Flags.FLAG_ONBOARDING_BUGREPORT_V2_ENABLED)
+ @UnsupportedAppUsage
public static final int BUGREPORT_MODE_ONBOARDING = IDumpstate.BUGREPORT_MODE_ONBOARDING;
/**
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index c60f949..fbec518 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -57,6 +57,7 @@
@UnsupportedAppUsage
Message mMessages;
+ private Message mLast;
@UnsupportedAppUsage
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
@@ -66,6 +67,10 @@
// Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.
private boolean mBlocked;
+ // Tracks the number of async message. We use this in enqueueMessage() to avoid searching the
+ // queue for async messages when inserting a message at the tail.
+ private int mAsyncMessageCount;
+
// The next barrier token.
// Barriers are indicated by messages with a null target whose arg1 field carries the token.
@UnsupportedAppUsage
@@ -364,12 +369,21 @@
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
+ if (prevMsg.next == null) {
+ mLast = prevMsg;
+ }
} else {
mMessages = msg.next;
+ if (msg.next == null) {
+ mLast = null;
+ }
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
+ if (msg.isAsynchronous()) {
+ mAsyncMessageCount--;
+ }
return msg;
}
} else {
@@ -492,6 +506,14 @@
msg.when = when;
msg.arg1 = token;
+ if (Flags.messageQueueTailTracking() && mLast != null && mLast.when <= when) {
+ /* Message goes to tail of list */
+ mLast.next = msg;
+ mLast = msg;
+ msg.next = null;
+ return token;
+ }
+
Message prev = null;
Message p = mMessages;
if (when != 0) {
@@ -500,6 +522,12 @@
p = p.next;
}
}
+
+ if (p == null) {
+ /* We reached the tail of the list, or list is empty. */
+ mLast = msg;
+ }
+
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
@@ -540,9 +568,15 @@
final boolean needWake;
if (prev != null) {
prev.next = p.next;
+ if (prev.next == null) {
+ mLast = prev;
+ }
needWake = false;
} else {
mMessages = p.next;
+ if (mMessages == null) {
+ mLast = null;
+ }
needWake = mMessages == null || mMessages.target != null;
}
p.recycleUnchecked();
@@ -582,24 +616,77 @@
msg.next = p;
mMessages = msg;
needWake = mBlocked;
- } else {
- // Inserted within the middle of the queue. Usually we don't have to wake
- // up the event queue unless there is a barrier at the head of the queue
- // and the message is the earliest asynchronous message in the queue.
- needWake = mBlocked && p.target == null && msg.isAsynchronous();
- Message prev;
- for (;;) {
- prev = p;
- p = p.next;
- if (p == null || when < p.when) {
- break;
- }
- if (needWake && p.isAsynchronous()) {
- needWake = false;
- }
+ if (p == null) {
+ mLast = mMessages;
}
- msg.next = p; // invariant: p == prev.next
- prev.next = msg;
+ } else {
+ // Message is to be inserted at tail or middle of queue. Usually we don't have to
+ // wake up the event queue unless there is a barrier at the head of the queue and
+ // the message is the earliest asynchronous message in the queue.
+ //
+ // For readability, we split this portion of the function into two blocks based on
+ // whether tail tracking is enabled. This has a minor implication for the case
+ // where tail tracking is disabled. See the comment below.
+ if (Flags.messageQueueTailTracking()) {
+ needWake = mBlocked && p.target == null && msg.isAsynchronous()
+ && mAsyncMessageCount == 0;
+ if (when >= mLast.when) {
+ msg.next = null;
+ mLast.next = msg;
+ mLast = msg;
+ } else {
+ // Inserted within the middle of the queue.
+ Message prev;
+ for (;;) {
+ prev = p;
+ p = p.next;
+ if (p == null || when < p.when) {
+ break;
+ }
+ }
+ if (p == null) {
+ /* Inserting at tail of queue */
+ mLast = msg;
+ }
+ msg.next = p; // invariant: p == prev.next
+ prev.next = msg;
+ }
+ } else {
+ needWake = mBlocked && p.target == null && msg.isAsynchronous();
+ Message prev;
+ for (;;) {
+ prev = p;
+ p = p.next;
+ if (p == null || when < p.when) {
+ break;
+ }
+ if (needWake && p.isAsynchronous()) {
+ needWake = false;
+ }
+ }
+ msg.next = p; // invariant: p == prev.next
+ prev.next = msg;
+
+ /*
+ * If this block is executing then we have a build without tail tracking -
+ * specifically: Flags.messageQueueTailTracking() == false. This is determined
+ * at build time so the flag won't change on us during runtime.
+ *
+ * Since we don't want to pepper the code with extra checks, we only check
+ * for tail tracking when we might use mLast. Otherwise, we continue to update
+ * mLast as the tail of the list.
+ *
+ * In this case however we are not maintaining mLast correctly. Since we never
+ * use it, this is fine. However, we run the risk of leaking a reference.
+ * So set mLast to null in this case to avoid any Message leaks. The other
+ * sites will never use the value so we are safe against null pointer derefs.
+ */
+ mLast = null;
+ }
+ }
+
+ if (msg.isAsynchronous()) {
+ mAsyncMessageCount++;
}
// We can assume mPtr != 0 because mQuitting is false.
@@ -692,10 +779,17 @@
&& (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
+ if (p.isAsynchronous()) {
+ mAsyncMessageCount--;
+ }
p.recycleUnchecked();
p = n;
}
+ if (p == null) {
+ mLast = mMessages;
+ }
+
// Remove all messages after front.
while (p != null) {
Message n = p.next;
@@ -703,8 +797,14 @@
if (n.target == h && n.what == what
&& (object == null || n.obj == object)) {
Message nn = n.next;
+ if (n.isAsynchronous()) {
+ mAsyncMessageCount--;
+ }
n.recycleUnchecked();
p.next = nn;
+ if (p.next == null) {
+ mLast = p;
+ }
continue;
}
}
@@ -726,10 +826,17 @@
&& (object == null || object.equals(p.obj))) {
Message n = p.next;
mMessages = n;
+ if (p.isAsynchronous()) {
+ mAsyncMessageCount--;
+ }
p.recycleUnchecked();
p = n;
}
+ if (p == null) {
+ mLast = mMessages;
+ }
+
// Remove all messages after front.
while (p != null) {
Message n = p.next;
@@ -737,8 +844,14 @@
if (n.target == h && n.what == what
&& (object == null || object.equals(n.obj))) {
Message nn = n.next;
+ if (n.isAsynchronous()) {
+ mAsyncMessageCount--;
+ }
n.recycleUnchecked();
p.next = nn;
+ if (p.next == null) {
+ mLast = p;
+ }
continue;
}
}
@@ -760,10 +873,17 @@
&& (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
+ if (p.isAsynchronous()) {
+ mAsyncMessageCount--;
+ }
p.recycleUnchecked();
p = n;
}
+ if (p == null) {
+ mLast = mMessages;
+ }
+
// Remove all messages after front.
while (p != null) {
Message n = p.next;
@@ -771,8 +891,14 @@
if (n.target == h && n.callback == r
&& (object == null || n.obj == object)) {
Message nn = n.next;
+ if (n.isAsynchronous()) {
+ mAsyncMessageCount--;
+ }
n.recycleUnchecked();
p.next = nn;
+ if (p.next == null) {
+ mLast = p;
+ }
continue;
}
}
@@ -794,10 +920,17 @@
&& (object == null || object.equals(p.obj))) {
Message n = p.next;
mMessages = n;
+ if (p.isAsynchronous()) {
+ mAsyncMessageCount--;
+ }
p.recycleUnchecked();
p = n;
}
+ if (p == null) {
+ mLast = mMessages;
+ }
+
// Remove all messages after front.
while (p != null) {
Message n = p.next;
@@ -805,8 +938,14 @@
if (n.target == h && n.callback == r
&& (object == null || object.equals(n.obj))) {
Message nn = n.next;
+ if (n.isAsynchronous()) {
+ mAsyncMessageCount--;
+ }
n.recycleUnchecked();
p.next = nn;
+ if (p.next == null) {
+ mLast = p;
+ }
continue;
}
}
@@ -829,18 +968,31 @@
&& (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
+ if (p.isAsynchronous()) {
+ mAsyncMessageCount--;
+ }
p.recycleUnchecked();
p = n;
}
+ if (p == null) {
+ mLast = mMessages;
+ }
+
// Remove all messages after front.
while (p != null) {
Message n = p.next;
if (n != null) {
if (n.target == h && (object == null || n.obj == object)) {
Message nn = n.next;
+ if (n.isAsynchronous()) {
+ mAsyncMessageCount--;
+ }
n.recycleUnchecked();
p.next = nn;
+ if (p.next == null) {
+ mLast = p;
+ }
continue;
}
}
@@ -862,18 +1014,31 @@
&& (object == null || object.equals(p.obj))) {
Message n = p.next;
mMessages = n;
+ if (p.isAsynchronous()) {
+ mAsyncMessageCount--;
+ }
p.recycleUnchecked();
p = n;
}
+ if (p == null) {
+ mLast = mMessages;
+ }
+
// Remove all messages after front.
while (p != null) {
Message n = p.next;
if (n != null) {
if (n.target == h && (object == null || object.equals(n.obj))) {
Message nn = n.next;
+ if (n.isAsynchronous()) {
+ mAsyncMessageCount--;
+ }
n.recycleUnchecked();
p.next = nn;
+ if (p.next == null) {
+ mLast = p;
+ }
continue;
}
}
@@ -890,6 +1055,8 @@
p = n;
}
mMessages = null;
+ mLast = null;
+ mAsyncMessageCount = 0;
}
private void removeAllFutureMessagesLocked() {
@@ -911,9 +1078,14 @@
p = n;
}
p.next = null;
+ mLast = p;
+
do {
p = n;
n = p.next;
+ if (p.isAsynchronous()) {
+ mAsyncMessageCount--;
+ }
p.recycleUnchecked();
} while (n != null);
}
diff --git a/core/java/android/os/PerformanceHintManager.java b/core/java/android/os/PerformanceHintManager.java
index 37bde3d..e6bfcd7 100644
--- a/core/java/android/os/PerformanceHintManager.java
+++ b/core/java/android/os/PerformanceHintManager.java
@@ -183,13 +183,14 @@
/** @hide */
@Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = {"CPU_LOAD_"}, value = {
+ @IntDef(prefix = {"CPU_LOAD_", "GPU_LOAD_"}, value = {
CPU_LOAD_UP,
CPU_LOAD_DOWN,
CPU_LOAD_RESET,
CPU_LOAD_RESUME,
GPU_LOAD_UP,
- GPU_LOAD_DOWN
+ GPU_LOAD_DOWN,
+ GPU_LOAD_RESET
})
public @interface Hint {}
@@ -204,7 +205,7 @@
}
/**
- * Updates this session's target duration for each cycle of work.
+ * Updates this session's target total duration for each cycle of work.
*
* @param targetDurationNanos the new desired duration in nanoseconds
*/
diff --git a/core/java/android/os/PowerMonitorReadings.java b/core/java/android/os/PowerMonitorReadings.java
index bb677d5..a0ab066 100644
--- a/core/java/android/os/PowerMonitorReadings.java
+++ b/core/java/android/os/PowerMonitorReadings.java
@@ -71,7 +71,7 @@
*/
@FlaggedApi("com.android.server.power.optimization.power_monitor_api")
@ElapsedRealtimeLong
- public long getTimestamp(@NonNull PowerMonitor powerMonitor) {
+ public long getTimestampMillis(@NonNull PowerMonitor powerMonitor) {
int offset = Arrays.binarySearch(mPowerMonitors, powerMonitor, POWER_MONITOR_COMPARATOR);
if (offset >= 0) {
return mTimestampsMs[offset];
diff --git a/core/java/android/os/WorkDuration.java b/core/java/android/os/WorkDuration.java
index 4fdc34f..2ebcd83 100644
--- a/core/java/android/os/WorkDuration.java
+++ b/core/java/android/os/WorkDuration.java
@@ -26,7 +26,7 @@
* in each component, see
* {@link PerformanceHintManager.Session#reportActualWorkDuration(WorkDuration)}.
*
- * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+ * All timings should be in {@link SystemClock#uptimeNanos()} and measured in wall time.
*/
@FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION)
public final class WorkDuration implements Parcelable {
@@ -50,17 +50,9 @@
public WorkDuration() {}
- public WorkDuration(long workPeriodStartTimestampNanos,
- long actualTotalDurationNanos,
- long actualCpuDurationNanos,
- long actualGpuDurationNanos) {
- mWorkPeriodStartTimestampNanos = workPeriodStartTimestampNanos;
- mActualTotalDurationNanos = actualTotalDurationNanos;
- mActualCpuDurationNanos = actualCpuDurationNanos;
- mActualGpuDurationNanos = actualGpuDurationNanos;
- }
-
/**
+ * Constructor for testing.
+ *
* @hide
*/
public WorkDuration(long workPeriodStartTimestampNanos,
@@ -86,7 +78,7 @@
/**
* Sets the work period start timestamp in nanoseconds.
*
- * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+ * All timings should be in {@link SystemClock#uptimeNanos()}.
*/
public void setWorkPeriodStartTimestampNanos(long workPeriodStartTimestampNanos) {
if (workPeriodStartTimestampNanos <= 0) {
@@ -99,7 +91,7 @@
/**
* Sets the actual total duration in nanoseconds.
*
- * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+ * All timings should be in {@link SystemClock#uptimeNanos()}.
*/
public void setActualTotalDurationNanos(long actualTotalDurationNanos) {
if (actualTotalDurationNanos <= 0) {
@@ -111,7 +103,7 @@
/**
* Sets the actual CPU duration in nanoseconds.
*
- * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+ * All timings should be in {@link SystemClock#uptimeNanos()}.
*/
public void setActualCpuDurationNanos(long actualCpuDurationNanos) {
if (actualCpuDurationNanos <= 0) {
@@ -123,7 +115,7 @@
/**
* Sets the actual GPU duration in nanoseconds.
*
- * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+ * All timings should be in {@link SystemClock#uptimeNanos()}.
*/
public void setActualGpuDurationNanos(long actualGpuDurationNanos) {
if (actualGpuDurationNanos < 0) {
@@ -135,7 +127,7 @@
/**
* Returns the work period start timestamp based in nanoseconds.
*
- * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+ * All timings should be in {@link SystemClock#uptimeNanos()}.
*/
public long getWorkPeriodStartTimestampNanos() {
return mWorkPeriodStartTimestampNanos;
@@ -144,7 +136,7 @@
/**
* Returns the actual total duration in nanoseconds.
*
- * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+ * All timings should be in {@link SystemClock#uptimeNanos()}.
*/
public long getActualTotalDurationNanos() {
return mActualTotalDurationNanos;
@@ -153,7 +145,7 @@
/**
* Returns the actual CPU duration in nanoseconds.
*
- * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+ * All timings should be in {@link SystemClock#uptimeNanos()}.
*/
public long getActualCpuDurationNanos() {
return mActualCpuDurationNanos;
@@ -162,7 +154,7 @@
/**
* Returns the actual GPU duration in nanoseconds.
*
- * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+ * All timings should be in {@link SystemClock#uptimeNanos()}.
*/
public long getActualGpuDurationNanos() {
return mActualGpuDurationNanos;
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index d6461462..82518bf 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -98,3 +98,19 @@
description: "Guards StrictMode APIs for detecting restricted network access."
bug: "317250784"
}
+
+flag {
+ name: "message_queue_tail_tracking"
+ namespace: "system_performance"
+ description: "track tail of message queue."
+ bug: "305311707"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "battery_part_status_api"
+ namespace: "phoenix"
+ description: "Feature flag for adding Health HAL v3 APIs."
+ is_fixed_read_only: true
+ bug: "309792384"
+}
diff --git a/core/java/android/os/health/SystemHealthManager.java b/core/java/android/os/health/SystemHealthManager.java
index 2d53341..322a8e6 100644
--- a/core/java/android/os/health/SystemHealthManager.java
+++ b/core/java/android/os/health/SystemHealthManager.java
@@ -25,8 +25,8 @@
import android.os.BatteryStats;
import android.os.Build;
import android.os.Bundle;
-import android.os.Handler;
import android.os.IPowerStatsService;
+import android.os.OutcomeReceiver;
import android.os.PowerMonitor;
import android.os.PowerMonitorReadings;
import android.os.Process;
@@ -39,6 +39,7 @@
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
+import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -161,12 +162,12 @@
* (on-device power rail monitor) rails and modeled energy consumers. If ODPM is unsupported
* on this device this method delivers an empty list.
*
- * @param handler optional Handler to deliver the callback. If not supplied, the callback
+ * @param executor optional Handler to deliver the callback. If not supplied, the callback
* may be invoked on an arbitrary thread.
* @param onResult callback for the result
*/
@FlaggedApi("com.android.server.power.optimization.power_monitor_api")
- public void getSupportedPowerMonitors(@Nullable Handler handler,
+ public void getSupportedPowerMonitors(@Nullable Executor executor,
@NonNull Consumer<List<PowerMonitor>> onResult) {
final List<PowerMonitor> result;
synchronized (mPowerMonitorsLock) {
@@ -180,15 +181,15 @@
}
}
if (result != null) {
- if (handler != null) {
- handler.post(() -> onResult.accept(result));
+ if (executor != null) {
+ executor.execute(() -> onResult.accept(result));
} else {
onResult.accept(result);
}
return;
}
try {
- mPowerStats.getSupportedPowerMonitors(new ResultReceiver(handler) {
+ mPowerStats.getSupportedPowerMonitors(new ResultReceiver(null) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
PowerMonitor[] array = resultData.getParcelableArray(
@@ -197,7 +198,11 @@
synchronized (mPowerMonitorsLock) {
mPowerMonitorsInfo = result;
}
- onResult.accept(result);
+ if (executor != null) {
+ executor.execute(()-> onResult.accept(result));
+ } else {
+ onResult.accept(result);
+ }
}
});
} catch (RemoteException e) {
@@ -213,17 +218,22 @@
* monitors.
*
* @param powerMonitors power monitors to be retrieved.
- * @param handler optional Handler to deliver the callbacks. If not supplied, the callback
- * may be invoked on an arbitrary thread.
- * @param onSuccess callback for the result
- * @param onError callback invoked in case of an error
+ * @param executor optional Executor to deliver the callbacks. If not supplied, the
+ * callback may be invoked on an arbitrary thread.
+ * @param onResult callback for the result
*/
@FlaggedApi("com.android.server.power.optimization.power_monitor_api")
public void getPowerMonitorReadings(@NonNull List<PowerMonitor> powerMonitors,
- @Nullable Handler handler, @NonNull Consumer<PowerMonitorReadings> onSuccess,
- @NonNull Consumer<RuntimeException> onError) {
+ @Nullable Executor executor,
+ @NonNull OutcomeReceiver<PowerMonitorReadings, RuntimeException> onResult) {
if (mPowerStats == null) {
- onError.accept(new IllegalArgumentException("Unsupported power monitor"));
+ IllegalArgumentException error =
+ new IllegalArgumentException("Unsupported power monitor");
+ if (executor != null) {
+ executor.execute(() -> onResult.onError(error));
+ } else {
+ onResult.onError(error);
+ }
return;
}
@@ -235,18 +245,31 @@
indices[i] = powerMonitorsArray[i].index;
}
try {
- mPowerStats.getPowerMonitorReadings(indices, new ResultReceiver(handler) {
+ mPowerStats.getPowerMonitorReadings(indices, new ResultReceiver(null) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
if (resultCode == IPowerStatsService.RESULT_SUCCESS) {
- onSuccess.accept(new PowerMonitorReadings(powerMonitorsArray,
+ PowerMonitorReadings result = new PowerMonitorReadings(powerMonitorsArray,
resultData.getLongArray(IPowerStatsService.KEY_ENERGY),
- resultData.getLongArray(IPowerStatsService.KEY_TIMESTAMPS)));
- } else if (resultCode == IPowerStatsService.RESULT_UNSUPPORTED_POWER_MONITOR) {
- onError.accept(new IllegalArgumentException("Unsupported power monitor"));
+ resultData.getLongArray(IPowerStatsService.KEY_TIMESTAMPS));
+ if (executor != null) {
+ executor.execute(() -> onResult.onResult(result));
+ } else {
+ onResult.onResult(result);
+ }
} else {
- onError.accept(new IllegalStateException(
- "Unrecognized result code " + resultCode));
+ RuntimeException error;
+ if (resultCode == IPowerStatsService.RESULT_UNSUPPORTED_POWER_MONITOR) {
+ error = new IllegalArgumentException("Unsupported power monitor");
+ } else {
+ error = new IllegalStateException(
+ "Unrecognized result code " + resultCode);
+ }
+ if (executor != null) {
+ executor.execute(() -> onResult.onError(error));
+ } else {
+ onResult.onError(error);
+ }
}
}
});
diff --git a/core/java/android/os/storage/StorageManagerInternal.java b/core/java/android/os/storage/StorageManagerInternal.java
index 8961846..6995ea8 100644
--- a/core/java/android/os/storage/StorageManagerInternal.java
+++ b/core/java/android/os/storage/StorageManagerInternal.java
@@ -193,7 +193,7 @@
* @see com.android.server.pm.Installer#createFsveritySetupAuthToken()
*/
public abstract IInstalld.IFsveritySetupAuthToken createFsveritySetupAuthToken(
- ParcelFileDescriptor authFd, int appUid, @UserIdInt int userId) throws IOException;
+ ParcelFileDescriptor authFd, int uid) throws IOException;
/**
* A proxy call to the corresponding method in Installer.
diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl
index 7cecfdc..471f95b 100644
--- a/core/java/android/permission/IPermissionManager.aidl
+++ b/core/java/android/permission/IPermissionManager.aidl
@@ -92,7 +92,7 @@
boolean isAutoRevokeExempted(String packageName, int userId);
- void registerAttributionSource(in AttributionSourceState source);
+ IBinder registerAttributionSource(in AttributionSourceState source);
boolean isRegisteredAttributionSource(in AttributionSourceState source);
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 91adc37..4af6e3a 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -23,6 +23,7 @@
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
import static android.os.Build.VERSION_CODES.S;
+import static android.permission.flags.Flags.serverSideAttributionRegistration;
import android.Manifest;
import android.annotation.CheckResult;
@@ -59,6 +60,7 @@
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
@@ -1464,13 +1466,19 @@
// We use a shared static token for sources that are not registered since the token's
// only used for process death detection. If we are about to use the source for security
// enforcement we need to replace the binder with a unique one.
- final AttributionSource registeredSource = source.withToken(new Binder());
try {
- mPermissionManager.registerAttributionSource(registeredSource.asState());
+ if (serverSideAttributionRegistration()) {
+ IBinder newToken = mPermissionManager.registerAttributionSource(source.asState());
+ return source.withToken(newToken);
+ } else {
+ AttributionSource registeredSource = source.withToken(new Binder());
+ mPermissionManager.registerAttributionSource(registeredSource.asState());
+ return registeredSource;
+ }
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
- return registeredSource;
+ return source;
}
/**
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 60143cc..39b6aeb 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -45,7 +45,8 @@
}
flag {
- name: "enhanced_confirmation_mode_apis"
+ name: "enhanced_confirmation_mode_apis_enabled"
+ is_fixed_read_only: true
namespace: "permissions"
description: "enable enhanced confirmation mode apis"
bug: "310220212"
@@ -73,6 +74,13 @@
}
flag {
+ name: "server_side_attribution_registration"
+ namespace: "permissions"
+ description: "controls whether the binder representing an AttributionSource is created in the system server, or client process"
+ bug: "310953959"
+}
+
+flag {
name: "wallet_role_enabled"
namespace: "wallet_integration"
description: "This flag is used to enabled the Wallet Role for all users on the device"
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 47065e1..ab98c94 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1931,9 +1931,7 @@
* A matching Activity will only be found if
* {@link NotificationManager#areAutomaticZenRulesUserManaged()} is true.
* <p>
- * Input: Intent's data URI set with an application name, using the "package" schema (like
- * "package:com.my.app").
- * Input: The id of the rule, provided in {@link #EXTRA_AUTOMATIC_ZEN_RULE_ID}.
+ * Input: The id of the rule, provided in the {@link #EXTRA_AUTOMATIC_ZEN_RULE_ID} extra.
* <p>
* Output: Nothing.
*/
@@ -3178,15 +3176,7 @@
}
public void destroy() {
- try {
- // If this process is the system server process, mArray is the same object as
- // the memory int array kept inside SettingsProvider, so skipping the close()
- if (!Settings.isInSystemServer() && !mArray.isClosed()) {
- mArray.close();
- }
- } catch (IOException e) {
- Log.e(TAG, "Error closing backing array", e);
- }
+ maybeCloseGenerationArray(mArray);
}
@Override
@@ -3199,6 +3189,21 @@
}
}
+ private static void maybeCloseGenerationArray(@Nullable MemoryIntArray array) {
+ if (array == null) {
+ return;
+ }
+ try {
+ // If this process is the system server process, the MemoryIntArray received from Parcel
+ // is the same object as the one kept inside SettingsProvider, so skipping the close().
+ if (!Settings.isInSystemServer() && !array.isClosed()) {
+ array.close();
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Error closing the generation tracking array", e);
+ }
+ }
+
private static final class ContentProviderHolder {
private final Object mLock = new Object();
@@ -3498,6 +3503,8 @@
mGenerationTrackers.put(name, new GenerationTracker(name,
array, index, generation,
mGenerationTrackerErrorHandler));
+ } else {
+ maybeCloseGenerationArray(array);
}
}
if (mGenerationTrackers.get(name) != null
@@ -3735,6 +3742,8 @@
new GenerationTracker(prefix, array, index, generation,
mGenerationTrackerErrorHandler));
currentGeneration = generation;
+ } else {
+ maybeCloseGenerationArray(array);
}
}
if (mGenerationTrackers.get(prefix) != null && currentGeneration
@@ -7312,6 +7321,28 @@
"bluetooth_le_broadcast_app_source_name";
/**
+ * This is used by LocalBluetoothLeBroadcast to downgrade the broadcast quality to improve
+ * compatibility.
+ *
+ * <ul>
+ * <li>0 = false
+ * <li>1 = true
+ * </ul>
+ *
+ * @hide
+ */
+ public static final String BLUETOOTH_LE_BROADCAST_IMPROVE_COMPATIBILITY =
+ "bluetooth_le_broadcast_improve_compatibility";
+
+ /**
+ * This is used by LocalBluetoothLeBroadcast to store the fallback active device address.
+ *
+ * @hide
+ */
+ public static final String BLUETOOTH_LE_BROADCAST_FALLBACK_ACTIVE_DEVICE_ADDRESS =
+ "bluetooth_le_broadcast_fallback_active_device_address";
+
+ /**
* Ringtone routing value for hearing aid. It routes ringtone to hearing aid or device
* speaker.
* <ul>
@@ -10131,7 +10162,9 @@
public static final int HUB_MODE_TUTORIAL_STARTED = 1;
/**
- * Indicates that the user has completed the hub mode tutorial.
+ * Any value greater than or equal to this value is considered that the user has
+ * completed the hub mode tutorial.
+ *
* One of the possible states for {@link #HUB_MODE_TUTORIAL_STATE}.
*
* @hide
@@ -10150,8 +10183,11 @@
/**
* Defines the user's current state of navigating through the hub mode tutorial.
- * The possible states are defined in {@link HubModeTutorialState}.
+ * Some possible states are defined in {@link HubModeTutorialState}.
*
+ * Any value greater than or equal to {@link HUB_MODE_TUTORIAL_COMPLETED} indicates that
+ * the user has completed that version of the hub mode tutorial. And tutorial may be
+ * shown again when a new version becomes available.
* @hide
*/
public static final String HUB_MODE_TUTORIAL_STATE = "hub_mode_tutorial_state";
@@ -13476,6 +13512,16 @@
@Readable
public static final String OTA_DISABLE_AUTOMATIC_UPDATE = "ota_disable_automatic_update";
+
+ /**
+ * Whether to boot with 16K page size compatible kernel
+ * 1 = Boot with 16K kernel
+ * 0 = Boot with 4K kernel (default)
+ * @hide
+ */
+ @Readable
+ public static final String ENABLE_16K_PAGES = "enable_16k_pages";
+
/** Timeout for package verification.
* @hide */
@Readable
diff --git a/core/java/android/provider/TEST_MAPPING b/core/java/android/provider/TEST_MAPPING
index 8b4a99e..d5ac7a7 100644
--- a/core/java/android/provider/TEST_MAPPING
+++ b/core/java/android/provider/TEST_MAPPING
@@ -28,5 +28,10 @@
}
]
}
+ ],
+ "postsubmit": [
+ {
+ "name": "CtsDeviceConfigTestCases"
+ }
]
}
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 30524a1..1994058 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -53,7 +53,7 @@
flag {
name: "frp_enforcement"
- namespace: "android_hw_security"
+ namespace: "hardware_backed_security"
description: "This flag controls whether PDB enforces FRP"
bug: "290312729"
is_fixed_read_only: true
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 7d9c0a3..2d657c2 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -445,6 +445,19 @@
}
/**
+ * Retrieves the current {@link android.app.Activity} associated with the dream.
+ * This method behaves similarly to calling {@link android.app.Activity#getActivity()}.
+ *
+ * @return The current activity, or null if the dream is not associated with an activity
+ * or not started.
+ *
+ * @hide
+ */
+ public Activity getActivity() {
+ return mActivity;
+ }
+
+ /**
* Inflates a layout resource and set it to be the content view for this Dream.
* Behaves similarly to {@link android.app.Activity#setContentView(int)}.
*
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 92c516c..7658af5 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -42,6 +42,7 @@
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
+import android.os.BadParcelableException;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -1056,7 +1057,7 @@
ParceledListSlice<StatusBarNotification> parceledList = getNotificationInterface()
.getActiveNotificationsFromListener(mWrapper, keys, trim);
return cleanUpNotificationList(parceledList);
- } catch (android.os.RemoteException ex) {
+ } catch (android.os.RemoteException | BadParcelableException ex) {
Log.v(TAG, "Unable to contact notification manager", ex);
}
return null;
diff --git a/core/java/android/service/notification/ZenDeviceEffects.java b/core/java/android/service/notification/ZenDeviceEffects.java
index 03ebae5..90049e6 100644
--- a/core/java/android/service/notification/ZenDeviceEffects.java
+++ b/core/java/android/service/notification/ZenDeviceEffects.java
@@ -20,7 +20,6 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.TestApi;
import android.app.Flags;
import android.os.Parcel;
import android.os.Parcelable;
@@ -37,8 +36,8 @@
@FlaggedApi(Flags.FLAG_MODES_API)
public final class ZenDeviceEffects implements Parcelable {
- /** Used to track which rule variables have been modified by the user.
- * Should be checked against the bitmask {@link #getUserModifiedFields()}.
+ /**
+ * Enum for the user-modifiable fields in this object.
* @hide
*/
@IntDef(flag = true, prefix = { "FIELD_" }, value = {
@@ -59,52 +58,42 @@
/**
* @hide
*/
- @TestApi
public static final int FIELD_GRAYSCALE = 1 << 0;
/**
* @hide
*/
- @TestApi
public static final int FIELD_SUPPRESS_AMBIENT_DISPLAY = 1 << 1;
/**
* @hide
*/
- @TestApi
public static final int FIELD_DIM_WALLPAPER = 1 << 2;
/**
* @hide
*/
- @TestApi
public static final int FIELD_NIGHT_MODE = 1 << 3;
/**
* @hide
*/
- @TestApi
public static final int FIELD_DISABLE_AUTO_BRIGHTNESS = 1 << 4;
/**
* @hide
*/
- @TestApi
public static final int FIELD_DISABLE_TAP_TO_WAKE = 1 << 5;
/**
* @hide
*/
- @TestApi
public static final int FIELD_DISABLE_TILT_TO_WAKE = 1 << 6;
/**
* @hide
*/
- @TestApi
public static final int FIELD_DISABLE_TOUCH = 1 << 7;
/**
* @hide
*/
- @TestApi
public static final int FIELD_MINIMIZE_RADIO_USAGE = 1 << 8;
/**
* @hide
*/
- @TestApi
public static final int FIELD_MAXIMIZE_DOZE = 1 << 9;
private final boolean mGrayscale;
@@ -119,13 +108,10 @@
private final boolean mMinimizeRadioUsage;
private final boolean mMaximizeDoze;
- private final @ModifiableField int mUserModifiedFields; // Bitwise representation
-
private ZenDeviceEffects(boolean grayscale, boolean suppressAmbientDisplay,
boolean dimWallpaper, boolean nightMode, boolean disableAutoBrightness,
boolean disableTapToWake, boolean disableTiltToWake, boolean disableTouch,
- boolean minimizeRadioUsage, boolean maximizeDoze,
- @ModifiableField int userModifiedFields) {
+ boolean minimizeRadioUsage, boolean maximizeDoze) {
mGrayscale = grayscale;
mSuppressAmbientDisplay = suppressAmbientDisplay;
mDimWallpaper = dimWallpaper;
@@ -136,7 +122,6 @@
mDisableTouch = disableTouch;
mMinimizeRadioUsage = minimizeRadioUsage;
mMaximizeDoze = maximizeDoze;
- mUserModifiedFields = userModifiedFields;
}
@Override
@@ -153,15 +138,14 @@
&& this.mDisableTiltToWake == that.mDisableTiltToWake
&& this.mDisableTouch == that.mDisableTouch
&& this.mMinimizeRadioUsage == that.mMinimizeRadioUsage
- && this.mMaximizeDoze == that.mMaximizeDoze
- && this.mUserModifiedFields == that.mUserModifiedFields;
+ && this.mMaximizeDoze == that.mMaximizeDoze;
}
@Override
public int hashCode() {
return Objects.hash(mGrayscale, mSuppressAmbientDisplay, mDimWallpaper, mNightMode,
mDisableAutoBrightness, mDisableTapToWake, mDisableTiltToWake, mDisableTouch,
- mMinimizeRadioUsage, mMaximizeDoze, mUserModifiedFields);
+ mMinimizeRadioUsage, mMaximizeDoze);
}
@Override
@@ -177,11 +161,11 @@
if (mDisableTouch) effects.add("disableTouch");
if (mMinimizeRadioUsage) effects.add("minimizeRadioUsage");
if (mMaximizeDoze) effects.add("maximizeDoze");
- return "[" + String.join(", ", effects) + "]"
- + " userModifiedFields: " + modifiedFieldsToString(mUserModifiedFields);
+ return "[" + String.join(", ", effects) + "]";
}
- private String modifiedFieldsToString(int bitmask) {
+ /** @hide */
+ public static String fieldsToString(@ModifiableField int bitmask) {
ArrayList<String> modified = new ArrayList<>();
if ((bitmask & FIELD_GRAYSCALE) != 0) {
modified.add("FIELD_GRAYSCALE");
@@ -312,7 +296,7 @@
return new ZenDeviceEffects(in.readBoolean(),
in.readBoolean(), in.readBoolean(), in.readBoolean(), in.readBoolean(),
in.readBoolean(), in.readBoolean(), in.readBoolean(), in.readBoolean(),
- in.readBoolean(), in.readInt());
+ in.readBoolean());
}
@Override
@@ -321,16 +305,6 @@
}
};
- /**
- * Gets the bitmask representing which fields are user modified. Bits are set using
- * {@link ModifiableField}.
- * @hide
- */
- @TestApi
- public @ModifiableField int getUserModifiedFields() {
- return mUserModifiedFields;
- }
-
@Override
public int describeContents() {
return 0;
@@ -348,7 +322,6 @@
dest.writeBoolean(mDisableTouch);
dest.writeBoolean(mMinimizeRadioUsage);
dest.writeBoolean(mMaximizeDoze);
- dest.writeInt(mUserModifiedFields);
}
/** Builder class for {@link ZenDeviceEffects} objects. */
@@ -365,7 +338,6 @@
private boolean mDisableTouch;
private boolean mMinimizeRadioUsage;
private boolean mMaximizeDoze;
- private @ModifiableField int mUserModifiedFields;
/**
* Instantiates a new {@link ZenPolicy.Builder} with all effects set to default (disabled).
@@ -388,7 +360,6 @@
mDisableTouch = zenDeviceEffects.shouldDisableTouch();
mMinimizeRadioUsage = zenDeviceEffects.shouldMinimizeRadioUsage();
mMaximizeDoze = zenDeviceEffects.shouldMaximizeDoze();
- mUserModifiedFields = zenDeviceEffects.mUserModifiedFields;
}
/**
@@ -510,24 +481,13 @@
return this;
}
- /**
- * Sets the bitmask representing which fields are user modified. See the FIELD_ constants.
- * @hide
- */
- @TestApi
- @NonNull
- public Builder setUserModifiedFields(@ModifiableField int userModifiedFields) {
- mUserModifiedFields = userModifiedFields;
- return this;
- }
-
/** Builds a {@link ZenDeviceEffects} object based on the builder's state. */
@NonNull
public ZenDeviceEffects build() {
return new ZenDeviceEffects(mGrayscale,
mSuppressAmbientDisplay, mDimWallpaper, mNightMode, mDisableAutoBrightness,
mDisableTapToWake, mDisableTiltToWake, mDisableTouch, mMinimizeRadioUsage,
- mMaximizeDoze, mUserModifiedFields);
+ mMaximizeDoze);
}
}
}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 45a0c20..c479877 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -64,6 +64,7 @@
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.time.Instant;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
@@ -204,8 +205,8 @@
private static final String ALLOW_ATT_SCREEN_ON = "visualScreenOn";
private static final String ALLOW_ATT_CONV = "convos";
private static final String ALLOW_ATT_CONV_FROM = "convosFrom";
- private static final String ALLOW_ATT_CHANNELS = "channels";
- private static final String USER_MODIFIED_FIELDS = "policyUserModifiedFields";
+ private static final String ALLOW_ATT_CHANNELS = "priorityChannels";
+ private static final String POLICY_USER_MODIFIED_FIELDS = "policyUserModifiedFields";
private static final String DISALLOW_TAG = "disallow";
private static final String DISALLOW_ATT_VISUAL_EFFECTS = "visualEffects";
private static final String STATE_TAG = "state";
@@ -233,6 +234,7 @@
private static final String MANUAL_TAG = "manual";
private static final String AUTOMATIC_TAG = "automatic";
+ private static final String AUTOMATIC_DELETED_TAG = "deleted";
private static final String RULE_ATT_ID = "ruleId";
private static final String RULE_ATT_ENABLED = "enabled";
@@ -251,6 +253,7 @@
private static final String RULE_ATT_USER_MODIFIED_FIELDS = "userModifiedFields";
private static final String RULE_ATT_ICON = "rule_icon";
private static final String RULE_ATT_TRIGGER_DESC = "triggerDesc";
+ private static final String RULE_ATT_DELETION_INSTANT = "deletionInstant";
private static final String DEVICE_EFFECT_DISPLAY_GRAYSCALE = "zdeDisplayGrayscale";
private static final String DEVICE_EFFECT_SUPPRESS_AMBIENT_DISPLAY =
@@ -292,6 +295,10 @@
@UnsupportedAppUsage
public ArrayMap<String, ZenRule> automaticRules = new ArrayMap<>();
+ // Note: Map is *pkg|conditionId* (see deletedRuleKey()) -> ZenRule,
+ // unlike automaticRules (which is id -> rule).
+ public final ArrayMap<String, ZenRule> deletedRules = new ArrayMap<>();
+
@UnsupportedAppUsage
public ZenModeConfig() {
}
@@ -306,15 +313,9 @@
allowMessagesFrom = source.readInt();
user = source.readInt();
manualRule = source.readParcelable(null, ZenRule.class);
- final int len = source.readInt();
- if (len > 0) {
- final String[] ids = new String[len];
- final ZenRule[] rules = new ZenRule[len];
- source.readStringArray(ids);
- source.readTypedArray(rules, ZenRule.CREATOR);
- for (int i = 0; i < len; i++) {
- automaticRules.put(ids[i], rules[i]);
- }
+ readRulesFromParcel(automaticRules, source);
+ if (Flags.modesApi()) {
+ readRulesFromParcel(deletedRules, source);
}
allowAlarms = source.readInt() == 1;
allowMedia = source.readInt() == 1;
@@ -328,6 +329,19 @@
}
}
+ private static void readRulesFromParcel(ArrayMap<String, ZenRule> ruleMap, Parcel source) {
+ final int len = source.readInt();
+ if (len > 0) {
+ final String[] ids = new String[len];
+ final ZenRule[] rules = new ZenRule[len];
+ source.readStringArray(ids);
+ source.readTypedArray(rules, ZenRule.CREATOR);
+ for (int i = 0; i < len; i++) {
+ ruleMap.put(ids[i], rules[i]);
+ }
+ }
+ }
+
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(allowCalls ? 1 : 0);
@@ -339,19 +353,9 @@
dest.writeInt(allowMessagesFrom);
dest.writeInt(user);
dest.writeParcelable(manualRule, 0);
- if (!automaticRules.isEmpty()) {
- final int len = automaticRules.size();
- final String[] ids = new String[len];
- final ZenRule[] rules = new ZenRule[len];
- for (int i = 0; i < len; i++) {
- ids[i] = automaticRules.keyAt(i);
- rules[i] = automaticRules.valueAt(i);
- }
- dest.writeInt(len);
- dest.writeStringArray(ids);
- dest.writeTypedArray(rules, 0);
- } else {
- dest.writeInt(0);
+ writeRulesToParcel(automaticRules, dest);
+ if (Flags.modesApi()) {
+ writeRulesToParcel(deletedRules, dest);
}
dest.writeInt(allowAlarms ? 1 : 0);
dest.writeInt(allowMedia ? 1 : 0);
@@ -365,6 +369,23 @@
}
}
+ private static void writeRulesToParcel(ArrayMap<String, ZenRule> ruleMap, Parcel dest) {
+ if (!ruleMap.isEmpty()) {
+ final int len = ruleMap.size();
+ final String[] ids = new String[len];
+ final ZenRule[] rules = new ZenRule[len];
+ for (int i = 0; i < len; i++) {
+ ids[i] = ruleMap.keyAt(i);
+ rules[i] = ruleMap.valueAt(i);
+ }
+ dest.writeInt(len);
+ dest.writeStringArray(ids);
+ dest.writeTypedArray(rules, 0);
+ } else {
+ dest.writeInt(0);
+ }
+ }
+
@Override
public String toString() {
StringBuilder sb = new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[')
@@ -389,23 +410,26 @@
} else {
sb.append(",areChannelsBypassingDnd=").append(areChannelsBypassingDnd);
}
- return sb.append(",\nautomaticRules=").append(rulesToString())
- .append(",\nmanualRule=").append(manualRule)
- .append(']').toString();
+ sb.append(",\nautomaticRules=").append(rulesToString(automaticRules))
+ .append(",\nmanualRule=").append(manualRule);
+ if (Flags.modesApi()) {
+ sb.append(",\ndeletedRules=").append(rulesToString(deletedRules));
+ }
+ return sb.append(']').toString();
}
- private String rulesToString() {
- if (automaticRules.isEmpty()) {
+ private static String rulesToString(ArrayMap<String, ZenRule> ruleList) {
+ if (ruleList.isEmpty()) {
return "{}";
}
- StringBuilder buffer = new StringBuilder(automaticRules.size() * 28);
+ StringBuilder buffer = new StringBuilder(ruleList.size() * 28);
buffer.append("{\n");
- for (int i = 0; i < automaticRules.size(); i++) {
+ for (int i = 0; i < ruleList.size(); i++) {
if (i > 0) {
buffer.append(",\n");
}
- Object value = automaticRules.valueAt(i);
+ Object value = ruleList.valueAt(i);
buffer.append(value);
}
buffer.append('}');
@@ -487,7 +511,9 @@
&& other.allowConversations == allowConversations
&& other.allowConversationsFrom == allowConversationsFrom;
if (Flags.modesApi()) {
- return eq && other.allowPriorityChannels == allowPriorityChannels;
+ return eq
+ && Objects.equals(other.deletedRules, deletedRules)
+ && other.allowPriorityChannels == allowPriorityChannels;
}
return eq;
}
@@ -644,12 +670,20 @@
DEFAULT_SUPPRESSED_VISUAL_EFFECTS);
} else if (MANUAL_TAG.equals(tag)) {
rt.manualRule = readRuleXml(parser);
- } else if (AUTOMATIC_TAG.equals(tag)) {
+ } else if (AUTOMATIC_TAG.equals(tag)
+ || (Flags.modesApi() && AUTOMATIC_DELETED_TAG.equals(tag))) {
final String id = parser.getAttributeValue(null, RULE_ATT_ID);
final ZenRule automaticRule = readRuleXml(parser);
if (id != null && automaticRule != null) {
automaticRule.id = id;
- rt.automaticRules.put(id, automaticRule);
+ if (Flags.modesApi() && AUTOMATIC_DELETED_TAG.equals(tag)) {
+ String deletedRuleKey = deletedRuleKey(automaticRule);
+ if (deletedRuleKey != null) {
+ rt.deletedRules.put(deletedRuleKey, automaticRule);
+ }
+ } else if (AUTOMATIC_TAG.equals(tag)) {
+ rt.automaticRules.put(id, automaticRule);
+ }
}
} else if (STATE_TAG.equals(tag)) {
rt.areChannelsBypassingDnd = safeBoolean(parser,
@@ -660,13 +694,24 @@
throw new IllegalStateException("Failed to reach END_DOCUMENT");
}
+ /** Generates the map key used for a {@link ZenRule} in {@link #deletedRules}. */
+ @Nullable
+ public static String deletedRuleKey(ZenRule rule) {
+ if (rule.pkg != null && rule.conditionId != null) {
+ return rule.pkg + "|" + rule.conditionId.toString();
+ } else {
+ return null;
+ }
+ }
+
/**
* Writes XML of current ZenModeConfig
* @param out serializer
* @param version uses XML_VERSION if version is null
* @throws IOException
*/
- public void writeXml(TypedXmlSerializer out, Integer version) throws IOException {
+ public void writeXml(TypedXmlSerializer out, Integer version, boolean forBackup)
+ throws IOException {
out.startTag(null, ZEN_TAG);
out.attribute(null, ZEN_ATT_VERSION, version == null
? Integer.toString(XML_VERSION) : Integer.toString(version));
@@ -707,6 +752,15 @@
writeRuleXml(automaticRule, out);
out.endTag(null, AUTOMATIC_TAG);
}
+ if (Flags.modesApi() && !forBackup) {
+ for (int i = 0; i < deletedRules.size(); i++) {
+ final ZenRule deletedRule = deletedRules.valueAt(i);
+ out.startTag(null, AUTOMATIC_DELETED_TAG);
+ out.attribute(null, RULE_ATT_ID, deletedRule.id);
+ writeRuleXml(deletedRule, out);
+ out.endTag(null, AUTOMATIC_DELETED_TAG);
+ }
+ }
out.startTag(null, STATE_TAG);
out.attributeBoolean(null, STATE_ATT_CHANNELS_BYPASSING_DND, areChannelsBypassingDnd);
@@ -752,6 +806,14 @@
rt.triggerDescription = parser.getAttributeValue(null, RULE_ATT_TRIGGER_DESC);
rt.type = safeInt(parser, RULE_ATT_TYPE, AutomaticZenRule.TYPE_UNKNOWN);
rt.userModifiedFields = safeInt(parser, RULE_ATT_USER_MODIFIED_FIELDS, 0);
+ rt.zenPolicyUserModifiedFields = safeInt(parser, POLICY_USER_MODIFIED_FIELDS, 0);
+ rt.zenDeviceEffectsUserModifiedFields = safeInt(parser,
+ DEVICE_EFFECT_USER_MODIFIED_FIELDS, 0);
+ Long deletionInstant = tryParseLong(
+ parser.getAttributeValue(null, RULE_ATT_DELETION_INSTANT), null);
+ if (deletionInstant != null) {
+ rt.deletionInstant = Instant.ofEpochMilli(deletionInstant);
+ }
}
return rt;
}
@@ -799,6 +861,13 @@
}
out.attributeInt(null, RULE_ATT_TYPE, rule.type);
out.attributeInt(null, RULE_ATT_USER_MODIFIED_FIELDS, rule.userModifiedFields);
+ out.attributeInt(null, POLICY_USER_MODIFIED_FIELDS, rule.zenPolicyUserModifiedFields);
+ out.attributeInt(null, DEVICE_EFFECT_USER_MODIFIED_FIELDS,
+ rule.zenDeviceEffectsUserModifiedFields);
+ if (rule.deletionInstant != null) {
+ out.attributeLong(null, RULE_ATT_DELETION_INSTANT,
+ rule.deletionInstant.toEpochMilli());
+ }
}
}
@@ -856,12 +925,11 @@
final int events = safeInt(parser, ALLOW_ATT_EVENTS, ZenPolicy.STATE_UNSET);
final int reminders = safeInt(parser, ALLOW_ATT_REMINDERS, ZenPolicy.STATE_UNSET);
if (Flags.modesApi()) {
- final int channels = safeInt(parser, ALLOW_ATT_CHANNELS, ZenPolicy.CHANNEL_TYPE_UNSET);
- if (channels != ZenPolicy.CHANNEL_TYPE_UNSET) {
- builder.allowChannels(channels);
+ final int channels = safeInt(parser, ALLOW_ATT_CHANNELS, ZenPolicy.STATE_UNSET);
+ if (channels != ZenPolicy.STATE_UNSET) {
+ builder.allowPriorityChannels(channels == ZenPolicy.STATE_ALLOW);
policySet = true;
}
- builder.setUserModifiedFields(safeInt(parser, USER_MODIFIED_FIELDS, 0));
}
if (calls != ZenPolicy.PEOPLE_TYPE_UNSET) {
@@ -973,8 +1041,7 @@
out);
if (Flags.modesApi()) {
- writeZenPolicyState(ALLOW_ATT_CHANNELS, policy.getAllowedChannels(), out);
- out.attributeInt(null, USER_MODIFIED_FIELDS, policy.getUserModifiedFields());
+ writeZenPolicyState(ALLOW_ATT_CHANNELS, policy.getPriorityChannels(), out);
}
}
@@ -990,7 +1057,7 @@
out.attributeInt(null, attr, val);
}
} else if (Flags.modesApi() && Objects.equals(attr, ALLOW_ATT_CHANNELS)) {
- if (val != ZenPolicy.CHANNEL_TYPE_UNSET) {
+ if (val != ZenPolicy.STATE_UNSET) {
out.attributeInt(null, attr, val);
}
} else {
@@ -1020,7 +1087,6 @@
.setShouldMinimizeRadioUsage(
safeBoolean(parser, DEVICE_EFFECT_MINIMIZE_RADIO_USAGE, false))
.setShouldMaximizeDoze(safeBoolean(parser, DEVICE_EFFECT_MAXIMIZE_DOZE, false))
- .setUserModifiedFields(safeInt(parser, DEVICE_EFFECT_USER_MODIFIED_FIELDS, 0))
.build();
return deviceEffects.hasEffects() ? deviceEffects : null;
@@ -1045,8 +1111,6 @@
writeBooleanIfTrue(out, DEVICE_EFFECT_MINIMIZE_RADIO_USAGE,
deviceEffects.shouldMinimizeRadioUsage());
writeBooleanIfTrue(out, DEVICE_EFFECT_MAXIMIZE_DOZE, deviceEffects.shouldMaximizeDoze());
- out.attributeInt(null, DEVICE_EFFECT_USER_MODIFIED_FIELDS,
- deviceEffects.getUserModifiedFields());
}
private static void writeBooleanIfTrue(TypedXmlSerializer out, String att, boolean value)
@@ -1175,8 +1239,7 @@
}
if (Flags.modesApi()) {
- builder.allowChannels(allowPriorityChannels ? ZenPolicy.CHANNEL_TYPE_PRIORITY
- : ZenPolicy.CHANNEL_TYPE_NONE);
+ builder.allowPriorityChannels(allowPriorityChannels);
}
return builder.build();
}
@@ -1306,7 +1369,7 @@
int state = defaultPolicy.state;
if (Flags.modesApi()) {
state = Policy.policyState(defaultPolicy.hasPriorityChannels(),
- getAllowPriorityChannelsWithDefault(zenPolicy.getAllowedChannels(),
+ ZenPolicy.stateToBoolean(zenPolicy.getPriorityChannels(),
DEFAULT_ALLOW_PRIORITY_CHANNELS));
}
@@ -1349,24 +1412,6 @@
}
/**
- * Gets whether priority channels are permitted by this channel type, with the specified
- * default if the value is unset. This effectively converts the channel enum to a boolean,
- * where "true" indicates priority channels are allowed to break through and "false" means
- * they are not.
- */
- public static boolean getAllowPriorityChannelsWithDefault(
- @ZenPolicy.ChannelType int channelType, boolean defaultAllowChannels) {
- switch (channelType) {
- case ZenPolicy.CHANNEL_TYPE_PRIORITY:
- return true;
- case ZenPolicy.CHANNEL_TYPE_NONE:
- return false;
- default:
- return defaultAllowChannels;
- }
- }
-
- /**
* Maps NotificationManager.Policy senders type to ZenPolicy.PeopleType
*/
public static @ZenPolicy.PeopleType int getZenPolicySenders(int senders) {
@@ -1997,7 +2042,10 @@
public String triggerDescription;
public String iconResName;
public boolean allowManualInvocation;
- public int userModifiedFields;
+ @AutomaticZenRule.ModifiableField public int userModifiedFields;
+ @ZenPolicy.ModifiableField public int zenPolicyUserModifiedFields;
+ @ZenDeviceEffects.ModifiableField public int zenDeviceEffectsUserModifiedFields;
+ @Nullable public Instant deletionInstant; // Only set on deleted rules.
public ZenRule() { }
@@ -2031,19 +2079,30 @@
triggerDescription = source.readString();
type = source.readInt();
userModifiedFields = source.readInt();
+ zenPolicyUserModifiedFields = source.readInt();
+ zenDeviceEffectsUserModifiedFields = source.readInt();
+ if (source.readInt() == 1) {
+ deletionInstant = Instant.ofEpochMilli(source.readLong());
+ }
}
}
/**
- * @see AutomaticZenRule#canUpdate()
+ * Whether this ZenRule can be updated by an app. In general, rules that have been
+ * customized by the user cannot be further updated by an app, with some exceptions:
+ * <ul>
+ * <li>Non user-configurable fields, like type, icon, configurationActivity, etc.
+ * <li>Name, if the name was not specifically modified by the user (to support language
+ * switches).
+ * </ul>
*/
@FlaggedApi(Flags.FLAG_MODES_API)
public boolean canBeUpdatedByApp() {
// The rule is considered updateable if its bitmask has no user modifications, and
// the bitmasks of the policy and device effects have no modification.
return userModifiedFields == 0
- && (zenPolicy == null || zenPolicy.getUserModifiedFields() == 0)
- && (zenDeviceEffects == null || zenDeviceEffects.getUserModifiedFields() == 0);
+ && zenPolicyUserModifiedFields == 0
+ && zenDeviceEffectsUserModifiedFields == 0;
}
@Override
@@ -2091,6 +2150,14 @@
dest.writeString(triggerDescription);
dest.writeInt(type);
dest.writeInt(userModifiedFields);
+ dest.writeInt(zenPolicyUserModifiedFields);
+ dest.writeInt(zenDeviceEffectsUserModifiedFields);
+ if (deletionInstant != null) {
+ dest.writeInt(1);
+ dest.writeLong(deletionInstant.toEpochMilli());
+ } else {
+ dest.writeInt(0);
+ }
}
}
@@ -2119,8 +2186,23 @@
.append(",allowManualInvocation=").append(allowManualInvocation)
.append(",iconResName=").append(iconResName)
.append(",triggerDescription=").append(triggerDescription)
- .append(",type=").append(type)
- .append(",userModifiedFields=").append(userModifiedFields);
+ .append(",type=").append(type);
+ if (userModifiedFields != 0) {
+ sb.append(",userModifiedFields=")
+ .append(AutomaticZenRule.fieldsToString(userModifiedFields));
+ }
+ if (zenPolicyUserModifiedFields != 0) {
+ sb.append(",zenPolicyUserModifiedFields=")
+ .append(ZenPolicy.fieldsToString(zenPolicyUserModifiedFields));
+ }
+ if (zenDeviceEffectsUserModifiedFields != 0) {
+ sb.append(",zenDeviceEffectsUserModifiedFields=")
+ .append(ZenDeviceEffects.fieldsToString(
+ zenDeviceEffectsUserModifiedFields));
+ }
+ if (deletionInstant != null) {
+ sb.append(",deletionInstant=").append(deletionInstant);
+ }
}
return sb.append(']').toString();
@@ -2180,7 +2262,11 @@
&& Objects.equals(other.iconResName, iconResName)
&& Objects.equals(other.triggerDescription, triggerDescription)
&& other.type == type
- && other.userModifiedFields == userModifiedFields;
+ && other.userModifiedFields == userModifiedFields
+ && other.zenPolicyUserModifiedFields == zenPolicyUserModifiedFields
+ && other.zenDeviceEffectsUserModifiedFields
+ == zenDeviceEffectsUserModifiedFields
+ && Objects.equals(other.deletionInstant, deletionInstant);
}
return finalEquals;
@@ -2192,7 +2278,8 @@
return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition,
component, configurationActivity, pkg, id, enabler, zenPolicy,
zenDeviceEffects, modified, allowManualInvocation, iconResName,
- triggerDescription, type, userModifiedFields);
+ triggerDescription, type, userModifiedFields, zenPolicyUserModifiedFields,
+ zenDeviceEffectsUserModifiedFields, deletionInstant);
}
return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition,
component, configurationActivity, pkg, id, enabler, zenPolicy, modified);
diff --git a/core/java/android/service/notification/ZenModeDiff.java b/core/java/android/service/notification/ZenModeDiff.java
index 8902368..91ef11c 100644
--- a/core/java/android/service/notification/ZenModeDiff.java
+++ b/core/java/android/service/notification/ZenModeDiff.java
@@ -30,6 +30,11 @@
/**
* ZenModeDiff is a utility class meant to encapsulate the diff between ZenModeConfigs and their
* subcomponents (automatic and manual ZenRules).
+ *
+ * <p>Note that this class is intended to detect <em>meaningful</em> differences, so objects that
+ * are not identical (as per their {@code equals()} implementation) can still produce an empty diff
+ * if only "metadata" fields are updated.
+ *
* @hide
*/
public class ZenModeDiff {
@@ -467,7 +472,6 @@
public static final String FIELD_ICON_RES = "iconResName";
public static final String FIELD_TRIGGER_DESCRIPTION = "triggerDescription";
public static final String FIELD_TYPE = "type";
- public static final String FIELD_USER_MODIFIED_FIELDS = "userModifiedFields";
// NOTE: new field strings must match the variable names in ZenModeConfig.ZenRule
// Special field to track whether this rule became active or inactive
@@ -563,10 +567,6 @@
if (!Objects.equals(from.iconResName, to.iconResName)) {
addField(FIELD_ICON_RES, new FieldDiff<>(from.iconResName, to.iconResName));
}
- if (from.userModifiedFields != to.userModifiedFields) {
- addField(FIELD_USER_MODIFIED_FIELDS,
- new FieldDiff<>(from.userModifiedFields, to.userModifiedFields));
- }
}
}
diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java
index 8477eb7..fb491d0 100644
--- a/core/java/android/service/notification/ZenPolicy.java
+++ b/core/java/android/service/notification/ZenPolicy.java
@@ -45,8 +45,8 @@
*/
public final class ZenPolicy implements Parcelable {
- /** Used to track which rule variables have been modified by the user.
- * Should be checked against the bitmask {@link #getUserModifiedFields()}.
+ /**
+ * Enum for the user-modifiable fields in this object.
* @hide
*/
@IntDef(flag = true, prefix = { "FIELD_" }, value = {
@@ -76,7 +76,6 @@
* the same time.
* @hide
*/
- @TestApi
@FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_MESSAGES = 1 << 0;
/**
@@ -84,7 +83,6 @@
* the same time.
* @hide
*/
- @TestApi
@FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_CALLS = 1 << 1;
/**
@@ -92,13 +90,11 @@
* set at the same time.
* @hide
*/
- @TestApi
@FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_CONVERSATIONS = 1 << 2;
/**
* @hide
*/
- @TestApi
@FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_ALLOW_CHANNELS = 1 << 3;
/**
@@ -109,73 +105,61 @@
/**
* @hide
*/
- @TestApi
@FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_PRIORITY_CATEGORY_EVENTS = 1 << 5;
/**
* @hide
*/
- @TestApi
@FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_PRIORITY_CATEGORY_REPEAT_CALLERS = 1 << 6;
/**
* @hide
*/
- @TestApi
@FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_PRIORITY_CATEGORY_ALARMS = 1 << 7;
/**
* @hide
*/
- @TestApi
@FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_PRIORITY_CATEGORY_MEDIA = 1 << 8;
/**
* @hide
*/
- @TestApi
@FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_PRIORITY_CATEGORY_SYSTEM = 1 << 9;
/**
* @hide
*/
- @TestApi
@FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_VISUAL_EFFECT_FULL_SCREEN_INTENT = 1 << 10;
/**
* @hide
*/
- @TestApi
@FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_VISUAL_EFFECT_LIGHTS = 1 << 11;
/**
* @hide
*/
- @TestApi
@FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_VISUAL_EFFECT_PEEK = 1 << 12;
/**
* @hide
*/
- @TestApi
@FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_VISUAL_EFFECT_STATUS_BAR = 1 << 13;
/**
* @hide
*/
- @TestApi
@FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_VISUAL_EFFECT_BADGE = 1 << 14;
/**
* @hide
*/
- @TestApi
@FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_VISUAL_EFFECT_AMBIENT = 1 << 15;
/**
* @hide
*/
- @TestApi
@FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_VISUAL_EFFECT_NOTIFICATION_LIST = 1 << 16;
@@ -184,8 +168,8 @@
private @PeopleType int mPriorityMessages = PEOPLE_TYPE_UNSET;
private @PeopleType int mPriorityCalls = PEOPLE_TYPE_UNSET;
private @ConversationSenders int mConversationSenders = CONVERSATION_SENDERS_UNSET;
- private @ChannelType int mAllowChannels = CHANNEL_TYPE_UNSET;
- private final @ModifiableField int mUserModifiedFields; // Bitwise representation
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ private @ChannelType int mAllowChannels = CHANNEL_POLICY_UNSET;
/** @hide */
@IntDef(prefix = { "PRIORITY_CATEGORY_" }, value = {
@@ -354,56 +338,52 @@
*/
public static final int STATE_DISALLOW = 2;
- /** @hide */
- @IntDef(prefix = { "CHANNEL_TYPE_" }, value = {
- CHANNEL_TYPE_UNSET,
- CHANNEL_TYPE_PRIORITY,
- CHANNEL_TYPE_NONE,
+ @IntDef(prefix = { "CHANNEL_POLICY_" }, value = {
+ CHANNEL_POLICY_UNSET,
+ CHANNEL_POLICY_PRIORITY,
+ CHANNEL_POLICY_NONE,
})
@Retention(RetentionPolicy.SOURCE)
- public @interface ChannelType {}
+ private @interface ChannelType {}
/**
* Indicates no explicit setting for which channels may bypass DND when this policy is active.
- * Defaults to {@link #CHANNEL_TYPE_PRIORITY}.
+ * Defaults to {@link #CHANNEL_POLICY_PRIORITY}.
*/
@FlaggedApi(Flags.FLAG_MODES_API)
- public static final int CHANNEL_TYPE_UNSET = 0;
+ private static final int CHANNEL_POLICY_UNSET = 0;
/**
* Indicates that channels marked as {@link NotificationChannel#canBypassDnd()} can bypass DND
* when this policy is active.
*/
@FlaggedApi(Flags.FLAG_MODES_API)
- public static final int CHANNEL_TYPE_PRIORITY = 1;
+ private static final int CHANNEL_POLICY_PRIORITY = 1;
/**
* Indicates that no channels can bypass DND when this policy is active, even those marked as
* {@link NotificationChannel#canBypassDnd()}.
*/
@FlaggedApi(Flags.FLAG_MODES_API)
- public static final int CHANNEL_TYPE_NONE = 2;
+ private static final int CHANNEL_POLICY_NONE = 2;
/** @hide */
public ZenPolicy() {
mPriorityCategories = new ArrayList<>(Collections.nCopies(NUM_PRIORITY_CATEGORIES, 0));
mVisualEffects = new ArrayList<>(Collections.nCopies(NUM_VISUAL_EFFECTS, 0));
- mUserModifiedFields = 0;
}
/** @hide */
@FlaggedApi(Flags.FLAG_MODES_API)
public ZenPolicy(List<Integer> priorityCategories, List<Integer> visualEffects,
@PeopleType int priorityMessages, @PeopleType int priorityCalls,
- @ConversationSenders int conversationSenders, @ChannelType int allowChannels,
- @ModifiableField int userModifiedFields) {
+ @ConversationSenders int conversationSenders, @ChannelType int allowChannels) {
mPriorityCategories = priorityCategories;
mVisualEffects = visualEffects;
mPriorityMessages = priorityMessages;
mPriorityCalls = priorityCalls;
mConversationSenders = conversationSenders;
mAllowChannels = allowChannels;
- mUserModifiedFields = userModifiedFields;
}
/**
@@ -584,16 +564,21 @@
}
/**
- * Which types of {@link NotificationChannel channels} this policy allows to bypass DND. When
- * this value is {@link #CHANNEL_TYPE_PRIORITY priority} channels, any channel with
- * canBypassDnd() may bypass DND; when it is {@link #CHANNEL_TYPE_NONE none}, even channels
- * with canBypassDnd() will be intercepted.
- * @return {@link #CHANNEL_TYPE_UNSET}, {@link #CHANNEL_TYPE_PRIORITY}, or
- * {@link #CHANNEL_TYPE_NONE}
+ * Whether this policy allows {@link NotificationChannel channels} marked as
+ * {@link NotificationChannel#canBypassDnd()} to bypass DND. If {@link #STATE_ALLOW}, these
+ * channels may bypass; if {@link #STATE_DISALLOW}, then even notifications from channels
+ * with {@link NotificationChannel#canBypassDnd()} will be intercepted.
*/
@FlaggedApi(Flags.FLAG_MODES_API)
- public @ChannelType int getAllowedChannels() {
- return mAllowChannels;
+ public @State int getPriorityChannels() {
+ switch (mAllowChannels) {
+ case CHANNEL_POLICY_PRIORITY:
+ return STATE_ALLOW;
+ case CHANNEL_POLICY_NONE:
+ return STATE_DISALLOW;
+ default:
+ return STATE_UNSET;
+ }
}
/**
@@ -628,8 +613,6 @@
* is not set, it is (@link STATE_UNSET} and will not change the current set policy.
*/
public static final class Builder {
- private @ModifiableField int mUserModifiedFields;
-
private ZenPolicy mZenPolicy;
public Builder() {
@@ -644,9 +627,6 @@
public Builder(@Nullable ZenPolicy policy) {
if (policy != null) {
mZenPolicy = policy.copy();
- if (Flags.modesApi()) {
- mUserModifiedFields = policy.mUserModifiedFields;
- }
} else {
mZenPolicy = new ZenPolicy();
}
@@ -657,11 +637,10 @@
*/
public @NonNull ZenPolicy build() {
if (Flags.modesApi()) {
- return new ZenPolicy(new ArrayList<Integer>(mZenPolicy.mPriorityCategories),
- new ArrayList<Integer>(mZenPolicy.mVisualEffects),
+ return new ZenPolicy(new ArrayList<>(mZenPolicy.mPriorityCategories),
+ new ArrayList<>(mZenPolicy.mVisualEffects),
mZenPolicy.mPriorityMessages, mZenPolicy.mPriorityCalls,
- mZenPolicy.mConversationSenders, mZenPolicy.mAllowChannels,
- mUserModifiedFields);
+ mZenPolicy.mConversationSenders, mZenPolicy.mAllowChannels);
} else {
return mZenPolicy.copy();
}
@@ -1016,32 +995,10 @@
* Set whether priority channels are permitted to break through DND.
*/
@FlaggedApi(Flags.FLAG_MODES_API)
- public @NonNull Builder allowChannels(@ChannelType int channelType) {
- mZenPolicy.mAllowChannels = channelType;
+ public @NonNull Builder allowPriorityChannels(boolean allow) {
+ mZenPolicy.mAllowChannels = allow ? CHANNEL_POLICY_PRIORITY : CHANNEL_POLICY_NONE;
return this;
}
-
- /**
- * Sets the user modified fields bitmask.
- * @hide
- */
- @TestApi
- @FlaggedApi(Flags.FLAG_MODES_API)
- public @NonNull Builder setUserModifiedFields(@ModifiableField int userModifiedFields) {
- mUserModifiedFields = userModifiedFields;
- return this;
- }
- }
-
- /**
- Gets the bitmask representing which fields are user modified. Bits are set using
- * {@link ModifiableField}.
- * @hide
- */
- @TestApi
- @FlaggedApi(Flags.FLAG_MODES_API)
- public @ModifiableField int getUserModifiedFields() {
- return mUserModifiedFields;
}
@Override
@@ -1058,7 +1015,6 @@
dest.writeInt(mConversationSenders);
if (Flags.modesApi()) {
dest.writeInt(mAllowChannels);
- dest.writeInt(mUserModifiedFields);
}
}
@@ -1074,7 +1030,7 @@
trimList(source.readArrayList(Integer.class.getClassLoader(),
Integer.class), NUM_VISUAL_EFFECTS),
source.readInt(), source.readInt(), source.readInt(),
- source.readInt(), source.readInt()
+ source.readInt()
);
} else {
policy = new ZenPolicy();
@@ -1109,14 +1065,12 @@
conversationTypeToString(mConversationSenders));
if (Flags.modesApi()) {
sb.append(", allowChannels=").append(channelTypeToString(mAllowChannels));
- sb.append(", userModifiedFields=")
- .append(modifiedFieldsToString(mUserModifiedFields));
}
return sb.append('}').toString();
}
- @FlaggedApi(Flags.FLAG_MODES_API)
- private String modifiedFieldsToString(@ModifiableField int bitmask) {
+ /** @hide */
+ public static String fieldsToString(@ModifiableField int bitmask) {
ArrayList<String> modified = new ArrayList<>();
if ((bitmask & FIELD_MESSAGES) != 0) {
modified.add("FIELD_MESSAGES");
@@ -1305,11 +1259,11 @@
@FlaggedApi(Flags.FLAG_MODES_API)
public static String channelTypeToString(@ChannelType int channelType) {
switch (channelType) {
- case CHANNEL_TYPE_UNSET:
+ case CHANNEL_POLICY_UNSET:
return "unset";
- case CHANNEL_TYPE_PRIORITY:
+ case CHANNEL_POLICY_PRIORITY:
return "priority";
- case CHANNEL_TYPE_NONE:
+ case CHANNEL_POLICY_NONE:
return "none";
}
return "invalidChannelType{" + channelType + "}";
@@ -1327,8 +1281,7 @@
&& other.mPriorityMessages == mPriorityMessages
&& other.mConversationSenders == mConversationSenders;
if (Flags.modesApi()) {
- return eq && other.mAllowChannels == mAllowChannels
- && other.mUserModifiedFields == mUserModifiedFields;
+ return eq && other.mAllowChannels == mAllowChannels;
}
return eq;
}
@@ -1337,7 +1290,7 @@
public int hashCode() {
if (Flags.modesApi()) {
return Objects.hash(mPriorityCategories, mVisualEffects, mPriorityCalls,
- mPriorityMessages, mConversationSenders, mAllowChannels, mUserModifiedFields);
+ mPriorityMessages, mConversationSenders, mAllowChannels);
}
return Objects.hash(mPriorityCategories, mVisualEffects, mPriorityCalls, mPriorityMessages,
mConversationSenders);
@@ -1389,11 +1342,11 @@
}
/** @hide */
- public boolean isCategoryAllowed(@PriorityCategory int category, boolean defaultVal) {
- switch (getZenPolicyPriorityCategoryState(category)) {
- case ZenPolicy.STATE_ALLOW:
+ public static boolean stateToBoolean(@State int state, boolean defaultVal) {
+ switch (state) {
+ case STATE_ALLOW:
return true;
- case ZenPolicy.STATE_DISALLOW:
+ case STATE_DISALLOW:
return false;
default:
return defaultVal;
@@ -1401,15 +1354,13 @@
}
/** @hide */
+ public boolean isCategoryAllowed(@PriorityCategory int category, boolean defaultVal) {
+ return stateToBoolean(getZenPolicyPriorityCategoryState(category), defaultVal);
+ }
+
+ /** @hide */
public boolean isVisualEffectAllowed(@VisualEffect int effect, boolean defaultVal) {
- switch (getZenPolicyVisualEffectState(effect)) {
- case ZenPolicy.STATE_ALLOW:
- return true;
- case ZenPolicy.STATE_DISALLOW:
- return false;
- default:
- return defaultVal;
- }
+ return stateToBoolean(getZenPolicyVisualEffectState(effect), defaultVal);
}
/**
@@ -1463,8 +1414,8 @@
// apply allowed channels
if (Flags.modesApi()) {
// if no channels are allowed, can't newly allow them
- if (mAllowChannels != CHANNEL_TYPE_NONE
- && policyToApply.mAllowChannels != CHANNEL_TYPE_UNSET) {
+ if (mAllowChannels != CHANNEL_POLICY_NONE
+ && policyToApply.mAllowChannels != CHANNEL_POLICY_UNSET) {
mAllowChannels = policyToApply.mAllowChannels;
}
}
@@ -1530,7 +1481,7 @@
proto.write(DNDPolicyProto.ALLOW_CONVERSATIONS_FROM, getPriorityConversationSenders());
if (Flags.modesApi()) {
- proto.write(DNDPolicyProto.ALLOW_CHANNELS, getAllowedChannels());
+ proto.write(DNDPolicyProto.ALLOW_CHANNELS, getPriorityChannels());
}
proto.flush();
diff --git a/core/java/android/service/notification/flags.aconfig b/core/java/android/service/notification/flags.aconfig
index a2ade6a..3008b8d 100644
--- a/core/java/android/service/notification/flags.aconfig
+++ b/core/java/android/service/notification/flags.aconfig
@@ -21,3 +21,11 @@
description: "This flag controls the redacting of sensitive notifications from untrusted NotificationListenerServices"
bug: "306271190"
}
+
+flag {
+ name: "callstyle_callback_api"
+ namespace: "systemui"
+ description: "Guards the new CallStyleNotificationEventsCallback"
+ bug: "305095040"
+ is_fixed_read_only: true
+}
\ No newline at end of file
diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java
index adc54f5..f2bdbf6 100644
--- a/core/java/android/service/voice/VisualQueryDetector.java
+++ b/core/java/android/service/voice/VisualQueryDetector.java
@@ -325,7 +325,7 @@
Slog.v(TAG, "BinderCallback#onQueryDetected");
Binder.withCleanCallingIdentity(() -> {
synchronized (mLock) {
- mCallback.onQueryDetected(partialQuery);
+ mExecutor.execute(()->mCallback.onQueryDetected(partialQuery));
}
});
}
@@ -335,7 +335,7 @@
Slog.v(TAG, "BinderCallback#onQueryFinished");
Binder.withCleanCallingIdentity(() -> {
synchronized (mLock) {
- mCallback.onQueryFinished();
+ mExecutor.execute(()->mCallback.onQueryFinished());
}
});
}
@@ -345,7 +345,7 @@
Slog.v(TAG, "BinderCallback#onQueryRejected");
Binder.withCleanCallingIdentity(() -> {
synchronized (mLock) {
- mCallback.onQueryRejected();
+ mExecutor.execute(()->mCallback.onQueryRejected());
}
});
}
diff --git a/core/java/android/service/wearable/OWNERS b/core/java/android/service/wearable/OWNERS
index 073e2d7..eca48b7 100644
--- a/core/java/android/service/wearable/OWNERS
+++ b/core/java/android/service/wearable/OWNERS
@@ -1,3 +1 @@
[email protected]
[email protected]
[email protected]
\ No newline at end of file
+include /core/java/android/app/wearable/OWNERS
\ No newline at end of file
diff --git a/core/java/android/speech/RecognizerIntent.java b/core/java/android/speech/RecognizerIntent.java
index 118d028..1ca7ac7 100644
--- a/core/java/android/speech/RecognizerIntent.java
+++ b/core/java/android/speech/RecognizerIntent.java
@@ -16,6 +16,9 @@
package android.speech;
+import static android.speech.flags.Flags.FLAG_MULTILANG_EXTRA_LAUNCH;
+
+import android.annotation.FlaggedApi;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
@@ -653,4 +656,30 @@
*/
public static final String EXTRA_LANGUAGE_SWITCH_ALLOWED_LANGUAGES =
"android.speech.extra.LANGUAGE_SWITCH_ALLOWED_LANGUAGES";
+
+ /**
+ * Optional integer to use for {@link #EXTRA_ENABLE_LANGUAGE_SWITCH}. If set, the language
+ * switch will be deactivated when LANGUAGE_SWITCH_MAX_SWITCHES reached.
+ *
+ * <p> Depending on the recognizer implementation, this flag may have no effect.
+ *
+ * @see #EXTRA_ENABLE_LANGUAGE_SWITCH
+ */
+ @FlaggedApi(FLAG_MULTILANG_EXTRA_LAUNCH)
+ public static final String EXTRA_LANGUAGE_SWITCH_MAX_SWITCHES =
+ "android.speech.extra.LANGUAGE_SWITCH_MAX_SWITCHES";
+
+ /**
+ * Optional integer to use for {@link #EXTRA_ENABLE_LANGUAGE_SWITCH}. If set, the language
+ * switch will only be activated for this value of ms of audio since the START_OF_SPEECH. This
+ * could provide a more stable recognition result when the language switch is only required in
+ * the beginning of the session.
+ *
+ * <p> Depending on the recognizer implementation, this flag may have no effect.
+ *
+ * @see #EXTRA_ENABLE_LANGUAGE_SWITCH
+ */
+ @FlaggedApi(FLAG_MULTILANG_EXTRA_LAUNCH)
+ public static final String EXTRA_LANGUAGE_SWITCH_INITIAL_ACTIVE_DURATION_TIME_MILLIS =
+ "android.speech.extra.LANGUAGE_SWITCH_INITIAL_ACTIVE_DURATION_TIME_MILLIS";
}
diff --git a/core/java/android/speech/flags/speech_flags.aconfig b/core/java/android/speech/flags/speech_flags.aconfig
new file mode 100644
index 0000000..fd80127
--- /dev/null
+++ b/core/java/android/speech/flags/speech_flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.speech.flags"
+
+flag {
+ name: "multilang_extra_launch"
+ namespace: "machine_learning"
+ description: "Feature flag for adding new extra for multi-lang feature"
+ bug: "312489931"
+}
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index cf1156d..fb57921 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -1681,6 +1681,10 @@
@EmergencyCallbackModeStopReason int reason) {
// not support. Can't override. Use TelephonyCallback.
}
+
+ public final void onSimultaneousCallingStateChanged(int[] subIds) {
+ // not supported on the deprecated interface - Use TelephonyCallback instead
+ }
}
private void log(String s) {
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index 19bcf28..dc6a035 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -18,6 +18,7 @@
import android.Manifest;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
@@ -33,15 +34,19 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.IPhoneStateListener;
+import com.android.internal.telephony.flags.Flags;
import dalvik.system.VMRuntime;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.Executor;
+import java.util.stream.Collectors;
/**
* A callback class for monitoring changes in specific telephony states
@@ -627,6 +632,18 @@
public static final int EVENT_EMERGENCY_CALLBACK_MODE_CHANGED = 40;
/**
+ * Event for listening to changes in simultaneous cellular calling subscriptions.
+ *
+ * @see SimultaneousCellularCallingSupportListener
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS)
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @SystemApi
+ public static final int EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED = 41;
+
+ /**
* @hide
*/
@IntDef(prefix = {"EVENT_"}, value = {
@@ -669,7 +686,8 @@
EVENT_LINK_CAPACITY_ESTIMATE_CHANGED,
EVENT_TRIGGER_NOTIFY_ANBR,
EVENT_MEDIA_QUALITY_STATUS_CHANGED,
- EVENT_EMERGENCY_CALLBACK_MODE_CHANGED
+ EVENT_EMERGENCY_CALLBACK_MODE_CHANGED,
+ EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED
})
@Retention(RetentionPolicy.SOURCE)
public @interface TelephonyEvent {
@@ -1373,6 +1391,44 @@
}
/**
+ * Interface for listening to changes in the simultaneous cellular calling state for active
+ * cellular subscriptions.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS)
+ @SystemApi
+ public interface SimultaneousCellularCallingSupportListener {
+ /**
+ * Notify the Listener that the subscriptions available for simultaneous <b>cellular</b>
+ * calling have changed.
+ * <p>
+ * If we have an ongoing <b>cellular</b> call on one subscription in this Set, a
+ * simultaneous incoming or outgoing <b>cellular</b> call is possible on any of the
+ * subscriptions in this Set. On a traditional Dual Sim Dual Standby device, simultaneous
+ * calling is not possible between subscriptions, where on a Dual Sim Dual Active device,
+ * simultaneous calling may be possible between subscriptions in certain network conditions.
+ * <p>
+ * Note: This listener only tracks the capability of the modem to perform simultaneous
+ * cellular calls and does not track the simultaneous calling state of scenarios based on
+ * multiple IMS registration over multiple transports (WiFi/Internet calling).
+ * <p>
+ * Note: This listener fires for all changes to cellular calling subscriptions independent
+ * of which subscription it is registered on.
+ *
+ * @param simultaneousCallingSubscriptionIds The Set of subscription IDs that support
+ * simultaneous calling. If there is an ongoing call on a subscription in this Set, then a
+ * simultaneous incoming or outgoing call is only possible for other subscriptions in this
+ * Set. If there is an ongoing call on a subscription that is not in this Set, then
+ * simultaneous calling is not possible at the current time.
+ *
+ */
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ void onSimultaneousCellularCallingSubscriptionsChanged(
+ @NonNull Set<Integer> simultaneousCallingSubscriptionIds);
+ }
+
+ /**
* Interface for call attributes listener.
*
* @hide
@@ -1976,6 +2032,17 @@
allowedNetworkType)));
}
+ public void onSimultaneousCallingStateChanged(int[] subIds) {
+ SimultaneousCellularCallingSupportListener listener =
+ (SimultaneousCellularCallingSupportListener) mTelephonyCallbackWeakRef.get();
+ if (listener == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(
+ () -> listener.onSimultaneousCellularCallingSubscriptionsChanged(
+ Arrays.stream(subIds).boxed().collect(Collectors.toSet()))));
+ }
+
public void onLinkCapacityEstimateChanged(
List<LinkCapacityEstimate> linkCapacityEstimateList) {
LinkCapacityEstimateChangedListener listener =
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 886727e..8935ab3 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -16,9 +16,11 @@
package android.telephony;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.compat.Compatibility;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
@@ -50,6 +52,7 @@
import com.android.internal.telephony.ICarrierPrivilegesCallback;
import com.android.internal.telephony.IOnSubscriptionsChangedListener;
import com.android.internal.telephony.ITelephonyRegistry;
+import com.android.server.telecom.flags.Flags;
import java.lang.ref.WeakReference;
import java.util.Arrays;
@@ -66,11 +69,13 @@
* or {@link PhoneCapability} changed. This might trigger callback from applications side through
* {@link android.telephony.PhoneStateListener}
*
- * TODO: limit API access to only carrier apps with certain permissions or apps running on
+ * Limit API access to only carrier apps with certain permissions or apps running on
* privileged UID.
*
* @hide
*/
+@SystemApi
+@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
public class TelephonyRegistryManager {
private static final String TAG = "TelephonyRegistryManager";
@@ -120,6 +125,7 @@
* @param listener an instance of {@link SubscriptionManager.OnSubscriptionsChangedListener}
* with onSubscriptionsChanged overridden.
* @param executor the executor that will execute callbacks.
+ * @hide
*/
public void addOnSubscriptionsChangedListener(
@NonNull SubscriptionManager.OnSubscriptionsChangedListener listener,
@@ -155,6 +161,7 @@
* invoke the listener fails.
*
* @param listener that is to be unregistered.
+ * @hide
*/
public void removeOnSubscriptionsChangedListener(
@NonNull SubscriptionManager.OnSubscriptionsChangedListener listener) {
@@ -180,6 +187,7 @@
* {@link SubscriptionManager.OnOpportunisticSubscriptionsChangedListener} with
* onOpportunisticSubscriptionsChanged overridden.
* @param executor an Executor that will execute callbacks.
+ * @hide
*/
public void addOnOpportunisticSubscriptionsChangedListener(
@NonNull SubscriptionManager.OnOpportunisticSubscriptionsChangedListener listener,
@@ -221,6 +229,7 @@
* listener fails.
*
* @param listener that is to be unregistered.
+ * @hide
*/
public void removeOnOpportunisticSubscriptionsChangedListener(
@NonNull SubscriptionManager.OnOpportunisticSubscriptionsChangedListener listener) {
@@ -252,6 +261,7 @@
* @param listener Listener providing callback
* @param events Events
* @param notifyNow Whether to notify instantly
+ * @hide
*/
public void listenFromListener(int subId, @NonNull boolean renounceFineLocationAccess,
@NonNull boolean renounceCoarseLocationAccess, @NonNull String pkg,
@@ -318,6 +328,7 @@
* @param active Whether the carrier network change is or shortly will be
* active. Set this value to true to begin showing alternative UI and false to stop.
* @see TelephonyManager#hasCarrierPrivileges
+ * @hide
*/
public void notifyCarrierNetworkChange(boolean active) {
try {
@@ -344,6 +355,7 @@
* @param active whether the carrier network change is or shortly will be active. Set this value
* to true to begin showing alternative UI and false to stop.
* @see TelephonyManager#hasCarrierPrivileges
+ * @hide
*/
public void notifyCarrierNetworkChange(int subscriptionId, boolean active) {
try {
@@ -362,6 +374,7 @@
* @param subId for which call state changed.
* @param state latest call state. e.g, offhook, ringing
* @param incomingNumber incoming phone number.
+ * @hide
*/
public void notifyCallStateChanged(int slotIndex, int subId, @CallState int state,
@Nullable String incomingNumber) {
@@ -380,6 +393,7 @@
* @param incomingNumber incoming phone number.
* @hide
*/
+ @SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
public void notifyCallStateChangedForAllSubscriptions(@CallState int state,
@Nullable String incomingNumber) {
@@ -424,6 +438,7 @@
* subId is invalid.
* @param subId for which the service state changed.
* @param state service state e.g, in service, out of service or roaming status.
+ * @hide
*/
public void notifyServiceStateChanged(int slotIndex, int subId, @NonNull ServiceState state) {
try {
@@ -441,6 +456,7 @@
* subId is invalid.
* @param subId for which the signalstrength changed.
* @param signalStrength e.g, signalstrength level {@see SignalStrength#getLevel()}
+ * @hide
*/
public void notifySignalStrengthChanged(int slotIndex, int subId,
@NonNull SignalStrength signalStrength) {
@@ -461,6 +477,7 @@
* @param subId for which message waiting indicator changed.
* @param msgWaitingInd {@code true} indicates there is message-waiting indicator, {@code false}
* otherwise.
+ * @hide
*/
public void notifyMessageWaitingChanged(int slotIndex, int subId, boolean msgWaitingInd) {
try {
@@ -477,6 +494,7 @@
* @param subId for which call forwarding status changed.
* @param callForwardInd {@code true} indicates there is call forwarding, {@code false}
* otherwise.
+ * @hide
*/
public void notifyCallForwardingChanged(int subId, boolean callForwardInd) {
try {
@@ -493,6 +511,7 @@
* @param subId for which data activity state changed.
* @param dataActivityType indicates the latest data activity type e.g. {@link
* TelephonyManager#DATA_ACTIVITY_IN}
+ * @hide
*/
public void notifyDataActivityChanged(int subId, @DataActivityType int dataActivityType) {
try {
@@ -511,6 +530,7 @@
* @param subId for which data activity state changed.
* @param dataActivityType indicates the latest data activity type e.g. {@link
* TelephonyManager#DATA_ACTIVITY_IN}
+ * @hide
*/
public void notifyDataActivityChanged(int slotIndex, int subId,
@DataActivityType int dataActivityType) {
@@ -532,6 +552,7 @@
*
* @see PreciseDataConnectionState
* @see TelephonyManager#DATA_DISCONNECTED
+ * @hide
*/
public void notifyDataConnectionForSubscriber(int slotIndex, int subId,
@NonNull PreciseDataConnectionState preciseState) {
@@ -552,6 +573,7 @@
* @param subId for which call quality state changed.
* @param callQuality Information about call quality e.g, call quality level
* @param networkType associated with this data connection. e.g, LTE
+ * @hide
*/
public void notifyCallQualityChanged(int slotIndex, int subId, @NonNull CallQuality callQuality,
@NetworkType int networkType) {
@@ -573,6 +595,7 @@
* {@link CarrierConfigManager#KEY_VOICE_RTP_THRESHOLDS_JITTER_INT}
*
* @param status media quality status
+ * @hide
*/
public void notifyMediaQualityStatusChanged(
int slotIndex, int subId, @NonNull MediaQualityStatus status) {
@@ -590,6 +613,7 @@
* @param slotIndex for which emergency number list changed. Can be derived from subId except
* when subId is invalid.
* @param subId for which emergency number list changed.
+ * @hide
*/
public void notifyEmergencyNumberList( int slotIndex, int subId) {
try {
@@ -602,14 +626,16 @@
/**
* Notify outgoing emergency call.
- * @param phoneId Sender phone ID.
+ * @param simSlotIndex Sender phone ID.
* @param subId Sender subscription ID.
* @param emergencyNumber Emergency number.
+ * @hide
*/
- public void notifyOutgoingEmergencyCall(int phoneId, int subId,
+ @SystemApi
+ public void notifyOutgoingEmergencyCall(int simSlotIndex, int subId,
@NonNull EmergencyNumber emergencyNumber) {
try {
- sRegistry.notifyOutgoingEmergencyCall(phoneId, subId, emergencyNumber);
+ sRegistry.notifyOutgoingEmergencyCall(simSlotIndex, subId, emergencyNumber);
} catch (RemoteException ex) {
// system process is dead
throw ex.rethrowFromSystemServer();
@@ -621,6 +647,7 @@
* @param phoneId Sender phone ID.
* @param subId Sender subscription ID.
* @param emergencyNumber Emergency number.
+ * @hide
*/
public void notifyOutgoingEmergencySms(int phoneId, int subId,
@NonNull EmergencyNumber emergencyNumber) {
@@ -639,6 +666,7 @@
* subId is invalid.
* @param subId for which radio power state changed.
* @param radioPowerState the current modem radio state.
+ * @hide
*/
public void notifyRadioPowerStateChanged(int slotIndex, int subId,
@RadioPowerState int radioPowerState) {
@@ -654,6 +682,7 @@
* Notify {@link PhoneCapability} changed.
*
* @param phoneCapability the capability of the modem group.
+ * @hide
*/
public void notifyPhoneCapabilityChanged(@NonNull PhoneCapability phoneCapability) {
try {
@@ -685,6 +714,7 @@
* when subId is invalid.
* @param subId for which data activation state changed.
* @param activationState sim activation state e.g, activated.
+ * @hide
*/
public void notifyDataActivationStateChanged(int slotIndex, int subId,
@SimActivationState int activationState) {
@@ -705,6 +735,7 @@
* subId is invalid.
* @param subId for which voice activation state changed.
* @param activationState sim activation state e.g, activated.
+ * @hide
*/
public void notifyVoiceActivationStateChanged(int slotIndex, int subId,
@SimActivationState int activationState) {
@@ -725,6 +756,7 @@
* when subId is invalid.
* @param subId for which mobile data state has changed.
* @param state {@code true} indicates mobile data is enabled/on. {@code false} otherwise.
+ * @hide
*/
public void notifyUserMobileDataStateChanged(int slotIndex, int subId, boolean state) {
try {
@@ -743,6 +775,7 @@
* when the device is in emergency-only mode.
* @param subscriptionId Subscription id for which display network info has changed.
* @param telephonyDisplayInfo The display info.
+ * @hide
*/
public void notifyDisplayInfoChanged(int slotIndex, int subscriptionId,
@NonNull TelephonyDisplayInfo telephonyDisplayInfo) {
@@ -759,6 +792,7 @@
*
* @param subId for which ims call disconnect.
* @param imsReasonInfo the reason for ims call disconnect.
+ * @hide
*/
public void notifyImsDisconnectCause(int subId, @NonNull ImsReasonInfo imsReasonInfo) {
try {
@@ -775,6 +809,7 @@
*
* @param subId for which srvcc state changed.
* @param state srvcc state
+ * @hide
*/
public void notifySrvccStateChanged(int subId, @SrvccState int state) {
try {
@@ -798,6 +833,7 @@
* @param imsServiceTypes Array of IMS call service type for ringing, foreground &
* background calls.
* @param imsCallTypes Array of IMS call type for ringing, foreground & background calls.
+ * @hide
*/
public void notifyPreciseCallState(int slotIndex, int subId,
@Annotation.PreciseCallStates int[] callStates, String[] imsCallIds,
@@ -822,6 +858,7 @@
* @param cause {@link DisconnectCause} for the disconnected call.
* @param preciseCause {@link android.telephony.PreciseDisconnectCause} for the disconnected
* call.
+ * @hide
*/
public void notifyDisconnectCause(int slotIndex, int subId, @DisconnectCauses int cause,
@PreciseDisconnectCauses int preciseCause) {
@@ -838,6 +875,7 @@
*
* <p>To be compatible with {@link TelephonyRegistry}, use {@link CellIdentity} which is
* parcelable, and convert to CellLocation in client code.
+ * @hide
*/
public void notifyCellLocation(int subId, @NonNull CellIdentity cellLocation) {
try {
@@ -854,6 +892,7 @@
*
* @param subId for which cellinfo changed.
* @param cellInfo A list of cellInfo associated with the given subscription.
+ * @hide
*/
public void notifyCellInfoChanged(int subId, @NonNull List<CellInfo> cellInfo) {
try {
@@ -866,6 +905,7 @@
/**
* Notify that the active data subscription ID has changed.
* @param activeDataSubId The new subscription ID for active data
+ * @hide
*/
public void notifyActiveDataSubIdChanged(int activeDataSubId) {
try {
@@ -896,6 +936,7 @@
* For UMTS, if a combined attach succeeds for PS only, then the GMM cause code shall be
* included as an additionalCauseCode. For LTE (ESM), cause codes are in
* TS 24.301 9.9.4.4. Integer.MAX_VALUE if this value is unused.
+ * @hide
*/
public void notifyRegistrationFailed(int slotIndex, int subId,
@NonNull CellIdentity cellIdentity, @NonNull String chosenPlmn,
@@ -914,6 +955,7 @@
* @param slotIndex for the phone object that got updated barring info.
* @param subId for which the BarringInfo changed.
* @param barringInfo updated BarringInfo.
+ * @hide
*/
public void notifyBarringInfoChanged(
int slotIndex, int subId, @NonNull BarringInfo barringInfo) {
@@ -931,6 +973,7 @@
* @param slotIndex for which physical channel configs changed.
* @param subId the subId
* @param configs a list of {@link PhysicalChannelConfig}, the configs of physical channel.
+ * @hide
*/
public void notifyPhysicalChannelConfigForSubscriber(int slotIndex, int subId,
List<PhysicalChannelConfig> configs) {
@@ -948,6 +991,7 @@
* @param enabled True if data is enabled, otherwise disabled.
* @param reason Reason for data enabled/disabled. See {@code REASON_*} in
* {@link TelephonyManager}.
+ * @hide
*/
public void notifyDataEnabled(int slotIndex, int subId, boolean enabled,
@TelephonyManager.DataEnabledReason int reason) {
@@ -966,6 +1010,7 @@
* @param subId for which allowed network types changed.
* @param reason an allowed network type reasons.
* @param allowedNetworkType an allowed network type bitmask value.
+ * @hide
*/
public void notifyAllowedNetworkTypesChanged(int slotIndex, int subId,
int reason, long allowedNetworkType) {
@@ -983,6 +1028,7 @@
* @param slotIndex for the phone object that gets the updated link capacity estimate
* @param subId for subscription that gets the updated link capacity estimate
* @param linkCapacityEstimateList a list of {@link LinkCapacityEstimate}
+ * @hide
*/
public void notifyLinkCapacityEstimateChanged(int slotIndex, int subId,
List<LinkCapacityEstimate> linkCapacityEstimateList) {
@@ -994,6 +1040,29 @@
}
}
+ /**
+ * Notify external listeners that the subscriptions supporting simultaneous cellular calling
+ * have changed.
+ * @param subIds The new set of subIds supporting simultaneous cellular calling.
+ * @hide
+ */
+ public void notifySimultaneousCellularCallingSubscriptionsChanged(
+ @NonNull Set<Integer> subIds) {
+ try {
+ sRegistry.notifySimultaneousCellularCallingSubscriptionsChanged(
+ subIds.stream().mapToInt(i -> i).toArray());
+ } catch (RemoteException ex) {
+ // system server crash
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Processes potential event changes from the provided {@link TelephonyCallback}.
+ *
+ * @param telephonyCallback callback for monitoring callback changes to the telephony state.
+ * @hide
+ */
public @NonNull Set<Integer> getEventsFromCallback(
@NonNull TelephonyCallback telephonyCallback) {
Set<Integer> eventList = new ArraySet<>();
@@ -1135,7 +1204,11 @@
eventList.add(TelephonyCallback.EVENT_EMERGENCY_CALLBACK_MODE_CHANGED);
}
-
+ if (telephonyCallback
+ instanceof TelephonyCallback.SimultaneousCellularCallingSupportListener) {
+ eventList.add(
+ TelephonyCallback.EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED);
+ }
return eventList;
}
@@ -1300,6 +1373,7 @@
* may encounter an {@link IllegalStateException} when trying to register more callbacks.
*
* @param callback The {@link TelephonyCallback} object to register.
+ * @hide
*/
public void registerTelephonyCallback(boolean renounceFineLocationAccess,
boolean renounceCoarseLocationAccess,
@@ -1319,6 +1393,7 @@
* Unregister an existing {@link TelephonyCallback}.
*
* @param callback The {@link TelephonyCallback} object to unregister.
+ * @hide
*/
public void unregisterTelephonyCallback(int subId, String pkgName, String attributionTag,
@NonNull TelephonyCallback callback, boolean notifyNow) {
@@ -1379,6 +1454,7 @@
* @param logicalSlotIndex The SIM slot to listen on
* @param executor The executor where {@code listener} will be invoked
* @param callback The callback to register
+ * @hide
*/
public void addCarrierPrivilegesCallback(
int logicalSlotIndex,
@@ -1413,6 +1489,7 @@
* Unregisters a {@link CarrierPrivilegesCallback}.
*
* @param callback The callback to unregister
+ * @hide
*/
public void removeCarrierPrivilegesCallback(@NonNull CarrierPrivilegesCallback callback) {
if (callback == null) {
@@ -1438,6 +1515,7 @@
* @param logicalSlotIndex The SIM slot the change occurred on
* @param privilegedPackageNames The updated set of packages names with carrier privileges
* @param privilegedUids The updated set of UIDs with carrier privileges
+ * @hide
*/
public void notifyCarrierPrivilegesChanged(
int logicalSlotIndex,
@@ -1463,6 +1541,7 @@
* @param logicalSlotIndex the SIM slot the change occurred on
* @param packageName the package name of the changed {@link CarrierService}
* @param uid the UID of the changed {@link CarrierService}
+ * @hide
*/
public void notifyCarrierServiceChanged(int logicalSlotIndex, @Nullable String packageName,
int uid) {
@@ -1479,6 +1558,7 @@
*
* @param executor The executor on which the callback will be executed.
* @param listener The CarrierConfigChangeListener to be registered with.
+ * @hide
*/
public void addCarrierConfigChangedListener(
@NonNull @CallbackExecutor Executor executor,
@@ -1519,6 +1599,7 @@
* Unregister to stop the notification when carrier configurations changed.
*
* @param listener The CarrierConfigChangeListener to be unregistered with.
+ * @hide
*/
public void removeCarrierConfigChangedListener(
@NonNull CarrierConfigManager.CarrierConfigChangeListener listener) {
@@ -1548,6 +1629,7 @@
* {@link TelephonyManager#UNKNOWN_CARRIER_ID}.
* @param specificCarrierId The optional specific carrier Id, may be {@link
* TelephonyManager#UNKNOWN_CARRIER_ID}.
+ * @hide
*/
public void notifyCarrierConfigChanged(int slotIndex, int subId, int carrierId,
int specificCarrierId) {
@@ -1569,6 +1651,7 @@
* @param subId Sender subscription ID.
* @param type for callback mode entry.
* See {@link TelephonyManager.EmergencyCallbackModeType}.
+ * @hide
*/
public void notifyCallBackModeStarted(int phoneId, int subId,
@TelephonyManager.EmergencyCallbackModeType int type) {
@@ -1589,6 +1672,7 @@
* See {@link TelephonyManager.EmergencyCallbackModeType}.
* @param reason for changing callback mode.
* See {@link TelephonyManager.EmergencyCallbackModeStopReason}.
+ * @hide
*/
public void notifyCallbackModeStopped(int phoneId, int subId,
@TelephonyManager.EmergencyCallbackModeType int type,
diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java
index 4c81888..a6d3bb4 100644
--- a/core/java/android/text/BoringLayout.java
+++ b/core/java/android/text/BoringLayout.java
@@ -454,7 +454,7 @@
line.set(paint, source, 0, source.length(), Layout.DIR_LEFT_TO_RIGHT,
Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null,
mEllipsizedStart, mEllipsizedStart + mEllipsizedCount, useFallbackLineSpacing);
- mMax = (int) Math.ceil(line.metrics(null, null, false));
+ mMax = (int) Math.ceil(line.metrics(null, null, false, null));
TextLine.recycle(line);
}
@@ -603,7 +603,7 @@
0 /* ellipsisStart, 0 since text has not been ellipsized at this point */,
0 /* ellipsisEnd, 0 since text has not been ellipsized at this point */,
useFallbackLineSpacing);
- fm.width = (int) Math.ceil(line.metrics(fm, fm.mDrawingBounds, false));
+ fm.width = (int) Math.ceil(line.metrics(fm, fm.mDrawingBounds, false, null));
TextLine.recycle(line);
return fm;
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index c9906cc..eca848a 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -18,6 +18,7 @@
import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
+import static com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION;
import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
@@ -50,8 +51,10 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.text.BreakIterator;
import java.util.Arrays;
import java.util.List;
+import java.util.Locale;
/**
* A base class that manages text layout in visual elements on
@@ -669,7 +672,8 @@
int start = previousLineEnd;
previousLineEnd = getLineStart(lineNum + 1);
final boolean justify = isJustificationRequired(lineNum);
- int end = getLineVisibleEnd(lineNum, start, previousLineEnd);
+ int end = getLineVisibleEnd(lineNum, start, previousLineEnd,
+ true /* trailingSpaceAtLastLineIsVisible */);
paint.setStartHyphenEdit(getStartHyphenEdit(lineNum));
paint.setEndHyphenEdit(getEndHyphenEdit(lineNum));
@@ -1056,7 +1060,7 @@
if (isJustificationRequired(line)) {
tl.justify(getJustifyWidth(line));
}
- tl.metrics(null, rectF, false);
+ tl.metrics(null, rectF, false, null);
float lineLeft = rectF.left;
float lineRight = rectF.right;
@@ -1456,7 +1460,7 @@
tl.set(mPaint, mText, start, end, dir, directions, hasTab, tabStops,
getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line),
isFallbackLineSpacingEnabled());
- float wid = tl.measure(offset - start, trailing, null, null);
+ float wid = tl.measure(offset - start, trailing, null, null, null);
TextLine.recycle(tl);
if (clamped && wid > mWidth) {
@@ -1792,12 +1796,69 @@
if (isJustificationRequired(line)) {
tl.justify(getJustifyWidth(line));
}
- final float width = tl.metrics(null, null, mUseBoundsForWidth);
+ final float width = tl.metrics(null, null, mUseBoundsForWidth, null);
TextLine.recycle(tl);
return width;
}
/**
+ * Returns the number of letter spacing unit in the line.
+ *
+ * <p>
+ * This API returns a number of letters that is a target of letter spacing. The letter spacing
+ * won't be added to the middle of the characters that are needed to be treated as a single,
+ * e.g., ligatured or conjunct form. Note that this value is different from the number of]
+ * grapheme clusters that is calculated by {@link BreakIterator#getCharacterInstance(Locale)}.
+ * For example, if the "fi" is ligatured, the ligatured form is treated as single uni and letter
+ * spacing is not added, but it has two separate grapheme cluster.
+ *
+ * <p>
+ * This value is used for calculating the letter spacing amount for the justification because
+ * the letter spacing is applied between clusters. For example, if extra {@code W} pixels needed
+ * to be filled by letter spacing, the amount of letter spacing to be applied is
+ * {@code W}/(letter spacing unit count - 1) px.
+ *
+ * @param line the index of the line
+ * @param includeTrailingWhitespace whether to include trailing whitespace
+ * @return the number of cluster count in the line.
+ */
+ @IntRange(from = 0)
+ @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+ public int getLineLetterSpacingUnitCount(@IntRange(from = 0) int line,
+ boolean includeTrailingWhitespace) {
+ final int start = getLineStart(line);
+ final int end = includeTrailingWhitespace ? getLineEnd(line)
+ : getLineVisibleEnd(line, getLineStart(line), getLineStart(line + 1),
+ false // trailingSpaceAtLastLineIsVisible: Treating trailing whitespaces at
+ // the last line as a invisible chars for single line justification.
+ );
+
+ final Directions directions = getLineDirections(line);
+ // Returned directions can actually be null
+ if (directions == null) {
+ return 0;
+ }
+ final int dir = getParagraphDirection(line);
+
+ final TextLine tl = TextLine.obtain();
+ final TextPaint paint = mWorkPaint;
+ paint.set(mPaint);
+ paint.setStartHyphenEdit(getStartHyphenEdit(line));
+ paint.setEndHyphenEdit(getEndHyphenEdit(line));
+ tl.set(paint, mText, start, end, dir, directions,
+ false, null, // tab width is not used for cluster counting.
+ getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line),
+ isFallbackLineSpacingEnabled());
+ if (mLineInfo == null) {
+ mLineInfo = new TextLine.LineInfo();
+ }
+ mLineInfo.setClusterCount(0);
+ tl.metrics(null, null, mUseBoundsForWidth, mLineInfo);
+ TextLine.recycle(tl);
+ return mLineInfo.getClusterCount();
+ }
+
+ /**
* Returns the signed horizontal extent of the specified line, excluding
* leading margin. If full is false, excludes trailing whitespace.
* @param line the index of the line
@@ -1823,7 +1884,7 @@
if (isJustificationRequired(line)) {
tl.justify(getJustifyWidth(line));
}
- final float width = tl.metrics(null, null, mUseBoundsForWidth);
+ final float width = tl.metrics(null, null, mUseBoundsForWidth, null);
TextLine.recycle(tl);
return width;
}
@@ -2432,14 +2493,21 @@
* is not counted) on the specified line.
*/
public int getLineVisibleEnd(int line) {
- return getLineVisibleEnd(line, getLineStart(line), getLineStart(line+1));
+ return getLineVisibleEnd(line, getLineStart(line), getLineStart(line + 1),
+ true /* trailingSpaceAtLastLineIsVisible */);
}
- private int getLineVisibleEnd(int line, int start, int end) {
+ private int getLineVisibleEnd(int line, int start, int end,
+ boolean trailingSpaceAtLastLineIsVisible) {
CharSequence text = mText;
char ch;
- if (line == getLineCount() - 1) {
- return end;
+
+ // Historically, trailing spaces at the last line is counted as visible. However, this
+ // doesn't work well for justification.
+ if (trailingSpaceAtLastLineIsVisible) {
+ if (line == getLineCount() - 1) {
+ return end;
+ }
}
for (; end > start; end--) {
@@ -2939,7 +3007,7 @@
tl.set(paint, text, start, end, dir, directions, hasTabs, tabStops,
0 /* ellipsisStart */, 0 /* ellipsisEnd */,
false /* use fallback line spacing. unused */);
- return margin + Math.abs(tl.metrics(null, null, useBoundsForWidth));
+ return margin + Math.abs(tl.metrics(null, null, useBoundsForWidth, null));
} finally {
TextLine.recycle(tl);
if (mt != null) {
@@ -3337,6 +3405,8 @@
private boolean mUseBoundsForWidth;
private @Nullable Paint.FontMetrics mMinimumFontMetrics;
+ private TextLine.LineInfo mLineInfo = null;
+
/** @hide */
@IntDef(prefix = { "DIR_" }, value = {
DIR_LEFT_TO_RIGHT,
diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java
index ac5eb3c..b268c2e 100644
--- a/core/java/android/text/MeasuredParagraph.java
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -671,9 +671,18 @@
if (mLtrWithoutBidi) {
// If the whole text is LTR direction, just apply whole region.
if (builder == null) {
- mWholeWidth += paint.getTextRunAdvances(
- mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */,
- mWidths.getRawArray(), start);
+ // For the compatibility reasons, the letter spacing should not be dropped at the
+ // left and right edge.
+ int oldFlag = paint.getFlags();
+ paint.setFlags(paint.getFlags()
+ | (Paint.TEXT_RUN_FLAG_LEFT_EDGE | Paint.TEXT_RUN_FLAG_RIGHT_EDGE));
+ try {
+ mWholeWidth += paint.getTextRunAdvances(
+ mCopiedBuffer, start, end - start, start, end - start,
+ false /* isRtl */, mWidths.getRawArray(), start);
+ } finally {
+ paint.setFlags(oldFlag);
+ }
} else {
builder.appendStyleRun(paint, config, end - start, false /* isRtl */);
}
@@ -690,9 +699,16 @@
final boolean isRtl = (level & 0x1) != 0;
if (builder == null) {
final int levelLength = levelEnd - levelStart;
- mWholeWidth += paint.getTextRunAdvances(
- mCopiedBuffer, levelStart, levelLength, levelStart, levelLength,
- isRtl, mWidths.getRawArray(), levelStart);
+ int oldFlag = paint.getFlags();
+ paint.setFlags(paint.getFlags()
+ | (Paint.TEXT_RUN_FLAG_LEFT_EDGE | Paint.TEXT_RUN_FLAG_RIGHT_EDGE));
+ try {
+ mWholeWidth += paint.getTextRunAdvances(
+ mCopiedBuffer, levelStart, levelLength, levelStart, levelLength,
+ isRtl, mWidths.getRawArray(), levelStart);
+ } finally {
+ paint.setFlags(oldFlag);
+ }
} else {
builder.appendStyleRun(paint, config, levelEnd - levelStart, isRtl);
}
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index f9abec0..2175b47 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -76,6 +76,21 @@
private RectF mTmpRectForPaintAPI;
private Rect mTmpRectForPrecompute;
+ // Recycling object for Paint APIs. Do not use outside getRunAdvances method.
+ private Paint.RunInfo mRunInfo;
+
+ public static final class LineInfo {
+ private int mClusterCount;
+
+ public int getClusterCount() {
+ return mClusterCount;
+ }
+
+ public void setClusterCount(int clusterCount) {
+ mClusterCount = clusterCount;
+ }
+ };
+
private boolean mUseFallbackExtent = false;
// The start and end of a potentially existing ellipsis on this text line.
@@ -270,12 +285,103 @@
// width.
return;
}
- final float width = Math.abs(measure(end, false, null, null));
+ final float width = Math.abs(measure(end, false, null, null, null));
mAddedWidthForJustify = (justifyWidth - width) / spaces;
mIsJustifying = true;
}
/**
+ * Returns the run flag of at the given BiDi run.
+ *
+ * @param bidiRunIndex a BiDi run index.
+ * @return a run flag of the given BiDi run.
+ */
+ @VisibleForTesting
+ public static int calculateRunFlag(int bidiRunIndex, int bidiRunCount, int lineDirection) {
+ if (bidiRunCount == 1) {
+ // Easy case. If there is only single run, it is most left and most right run.
+ return Paint.TEXT_RUN_FLAG_LEFT_EDGE | Paint.TEXT_RUN_FLAG_RIGHT_EDGE;
+ }
+ if (bidiRunIndex != 0 && bidiRunIndex != (bidiRunCount - 1)) {
+ // Easy case. If the given run is the middle of the line, it is not the most left or
+ // the most right run.
+ return 0;
+ }
+
+ int runFlag = 0;
+ // For the historical reasons, the BiDi implementation of Android works differently
+ // from the Java BiDi APIs. The mDirections holds the BiDi runs in visual order, but
+ // it is reversed order if the paragraph direction is RTL. So, the first BiDi run of
+ // mDirections is located the most left of the line if the paragraph direction is LTR.
+ // If the paragraph direction is RTL, the first BiDi run is located the most right of
+ // the line.
+ if (bidiRunIndex == 0) {
+ if (lineDirection == Layout.DIR_LEFT_TO_RIGHT) {
+ runFlag |= Paint.TEXT_RUN_FLAG_LEFT_EDGE;
+ } else {
+ runFlag |= Paint.TEXT_RUN_FLAG_RIGHT_EDGE;
+ }
+ }
+ if (bidiRunIndex == (bidiRunCount - 1)) {
+ if (lineDirection == Layout.DIR_LEFT_TO_RIGHT) {
+ runFlag |= Paint.TEXT_RUN_FLAG_RIGHT_EDGE;
+ } else {
+ runFlag |= Paint.TEXT_RUN_FLAG_LEFT_EDGE;
+ }
+ }
+ return runFlag;
+ }
+
+ /**
+ * Resolve the runFlag for the inline span range.
+ *
+ * @param runFlag the runFlag of the current BiDi run.
+ * @param isRtlRun true for RTL run, false for LTR run.
+ * @param runStart the inclusive BiDi run start offset.
+ * @param runEnd the exclusive BiDi run end offset.
+ * @param spanStart the inclusive span start offset.
+ * @param spanEnd the exclusive span end offset.
+ * @return the resolved runFlag.
+ */
+ @VisibleForTesting
+ public static int resolveRunFlagForSubSequence(int runFlag, boolean isRtlRun, int runStart,
+ int runEnd, int spanStart, int spanEnd) {
+ if (runFlag == 0) {
+ // Easy case. If the run is in the middle of the line, any inline span is also in the
+ // middle of the line.
+ return 0;
+ }
+ int localRunFlag = runFlag;
+ if ((runFlag & Paint.TEXT_RUN_FLAG_LEFT_EDGE) != 0) {
+ if (isRtlRun) {
+ if (spanEnd != runEnd) {
+ // In the RTL context, the last run is the most left run.
+ localRunFlag &= ~Paint.TEXT_RUN_FLAG_LEFT_EDGE;
+ }
+ } else { // LTR
+ if (spanStart != runStart) {
+ // In the LTR context, the first run is the most left run.
+ localRunFlag &= ~Paint.TEXT_RUN_FLAG_LEFT_EDGE;
+ }
+ }
+ }
+ if ((runFlag & Paint.TEXT_RUN_FLAG_RIGHT_EDGE) != 0) {
+ if (isRtlRun) {
+ if (spanStart != runStart) {
+ // In the RTL context, the start of the run is the most right run.
+ localRunFlag &= ~Paint.TEXT_RUN_FLAG_RIGHT_EDGE;
+ }
+ } else { // LTR
+ if (spanEnd != runEnd) {
+ // In the LTR context, the last run is the most right position.
+ localRunFlag &= ~Paint.TEXT_RUN_FLAG_RIGHT_EDGE;
+ }
+ }
+ }
+ return localRunFlag;
+ }
+
+ /**
* Renders the TextLine.
*
* @param c the canvas to render on
@@ -293,11 +399,13 @@
final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+ final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
+
int segStart = runStart;
for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
if (j == runLimit || charAt(j) == TAB_CHAR) {
h += drawRun(c, segStart, j, runIsRtl, x + h, top, y, bottom,
- runIndex != (runCount - 1) || j != mLen);
+ runIndex != (runCount - 1) || j != mLen, runFlag);
if (j != runLimit) { // charAt(j) == TAB_CHAR
h = mDir * nextTab(h * mDir);
@@ -315,10 +423,12 @@
* @param drawBounds output parameter for drawing bounding box. optional.
* @param returnDrawWidth true for returning width of the bounding box, false for returning
* total advances.
+ * @param lineInfo an optional output parameter for filling line information.
* @return the signed width of the line
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- public float metrics(FontMetricsInt fmi, @Nullable RectF drawBounds, boolean returnDrawWidth) {
+ public float metrics(FontMetricsInt fmi, @Nullable RectF drawBounds, boolean returnDrawWidth,
+ @Nullable LineInfo lineInfo) {
if (returnDrawWidth) {
if (drawBounds == null) {
if (mTmpRectForMeasure == null) {
@@ -327,7 +437,7 @@
drawBounds = mTmpRectForMeasure;
}
drawBounds.setEmpty();
- float w = measure(mLen, false, fmi, drawBounds);
+ float w = measure(mLen, false, fmi, drawBounds, lineInfo);
float boundsWidth = drawBounds.width();
if (Math.abs(w) > boundsWidth) {
return w;
@@ -337,7 +447,7 @@
return Math.signum(w) * boundsWidth;
}
} else {
- return measure(mLen, false, fmi, drawBounds);
+ return measure(mLen, false, fmi, drawBounds, lineInfo);
}
}
@@ -354,11 +464,12 @@
final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+ final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
int segStart = runStart;
for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
if (j == runLimit || charAt(j) == TAB_CHAR) {
horizontal += shapeRun(consumer, segStart, j, runIsRtl, x + horizontal,
- runIndex != (runCount - 1) || j != mLen);
+ runIndex != (runCount - 1) || j != mLen, runFlag);
if (j != runLimit) { // charAt(j) == TAB_CHAR
horizontal = mDir * nextTab(horizontal * mDir);
@@ -407,12 +518,13 @@
* the edge of the preceding run's edge. See example above.
* @param fmi receives metrics information about the requested character, can be null
* @param drawBounds output parameter for drawing bounding box. optional.
+ * @param lineInfo an optional output parameter for filling line information.
* @return the signed graphical offset from the leading margin to the requested character edge.
* The positive value means the offset is right from the leading edge. The negative
* value means the offset is left from the leading edge.
*/
public float measure(@IntRange(from = 0) int offset, boolean trailing,
- @NonNull FontMetricsInt fmi, @Nullable RectF drawBounds) {
+ @NonNull FontMetricsInt fmi, @Nullable RectF drawBounds, @Nullable LineInfo lineInfo) {
if (offset > mLen) {
throw new IndexOutOfBoundsException(
"offset(" + offset + ") should be less than line limit(" + mLen + ")");
@@ -423,11 +535,13 @@
}
float h = 0;
- for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
+ final int runCount = mDirections.getRunCount();
+ for (int runIndex = 0; runIndex < runCount; runIndex++) {
final int runStart = mDirections.getRunStart(runIndex);
if (runStart > mLen) break;
final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+ final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
int segStart = runStart;
for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
@@ -437,16 +551,16 @@
if (targetIsInThisSegment && sameDirection) {
return h + measureRun(segStart, offset, j, runIsRtl, fmi, drawBounds, null,
- 0, h);
+ 0, h, lineInfo, runFlag);
}
final float segmentWidth = measureRun(segStart, j, j, runIsRtl, fmi, drawBounds,
- null, 0, h);
+ null, 0, h, lineInfo, runFlag);
h += sameDirection ? segmentWidth : -segmentWidth;
if (targetIsInThisSegment) {
return h + measureRun(segStart, offset, j, runIsRtl, null, null, null, 0,
- h);
+ h, lineInfo, runFlag);
}
if (j != runLimit) { // charAt(j) == TAB_CHAR
@@ -525,19 +639,21 @@
+ "result, needed: " + mLen + " had: " + advances.length);
}
float h = 0;
- for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
+ final int runCount = mDirections.getRunCount();
+ for (int runIndex = 0; runIndex < runCount; runIndex++) {
final int runStart = mDirections.getRunStart(runIndex);
if (runStart > mLen) break;
final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+ final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
int segStart = runStart;
for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
if (j == runLimit || charAt(j) == TAB_CHAR) {
final boolean sameDirection = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
-
final float segmentWidth =
- measureRun(segStart, j, j, runIsRtl, null, null, advances, segStart, 0);
+ measureRun(segStart, j, j, runIsRtl, null, null, advances, segStart, 0,
+ null, runFlag);
final float oldh = h;
h += sameDirection ? segmentWidth : -segmentWidth;
@@ -578,7 +694,7 @@
}
/**
- * @see #measure(int, boolean, FontMetricsInt, RectF)
+ * @see #measure(int, boolean, FontMetricsInt, RectF, LineInfo)
* @return The measure results for all possible offsets
*/
@VisibleForTesting
@@ -589,11 +705,13 @@
}
float horizontal = 0;
- for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
+ final int runCount = mDirections.getRunCount();
+ for (int runIndex = 0; runIndex < runCount; runIndex++) {
final int runStart = mDirections.getRunStart(runIndex);
if (runStart > mLen) break;
final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+ final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
int segStart = runStart;
for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; ++j) {
@@ -610,7 +728,7 @@
final float previousSegEndHorizontal = measurement[segStart];
final float width =
measureRun(segStart, j, j, runIsRtl, fmi, null, measurement, segStart,
- 0);
+ 0, null, runFlag);
horizontal += sameDirection ? width : -width;
float currHorizontal = sameDirection ? oldHorizontal : horizontal;
@@ -667,22 +785,24 @@
* @param y the baseline
* @param bottom the bottom of the line
* @param needWidth true if the width value is required.
+ * @param runFlag the run flag to be applied for this run.
* @return the signed width of the run, based on the paragraph direction.
* Only valid if needWidth is true.
*/
private float drawRun(Canvas c, int start,
int limit, boolean runIsRtl, float x, int top, int y, int bottom,
- boolean needWidth) {
+ boolean needWidth, int runFlag) {
if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
- float w = -measureRun(start, limit, limit, runIsRtl, null, null, null, 0, 0);
+ float w = -measureRun(start, limit, limit, runIsRtl, null, null, null, 0, 0, null,
+ runFlag);
handleRun(start, limit, limit, runIsRtl, c, null, x + w, top,
- y, bottom, null, null, false, null, 0);
+ y, bottom, null, null, false, null, 0, null, runFlag);
return w;
}
return handleRun(start, limit, limit, runIsRtl, c, null, x, top,
- y, bottom, null, null, needWidth, null, 0);
+ y, bottom, null, null, needWidth, null, 0, null, runFlag);
}
/**
@@ -698,19 +818,22 @@
* @param advances receives the advance information about the requested run, can be null.
* @param advancesIndex the start index to fill in the advance information.
* @param x horizontal offset of the run.
+ * @param lineInfo an optional output parameter for filling line information.
+ * @param runFlag the run flag to be applied for this run.
* @return the signed width from the start of the run to the leading edge
* of the character at offset, based on the run (not paragraph) direction
*/
private float measureRun(int start, int offset, int limit, boolean runIsRtl,
@Nullable FontMetricsInt fmi, @Nullable RectF drawBounds, @Nullable float[] advances,
- int advancesIndex, float x) {
+ int advancesIndex, float x, @Nullable LineInfo lineInfo, int runFlag) {
if (drawBounds != null && (mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
- float w = -measureRun(start, offset, limit, runIsRtl, null, null, null, 0, 0);
+ float w = -measureRun(start, offset, limit, runIsRtl, null, null, null, 0, 0, null,
+ runFlag);
return handleRun(start, offset, limit, runIsRtl, null, null, x + w, 0, 0, 0, fmi,
- drawBounds, true, advances, advancesIndex);
+ drawBounds, true, advances, advancesIndex, lineInfo, runFlag);
}
return handleRun(start, offset, limit, runIsRtl, null, null, x, 0, 0, 0, fmi, drawBounds,
- true, advances, advancesIndex);
+ true, advances, advancesIndex, lineInfo, runFlag);
}
/**
@@ -722,21 +845,23 @@
* @param runIsRtl true if the run is right-to-left
* @param x the position of the run that is closest to the leading margin
* @param needWidth true if the width value is required.
+ * @param runFlag the run flag to be applied for this run.
* @return the signed width of the run, based on the paragraph direction.
* Only valid if needWidth is true.
*/
private float shapeRun(TextShaper.GlyphsConsumer consumer, int start,
- int limit, boolean runIsRtl, float x, boolean needWidth) {
+ int limit, boolean runIsRtl, float x, boolean needWidth, int runFlag) {
if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
- float w = -measureRun(start, limit, limit, runIsRtl, null, null, null, 0, 0);
+ float w = -measureRun(start, limit, limit, runIsRtl, null, null, null, 0, 0, null,
+ runFlag);
handleRun(start, limit, limit, runIsRtl, null, consumer, x + w, 0, 0, 0, null, null,
- false, null, 0);
+ false, null, 0, null, runFlag);
return w;
}
return handleRun(start, limit, limit, runIsRtl, null, consumer, x, 0, 0, 0, null, null,
- needWidth, null, 0);
+ needWidth, null, 0, null, runFlag);
}
@@ -1077,16 +1202,35 @@
private float getRunAdvance(TextPaint wp, int start, int end, int contextStart, int contextEnd,
boolean runIsRtl, int offset, @Nullable float[] advances, int advancesIndex,
- RectF drawingBounds) {
+ RectF drawingBounds, @Nullable LineInfo lineInfo) {
+ if (lineInfo != null) {
+ if (mRunInfo == null) {
+ mRunInfo = new Paint.RunInfo();
+ }
+ mRunInfo.setClusterCount(0);
+ } else {
+ mRunInfo = null;
+ }
if (mCharsValid) {
- return wp.getRunCharacterAdvance(mChars, start, end, contextStart, contextEnd,
- runIsRtl, offset, advances, advancesIndex, drawingBounds);
+ float r = wp.getRunCharacterAdvance(mChars, start, end, contextStart, contextEnd,
+ runIsRtl, offset, advances, advancesIndex, drawingBounds, mRunInfo);
+ if (lineInfo != null) {
+ lineInfo.setClusterCount(lineInfo.getClusterCount() + mRunInfo.getClusterCount());
+ }
+ return r;
} else {
final int delta = mStart;
- if (mComputed == null || advances != null) {
- return wp.getRunCharacterAdvance(mText, delta + start, delta + end,
+ // TODO: Add cluster information to the PrecomputedText for better performance of
+ // justification.
+ if (mComputed == null || advances != null || lineInfo != null) {
+ float r = wp.getRunCharacterAdvance(mText, delta + start, delta + end,
delta + contextStart, delta + contextEnd, runIsRtl,
- delta + offset, advances, advancesIndex, drawingBounds);
+ delta + offset, advances, advancesIndex, drawingBounds, mRunInfo);
+ if (lineInfo != null) {
+ lineInfo.setClusterCount(
+ lineInfo.getClusterCount() + mRunInfo.getClusterCount());
+ }
+ return r;
} else {
if (drawingBounds != null) {
if (mTmpRectForPrecompute == null) {
@@ -1120,6 +1264,8 @@
* @param decorations the list of locations and paremeters for drawing decorations
* @param advances receives the advance information about the requested run, can be null.
* @param advancesIndex the start index to fill in the advance information.
+ * @param lineInfo an optional output parameter for filling line information.
+ * @param runFlag the run flag to be applied for this run.
* @return the signed width of the run based on the run direction; only
* valid if needWidth is true
*/
@@ -1128,8 +1274,8 @@
Canvas c, TextShaper.GlyphsConsumer consumer, float x, int top, int y, int bottom,
FontMetricsInt fmi, RectF drawBounds, boolean needWidth, int offset,
@Nullable ArrayList<DecorationInfo> decorations,
- @Nullable float[] advances, int advancesIndex) {
-
+ @Nullable float[] advances, int advancesIndex, @Nullable LineInfo lineInfo,
+ int runFlag) {
if (mIsJustifying) {
wp.setWordSpacing(mAddedWidthForJustify);
}
@@ -1147,7 +1293,16 @@
}
float totalWidth = 0;
-
+ if ((runFlag & Paint.TEXT_RUN_FLAG_LEFT_EDGE) == Paint.TEXT_RUN_FLAG_LEFT_EDGE) {
+ wp.setFlags(wp.getFlags() | Paint.TEXT_RUN_FLAG_LEFT_EDGE);
+ } else {
+ wp.setFlags(wp.getFlags() & ~Paint.TEXT_RUN_FLAG_LEFT_EDGE);
+ }
+ if ((runFlag & Paint.TEXT_RUN_FLAG_RIGHT_EDGE) == Paint.TEXT_RUN_FLAG_RIGHT_EDGE) {
+ wp.setFlags(wp.getFlags() | Paint.TEXT_RUN_FLAG_RIGHT_EDGE);
+ } else {
+ wp.setFlags(wp.getFlags() & ~Paint.TEXT_RUN_FLAG_RIGHT_EDGE);
+ }
final int numDecorations = decorations == null ? 0 : decorations.size();
if (needWidth || ((c != null || consumer != null) && (wp.bgColor != 0
|| numDecorations != 0 || runIsRtl))) {
@@ -1155,7 +1310,8 @@
mTmpRectForPaintAPI = new RectF();
}
totalWidth = getRunAdvance(wp, start, end, contextStart, contextEnd, runIsRtl, offset,
- advances, advancesIndex, drawBounds == null ? null : mTmpRectForPaintAPI);
+ advances, advancesIndex, drawBounds == null ? null : mTmpRectForPaintAPI,
+ lineInfo);
if (drawBounds != null) {
if (runIsRtl) {
mTmpRectForPaintAPI.offset(x - totalWidth, 0);
@@ -1206,9 +1362,9 @@
final int decorationStart = Math.max(info.start, start);
final int decorationEnd = Math.min(info.end, offset);
float decorationStartAdvance = getRunAdvance(wp, start, end, contextStart,
- contextEnd, runIsRtl, decorationStart, null, 0, null);
+ contextEnd, runIsRtl, decorationStart, null, 0, null, null);
float decorationEndAdvance = getRunAdvance(wp, start, end, contextStart,
- contextEnd, runIsRtl, decorationEnd, null, 0, null);
+ contextEnd, runIsRtl, decorationEnd, null, 0, null, null);
final float decorationXLeft, decorationXRight;
if (runIsRtl) {
decorationXLeft = rightX - decorationEndAdvance;
@@ -1377,6 +1533,8 @@
* @param needWidth true if the width is required
* @param advances receives the advance information about the requested run, can be null.
* @param advancesIndex the start index to fill in the advance information.
+ * @param lineInfo an optional output parameter for filling line information.
+ * @param runFlag the run flag to be applied for this run.
* @return the signed width of the run based on the run direction; only
* valid if needWidth is true
*/
@@ -1384,7 +1542,8 @@
int limit, boolean runIsRtl, Canvas c,
TextShaper.GlyphsConsumer consumer, float x, int top, int y,
int bottom, FontMetricsInt fmi, RectF drawBounds, boolean needWidth,
- @Nullable float[] advances, int advancesIndex) {
+ @Nullable float[] advances, int advancesIndex, @Nullable LineInfo lineInfo,
+ int runFlag) {
if (measureLimit < start || measureLimit > limit) {
throw new IndexOutOfBoundsException("measureLimit (" + measureLimit + ") is out of "
@@ -1431,7 +1590,7 @@
wp.setEndHyphenEdit(adjustEndHyphenEdit(limit, wp.getEndHyphenEdit()));
return handleText(wp, start, limit, start, limit, runIsRtl, c, consumer, x, top,
y, bottom, fmi, drawBounds, needWidth, measureLimit, null, advances,
- advancesIndex);
+ advancesIndex, lineInfo, runFlag);
}
// Shaping needs to take into account context up to metric boundaries,
@@ -1512,6 +1671,9 @@
// and use.
activePaint.set(wp);
} else if (!equalAttributes(wp, activePaint)) {
+ final int spanRunFlag = resolveRunFlagForSubSequence(
+ runFlag, runIsRtl, start, measureLimit, activeStart, activeEnd);
+
// The style of the present chunk of text is substantially different from the
// style of the previous chunk. We need to handle the active piece of text
// and restart with the present chunk.
@@ -1523,7 +1685,7 @@
consumer, x, top, y, bottom, fmi, drawBounds,
needWidth || activeEnd < measureLimit,
Math.min(activeEnd, mlimit), mDecorations,
- advances, advancesIndex + activeStart - start);
+ advances, advancesIndex + activeStart - start, lineInfo, spanRunFlag);
activeStart = j;
activePaint.set(wp);
@@ -1543,6 +1705,9 @@
mDecorations.add(copy);
}
}
+
+ final int spanRunFlag = resolveRunFlagForSubSequence(
+ runFlag, runIsRtl, start, measureLimit, activeStart, activeEnd);
// Handle the final piece of text.
activePaint.setStartHyphenEdit(
adjustStartHyphenEdit(activeStart, mPaint.getStartHyphenEdit()));
@@ -1551,7 +1716,7 @@
x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, consumer, x,
top, y, bottom, fmi, drawBounds, needWidth || activeEnd < measureLimit,
Math.min(activeEnd, mlimit), mDecorations,
- advances, advancesIndex + activeStart - start);
+ advances, advancesIndex + activeStart - start, lineInfo, spanRunFlag);
}
return x - originalX;
@@ -1572,7 +1737,6 @@
*/
private void drawTextRun(Canvas c, TextPaint wp, int start, int end,
int contextStart, int contextEnd, boolean runIsRtl, float x, int y) {
-
if (mCharsValid) {
int count = end - start;
int contextCount = contextEnd - contextStart;
diff --git a/core/java/android/tracing/perfetto/CreateIncrementalStateArgs.java b/core/java/android/tracing/perfetto/CreateIncrementalStateArgs.java
new file mode 100644
index 0000000..819cc8c
--- /dev/null
+++ b/core/java/android/tracing/perfetto/CreateIncrementalStateArgs.java
@@ -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 android.tracing.perfetto;
+
+import android.annotation.Nullable;
+
+/**
+ * @hide
+ * @param <DataSourceInstanceType> The type of datasource instance this state applied to.
+ */
+public class CreateIncrementalStateArgs<DataSourceInstanceType extends DataSourceInstance> {
+ private final DataSource<DataSourceInstanceType, Object, Object> mDataSource;
+ private final int mInstanceIndex;
+
+ CreateIncrementalStateArgs(DataSource dataSource, int instanceIndex) {
+ this.mDataSource = dataSource;
+ this.mInstanceIndex = instanceIndex;
+ }
+
+ /**
+ * Gets the datasource instance for this state with a lock.
+ * releaseDataSourceInstanceLocked must be called before this can be called again.
+ * @return The data source instance for this state.
+ * Null if the datasource instance no longer exists.
+ */
+ public @Nullable DataSourceInstanceType getDataSourceInstanceLocked() {
+ return mDataSource.getDataSourceInstanceLocked(mInstanceIndex);
+ }
+}
diff --git a/core/java/android/tracing/perfetto/CreateTlsStateArgs.java b/core/java/android/tracing/perfetto/CreateTlsStateArgs.java
new file mode 100644
index 0000000..3fad2d1
--- /dev/null
+++ b/core/java/android/tracing/perfetto/CreateTlsStateArgs.java
@@ -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 android.tracing.perfetto;
+
+import android.annotation.Nullable;
+
+/**
+ * @hide
+ * @param <DataSourceInstanceType> The type of datasource instance this state applied to.
+ */
+public class CreateTlsStateArgs<DataSourceInstanceType extends DataSourceInstance> {
+ private final DataSource<DataSourceInstanceType, Object, Object> mDataSource;
+ private final int mInstanceIndex;
+
+ CreateTlsStateArgs(DataSource dataSource, int instanceIndex) {
+ this.mDataSource = dataSource;
+ this.mInstanceIndex = instanceIndex;
+ }
+
+ /**
+ * Gets the datasource instance for this state with a lock.
+ * releaseDataSourceInstanceLocked must be called before this can be called again.
+ * @return The data source instance for this state.
+ * Null if the datasource instance no longer exists.
+ */
+ public @Nullable DataSourceInstanceType getDataSourceInstanceLocked() {
+ return mDataSource.getDataSourceInstanceLocked(mInstanceIndex);
+ }
+}
diff --git a/core/java/android/tracing/perfetto/DataSource.java b/core/java/android/tracing/perfetto/DataSource.java
new file mode 100644
index 0000000..4e08aee
--- /dev/null
+++ b/core/java/android/tracing/perfetto/DataSource.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.tracing.perfetto;
+
+import android.util.proto.ProtoInputStream;
+
+/**
+ * Templated base class meant to be derived by embedders to create a custom data
+ * source.
+ *
+ * @param <DataSourceInstanceType> The type for the DataSource instances that will be created from
+ * this DataSource type.
+ * @param <TlsStateType> The type of the custom TLS state, if any is used.
+ * @param <IncrementalStateType> The type of the custom incremental state, if any is used.
+ *
+ * @hide
+ */
+public abstract class DataSource<DataSourceInstanceType extends DataSourceInstance,
+ TlsStateType, IncrementalStateType> {
+ protected final long mNativeObj;
+
+ public final String name;
+
+ /**
+ * A function implemented by each datasource to create a new data source instance.
+ *
+ * @param configStream A ProtoInputStream to read the tracing instance's config.
+ * @return A new data source instance setup with the provided config.
+ */
+ public abstract DataSourceInstanceType createInstance(
+ ProtoInputStream configStream, int instanceIndex);
+
+ /**
+ * Constructor for datasource base class.
+ *
+ * @param name The fully qualified name of the datasource.
+ */
+ public DataSource(String name) {
+ this.name = name;
+ this.mNativeObj = nativeCreate(this, name);
+ }
+
+ /**
+ * The main tracing method. Tracing code should call this passing a lambda as
+ * argument, with the following signature: void(TraceContext).
+ * <p>
+ * The lambda will be called synchronously (i.e., always before trace()
+ * returns) only if tracing is enabled and the data source has been enabled in
+ * the tracing config.
+ * <p>
+ * The lambda can be called more than once per trace() call, in the case of
+ * concurrent tracing sessions (or even if the data source is instantiated
+ * twice within the same trace config).
+ *
+ * @param fun The tracing lambda that will be called with the tracing contexts of each active
+ * tracing instance.
+ */
+ public final void trace(
+ TraceFunction<DataSourceInstanceType, TlsStateType, IncrementalStateType> fun) {
+ nativeTrace(mNativeObj, fun);
+ }
+
+ /**
+ * Flush any trace data from this datasource that has not yet been flushed.
+ */
+ public final void flush() {
+ nativeFlushAll(mNativeObj);
+ }
+
+ /**
+ * Override this method to create a custom TlsState object for your DataSource. A new instance
+ * will be created per trace instance per thread.
+ *
+ * NOTE: Should only be called from native side.
+ */
+ protected TlsStateType createTlsState(CreateTlsStateArgs<DataSourceInstanceType> args) {
+ return null;
+ }
+
+ /**
+ * Override this method to create and use a custom IncrementalState object for your DataSource.
+ *
+ * NOTE: Should only be called from native side.
+ */
+ protected IncrementalStateType createIncrementalState(
+ CreateIncrementalStateArgs<DataSourceInstanceType> args) {
+ return null;
+ }
+
+ /**
+ * Registers the data source on all tracing backends, including ones that
+ * connect after the registration. Doing so enables the data source to receive
+ * Setup/Start/Stop notifications and makes the trace() method work when
+ * tracing is enabled and the data source is selected.
+ * <p>
+ * NOTE: Once registered, we cannot unregister the data source. Therefore, we should avoid
+ * creating and registering data source where not strictly required. This is a fundamental
+ * limitation of Perfetto itself.
+ *
+ * @param params Params to initialize the datasource with.
+ */
+ public void register(DataSourceParams params) {
+ nativeRegisterDataSource(this.mNativeObj, params.bufferExhaustedPolicy);
+ }
+
+ /**
+ * Gets the datasource instance with a specified index.
+ * IMPORTANT: releaseDataSourceInstance must be called after using the datasource instance.
+ * @param instanceIndex The index of the datasource to lock and get.
+ * @return The DataSourceInstance at index instanceIndex.
+ * Null if the datasource instance at the requested index doesn't exist.
+ */
+ public DataSourceInstanceType getDataSourceInstanceLocked(int instanceIndex) {
+ return (DataSourceInstanceType) nativeGetPerfettoInstanceLocked(mNativeObj, instanceIndex);
+ }
+
+ /**
+ * Unlock the datasource at the specified index.
+ * @param instanceIndex The index of the datasource to unlock.
+ */
+ protected void releaseDataSourceInstance(int instanceIndex) {
+ nativeReleasePerfettoInstanceLocked(mNativeObj, instanceIndex);
+ }
+
+ /**
+ * Called from native side when a new tracing instance starts.
+ *
+ * @param rawConfig byte array of the PerfettoConfig encoded proto.
+ * @return A new Java DataSourceInstance object.
+ */
+ private DataSourceInstanceType createInstance(byte[] rawConfig, int instanceIndex) {
+ final ProtoInputStream inputStream = new ProtoInputStream(rawConfig);
+ return this.createInstance(inputStream, instanceIndex);
+ }
+
+ private static native void nativeRegisterDataSource(
+ long dataSourcePtr, int bufferExhaustedPolicy);
+
+ private static native long nativeCreate(DataSource thiz, String name);
+ private static native void nativeTrace(
+ long nativeDataSourcePointer, TraceFunction traceFunction);
+ private static native void nativeFlushAll(long nativeDataSourcePointer);
+ private static native long nativeGetFinalizer();
+
+ private static native DataSourceInstance nativeGetPerfettoInstanceLocked(
+ long dataSourcePtr, int dsInstanceIdx);
+ private static native void nativeReleasePerfettoInstanceLocked(
+ long dataSourcePtr, int dsInstanceIdx);
+}
diff --git a/core/java/android/tracing/perfetto/DataSourceInstance.java b/core/java/android/tracing/perfetto/DataSourceInstance.java
new file mode 100644
index 0000000..4994501
--- /dev/null
+++ b/core/java/android/tracing/perfetto/DataSourceInstance.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.tracing.perfetto;
+
+/**
+ * @hide
+ */
+public abstract class DataSourceInstance implements AutoCloseable {
+ private final DataSource mDataSource;
+ private final int mInstanceIndex;
+
+ public DataSourceInstance(DataSource dataSource, int instanceIndex) {
+ this.mDataSource = dataSource;
+ this.mInstanceIndex = instanceIndex;
+ }
+
+ /**
+ * Executed when the tracing instance starts running.
+ * <p>
+ * NOTE: This callback executes on the Perfetto internal thread and is blocking.
+ * Anything that is run in this callback should execute quickly.
+ *
+ * @param args Start arguments.
+ */
+ protected void onStart(StartCallbackArguments args) {}
+
+ /**
+ * Executed when a flush is triggered.
+ * <p>
+ * NOTE: This callback executes on the Perfetto internal thread and is blocking.
+ * Anything that is run in this callback should execute quickly.
+ * @param args Flush arguments.
+ */
+ protected void onFlush(FlushCallbackArguments args) {}
+
+ /**
+ * Executed when the tracing instance is stopped.
+ * <p>
+ * NOTE: This callback executes on the Perfetto internal thread and is blocking.
+ * Anything that is run in this callback should execute quickly.
+ * @param args Stop arguments.
+ */
+ protected void onStop(StopCallbackArguments args) {}
+
+ @Override
+ public final void close() {
+ this.release();
+ }
+
+ /**
+ * Release the lock on the datasource once you are finished using it.
+ * Only required to be called when instance was retrieved with
+ * `DataSource#getDataSourceInstanceLocked`.
+ */
+ public final void release() {
+ mDataSource.releaseDataSourceInstance(mInstanceIndex);
+ }
+}
diff --git a/core/java/android/tracing/perfetto/DataSourceParams.java b/core/java/android/tracing/perfetto/DataSourceParams.java
new file mode 100644
index 0000000..6cd04e3
--- /dev/null
+++ b/core/java/android/tracing/perfetto/DataSourceParams.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.tracing.perfetto;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * DataSource Parameters
+ *
+ * @hide
+ */
+public class DataSourceParams {
+ /**
+ * @hide
+ */
+ @IntDef(value = {
+ PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP,
+ PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PerfettoDsBufferExhausted {}
+
+ // If the data source runs out of space when trying to acquire a new chunk,
+ // it will drop data.
+ public static final int PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP = 0;
+
+ // If the data source runs out of space when trying to acquire a new chunk,
+ // it will stall, retry and eventually abort if a free chunk is not acquired
+ // after a while.
+ public static final int PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT = 1;
+
+ public static DataSourceParams DEFAULTS =
+ new DataSourceParams(PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP);
+
+ public DataSourceParams(@PerfettoDsBufferExhausted int bufferExhaustedPolicy) {
+ this.bufferExhaustedPolicy = bufferExhaustedPolicy;
+ }
+
+ public final @PerfettoDsBufferExhausted int bufferExhaustedPolicy;
+}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl b/core/java/android/tracing/perfetto/FlushCallbackArguments.java
similarity index 81%
rename from core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
rename to core/java/android/tracing/perfetto/FlushCallbackArguments.java
index ce92b6d..ecf6aee 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
+++ b/core/java/android/tracing/perfetto/FlushCallbackArguments.java
@@ -13,10 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.companion.virtual.camera;
+
+package android.tracing.perfetto;
/**
- * The configuration of a single virtual camera stream.
* @hide
*/
-parcelable VirtualCameraStreamConfig;
\ No newline at end of file
+public class FlushCallbackArguments {
+}
diff --git a/core/java/android/tracing/perfetto/InitArguments.java b/core/java/android/tracing/perfetto/InitArguments.java
new file mode 100644
index 0000000..da8c273
--- /dev/null
+++ b/core/java/android/tracing/perfetto/InitArguments.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.tracing.perfetto;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * @hide
+ */
+public class InitArguments {
+ public final @PerfettoBackend int backends;
+
+ /**
+ * @hide
+ */
+ @IntDef(value = {
+ PERFETTO_BACKEND_IN_PROCESS,
+ PERFETTO_BACKEND_SYSTEM,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PerfettoBackend {}
+
+ // The in-process tracing backend. Keeps trace buffers in the process memory.
+ public static final int PERFETTO_BACKEND_IN_PROCESS = (1 << 0);
+
+ // The system tracing backend. Connects to the system tracing service (e.g.
+ // on Linux/Android/Mac uses a named UNIX socket).
+ public static final int PERFETTO_BACKEND_SYSTEM = (1 << 1);
+
+ public static InitArguments DEFAULTS = new InitArguments(PERFETTO_BACKEND_SYSTEM);
+
+ public static InitArguments TESTING = new InitArguments(PERFETTO_BACKEND_IN_PROCESS);
+
+ public InitArguments(@PerfettoBackend int backends) {
+ this.backends = backends;
+ }
+}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl b/core/java/android/tracing/perfetto/Producer.java
similarity index 61%
copy from core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
copy to core/java/android/tracing/perfetto/Producer.java
index ce92b6d..a1b3eb7 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
+++ b/core/java/android/tracing/perfetto/Producer.java
@@ -13,10 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.companion.virtual.camera;
+
+package android.tracing.perfetto;
/**
- * The configuration of a single virtual camera stream.
* @hide
*/
-parcelable VirtualCameraStreamConfig;
\ No newline at end of file
+public class Producer {
+
+ /**
+ * Initializes the global Perfetto producer.
+ *
+ * @param args arguments on how to initialize the Perfetto producer.
+ */
+ public static void init(InitArguments args) {
+ nativePerfettoProducerInit(args.backends);
+ }
+
+ private static native void nativePerfettoProducerInit(int backends);
+}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl b/core/java/android/tracing/perfetto/StartCallbackArguments.java
similarity index 81%
copy from core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
copy to core/java/android/tracing/perfetto/StartCallbackArguments.java
index ce92b6d..9739d27 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
+++ b/core/java/android/tracing/perfetto/StartCallbackArguments.java
@@ -13,10 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.companion.virtual.camera;
+
+package android.tracing.perfetto;
/**
- * The configuration of a single virtual camera stream.
* @hide
*/
-parcelable VirtualCameraStreamConfig;
\ No newline at end of file
+public class StartCallbackArguments {
+}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl b/core/java/android/tracing/perfetto/StopCallbackArguments.java
similarity index 81%
copy from core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
copy to core/java/android/tracing/perfetto/StopCallbackArguments.java
index ce92b6d..0cd1a18 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
+++ b/core/java/android/tracing/perfetto/StopCallbackArguments.java
@@ -13,10 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.companion.virtual.camera;
+
+package android.tracing.perfetto;
/**
- * The configuration of a single virtual camera stream.
* @hide
*/
-parcelable VirtualCameraStreamConfig;
\ No newline at end of file
+public class StopCallbackArguments {
+}
diff --git a/core/java/android/tracing/perfetto/TraceFunction.java b/core/java/android/tracing/perfetto/TraceFunction.java
new file mode 100644
index 0000000..62941df
--- /dev/null
+++ b/core/java/android/tracing/perfetto/TraceFunction.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.tracing.perfetto;
+
+import java.io.IOException;
+
+/**
+ * The interface for the trace function called from native on a trace call with a context.
+ *
+ * @param <DataSourceInstanceType> The type of DataSource this tracing context is for.
+ * @param <TlsStateType> The type of the custom TLS state, if any is used.
+ * @param <IncrementalStateType> The type of the custom incremental state, if any is used.
+ *
+ * @hide
+ */
+public interface TraceFunction<DataSourceInstanceType extends DataSourceInstance,
+ TlsStateType, IncrementalStateType> {
+
+ /**
+ * This function will be called synchronously (i.e., always before trace() returns) only if
+ * tracing is enabled and the data source has been enabled in the tracing config.
+ * It can be called more than once per trace() call, in the case of concurrent tracing sessions
+ * (or even if the data source is instantiated twice within the same trace config).
+ *
+ * @param ctx the tracing context to trace for in the trace function.
+ */
+ void trace(TracingContext<DataSourceInstanceType, TlsStateType, IncrementalStateType> ctx)
+ throws IOException;
+}
diff --git a/core/java/android/tracing/perfetto/TracingContext.java b/core/java/android/tracing/perfetto/TracingContext.java
new file mode 100644
index 0000000..0bce26e
--- /dev/null
+++ b/core/java/android/tracing/perfetto/TracingContext.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.tracing.perfetto;
+
+import android.util.proto.ProtoOutputStream;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Argument passed to the lambda function passed to Trace().
+ *
+ * @param <DataSourceInstanceType> The type of the datasource this tracing context is for.
+ * @param <TlsStateType> The type of the custom TLS state, if any is used.
+ * @param <IncrementalStateType> The type of the custom incremental state, if any is used.
+ *
+ * @hide
+ */
+public class TracingContext<DataSourceInstanceType extends DataSourceInstance,
+ TlsStateType, IncrementalStateType> {
+
+ private final long mContextPtr;
+ private final TlsStateType mTlsState;
+ private final IncrementalStateType mIncrementalState;
+ private final List<ProtoOutputStream> mTracePackets = new ArrayList<>();
+
+ // Should only be created from the native side.
+ private TracingContext(long contextPtr, TlsStateType tlsState,
+ IncrementalStateType incrementalState) {
+ this.mContextPtr = contextPtr;
+ this.mTlsState = tlsState;
+ this.mIncrementalState = incrementalState;
+ }
+
+ /**
+ * Creates a new output stream to be used to write a trace packet to. The output stream will be
+ * encoded to the proto binary representation when the callback trace function finishes and
+ * send over to the native side to be included in the proto buffer.
+ *
+ * @return A proto output stream to write a trace packet proto to
+ */
+ public ProtoOutputStream newTracePacket() {
+ final ProtoOutputStream os = new ProtoOutputStream(0);
+ mTracePackets.add(os);
+ return os;
+ }
+
+ /**
+ * Forces a commit of the thread-local tracing data written so far to the
+ * service. This is almost never required (tracing data is periodically
+ * committed as trace pages are filled up) and has a non-negligible
+ * performance hit (requires an IPC + refresh of the current thread-local
+ * chunk). The only case when this should be used is when handling OnStop()
+ * asynchronously, to ensure sure that the data is committed before the
+ * Stop timeout expires.
+ */
+ public void flush() {
+ nativeFlush(this, mContextPtr);
+ }
+
+ /**
+ * Can optionally be used to store custom per-sequence
+ * session data, which is not reset when incremental state is cleared
+ * (e.g. configuration options).
+ *
+ * @return The TlsState instance for the tracing thread and instance.
+ */
+ public TlsStateType getCustomTlsState() {
+ return this.mTlsState;
+ }
+
+ /**
+ * Can optionally be used store custom per-sequence
+ * incremental data (e.g., interning tables).
+ *
+ * @return The current IncrementalState object instance.
+ */
+ public IncrementalStateType getIncrementalState() {
+ return this.mIncrementalState;
+ }
+
+ // Called from native to get trace packets
+ private byte[][] getAndClearAllPendingTracePackets() {
+ byte[][] res = new byte[mTracePackets.size()][];
+ for (int i = 0; i < mTracePackets.size(); i++) {
+ ProtoOutputStream tracePacket = mTracePackets.get(i);
+ res[i] = tracePacket.getBytes();
+ }
+
+ mTracePackets.clear();
+ return res;
+ }
+
+ // private static native void nativeFlush(long nativeDataSourcePointer);
+ private static native void nativeFlush(TracingContext thiz, long ctxPointer);
+}
diff --git a/core/java/android/tracing/transition/TransitionDataSource.java b/core/java/android/tracing/transition/TransitionDataSource.java
new file mode 100644
index 0000000..baece75
--- /dev/null
+++ b/core/java/android/tracing/transition/TransitionDataSource.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.tracing.transition;
+
+import android.tracing.perfetto.CreateTlsStateArgs;
+import android.tracing.perfetto.DataSource;
+import android.tracing.perfetto.DataSourceInstance;
+import android.tracing.perfetto.FlushCallbackArguments;
+import android.tracing.perfetto.StartCallbackArguments;
+import android.tracing.perfetto.StopCallbackArguments;
+import android.util.proto.ProtoInputStream;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @hide
+ */
+public class TransitionDataSource
+ extends DataSource<DataSourceInstance, TransitionDataSource.TlsState, Void> {
+ public static String DATA_SOURCE_NAME = "com.android.wm.shell.transition";
+
+ private final Runnable mOnStartStaticCallback;
+ private final Runnable mOnFlushStaticCallback;
+ private final Runnable mOnStopStaticCallback;
+
+ public TransitionDataSource(Runnable onStart, Runnable onFlush, Runnable onStop) {
+ super(DATA_SOURCE_NAME);
+ this.mOnStartStaticCallback = onStart;
+ this.mOnFlushStaticCallback = onFlush;
+ this.mOnStopStaticCallback = onStop;
+ }
+
+ @Override
+ protected TlsState createTlsState(CreateTlsStateArgs<DataSourceInstance> args) {
+ return new TlsState();
+ }
+
+ public class TlsState {
+ public final Map<String, Integer> handlerMapping = new HashMap<>();
+ }
+
+ @Override
+ public DataSourceInstance createInstance(ProtoInputStream configStream, int instanceIndex) {
+ return new DataSourceInstance(this, instanceIndex) {
+ @Override
+ protected void onStart(StartCallbackArguments args) {
+ mOnStartStaticCallback.run();
+ }
+
+ @Override
+ protected void onFlush(FlushCallbackArguments args) {
+ mOnFlushStaticCallback.run();
+ }
+
+ @Override
+ protected void onStop(StopCallbackArguments args) {
+ mOnStopStaticCallback.run();
+ }
+ };
+ }
+}
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index a74cbe4..f0e673b 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -378,6 +378,13 @@
}
/**
+ * @hide
+ */
+ public Looper getLooper() {
+ return mLooper;
+ }
+
+ /**
* The amount of time, in milliseconds, between each frame of the animation.
* <p>
* This is a requested time that the animation will attempt to honor, but the actual delay
diff --git a/core/java/android/view/EventLogTags.logtags b/core/java/android/view/EventLogTags.logtags
index cc0b18a..f1cd671 100644
--- a/core/java/android/view/EventLogTags.logtags
+++ b/core/java/android/view/EventLogTags.logtags
@@ -59,5 +59,10 @@
# Enqueue Input Event
62002 view_enqueue_input_event (eventType|3),(action|3)
+# following other view events defined in system/logging/logcat/event.logtags
+# ViewRoot Draw Events
+60004 viewroot_draw_event (window|3),(event|3)
+
+
# NOTE - the range 1000000-2000000 is reserved for partners and others who
# want to define their own log tags without conflicting with the core platform.
\ No newline at end of file
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 36b74e3..7903050 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -69,12 +69,13 @@
import android.view.displayhash.DisplayHash;
import android.view.displayhash.VerifiedDisplayHash;
import android.window.AddToSurfaceSyncGroupResult;
+import android.window.IScreenRecordingCallback;
import android.window.ISurfaceSyncGroupCompletedListener;
import android.window.ITaskFpsCallback;
-import android.window.ScreenCapture;
-import android.window.WindowContextInfo;
import android.window.ITrustedPresentationListener;
+import android.window.ScreenCapture;
import android.window.TrustedPresentationThresholds;
+import android.window.WindowContextInfo;
/**
* System private interface to the window manager.
@@ -1083,4 +1084,8 @@
void unregisterTrustedPresentationListener(in ITrustedPresentationListener listener, int id);
+
+ boolean registerScreenRecordingCallback(IScreenRecordingCallback callback);
+
+ void unregisterScreenRecordingCallback(IScreenRecordingCallback callback);
}
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 7850554..ba7874e 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -39,8 +39,10 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
+import android.view.flags.Flags;
import dalvik.system.CloseGuard;
+import dalvik.system.VMRuntime;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -101,6 +103,10 @@
long nativeObject, float frameRate, int compatibility, int changeFrameRateStrategy);
private static native void nativeDestroy(long nativeObject);
+ // 5MB is a wild guess for what the average surface should be. On most new phones, a full-screen
+ // surface is about 9MB... but not all surfaces are screen size. This should be a nice balance.
+ private static final long SURFACE_NATIVE_ALLOCATION_SIZE_BYTES = 5_000_000;
+
public static final @android.annotation.NonNull Parcelable.Creator<Surface> CREATOR =
new Parcelable.Creator<Surface>() {
@Override
@@ -331,6 +337,7 @@
*/
@UnsupportedAppUsage
public Surface() {
+ registerNativeMemoryUsage();
}
/**
@@ -343,6 +350,7 @@
*/
public Surface(@NonNull SurfaceControl from) {
copyFrom(from);
+ registerNativeMemoryUsage();
}
/**
@@ -370,6 +378,7 @@
mName = surfaceTexture.toString();
setNativeObjectLocked(nativeCreateFromSurfaceTexture(surfaceTexture));
}
+ registerNativeMemoryUsage();
}
/* called from android_view_Surface_createFromIGraphicBufferProducer() */
@@ -378,6 +387,7 @@
synchronized (mLock) {
setNativeObjectLocked(nativeObject);
}
+ registerNativeMemoryUsage();
}
@Override
@@ -389,6 +399,7 @@
release();
} finally {
super.finalize();
+ freeNativeMemoryUsage();
}
}
@@ -1243,4 +1254,16 @@
return mIsWideColorGamut;
}
}
+
+ private static void registerNativeMemoryUsage() {
+ if (Flags.enableSurfaceNativeAllocRegistration()) {
+ VMRuntime.getRuntime().registerNativeAllocation(SURFACE_NATIVE_ALLOCATION_SIZE_BYTES);
+ }
+ }
+
+ private static void freeNativeMemoryUsage() {
+ if (Flags.enableSurfaceNativeAllocRegistration()) {
+ VMRuntime.getRuntime().registerNativeFree(SURFACE_NATIVE_ALLOCATION_SIZE_BYTES);
+ }
+ }
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 674f22c..3ed0385 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -294,8 +294,8 @@
private static native void nativeClearTrustedPresentationCallback(long transactionObj,
long nativeObject);
private static native StalledTransactionInfo nativeGetStalledTransactionInfo(int pid);
- private static native void nativeSetDesiredPresentTime(long transactionObj,
- long desiredPresentTime);
+ private static native void nativeSetDesiredPresentTimeNanos(long transactionObj,
+ long desiredPresentTimeNanos);
private static native void nativeSetFrameTimeline(long transactionObj,
long vsyncId);
@@ -2563,12 +2563,12 @@
*/
@FlaggedApi(Flags.FLAG_SDK_DESIRED_PRESENT_TIME)
public static final class TransactionStats {
- private long mLatchTime;
+ private long mLatchTimeNanos;
private SyncFence mSyncFence;
// called from native
- private TransactionStats(long latchTime, long presentFencePtr) {
- mLatchTime = latchTime;
+ private TransactionStats(long latchTimeNanos, long presentFencePtr) {
+ mLatchTimeNanos = latchTimeNanos;
mSyncFence = new SyncFence(presentFencePtr);
}
@@ -2581,12 +2581,12 @@
}
/**
- * Returns the timestamp of when the frame was latched by the framework and queued for
- * presentation.
+ * Returns the timestamp (in CLOCK_MONOTONIC) of when the frame was latched by the
+ * framework and queued for presentation.
*/
@FlaggedApi(Flags.FLAG_SDK_DESIRED_PRESENT_TIME)
- public long getLatchTime() {
- return mLatchTime;
+ public long getLatchTimeNanos() {
+ return mLatchTimeNanos;
}
/**
@@ -4426,8 +4426,8 @@
}
/**
- * Specifies a desiredPresentTime for the transaction. The framework will try to present
- * the transaction at or after the time specified.
+ * Specifies a desiredPresentTimeNanos for the transaction. The framework will try to
+ * present the transaction at or after the time specified.
*
* Transactions will not be presented until all of their acquire fences have signaled even
* if the app requests an earlier present time.
@@ -4436,17 +4436,17 @@
* a desired present time that is before x, the later transaction will not preempt the
* earlier transaction.
*
- * @param desiredPresentTime The desired time (in CLOCK_MONOTONIC) for the transaction.
+ * @param desiredPresentTimeNanos The desired time (in CLOCK_MONOTONIC) for the transaction.
* @return This transaction
*/
@FlaggedApi(Flags.FLAG_SDK_DESIRED_PRESENT_TIME)
@NonNull
- public Transaction setDesiredPresentTime(long desiredPresentTime) {
+ public Transaction setDesiredPresentTimeNanos(long desiredPresentTimeNanos) {
if (!Flags.sdkDesiredPresentTime()) {
Log.w(TAG, "addTransactionCompletedListener was called but flag is disabled");
return this;
}
- nativeSetDesiredPresentTime(mNativeObject, desiredPresentTime);
+ nativeSetDesiredPresentTimeNanos(mNativeObject, desiredPresentTimeNanos);
return this;
}
/**
diff --git a/core/java/android/view/SurfaceControlInputReceiver.java b/core/java/android/view/SurfaceControlInputReceiver.java
new file mode 100644
index 0000000..81e44485
--- /dev/null
+++ b/core/java/android/view/SurfaceControlInputReceiver.java
@@ -0,0 +1,43 @@
+/*
+ * 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 android.view;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+
+import com.android.window.flags.Flags;
+
+/**
+ * Provides a mechanism for a SurfaceControl to receive input events.
+ */
+@FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER)
+public interface SurfaceControlInputReceiver {
+ /**
+ * When input events are batched, this is called at most once per frame. When non batched, this
+ * is called immediately for the input event.
+ *
+ * @param event The input event that was received. This input event object will become invalid
+ * and recycled after this method is invoked. If there is need to persist this
+ * object beyond the scope of this method, the overriding code should make a copy
+ * of this object. For example, using
+ * {@link MotionEvent#obtain(MotionEvent other)} or
+ * {@link KeyEvent#KeyEvent(KeyEvent)} }
+ * @return true if the event was handled, false otherwise.
+ */
+ boolean onInputEvent(@NonNull InputEvent event);
+
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 257ecc5..c98d1d7 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -15015,6 +15015,7 @@
}
/** @hide */
+ @Nullable
View getSelfOrParentImportantForA11y() {
if (isImportantForAccessibility()) return this;
ViewParent parent = getParentForAccessibility();
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index c66f3c8..e0bda91 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -26,6 +26,7 @@
import static android.view.InputDevice.SOURCE_CLASS_NONE;
import static android.view.InsetsSource.ID_IME;
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_NO_PREFERENCE;
import static android.view.View.PFLAG_DRAW_ANIMATION;
import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
@@ -87,6 +88,7 @@
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
+import static android.view.accessibility.Flags.fixMergedContentChangeEvent;
import static android.view.accessibility.Flags.forceInvertColor;
import static android.view.accessibility.Flags.reduceWindowContentChangedEventThrottle;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
@@ -285,6 +287,7 @@
private static final boolean DEBUG_TOUCH_NAVIGATION = false || LOCAL_LOGV;
private static final boolean DEBUG_BLAST = false || LOCAL_LOGV;
private static final int LOGTAG_INPUT_FOCUS = 62001;
+ private static final int LOGTAG_VIEWROOT_DRAW_EVENT = 60004;
/**
* Set to false if we do not want to use the multi threaded renderer even though
@@ -1009,8 +1012,10 @@
// Used to check if there were any view invalidations in
// the previous time frame (FRAME_RATE_IDLENESS_REEVALUATE_TIME).
private boolean mHasInvalidation = false;
- // Used to check if it is in the touch boosting period.
+ // Used to check if it is in the frame rate boosting period.
private boolean mIsFrameRateBoosting = false;
+ // Used to check if it is in touch boosting period.
+ private boolean mIsTouchBoosting = false;
// Used to check if there is a message in the message queue
// for idleness handling.
private boolean mHasIdledMessage = false;
@@ -4749,13 +4754,7 @@
}
private void reportDrawFinished(@Nullable Transaction t, int seqId) {
- if (DEBUG_BLAST) {
- Log.d(mTag, "reportDrawFinished");
- }
-
- if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
- Trace.instant(Trace.TRACE_TAG_VIEW, "reportDrawFinished " + mTag + " seqId=" + seqId);
- }
+ logAndTrace("reportDrawFinished seqId=" + seqId);
try {
mWindowSession.finishDrawing(mWindow, t, seqId);
} catch (RemoteException e) {
@@ -6425,11 +6424,12 @@
* Lower the frame rate after the boosting period (FRAME_RATE_TOUCH_BOOST_TIME).
*/
mIsFrameRateBoosting = false;
+ mIsTouchBoosting = false;
setPreferredFrameRateCategory(Math.max(mPreferredFrameRateCategory,
mLastPreferredFrameRateCategory));
break;
case MSG_CHECK_INVALIDATION_IDLE:
- if (!mHasInvalidation && !mIsFrameRateBoosting) {
+ if (!mHasInvalidation && !mIsFrameRateBoosting && !mIsTouchBoosting) {
mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
setPreferredFrameRateCategory(mPreferredFrameRateCategory);
mHasIdledMessage = false;
@@ -7452,7 +7452,7 @@
// For the variable refresh rate project
if (handled && shouldTouchBoost(action, mWindowAttributes.type)) {
// set the frame rate to the maximum value.
- mIsFrameRateBoosting = true;
+ mIsTouchBoosting = true;
setPreferredFrameRateCategory(mPreferredFrameRateCategory);
}
/**
@@ -11509,6 +11509,15 @@
event.setContentChangeTypes(mChangeTypes);
if (mAction.isPresent()) event.setAction(mAction.getAsInt());
if (AccessibilityEvent.DEBUG_ORIGIN) event.originStackTrace = mOrigin;
+
+ if (fixMergedContentChangeEvent()) {
+ if ((mChangeTypes & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE) != 0) {
+ final View importantParent = source.getSelfOrParentImportantForA11y();
+ if (importantParent != null) {
+ source = importantParent;
+ }
+ }
+ }
source.sendAccessibilityEventUnchecked(event);
} else {
mLastEventTimeMillis = 0;
@@ -11543,14 +11552,29 @@
}
if (mSource != null) {
- // If there is no common predecessor, then mSource points to
- // a removed view, hence in this case always prefer the source.
- View predecessor = getCommonPredecessor(mSource, source);
- if (predecessor != null) {
- predecessor = predecessor.getSelfOrParentImportantForA11y();
+ if (fixMergedContentChangeEvent()) {
+ View newSource = getCommonPredecessor(mSource, source);
+ if (newSource == null) {
+ // If there is no common predecessor, then mSource points to
+ // a removed view, hence in this case always prefer the source.
+ newSource = source;
+ }
+
+ mChangeTypes |= changeType;
+ if (mSource != newSource) {
+ mChangeTypes |= AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE;
+ mSource = newSource;
+ }
+ } else {
+ // If there is no common predecessor, then mSource points to
+ // a removed view, hence in this case always prefer the source.
+ View predecessor = getCommonPredecessor(mSource, source);
+ if (predecessor != null) {
+ predecessor = predecessor.getSelfOrParentImportantForA11y();
+ }
+ mSource = (predecessor != null) ? predecessor : source;
+ mChangeTypes |= changeType;
}
- mSource = (predecessor != null) ? predecessor : source;
- mChangeTypes |= changeType;
final int performingAction = mAccessibilityManager.getPerformingAction();
if (performingAction != 0) {
@@ -12169,7 +12193,10 @@
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.instant(Trace.TRACE_TAG_VIEW, mTag + "-" + msg);
}
- Log.d(mTag, msg);
+ if (DEBUG_BLAST) {
+ Log.d(mTag, msg);
+ }
+ EventLog.writeEvent(LOGTAG_VIEWROOT_DRAW_EVENT, mTag, msg);
}
private void setPreferredFrameRateCategory(int preferredFrameRateCategory) {
@@ -12177,8 +12204,16 @@
return;
}
- int frameRateCategory = mIsFrameRateBoosting || mInsetsAnimationRunning
- ? FRAME_RATE_CATEGORY_HIGH : preferredFrameRateCategory;
+ int frameRateCategory = mIsTouchBoosting
+ ? FRAME_RATE_CATEGORY_HIGH_HINT : preferredFrameRateCategory;
+
+ // FRAME_RATE_CATEGORY_HIGH has a higher precedence than FRAME_RATE_CATEGORY_HIGH_HINT
+ // 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) {
+ frameRateCategory = FRAME_RATE_CATEGORY_HIGH;
+ }
try {
if (mLastPreferredFrameRateCategory != frameRateCategory) {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index d8fa415..42355bb 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -109,6 +109,7 @@
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemProperties;
@@ -1358,9 +1359,8 @@
* android:value="false"/>
* </application>
* </pre>
- * @hide
*/
- // TODO(b/294227289): Make this public API
+ @FlaggedApi(Flags.FLAG_APP_COMPAT_PROPERTIES_API)
String PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE =
"android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE";
@@ -1402,9 +1402,8 @@
* android:value="false"/>
* </application>
* </pre>
- * @hide
*/
- // TODO(b/294227289): Make this public API
+ @FlaggedApi(Flags.FLAG_APP_COMPAT_PROPERTIES_API)
String PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE =
"android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE";
@@ -6015,4 +6014,94 @@
default void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) {
throw new UnsupportedOperationException();
}
+
+ /**
+ * Registers a {@link SurfaceControlInputReceiver} for a {@link SurfaceControl} that will
+ * receive batched input event. For those events that are batched, the invocation will happen
+ * once per {@link Choreographer} frame, and other input events will be delivered immediately.
+ * This is different from
+ * {@link #registerUnbatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Looper,
+ * SurfaceControlInputReceiver)} in that the input events are received batched. The caller must
+ * invoke {@link #unregisterSurfaceControlInputReceiver(IBinder)} to clean up the resources when
+ * no longer needing to use the {@link SurfaceControlInputReceiver}
+ *
+ * @param displayId The display that the SurfaceControl will be placed on. Input will
+ * only work
+ * if SurfaceControl is on that display and that display was touched.
+ * @param surfaceControl The SurfaceControl to register the InputChannel for
+ * @param hostToken The host token to link the InputChannel for. This is primarily for ANRs
+ * to ensure the host receives the ANR if any issues with touch on the
+ * InputChannel
+ * @param choreographer The Choreographer used for batching. This should match the rendering
+ * Choreographer.
+ * @param receiver The SurfaceControlInputReceiver that will receive the input events
+ * @return an {@link IBinder} token that is used to unregister the input receiver via
+ * {@link #unregisterSurfaceControlInputReceiver(IBinder)}.
+ * @see #registerUnbatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Looper,
+ * SurfaceControlInputReceiver)
+ */
+ @FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER)
+ @NonNull
+ default IBinder registerBatchedSurfaceControlInputReceiver(int displayId,
+ @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl,
+ @NonNull Choreographer choreographer, @NonNull SurfaceControlInputReceiver receiver) {
+ throw new UnsupportedOperationException(
+ "registerBatchedSurfaceControlInputReceiver is not implemented");
+ }
+
+ /**
+ * Registers a {@link SurfaceControlInputReceiver} for a {@link SurfaceControl} that will
+ * receive every input event. This is different than calling @link
+ * #registerBatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Choreographer,
+ * SurfaceControlInputReceiver)} in that the input events are received unbatched. The caller
+ * must invoke {@link #unregisterSurfaceControlInputReceiver(IBinder)} to clean up the resources
+ * when no longer needing to use the {@link SurfaceControlInputReceiver}
+ *
+ * @param displayId The display that the SurfaceControl will be placed on. Input will only
+ * work if SurfaceControl is on that display and that display was
+ * touched.
+ * @param hostToken The host token to link the InputChannel for. This is primarily for ANRs
+ * to ensure the host receives the ANR if any issues with touch on the
+ * InputChannel
+ * @param surfaceControl The SurfaceControl to register the InputChannel for
+ * @param looper The looper to use when invoking callbacks.
+ * @param receiver The SurfaceControlInputReceiver that will receive the input events
+ * @return an {@link IBinder} token that is used to unregister the input receiver via
+ * {@link #unregisterSurfaceControlInputReceiver(IBinder)}.
+ * @see #registerBatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Choreographer,
+ * SurfaceControlInputReceiver)
+ **/
+ @FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER)
+ @NonNull
+ default IBinder registerUnbatchedSurfaceControlInputReceiver(int displayId,
+ @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl,
+ @NonNull Looper looper, @NonNull SurfaceControlInputReceiver receiver) {
+ throw new UnsupportedOperationException(
+ "registerUnbatchedSurfaceControlInputReceiver is not implemented");
+ }
+
+ /**
+ * Unregisters and cleans up the registered {@link SurfaceControlInputReceiver} for the
+ * specified token.
+ * <p>
+ * Must be called on the same {@link Looper} thread to which was passed to the
+ * {@link #registerBatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl,
+ * Choreographer,
+ * SurfaceControlInputReceiver)} or
+ * {@link #registerUnbatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Looper,
+ * SurfaceControlInputReceiver)}
+ *
+ * @param token The token that was returned via
+ * {@link #registerBatchedSurfaceControlInputReceiver(int, IBinder,
+ * SurfaceControl,
+ * Choreographer, SurfaceControlInputReceiver)} or
+ * {@link #registerUnbatchedSurfaceControlInputReceiver(int, IBinder,
+ * SurfaceControl,
+ * Looper, SurfaceControlInputReceiver)}
+ */
+ @FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER)
+ default void unregisterSurfaceControlInputReceiver(@NonNull IBinder token) {
+ throw new UnsupportedOperationException(
+ "unregisterSurfaceControlInputReceiver is not implemented");
+ }
}
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index f1e4061..8d40f9a 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -16,6 +16,8 @@
package android.view;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -24,8 +26,10 @@
import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.graphics.HardwareRenderer;
+import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
+import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
@@ -46,6 +50,7 @@
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.WeakHashMap;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
@@ -151,6 +156,9 @@
private final TrustedPresentationListener mTrustedPresentationListener =
new TrustedPresentationListener();
+ private final ConcurrentHashMap<IBinder, InputEventReceiver> mSurfaceControlInputReceivers =
+ new ConcurrentHashMap<>();
+
private WindowManagerGlobal() {
}
@@ -808,6 +816,74 @@
mTrustedPresentationListener.removeListener(listener);
}
+ IBinder registerBatchedSurfaceControlInputReceiver(int displayId,
+ @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl,
+ @NonNull Choreographer choreographer, @NonNull SurfaceControlInputReceiver receiver) {
+ IBinder clientToken = new Binder();
+ InputChannel inputChannel = new InputChannel();
+ try {
+ WindowManagerGlobal.getWindowSession().grantInputChannel(displayId, surfaceControl,
+ clientToken, hostToken, 0, 0, TYPE_APPLICATION, 0, null, null,
+ surfaceControl.getName(), inputChannel);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to create input channel", e);
+ e.rethrowAsRuntimeException();
+ }
+
+ mSurfaceControlInputReceivers.put(clientToken,
+ new BatchedInputEventReceiver(inputChannel, choreographer.getLooper(),
+ choreographer) {
+ @Override
+ public void onInputEvent(InputEvent event) {
+ boolean handled = receiver.onInputEvent(event);
+ finishInputEvent(event, handled);
+ }
+ });
+ return clientToken;
+ }
+
+ IBinder registerUnbatchedSurfaceControlInputReceiver(
+ int displayId, @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl,
+ @NonNull Looper looper, @NonNull SurfaceControlInputReceiver receiver) {
+ IBinder clientToken = new Binder();
+ InputChannel inputChannel = new InputChannel();
+ try {
+ WindowManagerGlobal.getWindowSession().grantInputChannel(displayId, surfaceControl,
+ clientToken, hostToken, 0, 0, TYPE_APPLICATION, 0, null, null,
+ surfaceControl.getName(), inputChannel);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to create input channel", e);
+ e.rethrowAsRuntimeException();
+ }
+
+ mSurfaceControlInputReceivers.put(clientToken,
+ new InputEventReceiver(inputChannel, looper) {
+ @Override
+ public void onInputEvent(InputEvent event) {
+ boolean handled = receiver.onInputEvent(event);
+ finishInputEvent(event, handled);
+ }
+ });
+
+ return clientToken;
+ }
+
+ void unregisterSurfaceControlInputReceiver(IBinder token) {
+ InputEventReceiver inputEventReceiver = mSurfaceControlInputReceivers.get(token);
+ if (inputEventReceiver == null) {
+ Log.w(TAG, "No registered input event receiver with token: " + token);
+ return;
+ }
+ try {
+ WindowManagerGlobal.getWindowSession().remove(token);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to remove input channel", e);
+ e.rethrowAsRuntimeException();
+ }
+
+ inputEventReceiver.dispose();
+ }
+
private final class TrustedPresentationListener extends
ITrustedPresentationListener.Stub {
private static int sId = 0;
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index b4b1fde..aaf5fcc 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -32,6 +32,7 @@
import android.graphics.Region;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.Looper;
import android.os.RemoteException;
import android.os.StrictMode;
import android.util.Log;
@@ -520,6 +521,28 @@
@Override
public void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) {
mGlobal.unregisterTrustedPresentationListener(listener);
+ }
+ @NonNull
+ @Override
+ public IBinder registerBatchedSurfaceControlInputReceiver(int displayId,
+ @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl,
+ @NonNull Choreographer choreographer, @NonNull SurfaceControlInputReceiver receiver) {
+ return mGlobal.registerBatchedSurfaceControlInputReceiver(displayId, hostToken,
+ surfaceControl, choreographer, receiver);
+ }
+
+ @NonNull
+ @Override
+ public IBinder registerUnbatchedSurfaceControlInputReceiver(
+ int displayId, @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl,
+ @NonNull Looper looper, @NonNull SurfaceControlInputReceiver receiver) {
+ return mGlobal.registerUnbatchedSurfaceControlInputReceiver(displayId, hostToken,
+ surfaceControl, looper, receiver);
+ }
+
+ @Override
+ public void unregisterSurfaceControlInputReceiver(@NonNull IBinder token) {
+ mGlobal.unregisterSurfaceControlInputReceiver(token);
}
}
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index c7355c1..efae57c 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -53,6 +53,13 @@
flag {
namespace: "accessibility"
+ name: "fix_merged_content_change_event"
+ description: "Fixes event type and source of content change event merged in ViewRootImpl"
+ bug: "277305460"
+}
+
+flag {
+ namespace: "accessibility"
name: "flash_notification_system_api"
description: "Makes flash notification APIs as system APIs for calling from mainline module"
bug: "303131332"
diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig
index a74b06a..9f9b7b4 100644
--- a/core/java/android/view/flags/view_flags.aconfig
+++ b/core/java/android/view/flags/view_flags.aconfig
@@ -1,6 +1,13 @@
package: "android.view.flags"
flag {
+ name: "enable_surface_native_alloc_registration"
+ namespace: "toolkit"
+ description: "Feature flag for registering surfaces with the VM for faster cleanup"
+ bug: "306193257"
+}
+
+flag {
name: "enable_use_measure_cache_during_force_layout"
namespace: "toolkit"
description: "Enables using the measure cache during a view force layout from the second "
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index bb7677d6..ccc5dbb 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -47,3 +47,10 @@
is_fixed_read_only: true
}
+flag {
+ name: "use_zero_jank_proxy"
+ namespace: "input_method"
+ description: "Feature flag for using a proxy that uses async calls to achieve zero jank for IMMS calls."
+ bug: "293640003"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
index ef50045..1d2f653 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -16,6 +16,9 @@
package android.view.textclassifier;
+import static android.service.notification.Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS;
+
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -108,6 +111,9 @@
String TYPE_DATE_TIME = "datetime";
/** Flight number in IATA format. */
String TYPE_FLIGHT_NUMBER = "flight";
+ /** One-time login codes */
+ @FlaggedApi(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
+ String TYPE_OTP_CODE = "otp_code";
/**
* Word that users may be interested to look up for meaning.
* @hide
@@ -126,7 +132,8 @@
TYPE_DATE,
TYPE_DATE_TIME,
TYPE_FLIGHT_NUMBER,
- TYPE_DICTIONARY
+ TYPE_DICTIONARY,
+ TYPE_OTP_CODE
})
@interface EntityType {}
diff --git a/core/java/android/webkit/URLUtil.java b/core/java/android/webkit/URLUtil.java
index 828ec26..c6271d2 100644
--- a/core/java/android/webkit/URLUtil.java
+++ b/core/java/android/webkit/URLUtil.java
@@ -16,20 +16,40 @@
package android.webkit;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
import android.net.ParseException;
import android.net.Uri;
import android.net.WebAddress;
+import android.os.Build;
import android.util.Log;
import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.nio.charset.Charset;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class URLUtil {
+ /**
+ * This feature enables parsing of Content-Disposition headers that conform to RFC 6266. In
+ * particular, this enables parsing of {@code filename*} values which can use a different
+ * character encoding.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ @FlaggedApi(android.os.Flags.FLAG_ANDROID_OS_BUILD_VANILLA_ICE_CREAM)
+ public static final long PARSE_CONTENT_DISPOSITION_USING_RFC_6266 = 319400769L;
+
private static final String LOGTAG = "webkit";
private static final boolean TRACE = false;
@@ -293,21 +313,58 @@
/**
* Guesses canonical filename that a download would have, using the URL and contentDisposition.
- * File extension, if not defined, is added based on the mimetype
+ *
+ * <p>File extension, if not defined, is added based on the mimetype.
+ *
+ * <p>The {@code contentDisposition} argument will be treated differently depending on
+ * targetSdkVersion.
+ *
+ * <ul>
+ * <li>For targetSDK versions < {@code VANILLA_ICE_CREAM} it will be parsed based on RFC
+ * 2616.
+ * <li>For targetSDK versions >= {@code VANILLA_ICE_CREAM} it will be parsed based on RFC
+ * 6266.
+ * </ul>
+ *
+ * In practice, this means that from {@code VANILLA_ICE_CREAM}, this method will be able to
+ * parse {@code filename*} directives in the {@code contentDisposition} string.
+ *
+ * <p>The function also changed in the following ways in {@code VANILLA_ICE_CREAM}:
+ *
+ * <ul>
+ * <li>If the suggested file type extension doesn't match the passed {@code mimeType}, the
+ * method will append the appropriate extension instead of replacing the current
+ * extension.
+ * <li>If the suggested file name contains a path separator ({@code "/"}), the method will
+ * replace this with the underscore character ({@code "_"}) instead of splitting the
+ * result and only using the last part.
+ * </ul>
*
* @param url Url to the content
* @param contentDisposition Content-Disposition HTTP header or {@code null}
* @param mimeType Mime-type of the content or {@code null}
* @return suggested filename
*/
- public static final String guessFileName(
+ public static String guessFileName(
+ String url, @Nullable String contentDisposition, @Nullable String mimeType) {
+ if (android.os.Flags.androidOsBuildVanillaIceCream()) {
+ if (Compatibility.isChangeEnabled(PARSE_CONTENT_DISPOSITION_USING_RFC_6266)) {
+ return guessFileNameRfc6266(url, contentDisposition, mimeType);
+ }
+ }
+
+ return guessFileNameRfc2616(url, contentDisposition, mimeType);
+ }
+
+ /** Legacy implementation of guessFileName, based on RFC 2616. */
+ private static String guessFileNameRfc2616(
String url, @Nullable String contentDisposition, @Nullable String mimeType) {
String filename = null;
String extension = null;
// If we couldn't do anything with the hint, move toward the content disposition
if (contentDisposition != null) {
- filename = parseContentDisposition(contentDisposition);
+ filename = parseContentDispositionRfc2616(contentDisposition);
if (filename != null) {
int index = filename.lastIndexOf('/') + 1;
if (index > 0) {
@@ -384,6 +441,128 @@
return filename + extension;
}
+ /**
+ * Guesses canonical filename that a download would have, using the URL and contentDisposition.
+ * Uses RFC 6266 for parsing the contentDisposition header value.
+ */
+ @NonNull
+ private static String guessFileNameRfc6266(
+ @NonNull String url, @Nullable String contentDisposition, @Nullable String mimeType) {
+ String filename = getFilenameSuggestion(url, contentDisposition);
+ // Split filename between base and extension
+ // Add an extension if filename does not have one
+ String extensionFromMimeType = suggestExtensionFromMimeType(mimeType);
+
+ if (filename.indexOf('.') < 0) {
+ // Filename does not have an extension, use the suggested one.
+ return filename + extensionFromMimeType;
+ }
+
+ // Filename already contains at least one dot.
+ // Compare the last segment of the extension against the mime type.
+ // If there's a mismatch, add the suggested extension instead.
+ if (mimeType != null && extensionDifferentFromMimeType(filename, mimeType)) {
+ return filename + extensionFromMimeType;
+ }
+ return filename;
+ }
+
+ /**
+ * Get the suggested file name from the {@code contentDisposition} or {@code url}. Will ensure
+ * that the filename contains no path separators by replacing them with the {@code "_"}
+ * character.
+ */
+ @NonNull
+ private static String getFilenameSuggestion(String url, @Nullable String contentDisposition) {
+ // First attempt to parse the Content-Disposition header if available
+ if (contentDisposition != null) {
+ String filename = getFilenameFromContentDispositionRfc6266(contentDisposition);
+ if (filename != null) {
+ return replacePathSeparators(filename);
+ }
+ }
+
+ // Try to generate a filename based on the URL.
+ if (url != null) {
+ Uri parsedUri = Uri.parse(url);
+ String lastPathSegment = parsedUri.getLastPathSegment();
+ if (lastPathSegment != null) {
+ return replacePathSeparators(lastPathSegment);
+ }
+ }
+
+ // Finally, if couldn't get filename from URI, get a generic filename.
+ return "downloadfile";
+ }
+
+ /**
+ * Replace all instances of {@code "/"} with {@code "_"} to avoid filenames that navigate the
+ * path.
+ */
+ @NonNull
+ private static String replacePathSeparators(@NonNull String raw) {
+ return raw.replaceAll("/", "_");
+ }
+
+ /**
+ * Check if the {@code filename} has an extension that is different from the expected one based
+ * on the {@code mimeType}.
+ */
+ private static boolean extensionDifferentFromMimeType(
+ @NonNull String filename, @NonNull String mimeType) {
+ int lastDotIndex = filename.lastIndexOf('.');
+ String typeFromExt =
+ MimeTypeMap.getSingleton()
+ .getMimeTypeFromExtension(filename.substring(lastDotIndex + 1));
+ return typeFromExt != null && !typeFromExt.equalsIgnoreCase(mimeType);
+ }
+
+ /**
+ * Get a candidate file extension (including the {@code .}) for the given mimeType. will return
+ * {@code ".bin"} if {@code mimeType} is {@code null}
+ *
+ * @param mimeType Reported mimetype
+ * @return A file extension, including the {@code .}
+ */
+ @NonNull
+ private static String suggestExtensionFromMimeType(@Nullable String mimeType) {
+ if (mimeType == null) {
+ return ".bin";
+ }
+ String extensionFromMimeType =
+ MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
+ if (extensionFromMimeType != null) {
+ return "." + extensionFromMimeType;
+ }
+ if (mimeType.equalsIgnoreCase("text/html")) {
+ return ".html";
+ } else if (mimeType.toLowerCase(Locale.ROOT).startsWith("text/")) {
+ return ".txt";
+ } else {
+ return ".bin";
+ }
+ }
+
+ /**
+ * Parse the Content-Disposition HTTP Header.
+ *
+ * <p>Behavior depends on targetSdkVersion.
+ *
+ * <ul>
+ * <li>For targetSDK versions < {@code VANILLA_ICE_CREAM} it will parse based on RFC 2616.
+ * <li>For targetSDK versions >= {@code VANILLA_ICE_CREAM} it will parse based on RFC 6266.
+ * </ul>
+ */
+ @UnsupportedAppUsage
+ static String parseContentDisposition(String contentDisposition) {
+ if (android.os.Flags.androidOsBuildVanillaIceCream()) {
+ if (Compatibility.isChangeEnabled(PARSE_CONTENT_DISPOSITION_USING_RFC_6266)) {
+ return getFilenameFromContentDispositionRfc6266(contentDisposition);
+ }
+ }
+ return parseContentDispositionRfc2616(contentDisposition);
+ }
+
/** Regex used to parse content-disposition headers */
private static final Pattern CONTENT_DISPOSITION_PATTERN =
Pattern.compile(
@@ -391,15 +570,14 @@
Pattern.CASE_INSENSITIVE);
/**
- * Parse the Content-Disposition HTTP Header. The format of the header is defined here:
- * http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html This header provides a filename for
- * content that is going to be downloaded to the file system. We only support the attachment
- * type. Note that RFC 2616 specifies the filename value must be double-quoted. Unfortunately
- * some servers do not quote the value so to maintain consistent behaviour with other browsers,
- * we allow unquoted values too.
+ * Parse the Content-Disposition HTTP Header. The format of the header is defined here: <a
+ * href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html">rfc2616 Section 19</a>. This
+ * header provides a filename for content that is going to be downloaded to the file system. We
+ * only support the attachment type. Note that RFC 2616 specifies the filename value must be
+ * double-quoted. Unfortunately some servers do not quote the value so to maintain consistent
+ * behaviour with other browsers, we allow unquoted values too.
*/
- @UnsupportedAppUsage
- static String parseContentDisposition(String contentDisposition) {
+ private static String parseContentDispositionRfc2616(String contentDisposition) {
try {
Matcher m = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition);
if (m.find()) {
@@ -410,4 +588,136 @@
}
return null;
}
+
+ /**
+ * Pattern for parsing individual content disposition key-value pairs.
+ *
+ * <p>The pattern will attempt to parse the value as either single-, double-, or unquoted. For
+ * the single- and double-quoted options, the pattern allows escaped quotes as part of the
+ * value, as per <a href="https://datatracker.ietf.org/doc/html/rfc2616#section-2.2">rfc2616
+ * section-2.2</a>
+ */
+ @SuppressWarnings("RegExpRepeatedSpace") // Spaces are only for readability.
+ private static final Pattern DISPOSITION_PATTERN =
+ Pattern.compile(
+ """
+ \\s*(\\S+?) # Group 1: parameter name
+ \\s*=\\s* # Match equals sign
+ (?: # non-capturing group of options
+ '( (?: [^'\\\\] | \\\\. )* )' # Group 2: single-quoted
+ | "( (?: [^"\\\\] | \\\\. )* )" # Group 3: double-quoted
+ | ( [^'"][^;\\s]* ) # Group 4: un-quoted parameter
+ )\\s*;? # Optional end semicolon""",
+ Pattern.COMMENTS);
+
+ /**
+ * Extract filename from a {@code Content-Disposition} header value.
+ *
+ * <p>This method implements the parsing defined in <a
+ * href="https://datatracker.ietf.org/doc/html/rfc6266">RFC 6266</a>, supporting both the {@code
+ * filename} and {@code filename*} disposition parameters. If the passed header value has the
+ * {@code "inline"} disposition type, this method will return {@code null} to indicate that a
+ * download was not intended.
+ *
+ * <p>If both {@code filename*} and {@code filename} is present, the former will be returned, as
+ * per the RFC. Invalid encoded values will be ignored.
+ *
+ * @param contentDisposition Value of {@code Content-Disposition} header.
+ * @return The filename suggested by the header or {@code null} if no filename could be parsed
+ * from the header value.
+ */
+ @Nullable
+ private static String getFilenameFromContentDispositionRfc6266(
+ @NonNull String contentDisposition) {
+ String[] parts = contentDisposition.trim().split(";", 2);
+ if (parts.length < 2) {
+ // Need at least 2 parts, the `disposition-type` and at least one `disposition-parm`.
+ return null;
+ }
+ String dispositionType = parts[0].trim();
+ if ("inline".equalsIgnoreCase(dispositionType)) {
+ // "inline" should not result in a download.
+ // Unknown disposition types should be handles as "attachment"
+ // https://datatracker.ietf.org/doc/html/rfc6266#section-4.2
+ return null;
+ }
+ String dispositionParameters = parts[1];
+ Matcher matcher = DISPOSITION_PATTERN.matcher(dispositionParameters);
+ String filename = null;
+ String filenameExt = null;
+ while (matcher.find()) {
+ String parameter = matcher.group(1);
+ String value;
+ if (matcher.group(2) != null) {
+ value = removeSlashEscapes(matcher.group(2)); // Value was single-quoted
+ } else if (matcher.group(3) != null) {
+ value = removeSlashEscapes(matcher.group(3)); // Value was double-quoted
+ } else {
+ value = matcher.group(4); // Value was un-quoted
+ }
+
+ if (parameter == null || value == null) {
+ continue;
+ }
+
+ if ("filename*".equalsIgnoreCase(parameter)) {
+ filenameExt = parseExtValueString(value);
+ } else if ("filename".equalsIgnoreCase(parameter)) {
+ filename = value;
+ }
+ }
+
+ // RFC 6266 dictates the filenameExt should be preferred if present.
+ if (filenameExt != null) {
+ return filenameExt;
+ }
+ return filename;
+ }
+
+ /** Replace escapes of the \X form with X. */
+ private static String removeSlashEscapes(String raw) {
+ if (raw == null) {
+ return null;
+ }
+ return raw.replaceAll("\\\\(.)", "$1");
+ }
+
+ /**
+ * Parse an extended value string which can be percent-encoded. Return {@code} null if unable to
+ * parse the string.
+ */
+ private static String parseExtValueString(String raw) {
+ String[] parts = raw.split("'", 3);
+ if (parts.length < 3) {
+ return null;
+ }
+
+ String encoding = parts[0];
+ // Intentionally ignore parts[1] (language).
+ String valueChars = parts[2];
+
+ try {
+ // The URLDecoder force-decodes + as " "
+ // so preemptively replace all values with the encoded value to preserve them.
+ Charset charset = Charset.forName(encoding);
+ String valueWithEncodedPlus = encodePlusCharacters(valueChars, charset);
+ return URLDecoder.decode(valueWithEncodedPlus, charset);
+ } catch (RuntimeException ignored) {
+ return null; // Ignoring an un-parsable value is within spec.
+ }
+ }
+
+ /**
+ * Replace all instances of {@code "+"} with the percent-encoded equivalent for the given {@code
+ * charset}.
+ */
+ @NonNull
+ private static String encodePlusCharacters(@NonNull String valueChars, Charset charset) {
+ StringBuilder sb = new StringBuilder();
+ for (byte b : charset.encode("+").array()) {
+ // Formatting a byte is not possible with TextUtils.formatSimple
+ sb.append(String.format("%02x", b));
+ }
+ return valueChars.replaceAll("\\+", sb.toString());
+ }
}
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 0ef37d1..53b047a 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -16,6 +16,8 @@
package android.webkit;
+import static android.webkit.Flags.updateServiceV2;
+
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.UptimeMillisLong;
@@ -33,6 +35,7 @@
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.Trace;
+import android.text.TextUtils;
import android.util.AndroidRuntimeException;
import android.util.ArraySet;
import android.util.Log;
@@ -410,6 +413,21 @@
}
}
+ // Returns whether the given package is enabled.
+ // This state can be changed by the user from Settings->Apps
+ private static boolean isEnabledPackage(PackageInfo packageInfo) {
+ if (packageInfo == null) return false;
+ return packageInfo.applicationInfo.enabled;
+ }
+
+ // Return {@code true} if the package is installed and not hidden
+ private static boolean isInstalledPackage(PackageInfo packageInfo) {
+ if (packageInfo == null) return false;
+ return (((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0)
+ && ((packageInfo.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_HIDDEN)
+ == 0));
+ }
+
@UnsupportedAppUsage
private static Context getWebViewContextAndSetProvider() throws MissingWebViewPackageException {
Application initialApplication = AppGlobals.getInitialApplication();
@@ -456,6 +474,21 @@
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
}
+ if (updateServiceV2() && !isInstalledPackage(newPackageInfo)) {
+ throw new MissingWebViewPackageException(
+ TextUtils.formatSimple(
+ "Current WebView Package (%s) is not installed for the current "
+ + "user",
+ newPackageInfo.packageName));
+ }
+
+ if (updateServiceV2() && !isEnabledPackage(newPackageInfo)) {
+ throw new MissingWebViewPackageException(
+ TextUtils.formatSimple(
+ "Current WebView Package (%s) is not enabled for the current user",
+ newPackageInfo.packageName));
+ }
+
// Validate the newly fetched package info, throws MissingWebViewPackageException on
// failure
verifyPackageInfo(response.packageInfo, newPackageInfo);
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl b/core/java/android/window/IScreenRecordingCallback.aidl
similarity index 74%
copy from core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
copy to core/java/android/window/IScreenRecordingCallback.aidl
index ce92b6d..560ee75 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
+++ b/core/java/android/window/IScreenRecordingCallback.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
@@ -13,10 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.companion.virtual.camera;
+
+package android.window;
/**
- * The configuration of a single virtual camera stream.
* @hide
*/
-parcelable VirtualCameraStreamConfig;
\ No newline at end of file
+oneway interface IScreenRecordingCallback {
+ void onScreenRecordingStateChanged(boolean visibleInScreenRecording);
+}
diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java
index bdaad2b..473b814 100644
--- a/core/java/android/window/SplashScreenView.java
+++ b/core/java/android/window/SplashScreenView.java
@@ -47,6 +47,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
+import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
@@ -337,7 +338,14 @@
"SplashScreenView");
ImageView imageView = new ImageView(viewContext);
imageView.setBackground(mIconDrawable);
- viewHost.setView(imageView, mIconSize, mIconSize);
+ final int windowFlag = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+ final WindowManager.LayoutParams lp =
+ new WindowManager.LayoutParams(mIconSize, mIconSize,
+ WindowManager.LayoutParams.TYPE_APPLICATION, windowFlag,
+ PixelFormat.TRANSPARENT);
+ viewHost.setView(imageView, lp);
SurfaceControlViewHost.SurfacePackage surfacePackage = viewHost.getSurfacePackage();
surfaceView.setChildSurfacePackage(surfacePackage);
view.mSurfacePackage = surfacePackage;
diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java
index acc6a749..7b8cdff 100644
--- a/core/java/android/window/TaskFragmentOperation.java
+++ b/core/java/android/window/TaskFragmentOperation.java
@@ -125,6 +125,16 @@
*/
public static final int OP_TYPE_SET_DIM_ON_TASK = 16;
+ /**
+ * Sets this TaskFragment to move to bottom of the Task if any of the activities below it is
+ * launched in a mode requiring clear top.
+ *
+ * This is only allowed for system organizers. See
+ * {@link com.android.server.wm.TaskFragmentOrganizerController#registerOrganizer(
+ * ITaskFragmentOrganizer, boolean)}
+ */
+ public static final int OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH = 17;
+
@IntDef(prefix = { "OP_TYPE_" }, value = {
OP_TYPE_UNKNOWN,
OP_TYPE_CREATE_TASK_FRAGMENT,
@@ -144,6 +154,7 @@
OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE,
OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE,
OP_TYPE_SET_DIM_ON_TASK,
+ OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH,
})
@Retention(RetentionPolicy.SOURCE)
public @interface OperationType {}
@@ -173,12 +184,14 @@
private final boolean mDimOnTask;
+ private final boolean mMoveToBottomIfClearWhenLaunch;
+
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 isolatedNav, boolean dimOnTask, boolean moveToBottomIfClearWhenLaunch) {
mOpType = opType;
mTaskFragmentCreationParams = taskFragmentCreationParams;
mActivityToken = activityToken;
@@ -188,6 +201,7 @@
mAnimationParams = animationParams;
mIsolatedNav = isolatedNav;
mDimOnTask = dimOnTask;
+ mMoveToBottomIfClearWhenLaunch = moveToBottomIfClearWhenLaunch;
}
private TaskFragmentOperation(Parcel in) {
@@ -200,6 +214,7 @@
mAnimationParams = in.readTypedObject(TaskFragmentAnimationParams.CREATOR);
mIsolatedNav = in.readBoolean();
mDimOnTask = in.readBoolean();
+ mMoveToBottomIfClearWhenLaunch = in.readBoolean();
}
@Override
@@ -213,6 +228,7 @@
dest.writeTypedObject(mAnimationParams, flags);
dest.writeBoolean(mIsolatedNav);
dest.writeBoolean(mDimOnTask);
+ dest.writeBoolean(mMoveToBottomIfClearWhenLaunch);
}
@NonNull
@@ -300,6 +316,14 @@
return mDimOnTask;
}
+ /**
+ * Returns whether the TaskFragment should move to bottom of task when any activity below it
+ * is launched in clear top mode.
+ */
+ public boolean isMoveToBottomIfClearWhenLaunch() {
+ return mMoveToBottomIfClearWhenLaunch;
+ }
+
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
@@ -324,6 +348,7 @@
}
sb.append(", isolatedNav=").append(mIsolatedNav);
sb.append(", dimOnTask=").append(mDimOnTask);
+ sb.append(", moveToBottomIfClearWhenLaunch=").append(mMoveToBottomIfClearWhenLaunch);
sb.append('}');
return sb.toString();
@@ -332,7 +357,8 @@
@Override
public int hashCode() {
return Objects.hash(mOpType, mTaskFragmentCreationParams, mActivityToken, mActivityIntent,
- mBundle, mSecondaryFragmentToken, mAnimationParams, mIsolatedNav, mDimOnTask);
+ mBundle, mSecondaryFragmentToken, mAnimationParams, mIsolatedNav, mDimOnTask,
+ mMoveToBottomIfClearWhenLaunch);
}
@Override
@@ -349,7 +375,8 @@
&& Objects.equals(mSecondaryFragmentToken, other.mSecondaryFragmentToken)
&& Objects.equals(mAnimationParams, other.mAnimationParams)
&& mIsolatedNav == other.mIsolatedNav
- && mDimOnTask == other.mDimOnTask;
+ && mDimOnTask == other.mDimOnTask
+ && mMoveToBottomIfClearWhenLaunch == other.mMoveToBottomIfClearWhenLaunch;
}
@Override
@@ -385,6 +412,8 @@
private boolean mDimOnTask;
+ private boolean mMoveToBottomIfClearWhenLaunch;
+
/**
* @param opType the {@link OperationType} of this {@link TaskFragmentOperation}.
*/
@@ -466,13 +495,23 @@
}
/**
+ * Sets whether the TaskFragment should move to bottom of task when any activity below it
+ * is launched in clear top mode.
+ */
+ @NonNull
+ public Builder setMoveToBottomIfClearWhenLaunch(boolean moveToBottomIfClearWhenLaunch) {
+ mMoveToBottomIfClearWhenLaunch = moveToBottomIfClearWhenLaunch;
+ return this;
+ }
+
+ /**
* Constructs the {@link TaskFragmentOperation}.
*/
@NonNull
public TaskFragmentOperation build() {
return new TaskFragmentOperation(mOpType, mTaskFragmentCreationParams, mActivityToken,
mActivityIntent, mBundle, mSecondaryFragmentToken, mAnimationParams,
- mIsolatedNav, mDimOnTask);
+ mIsolatedNav, mDimOnTask, mMoveToBottomIfClearWhenLaunch);
}
}
}
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index bceb872..feae173 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -421,8 +421,11 @@
final String perChangeLineStart = shouldPrettyPrint ? "\n" + innerPrefix : "";
StringBuilder sb = new StringBuilder();
sb.append("{id=").append(mDebugId).append(" t=").append(transitTypeToString(mType))
- .append(" f=0x").append(Integer.toHexString(mFlags)).append(" trk=").append(mTrack)
- .append(" r=[");
+ .append(" f=0x").append(Integer.toHexString(mFlags)).append(" trk=").append(mTrack);
+ if (mOptions != null) {
+ sb.append(" opt=").append(mOptions);
+ }
+ sb.append(" r=[");
for (int i = 0; i < mRoots.size(); ++i) {
if (i > 0) {
sb.append(',');
@@ -1211,21 +1214,31 @@
@NonNull
private static String typeToString(int mode) {
- switch(mode) {
- case ANIM_CUSTOM: return "ANIM_CUSTOM";
- case ANIM_CLIP_REVEAL: return "ANIM_CLIP_REVEAL";
- case ANIM_SCALE_UP: return "ANIM_SCALE_UP";
- case ANIM_THUMBNAIL_SCALE_UP: return "ANIM_THUMBNAIL_SCALE_UP";
- case ANIM_THUMBNAIL_SCALE_DOWN: return "ANIM_THUMBNAIL_SCALE_DOWN";
- case ANIM_OPEN_CROSS_PROFILE_APPS: return "ANIM_OPEN_CROSS_PROFILE_APPS";
- default: return "<unknown:" + mode + ">";
- }
+ return switch (mode) {
+ case ANIM_CUSTOM -> "CUSTOM";
+ case ANIM_SCALE_UP -> "SCALE_UP";
+ case ANIM_THUMBNAIL_SCALE_UP -> "THUMBNAIL_SCALE_UP";
+ case ANIM_THUMBNAIL_SCALE_DOWN -> "THUMBNAIL_SCALE_DOWN";
+ case ANIM_SCENE_TRANSITION -> "SCENE_TRANSITION";
+ case ANIM_CLIP_REVEAL -> "CLIP_REVEAL";
+ case ANIM_OPEN_CROSS_PROFILE_APPS -> "OPEN_CROSS_PROFILE_APPS";
+ case ANIM_FROM_STYLE -> "FROM_STYLE";
+ default -> "<" + mode + ">";
+ };
}
@Override
public String toString() {
- return "{ AnimationOptions type= " + typeToString(mType) + " package=" + mPackageName
- + " override=" + mOverrideTaskTransition + " b=" + mTransitionBounds + "}";
+ final StringBuilder sb = new StringBuilder(32);
+ sb.append("{t=").append(typeToString(mType));
+ if (mOverrideTaskTransition) {
+ sb.append(" overrideTask=true");
+ }
+ if (!mTransitionBounds.isEmpty()) {
+ sb.append(" bounds=").append(mTransitionBounds);
+ }
+ sb.append('}');
+ return sb.toString();
}
/** Customized activity transition. */
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index 4b5595f..cbf6367 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -43,3 +43,10 @@
description: "Whether the API PackageManager.USER_MIN_ASPECT_RATIO_APP_DEFAULT is available"
bug: "310816437"
}
+
+flag {
+ name: "allow_hide_scm_button"
+ namespace: "large_screen_experiences_app_compat"
+ description: "Whether we should allow hiding the size compat restart button"
+ bug: "318840081"
+}
diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig
index ad0e9a4..f67eefa 100644
--- a/core/java/android/window/flags/responsible_apis.aconfig
+++ b/core/java/android/window/flags/responsible_apis.aconfig
@@ -8,6 +8,13 @@
}
flag {
+ name: "bal_require_opt_in_same_uid"
+ namespace: "responsible_apis"
+ description: "Require the PendingIntent creator/sender to opt in if it is the same UID"
+ bug: "296478951"
+}
+
+flag {
name: "bal_dont_bring_existing_background_task_stack_to_fg"
namespace: "responsible_apis"
description: "When starting a PendingIntent with ONLY creator privileges, don't bring the existing task stack to foreground"
diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig
index 3794c5d..751c1a8 100644
--- a/core/java/android/window/flags/window_surfaces.aconfig
+++ b/core/java/android/window/flags/window_surfaces.aconfig
@@ -72,3 +72,19 @@
is_fixed_read_only: true
bug: "295038072"
}
+
+flag {
+ namespace: "window_surfaces"
+ name: "surface_control_input_receiver"
+ description: "Enable public API to register an InputReceiver for a SurfaceControl"
+ is_fixed_read_only: true
+ bug: "278757236"
+}
+
+flag {
+ namespace: "window_surfaces"
+ name: "screen_recording_callbacks"
+ description: "Enable screen recording callbacks public API"
+ is_fixed_read_only: true
+ bug: "304574518"
+}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index f2bce9c..2c5fbd7 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -69,11 +69,10 @@
}
flag {
- name: "predictive_back_system_animations"
+ name: "predictive_back_system_anims"
namespace: "systemui"
description: "Predictive back for system animations"
- bug: "309545085"
- is_fixed_read_only: true
+ bug: "320510464"
}
flag {
@@ -90,4 +89,12 @@
description: "Feature flag to enable a multi-instance system ui component property."
bug: "262864589"
is_fixed_read_only: true
+}
+
+flag {
+ name: "insets_decoupled_configuration"
+ namespace: "windowing_frontend"
+ description: "Configuration decoupled from insets"
+ bug: "151861875"
+ is_fixed_read_only: true
}
\ No newline at end of file
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index f743ab7..6e5807b 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -46,4 +46,11 @@
description: "To dispatch ActivityWindowInfo through ClientTransaction"
bug: "287582673"
is_fixed_read_only: true
+}
+
+flag {
+ namespace: "windowing_sdk"
+ name: "embedded_activity_back_nav_flag"
+ description: "Refines embedded activity back navigation behavior"
+ bug: "240575809"
}
\ No newline at end of file
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
index 4fe9aea..85bdbb9 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -80,5 +80,11 @@
in Bundle extras, in IntentSender resultIntent);
boolean isRequestPinAppWidgetSupported();
oneway void noteAppWidgetTapped(in String callingPackage, in int appWidgetId);
+ void setWidgetPreview(in ComponentName providerComponent, in int widgetCategories,
+ in RemoteViews preview);
+ @nullable RemoteViews getWidgetPreview(in String callingPackage,
+ in ComponentName providerComponent, in int profileId, in int widgetCategory);
+ void removeWidgetPreview(in ComponentName providerComponent, in int widgetCategories);
+
}
diff --git a/core/java/com/android/internal/display/RefreshRateSettingsUtils.java b/core/java/com/android/internal/display/RefreshRateSettingsUtils.java
index e55c641..fab8984 100644
--- a/core/java/com/android/internal/display/RefreshRateSettingsUtils.java
+++ b/core/java/com/android/internal/display/RefreshRateSettingsUtils.java
@@ -33,13 +33,14 @@
/**
* Find the highest refresh rate among all the modes of the default display.
*
+ * This method will acquire DisplayManager.mLock, so calling it while holding other locks
+ * should be done with care.
* @param context The context
* @return The highest refresh rate
*/
public static float findHighestRefreshRateForDefaultDisplay(Context context) {
final DisplayManager dm = context.getSystemService(DisplayManager.class);
final Display display = dm.getDisplay(Display.DEFAULT_DISPLAY);
-
if (display == null) {
Log.w(TAG, "No valid default display device");
return DEFAULT_REFRESH_RATE;
diff --git a/core/java/com/android/internal/os/MonotonicClock.java b/core/java/com/android/internal/os/MonotonicClock.java
index c3bcfa6..eca6f58 100644
--- a/core/java/com/android/internal/os/MonotonicClock.java
+++ b/core/java/com/android/internal/os/MonotonicClock.java
@@ -17,11 +17,11 @@
package com.android.internal.os;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.util.AtomicFile;
import android.util.Log;
import android.util.Xml;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
@@ -49,20 +49,27 @@
private final AtomicFile mFile;
private final Clock mClock;
- private long mTimeshift;
+ private final long mTimeshift;
public static final long UNDEFINED = -1;
public MonotonicClock(File file) {
- mFile = new AtomicFile(file);
- mClock = Clock.SYSTEM_CLOCK;
- read();
+ this (file, Clock.SYSTEM_CLOCK.elapsedRealtime(), Clock.SYSTEM_CLOCK);
}
public MonotonicClock(long monotonicTime, @NonNull Clock clock) {
+ this(null, monotonicTime, clock);
+ }
+
+ public MonotonicClock(@Nullable File file, long monotonicTime, @NonNull Clock clock) {
mClock = clock;
- mFile = null;
- mTimeshift = monotonicTime - mClock.elapsedRealtime();
+ if (file != null) {
+ mFile = new AtomicFile(file);
+ mTimeshift = read(monotonicTime - mClock.elapsedRealtime());
+ } else {
+ mFile = null;
+ mTimeshift = monotonicTime - mClock.elapsedRealtime();
+ }
}
/**
@@ -81,15 +88,16 @@
return mTimeshift + elapsedRealtimeMs;
}
- private void read() {
+ private long read(long defaultTimeshift) {
if (!mFile.exists()) {
- return;
+ return defaultTimeshift;
}
try {
- readXml(new ByteArrayInputStream(mFile.readFully()), Xml.newBinaryPullParser());
+ return readXml(new ByteArrayInputStream(mFile.readFully()), Xml.newBinaryPullParser());
} catch (IOException e) {
Log.e(TAG, "Cannot load monotonic clock from " + mFile.getBaseFile(), e);
+ return defaultTimeshift;
}
}
@@ -102,18 +110,21 @@
return;
}
- try (FileOutputStream out = mFile.startWrite()) {
+ FileOutputStream out = null;
+ try {
+ out = mFile.startWrite();
writeXml(out, Xml.newBinarySerializer());
+ mFile.finishWrite(out);
} catch (IOException e) {
Log.e(TAG, "Cannot write monotonic clock to " + mFile.getBaseFile(), e);
+ mFile.failWrite(out);
}
}
/**
* Parses an XML file containing the persistent state of the monotonic clock.
*/
- @VisibleForTesting
- public void readXml(InputStream inputStream, TypedXmlPullParser parser) throws IOException {
+ private long readXml(InputStream inputStream, TypedXmlPullParser parser) throws IOException {
long savedTimeshift = 0;
try {
parser.setInput(inputStream, StandardCharsets.UTF_8.name());
@@ -128,14 +139,13 @@
} catch (XmlPullParserException e) {
throw new IOException(e);
}
- mTimeshift = savedTimeshift - mClock.elapsedRealtime();
+ return savedTimeshift - mClock.elapsedRealtime();
}
/**
* Creates an XML file containing the persistent state of the monotonic clock.
*/
- @VisibleForTesting
- public void writeXml(OutputStream out, TypedXmlSerializer serializer) throws IOException {
+ private void writeXml(OutputStream out, TypedXmlSerializer serializer) throws IOException {
serializer.setOutput(out, StandardCharsets.UTF_8.name());
serializer.startDocument(null, true);
serializer.startTag(null, XML_TAG_MONOTONIC_TIME);
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedAttributionImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedAttributionImpl.java
index e3bfb38..e419d13 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedAttributionImpl.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedAttributionImpl.java
@@ -38,7 +38,7 @@
public class ParsedAttributionImpl implements ParsedAttribution, Parcelable {
/** Maximum amount of attributions per package */
- static final int MAX_NUM_ATTRIBUTIONS = 10000;
+ static final int MAX_NUM_ATTRIBUTIONS = 400;
/** Tag of the attribution */
private @NonNull String tag;
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
index 12aff1c..5d82d04 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
@@ -29,7 +29,6 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
-import android.multiuser.Flags;
import android.os.Build;
import android.os.PatternMatcher;
import android.util.Slog;
@@ -127,10 +126,6 @@
.setFlags(provider.getFlags() | flag(ProviderInfo.FLAG_SINGLE_USER,
R.styleable.AndroidManifestProvider_singleUser, sa));
- if (Flags.enableSystemUserOnlyForServicesAndProviders()) {
- provider.setFlags(provider.getFlags() | flag(ProviderInfo.FLAG_SYSTEM_USER_ONLY,
- R.styleable.AndroidManifestProvider_systemUserOnly, sa));
- }
visibleToEphemeral = sa.getBoolean(
R.styleable.AndroidManifestProvider_visibleToInstantApps, false);
if (visibleToEphemeral) {
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
index 4ac542f8..a1dd19a3 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
@@ -29,7 +29,6 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
-import android.multiuser.Flags;
import android.os.Build;
import com.android.internal.R;
@@ -106,11 +105,6 @@
| flag(ServiceInfo.FLAG_SINGLE_USER,
R.styleable.AndroidManifestService_singleUser, sa)));
- if (Flags.enableSystemUserOnlyForServicesAndProviders()) {
- service.setFlags(service.getFlags() | flag(ServiceInfo.FLAG_SYSTEM_USER_ONLY,
- R.styleable.AndroidManifestService_systemUserOnly, sa));
- }
-
visibleToEphemeral = sa.getBoolean(
R.styleable.AndroidManifestService_visibleToInstantApps, false);
if (visibleToEphemeral) {
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 03cfd4f..969f95d 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -80,4 +80,5 @@
void onMediaQualityStatusChanged(in MediaQualityStatus mediaQualityStatus);
void onCallBackModeStarted(int type);
void onCallBackModeStopped(int type, int reason);
+ void onSimultaneousCallingStateChanged(in int[] subIds);
}
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index aab2242..0203ea4 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -104,6 +104,7 @@
void notifyAllowedNetworkTypesChanged(in int phoneId, in int subId, in int reason, in long allowedNetworkType);
void notifyLinkCapacityEstimateChanged(in int phoneId, in int subId,
in List<LinkCapacityEstimate> linkCapacityEstimateList);
+ void notifySimultaneousCellularCallingSubscriptionsChanged(in int[] subIds);
void addCarrierPrivilegesCallback(
int phoneId, ICarrierPrivilegesCallback callback, String pkg, String featureId);
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index b87027c..4191936 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -120,8 +120,9 @@
public static @NonNull <I, O> List<O> map(@Nullable List<I> cur,
Function<? super I, ? extends O> f) {
if (isEmpty(cur)) return Collections.emptyList();
- final ArrayList<O> result = new ArrayList<>();
- for (int i = 0; i < cur.size(); i++) {
+ final int size = cur.size();
+ final ArrayList<O> result = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
result.add(f.apply(cur.get(i)));
}
return result;
@@ -133,7 +134,7 @@
public static @NonNull <I, O> Set<O> map(@Nullable Set<I> cur,
Function<? super I, ? extends O> f) {
if (isEmpty(cur)) return emptySet();
- ArraySet<O> result = new ArraySet<>();
+ ArraySet<O> result = new ArraySet<>(cur.size());
if (cur instanceof ArraySet) {
ArraySet<I> arraySet = (ArraySet<I>) cur;
int size = arraySet.size();
@@ -163,7 +164,7 @@
Function<? super I, ? extends O> f) {
if (isEmpty(cur)) return Collections.emptyList();
List<O> result = null;
- for (int i = 0; i < cur.size(); i++) {
+ for (int i = 0, size = cur.size(); i < size; i++) {
O transformed = f.apply(cur.get(i));
if (transformed != null) {
result = add(result, transformed);
@@ -239,7 +240,7 @@
public static @NonNull <T> List<T> filter(@Nullable List<?> list, Class<T> c) {
if (isEmpty(list)) return Collections.emptyList();
ArrayList<T> result = null;
- for (int i = 0; i < list.size(); i++) {
+ for (int i = 0, size = list.size(); i < size; i++) {
final Object item = list.get(i);
if (c.isInstance(item)) {
result = ArrayUtils.add(result, (T) item);
@@ -273,7 +274,7 @@
public static @Nullable <T> T find(@Nullable List<T> items,
java.util.function.Predicate<T> predicate) {
if (isEmpty(items)) return null;
- for (int i = 0; i < items.size(); i++) {
+ for (int i = 0, size = items.size(); i < size; i++) {
final T item = items.get(i);
if (predicate.test(item)) return item;
}
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index 58ee2b2..13efaf0 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -26,6 +26,8 @@
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FACE_WAKE_AND_UNLOCK;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FINGERPRINT_WAKE_AND_UNLOCK;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD;
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE;
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOAD_SHARE_SHEET;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOCKSCREEN_UNLOCK;
@@ -234,6 +236,19 @@
*/
public static final int ACTION_BACK_SYSTEM_ANIMATION = 25;
+ /**
+ * Time notifications spent in hidden state for performance reasons. We might temporary
+ * hide notifications after display size changes (e.g. fold/unfold of a foldable device)
+ * and measure them while they are hidden to unblock rendering of the rest of the UI.
+ */
+ public static final int ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE = 26;
+
+ /**
+ * The same as {@link ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE} but tracks time only
+ * when the notifications are hidden and when the shade is open or keyguard is visible.
+ */
+ public static final int ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN = 27;
+
private static final int[] ACTIONS_ALL = {
ACTION_EXPAND_PANEL,
ACTION_TOGGLE_RECENTS,
@@ -261,6 +276,8 @@
ACTION_NOTIFICATION_BIG_PICTURE_LOADED,
ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME,
ACTION_BACK_SYSTEM_ANIMATION,
+ ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE,
+ ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN,
};
/** @hide */
@@ -291,6 +308,8 @@
ACTION_NOTIFICATION_BIG_PICTURE_LOADED,
ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME,
ACTION_BACK_SYSTEM_ANIMATION,
+ ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE,
+ ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Action {
@@ -324,6 +343,8 @@
UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATION_BIG_PICTURE_LOADED,
UIACTION_LATENCY_REPORTED__ACTION__ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME,
UIACTION_LATENCY_REPORTED__ACTION__ACTION_BACK_SYSTEM_ANIMATION,
+ UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE,
+ UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN,
};
private final Object mLock = new Object();
@@ -514,6 +535,10 @@
return "ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME";
case UIACTION_LATENCY_REPORTED__ACTION__ACTION_BACK_SYSTEM_ANIMATION:
return "ACTION_BACK_SYSTEM_ANIMATION";
+ case UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE:
+ return "ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE";
+ case UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN:
+ return "ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN";
default:
throw new IllegalArgumentException("Invalid action");
}
diff --git a/core/java/com/android/internal/util/RingBuffer.java b/core/java/com/android/internal/util/RingBuffer.java
index 0488659..342ba1b6 100644
--- a/core/java/com/android/internal/util/RingBuffer.java
+++ b/core/java/com/android/internal/util/RingBuffer.java
@@ -19,7 +19,10 @@
import static com.android.internal.util.Preconditions.checkArgumentPositive;
import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
+import java.util.function.IntFunction;
+import java.util.function.Supplier;
/**
* A simple ring buffer structure with bounded capacity backed by an array.
@@ -30,16 +33,35 @@
@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class RingBuffer<T> {
+ private final Supplier<T> mNewItem;
// Array for storing events.
private final T[] mBuffer;
// Cursor keeping track of the logical end of the array. This cursor never
// wraps and instead keeps track of the total number of append() operations.
private long mCursor = 0;
+ /**
+ * @deprecated This uses reflection to create new instances.
+ * Use {@link #RingBuffer(Supplier, IntFunction, int)}} instead.
+ */
+ @Deprecated
public RingBuffer(Class<T> c, int capacity) {
+ this(() -> (T) createNewItem(c), cap -> (T[]) Array.newInstance(c, cap), capacity);
+ }
+
+ private static Object createNewItem(Class c) {
+ try {
+ return c.getDeclaredConstructor().newInstance();
+ } catch (IllegalAccessException | InstantiationException | NoSuchMethodException
+ | InvocationTargetException e) {
+ return null;
+ }
+ }
+
+ public RingBuffer(Supplier<T> newItem, IntFunction<T[]> newBacking, int capacity) {
checkArgumentPositive(capacity, "A RingBuffer cannot have 0 capacity");
- // Java cannot create generic arrays without a runtime hint.
- mBuffer = (T[]) Array.newInstance(c, capacity);
+ mBuffer = newBacking.apply(capacity);
+ mNewItem = newItem;
}
public int size() {
@@ -69,22 +91,11 @@
public T getNextSlot() {
final int nextSlotIdx = indexOf(mCursor++);
if (mBuffer[nextSlotIdx] == null) {
- mBuffer[nextSlotIdx] = createNewItem();
+ mBuffer[nextSlotIdx] = mNewItem.get();
}
return mBuffer[nextSlotIdx];
}
- /**
- * @return a new object of type <T> or null if a new object could not be created.
- */
- protected T createNewItem() {
- try {
- return (T) mBuffer.getClass().getComponentType().newInstance();
- } catch (IllegalAccessException | InstantiationException e) {
- return null;
- }
- }
-
public T[] toArray() {
// Only generic way to create a T[] from another T[]
T[] out = Arrays.copyOf(mBuffer, size(), (Class<T[]>) mBuffer.getClass());
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index bf8e613..757978b 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1384,8 +1384,8 @@
}
public boolean isUserInLockdown(int userId) {
- return getStrongAuthForUser(userId)
- == StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
+ return (getStrongAuthForUser(userId)
+ & StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN) != 0;
}
private static class WrappedCallback extends ICheckCredentialProgressCallback.Stub {
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 2a744e3..c8fd246 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -269,6 +269,9 @@
"android_window_WindowInfosListener.cpp",
"android_window_ScreenCapture.cpp",
"jni_common.cpp",
+ "android_tracing_PerfettoDataSource.cpp",
+ "android_tracing_PerfettoDataSourceInstance.cpp",
+ "android_tracing_PerfettoProducer.cpp",
],
static_libs: [
@@ -282,6 +285,7 @@
"libscrypt_static",
"libstatssocket_lazy",
"libskia",
+ "libperfetto_client_experimental",
],
shared_libs: [
@@ -355,6 +359,7 @@
"server_configurable_flags",
"libimage_io",
"libultrahdr",
+ "libperfetto_c",
],
export_shared_lib_headers: [
// our headers include libnativewindow's public headers
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 17aad43..7a16318 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -220,6 +220,9 @@
extern int register_android_window_WindowInfosListener(JNIEnv* env);
extern int register_android_window_ScreenCapture(JNIEnv* env);
extern int register_jni_common(JNIEnv* env);
+extern int register_android_tracing_PerfettoDataSource(JNIEnv* env);
+extern int register_android_tracing_PerfettoDataSourceInstance(JNIEnv* env);
+extern int register_android_tracing_PerfettoProducer(JNIEnv* env);
// Namespace for Android Runtime flags applied during boot time.
static const char* RUNTIME_NATIVE_BOOT_NAMESPACE = "runtime_native_boot";
@@ -1675,6 +1678,10 @@
REG_JNI(register_android_window_WindowInfosListener),
REG_JNI(register_android_window_ScreenCapture),
REG_JNI(register_jni_common),
+
+ REG_JNI(register_android_tracing_PerfettoDataSource),
+ REG_JNI(register_android_tracing_PerfettoDataSourceInstance),
+ REG_JNI(register_android_tracing_PerfettoProducer),
};
/*
diff --git a/core/jni/android_hardware_OverlayProperties.cpp b/core/jni/android_hardware_OverlayProperties.cpp
index 5b95ee7..f6fe3dd 100644
--- a/core/jni/android_hardware_OverlayProperties.cpp
+++ b/core/jni/android_hardware_OverlayProperties.cpp
@@ -61,13 +61,12 @@
static_cast<int32_t>(HAL_PIXEL_FORMAT_RGBA_FP16)) !=
i.pixelFormats.end() &&
std::find(i.standards.begin(), i.standards.end(),
- static_cast<int32_t>(HAL_DATASPACE_STANDARD_BT2020)) !=
+ static_cast<int32_t>(HAL_DATASPACE_STANDARD_BT709)) !=
i.standards.end() &&
std::find(i.transfers.begin(), i.transfers.end(),
- static_cast<int32_t>(HAL_DATASPACE_TRANSFER_ST2084)) !=
- i.transfers.end() &&
+ static_cast<int32_t>(HAL_DATASPACE_TRANSFER_SRGB)) != i.transfers.end() &&
std::find(i.ranges.begin(), i.ranges.end(),
- static_cast<int32_t>(HAL_DATASPACE_RANGE_FULL)) != i.ranges.end()) {
+ static_cast<int32_t>(HAL_DATASPACE_RANGE_EXTENDED)) != i.ranges.end()) {
return true;
}
}
diff --git a/core/jni/android_os_VintfObject.cpp b/core/jni/android_os_VintfObject.cpp
index b651711..a5b2f65 100644
--- a/core/jni/android_os_VintfObject.cpp
+++ b/core/jni/android_os_VintfObject.cpp
@@ -96,8 +96,11 @@
static jint android_os_VintfObject_verifyBuildAtBoot(JNIEnv* env, jclass) {
std::string error;
+ // Use temporary VintfObject, not the shared instance, to release memory
+ // after check.
int32_t status =
- VintfObject::GetInstance()
+ VintfObject::Builder()
+ .build()
->checkCompatibility(&error, ENABLE_ALL_CHECKS.disableAvb().disableKernel());
if (status)
LOG(WARNING) << "VintfObject.verifyBuildAtBoot() returns " << status << ": " << error;
diff --git a/core/jni/android_tracing_PerfettoDataSource.cpp b/core/jni/android_tracing_PerfettoDataSource.cpp
new file mode 100644
index 0000000..d710698
--- /dev/null
+++ b/core/jni/android_tracing_PerfettoDataSource.cpp
@@ -0,0 +1,437 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "Perfetto"
+
+#include "android_tracing_PerfettoDataSource.h"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+#include <nativehelper/JNIHelp.h>
+#include <perfetto/public/data_source.h>
+#include <perfetto/public/producer.h>
+#include <perfetto/public/protos/trace/test_event.pzc.h>
+#include <perfetto/public/protos/trace/trace_packet.pzc.h>
+#include <perfetto/tracing.h>
+#include <utils/Log.h>
+#include <utils/RefBase.h>
+
+#include <sstream>
+#include <thread>
+
+#include "core_jni_helpers.h"
+
+namespace android {
+
+static struct {
+ jclass clazz;
+ jmethodID createInstance;
+ jmethodID createTlsState;
+ jmethodID createIncrementalState;
+} gPerfettoDataSourceClassInfo;
+
+static struct {
+ jclass clazz;
+ jmethodID init;
+ jmethodID getAndClearAllPendingTracePackets;
+} gTracingContextClassInfo;
+
+static struct {
+ jclass clazz;
+ jmethodID init;
+} gCreateTlsStateArgsClassInfo;
+
+static struct {
+ jclass clazz;
+ jmethodID init;
+} gCreateIncrementalStateArgsClassInfo;
+
+static JavaVM* gVm;
+
+struct TlsState {
+ jobject jobj;
+};
+
+struct IncrementalState {
+ jobject jobj;
+};
+
+static void traceAllPendingPackets(JNIEnv* env, jobject jCtx, PerfettoDsTracerIterator* ctx) {
+ jobjectArray packets =
+ (jobjectArray)env
+ ->CallObjectMethod(jCtx,
+ gTracingContextClassInfo.getAndClearAllPendingTracePackets);
+ if (env->ExceptionOccurred()) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+
+ LOG_ALWAYS_FATAL("Failed to call java context finalize method");
+ }
+
+ int packets_count = env->GetArrayLength(packets);
+ for (int i = 0; i < packets_count; i++) {
+ jbyteArray packet_proto_buffer = (jbyteArray)env->GetObjectArrayElement(packets, i);
+
+ jbyte* raw_proto_buffer = env->GetByteArrayElements(packet_proto_buffer, 0);
+ int buffer_size = env->GetArrayLength(packet_proto_buffer);
+
+ struct PerfettoDsRootTracePacket trace_packet;
+ PerfettoDsTracerPacketBegin(ctx, &trace_packet);
+ PerfettoPbMsgAppendBytes(&trace_packet.msg.msg, (const uint8_t*)raw_proto_buffer,
+ buffer_size);
+ PerfettoDsTracerPacketEnd(ctx, &trace_packet);
+ }
+}
+
+PerfettoDataSource::PerfettoDataSource(JNIEnv* env, jobject javaDataSource,
+ std::string dataSourceName)
+ : dataSourceName(std::move(dataSourceName)),
+ mJavaDataSource(env->NewGlobalRef(javaDataSource)) {}
+
+jobject PerfettoDataSource::newInstance(JNIEnv* env, void* ds_config, size_t ds_config_size,
+ PerfettoDsInstanceIndex inst_id) {
+ jbyteArray configArray = env->NewByteArray(ds_config_size);
+
+ void* temp = env->GetPrimitiveArrayCritical((jarray)configArray, 0);
+ memcpy(temp, ds_config, ds_config_size);
+ env->ReleasePrimitiveArrayCritical(configArray, temp, 0);
+
+ jobject instance =
+ env->CallObjectMethod(mJavaDataSource, gPerfettoDataSourceClassInfo.createInstance,
+ configArray, inst_id);
+
+ if (env->ExceptionCheck()) {
+ LOGE_EX(env);
+ env->ExceptionClear();
+ LOG_ALWAYS_FATAL("Failed to create new Java Perfetto datasource instance");
+ }
+
+ return instance;
+}
+
+jobject PerfettoDataSource::createTlsStateGlobalRef(JNIEnv* env, PerfettoDsInstanceIndex inst_id) {
+ ScopedLocalRef<jobject> args(env,
+ env->NewObject(gCreateTlsStateArgsClassInfo.clazz,
+ gCreateTlsStateArgsClassInfo.init, mJavaDataSource,
+ inst_id));
+
+ ScopedLocalRef<jobject> tslState(env,
+ env->CallObjectMethod(mJavaDataSource,
+ gPerfettoDataSourceClassInfo
+ .createTlsState,
+ args.get()));
+
+ if (env->ExceptionCheck()) {
+ LOGE_EX(env);
+ env->ExceptionClear();
+ LOG_ALWAYS_FATAL("Failed to create new Java Perfetto incremental state");
+ }
+
+ return env->NewGlobalRef(tslState.get());
+}
+
+jobject PerfettoDataSource::createIncrementalStateGlobalRef(JNIEnv* env,
+ PerfettoDsInstanceIndex inst_id) {
+ ScopedLocalRef<jobject> args(env,
+ env->NewObject(gCreateIncrementalStateArgsClassInfo.clazz,
+ gCreateIncrementalStateArgsClassInfo.init,
+ mJavaDataSource, inst_id));
+
+ ScopedLocalRef<jobject> incrementalState(env,
+ env->CallObjectMethod(mJavaDataSource,
+ gPerfettoDataSourceClassInfo
+ .createIncrementalState,
+ args.get()));
+
+ if (env->ExceptionCheck()) {
+ LOGE_EX(env);
+ env->ExceptionClear();
+ LOG_ALWAYS_FATAL("Failed to create Java Perfetto incremental state");
+ }
+
+ return env->NewGlobalRef(incrementalState.get());
+}
+
+void PerfettoDataSource::trace(JNIEnv* env, jobject traceFunction) {
+ PERFETTO_DS_TRACE(dataSource, ctx) {
+ TlsState* tls_state =
+ reinterpret_cast<TlsState*>(PerfettoDsGetCustomTls(&dataSource, &ctx));
+ IncrementalState* incr_state = reinterpret_cast<IncrementalState*>(
+ PerfettoDsGetIncrementalState(&dataSource, &ctx));
+
+ ScopedLocalRef<jobject> jCtx(env,
+ env->NewObject(gTracingContextClassInfo.clazz,
+ gTracingContextClassInfo.init, &ctx,
+ tls_state->jobj, incr_state->jobj));
+
+ jclass objclass = env->GetObjectClass(traceFunction);
+ jmethodID method =
+ env->GetMethodID(objclass, "trace", "(Landroid/tracing/perfetto/TracingContext;)V");
+ if (method == 0) {
+ LOG_ALWAYS_FATAL("Failed to get method id");
+ }
+
+ env->ExceptionClear();
+
+ env->CallVoidMethod(traceFunction, method, jCtx.get());
+ if (env->ExceptionOccurred()) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ LOG_ALWAYS_FATAL("Failed to call java trace method");
+ }
+
+ traceAllPendingPackets(env, jCtx.get(), &ctx);
+ }
+}
+
+void PerfettoDataSource::flushAll() {
+ PERFETTO_DS_TRACE(dataSource, ctx) {
+ PerfettoDsTracerFlush(&ctx, nullptr, nullptr);
+ }
+}
+
+PerfettoDataSource::~PerfettoDataSource() {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ env->DeleteWeakGlobalRef(mJavaDataSource);
+}
+
+jlong nativeCreate(JNIEnv* env, jclass clazz, jobject javaDataSource, jstring name) {
+ const char* nativeString = env->GetStringUTFChars(name, 0);
+ PerfettoDataSource* dataSource = new PerfettoDataSource(env, javaDataSource, nativeString);
+ env->ReleaseStringUTFChars(name, nativeString);
+
+ dataSource->incStrong((void*)nativeCreate);
+
+ return reinterpret_cast<jlong>(dataSource);
+}
+
+void nativeDestroy(void* ptr) {
+ PerfettoDataSource* dataSource = reinterpret_cast<PerfettoDataSource*>(ptr);
+ dataSource->decStrong((void*)nativeCreate);
+}
+
+static jlong nativeGetFinalizer(JNIEnv* /* env */, jclass /* clazz */) {
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(&nativeDestroy));
+}
+
+void nativeTrace(JNIEnv* env, jclass clazz, jlong dataSourcePtr, jobject traceFunctionInterface) {
+ sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
+
+ datasource->trace(env, traceFunctionInterface);
+}
+
+void nativeFlush(JNIEnv* env, jclass clazz, jobject jCtx, jlong ctxPtr) {
+ auto* ctx = reinterpret_cast<struct PerfettoDsTracerIterator*>(ctxPtr);
+ traceAllPendingPackets(env, jCtx, ctx);
+ PerfettoDsTracerFlush(ctx, nullptr, nullptr);
+}
+
+void nativeFlushAll(JNIEnv* env, jclass clazz, jlong ptr) {
+ sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(ptr);
+ datasource->flushAll();
+}
+
+void nativeRegisterDataSource(JNIEnv* env, jclass clazz, jlong datasource_ptr,
+ int buffer_exhausted_policy) {
+ sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(datasource_ptr);
+
+ struct PerfettoDsParams params = PerfettoDsParamsDefault();
+ params.buffer_exhausted_policy = (PerfettoDsBufferExhaustedPolicy)buffer_exhausted_policy;
+
+ params.user_arg = reinterpret_cast<void*>(datasource.get());
+
+ params.on_setup_cb = [](struct PerfettoDsImpl*, PerfettoDsInstanceIndex inst_id,
+ void* ds_config, size_t ds_config_size, void* user_arg,
+ struct PerfettoDsOnSetupArgs*) -> void* {
+ JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+
+ auto* datasource = reinterpret_cast<PerfettoDataSource*>(user_arg);
+
+ ScopedLocalRef<jobject> java_data_source_instance(env,
+ datasource->newInstance(env, ds_config,
+ ds_config_size,
+ inst_id));
+
+ auto* datasource_instance =
+ new PerfettoDataSourceInstance(env, java_data_source_instance.get(), inst_id);
+
+ return static_cast<void*>(datasource_instance);
+ };
+
+ params.on_create_tls_cb = [](struct PerfettoDsImpl* ds_impl, PerfettoDsInstanceIndex inst_id,
+ struct PerfettoDsTracerImpl* tracer, void* user_arg) -> void* {
+ JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+
+ auto* datasource = reinterpret_cast<PerfettoDataSource*>(user_arg);
+
+ jobject java_tls_state = datasource->createTlsStateGlobalRef(env, inst_id);
+
+ auto* tls_state = new TlsState(java_tls_state);
+
+ return static_cast<void*>(tls_state);
+ };
+
+ params.on_delete_tls_cb = [](void* ptr) {
+ JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+
+ TlsState* tls_state = reinterpret_cast<TlsState*>(ptr);
+ env->DeleteGlobalRef(tls_state->jobj);
+ delete tls_state;
+ };
+
+ params.on_create_incr_cb = [](struct PerfettoDsImpl* ds_impl, PerfettoDsInstanceIndex inst_id,
+ struct PerfettoDsTracerImpl* tracer, void* user_arg) -> void* {
+ JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+
+ auto* datasource = reinterpret_cast<PerfettoDataSource*>(user_arg);
+ jobject java_incr_state = datasource->createIncrementalStateGlobalRef(env, inst_id);
+
+ auto* incr_state = new IncrementalState(java_incr_state);
+ return static_cast<void*>(incr_state);
+ };
+
+ params.on_delete_incr_cb = [](void* ptr) {
+ JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+
+ IncrementalState* incr_state = reinterpret_cast<IncrementalState*>(ptr);
+ env->DeleteGlobalRef(incr_state->jobj);
+ delete incr_state;
+ };
+
+ params.on_start_cb = [](struct PerfettoDsImpl*, PerfettoDsInstanceIndex, void*, void* inst_ctx,
+ struct PerfettoDsOnStartArgs*) {
+ JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+
+ auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
+ datasource_instance->onStart(env);
+ };
+
+ params.on_flush_cb = [](struct PerfettoDsImpl*, PerfettoDsInstanceIndex, void*, void* inst_ctx,
+ struct PerfettoDsOnFlushArgs*) {
+ JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+
+ auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
+ datasource_instance->onFlush(env);
+ };
+
+ params.on_stop_cb = [](struct PerfettoDsImpl*, PerfettoDsInstanceIndex inst_id, void* user_arg,
+ void* inst_ctx, struct PerfettoDsOnStopArgs*) {
+ JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+
+ auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
+ 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);
+ delete datasource_instance;
+ };
+
+ PerfettoDsRegister(&datasource->dataSource, datasource->dataSourceName.c_str(), params);
+}
+
+jobject nativeGetPerfettoInstanceLocked(JNIEnv* env, jclass clazz, jlong dataSourcePtr,
+ PerfettoDsInstanceIndex 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
+ return nullptr;
+ }
+
+ return datasource_instance->GetJavaDataSourceInstance();
+}
+
+void nativeReleasePerfettoInstanceLocked(JNIEnv* env, jclass clazz, jlong dataSourcePtr,
+ PerfettoDsInstanceIndex instance_idx) {
+ sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
+ PerfettoDsImplReleaseInstanceLocked(datasource->dataSource.impl, instance_idx);
+}
+
+const JNINativeMethod gMethods[] = {
+ /* name, signature, funcPtr */
+ {"nativeCreate", "(Landroid/tracing/perfetto/DataSource;Ljava/lang/String;)J",
+ (void*)nativeCreate},
+ {"nativeTrace", "(JLandroid/tracing/perfetto/TraceFunction;)V", (void*)nativeTrace},
+ {"nativeFlushAll", "(J)V", (void*)nativeFlushAll},
+ {"nativeGetFinalizer", "()J", (void*)nativeGetFinalizer},
+ {"nativeRegisterDataSource", "(JI)V", (void*)nativeRegisterDataSource},
+ {"nativeGetPerfettoInstanceLocked", "(JI)Landroid/tracing/perfetto/DataSourceInstance;",
+ (void*)nativeGetPerfettoInstanceLocked},
+ {"nativeReleasePerfettoInstanceLocked", "(JI)V",
+ (void*)nativeReleasePerfettoInstanceLocked},
+};
+
+const JNINativeMethod gMethodsTracingContext[] = {
+ /* name, signature, funcPtr */
+ {"nativeFlush", "(Landroid/tracing/perfetto/TracingContext;J)V", (void*)nativeFlush},
+};
+
+int register_android_tracing_PerfettoDataSource(JNIEnv* env) {
+ int res = jniRegisterNativeMethods(env, "android/tracing/perfetto/DataSource", gMethods,
+ NELEM(gMethods));
+
+ LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
+
+ res = jniRegisterNativeMethods(env, "android/tracing/perfetto/TracingContext",
+ gMethodsTracingContext, NELEM(gMethodsTracingContext));
+
+ LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
+
+ if (env->GetJavaVM(&gVm) != JNI_OK) {
+ LOG_ALWAYS_FATAL("Failed to get JavaVM from JNIEnv: %p", env);
+ }
+
+ jclass clazz = env->FindClass("android/tracing/perfetto/DataSource");
+ gPerfettoDataSourceClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+ gPerfettoDataSourceClassInfo.createInstance =
+ env->GetMethodID(gPerfettoDataSourceClassInfo.clazz, "createInstance",
+ "([BI)Landroid/tracing/perfetto/DataSourceInstance;");
+ gPerfettoDataSourceClassInfo.createTlsState =
+ env->GetMethodID(gPerfettoDataSourceClassInfo.clazz, "createTlsState",
+ "(Landroid/tracing/perfetto/CreateTlsStateArgs;)Ljava/lang/Object;");
+ gPerfettoDataSourceClassInfo.createIncrementalState =
+ env->GetMethodID(gPerfettoDataSourceClassInfo.clazz, "createIncrementalState",
+ "(Landroid/tracing/perfetto/CreateIncrementalStateArgs;)Ljava/lang/"
+ "Object;");
+
+ clazz = env->FindClass("android/tracing/perfetto/TracingContext");
+ gTracingContextClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+ gTracingContextClassInfo.init = env->GetMethodID(gTracingContextClassInfo.clazz, "<init>",
+ "(JLjava/lang/Object;Ljava/lang/Object;)V");
+ gTracingContextClassInfo.getAndClearAllPendingTracePackets =
+ env->GetMethodID(gTracingContextClassInfo.clazz, "getAndClearAllPendingTracePackets",
+ "()[[B");
+
+ clazz = env->FindClass("android/tracing/perfetto/CreateTlsStateArgs");
+ gCreateTlsStateArgsClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+ gCreateTlsStateArgsClassInfo.init =
+ env->GetMethodID(gCreateTlsStateArgsClassInfo.clazz, "<init>",
+ "(Landroid/tracing/perfetto/DataSource;I)V");
+
+ clazz = env->FindClass("android/tracing/perfetto/CreateIncrementalStateArgs");
+ gCreateIncrementalStateArgsClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+ gCreateIncrementalStateArgsClassInfo.init =
+ env->GetMethodID(gCreateIncrementalStateArgsClassInfo.clazz, "<init>",
+ "(Landroid/tracing/perfetto/DataSource;I)V");
+
+ return 0;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/core/jni/android_tracing_PerfettoDataSource.h b/core/jni/android_tracing_PerfettoDataSource.h
new file mode 100644
index 0000000..4ddf1d8
--- /dev/null
+++ b/core/jni/android_tracing_PerfettoDataSource.h
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "Perfetto"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+#include <nativehelper/JNIHelp.h>
+#include <perfetto/public/data_source.h>
+#include <perfetto/public/producer.h>
+#include <perfetto/public/protos/trace/test_event.pzc.h>
+#include <perfetto/public/protos/trace/trace_packet.pzc.h>
+#include <perfetto/tracing.h>
+#include <utils/Log.h>
+#include <utils/RefBase.h>
+
+#include <sstream>
+#include <thread>
+
+#include "android_tracing_PerfettoDataSourceInstance.h"
+#include "core_jni_helpers.h"
+
+namespace android {
+
+class PerfettoDataSource : public virtual RefBase {
+public:
+ const std::string dataSourceName;
+ struct PerfettoDs dataSource = PERFETTO_DS_INIT();
+
+ PerfettoDataSource(JNIEnv* env, jobject java_data_source, std::string data_source_name);
+ ~PerfettoDataSource();
+
+ jobject newInstance(JNIEnv* env, void* ds_config, size_t ds_config_size,
+ PerfettoDsInstanceIndex inst_id);
+
+ jobject createTlsStateGlobalRef(JNIEnv* env, PerfettoDsInstanceIndex inst_id);
+ jobject createIncrementalStateGlobalRef(JNIEnv* env, PerfettoDsInstanceIndex inst_id);
+ void trace(JNIEnv* env, jobject trace_function);
+ void flushAll();
+
+private:
+ jobject mJavaDataSource;
+ std::map<PerfettoDsInstanceIndex, PerfettoDataSourceInstance*> mInstances;
+};
+
+} // namespace android
\ No newline at end of file
diff --git a/core/jni/android_tracing_PerfettoDataSourceInstance.cpp b/core/jni/android_tracing_PerfettoDataSourceInstance.cpp
new file mode 100644
index 0000000..e659bf1
--- /dev/null
+++ b/core/jni/android_tracing_PerfettoDataSourceInstance.cpp
@@ -0,0 +1,138 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "Perfetto"
+
+#include "android_tracing_PerfettoDataSourceInstance.h"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+#include <nativehelper/JNIHelp.h>
+#include <perfetto/public/data_source.h>
+#include <perfetto/public/producer.h>
+#include <perfetto/public/protos/trace/test_event.pzc.h>
+#include <perfetto/public/protos/trace/trace_packet.pzc.h>
+#include <perfetto/tracing.h>
+#include <utils/Log.h>
+#include <utils/RefBase.h>
+
+#include <sstream>
+#include <thread>
+
+#include "core_jni_helpers.h"
+
+namespace android {
+
+static struct {
+ jclass clazz;
+ jmethodID init;
+} gStartCallbackArgumentsClassInfo;
+
+static struct {
+ jclass clazz;
+ jmethodID init;
+} gFlushCallbackArgumentsClassInfo;
+
+static struct {
+ jclass clazz;
+ jmethodID init;
+} gStopCallbackArgumentsClassInfo;
+
+static JavaVM* gVm;
+
+void callJavaMethodWithArgsObject(JNIEnv* env, jobject classRef, jmethodID method, jobject args) {
+ ScopedLocalRef<jobject> localClassRef(env, env->NewLocalRef(classRef));
+
+ if (localClassRef == nullptr) {
+ ALOGE("Weak reference went out of scope");
+ return;
+ }
+
+ env->CallVoidMethod(localClassRef.get(), method, args);
+
+ if (env->ExceptionCheck()) {
+ env->ExceptionDescribe();
+ LOGE_EX(env);
+ env->ExceptionClear();
+ }
+}
+
+PerfettoDataSourceInstance::PerfettoDataSourceInstance(JNIEnv* env, jobject javaDataSourceInstance,
+ PerfettoDsInstanceIndex inst_idx)
+ : inst_idx(inst_idx), mJavaDataSourceInstance(env->NewGlobalRef(javaDataSourceInstance)) {}
+
+PerfettoDataSourceInstance::~PerfettoDataSourceInstance() {
+ JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+ env->DeleteGlobalRef(mJavaDataSourceInstance);
+}
+
+void PerfettoDataSourceInstance::onStart(JNIEnv* env) {
+ ScopedLocalRef<jobject> args(env,
+ env->NewObject(gStartCallbackArgumentsClassInfo.clazz,
+ gStartCallbackArgumentsClassInfo.init));
+ jclass cls = env->GetObjectClass(mJavaDataSourceInstance);
+ jmethodID mid = env->GetMethodID(cls, "onStart",
+ "(Landroid/tracing/perfetto/StartCallbackArguments;)V");
+
+ callJavaMethodWithArgsObject(env, mJavaDataSourceInstance, mid, args.get());
+}
+
+void PerfettoDataSourceInstance::onFlush(JNIEnv* env) {
+ ScopedLocalRef<jobject> args(env,
+ env->NewObject(gFlushCallbackArgumentsClassInfo.clazz,
+ gFlushCallbackArgumentsClassInfo.init));
+ jclass cls = env->GetObjectClass(mJavaDataSourceInstance);
+ jmethodID mid = env->GetMethodID(cls, "onFlush",
+ "(Landroid/tracing/perfetto/FlushCallbackArguments;)V");
+
+ callJavaMethodWithArgsObject(env, mJavaDataSourceInstance, mid, args.get());
+}
+
+void PerfettoDataSourceInstance::onStop(JNIEnv* env) {
+ ScopedLocalRef<jobject> args(env,
+ env->NewObject(gStopCallbackArgumentsClassInfo.clazz,
+ gStopCallbackArgumentsClassInfo.init));
+ jclass cls = env->GetObjectClass(mJavaDataSourceInstance);
+ jmethodID mid =
+ env->GetMethodID(cls, "onStop", "(Landroid/tracing/perfetto/StopCallbackArguments;)V");
+
+ callJavaMethodWithArgsObject(env, mJavaDataSourceInstance, mid, args.get());
+}
+
+int register_android_tracing_PerfettoDataSourceInstance(JNIEnv* env) {
+ if (env->GetJavaVM(&gVm) != JNI_OK) {
+ LOG_ALWAYS_FATAL("Failed to get JavaVM from JNIEnv: %p", env);
+ }
+
+ jclass clazz = env->FindClass("android/tracing/perfetto/StartCallbackArguments");
+ gStartCallbackArgumentsClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+ gStartCallbackArgumentsClassInfo.init =
+ env->GetMethodID(gStartCallbackArgumentsClassInfo.clazz, "<init>", "()V");
+
+ clazz = env->FindClass("android/tracing/perfetto/FlushCallbackArguments");
+ gFlushCallbackArgumentsClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+ gFlushCallbackArgumentsClassInfo.init =
+ env->GetMethodID(gFlushCallbackArgumentsClassInfo.clazz, "<init>", "()V");
+
+ clazz = env->FindClass("android/tracing/perfetto/StopCallbackArguments");
+ gStopCallbackArgumentsClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+ gStopCallbackArgumentsClassInfo.init =
+ env->GetMethodID(gStopCallbackArgumentsClassInfo.clazz, "<init>", "()V");
+
+ return 0;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/core/jni/android_tracing_PerfettoDataSourceInstance.h b/core/jni/android_tracing_PerfettoDataSourceInstance.h
new file mode 100644
index 0000000..d577655
--- /dev/null
+++ b/core/jni/android_tracing_PerfettoDataSourceInstance.h
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "Perfetto"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+#include <nativehelper/JNIHelp.h>
+#include <perfetto/public/data_source.h>
+#include <perfetto/public/producer.h>
+#include <perfetto/public/protos/trace/test_event.pzc.h>
+#include <perfetto/public/protos/trace/trace_packet.pzc.h>
+#include <perfetto/tracing.h>
+#include <utils/Log.h>
+#include <utils/RefBase.h>
+
+#include <sstream>
+#include <thread>
+
+#include "core_jni_helpers.h"
+
+namespace android {
+
+class PerfettoDataSourceInstance {
+public:
+ PerfettoDataSourceInstance(JNIEnv* env, jobject javaDataSourceInstance,
+ PerfettoDsInstanceIndex inst_idx);
+ ~PerfettoDataSourceInstance();
+
+ void onStart(JNIEnv* env);
+ void onFlush(JNIEnv* env);
+ void onStop(JNIEnv* env);
+
+ jobject GetJavaDataSourceInstance() {
+ return mJavaDataSourceInstance;
+ }
+
+ PerfettoDsInstanceIndex getIndex() {
+ return inst_idx;
+ }
+
+private:
+ PerfettoDsInstanceIndex inst_idx;
+ jobject mJavaDataSourceInstance;
+};
+} // namespace android
\ No newline at end of file
diff --git a/core/jni/android_tracing_PerfettoProducer.cpp b/core/jni/android_tracing_PerfettoProducer.cpp
new file mode 100644
index 0000000..ce72f58
--- /dev/null
+++ b/core/jni/android_tracing_PerfettoProducer.cpp
@@ -0,0 +1,58 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "Perfetto"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+#include <nativehelper/JNIHelp.h>
+#include <perfetto/public/data_source.h>
+#include <perfetto/public/producer.h>
+#include <perfetto/public/protos/trace/test_event.pzc.h>
+#include <perfetto/public/protos/trace/trace_packet.pzc.h>
+#include <perfetto/tracing.h>
+#include <utils/Log.h>
+#include <utils/RefBase.h>
+
+#include <sstream>
+#include <thread>
+
+#include "android_tracing_PerfettoDataSource.h"
+#include "core_jni_helpers.h"
+
+namespace android {
+
+void perfettoProducerInit(JNIEnv* env, jclass clazz, int backends) {
+ struct PerfettoProducerInitArgs args = PERFETTO_PRODUCER_INIT_ARGS_INIT();
+ args.backends = (PerfettoBackendTypes)backends;
+ PerfettoProducerInit(args);
+}
+
+const JNINativeMethod gMethods[] = {
+ /* name, signature, funcPtr */
+ {"nativePerfettoProducerInit", "(I)V", (void*)perfettoProducerInit},
+};
+
+int register_android_tracing_PerfettoProducer(JNIEnv* env) {
+ int res = jniRegisterNativeMethods(env, "android/tracing/perfetto/Producer", gMethods,
+ NELEM(gMethods));
+
+ LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
+
+ return 0;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 55326da..25b2aaf 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -1938,11 +1938,11 @@
transaction->setFrameTimelineInfo(ftInfo);
}
-static void nativeSetDesiredPresentTime(JNIEnv* env, jclass clazz, jlong transactionObj,
- jlong desiredPresentTime) {
+static void nativeSetDesiredPresentTimeNanos(JNIEnv* env, jclass clazz, jlong transactionObj,
+ jlong desiredPresentTimeNanos) {
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
- transaction->setDesiredPresentTime(desiredPresentTime);
+ transaction->setDesiredPresentTime(desiredPresentTimeNanos);
}
static void nativeAddTransactionCommittedListener(JNIEnv* env, jclass clazz, jlong transactionObj,
@@ -2412,8 +2412,8 @@
{"getNativeTrustedPresentationCallbackFinalizer", "()J", (void*)getNativeTrustedPresentationCallbackFinalizer },
{"nativeGetStalledTransactionInfo", "(I)Landroid/gui/StalledTransactionInfo;",
(void*) nativeGetStalledTransactionInfo },
- {"nativeSetDesiredPresentTime", "(JJ)V",
- (void*) nativeSetDesiredPresentTime },
+ {"nativeSetDesiredPresentTimeNanos", "(JJ)V",
+ (void*) nativeSetDesiredPresentTimeNanos },
// clang-format on
};
diff --git a/core/proto/OWNERS b/core/proto/OWNERS
index db391f7..a854e36 100644
--- a/core/proto/OWNERS
+++ b/core/proto/OWNERS
@@ -18,6 +18,7 @@
per-file apphibernationservice.proto = file:/core/java/android/apphibernation/OWNERS
per-file android/hardware/sensorprivacy.proto = [email protected],[email protected]
per-file background_install_control.proto = [email protected],[email protected],[email protected]
+per-file android/content/intent.proto = file:/PACKAGE_MANAGER_OWNERS
# Biometrics
[email protected]
@@ -31,5 +32,3 @@
# Accessibility
[email protected]
[email protected]
[email protected]
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 52e0124..b63021d 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -397,6 +397,8 @@
optional bool should_override_min_aspect_ratio = 42;
optional bool should_ignore_orientation_request_loop = 43;
optional bool should_override_force_resize_app = 44;
+ optional bool should_enable_user_aspect_ratio_settings = 45;
+ optional bool is_user_fullscreen_override_enabled = 46;
}
/* represents WindowToken */
diff --git a/core/proto/android/service/notification.proto b/core/proto/android/service/notification.proto
index a2978be..ad36b1c 100644
--- a/core/proto/android/service/notification.proto
+++ b/core/proto/android/service/notification.proto
@@ -360,7 +360,7 @@
optional ConversationType allow_conversations_from = 19;
- optional ChannelType allow_channels = 20;
+ optional ChannelPolicy allow_channels = 20;
}
// Enum identifying the type of rule that changed; values set to match ones used in the
@@ -373,8 +373,8 @@
// Enum used in DNDPolicyProto to indicate the type of channels permitted to
// break through DND. Mirrors values in ZenPolicy.
-enum ChannelType {
- CHANNEL_TYPE_UNSET = 0;
- CHANNEL_TYPE_PRIORITY = 1;
- CHANNEL_TYPE_NONE = 2;
+enum ChannelPolicy {
+ CHANNEL_POLICY_UNSET = 0;
+ CHANNEL_POLICY_PRIORITY = 1;
+ CHANNEL_POLICY_NONE = 2;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ef6caef..8ae77a7 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2956,6 +2956,16 @@
<permission android:name="android.permission.MANAGE_SENSORS"
android:protectionLevel="signature" />
+ <!-- Must be required by a DomainSelectionService to ensure that only the
+ system can bind to it.
+ <p>Protection level: signature
+ @SystemApi
+ @hide
+ @FlaggedApi("com.android.internal.telephony.flags.ap_domain_selection_enabled")
+ -->
+ <permission android:name="android.permission.BIND_DOMAIN_SELECTION_SERVICE"
+ android:protectionLevel="signature" />
+
<!-- Must be required by an ImsService to ensure that only the
system can bind to it.
<p>Protection level: signature|privileged|vendorPrivileged
@@ -3042,6 +3052,17 @@
<permission android:name="android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE"
android:protectionLevel="internal|role" />
+ <!-- Used to provide the Telecom framework with access to the last known call ID.
+ <p>Protection level: signature
+ @SystemApi
+ @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")
+ @hide
+ -->
+ <permission android:name="android.permission.ACCESS_LAST_KNOWN_CELL_ID"
+ android:protectionLevel="signature"
+ android:label="@string/permlab_accessLastKnownCellId"
+ android:description="@string/permdesc_accessLastKnownCellId"/>
+
<!-- ================================== -->
<!-- Permissions for sdcard interaction -->
<!-- ================================== -->
@@ -3160,7 +3181,7 @@
types of interactions
@hide -->
<permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"
- android:protectionLevel="signature|installer|role" />
+ android:protectionLevel="signature|installer|module|role" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<!-- Allows interaction across profiles in the same profile group. -->
@@ -5055,7 +5076,7 @@
<p>Intended for use by ROLE_ASSISTANT and signature apps only.
-->
<permission android:name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE"
- android:protectionLevel="signature|role"/>
+ android:protectionLevel="signature|module|role"/>
<!-- Must be required by a {@link android.service.autofill.AutofillService},
to ensure that only the system can bind to it.
@@ -5214,6 +5235,14 @@
<permission android:name="android.permission.BIND_REMOTE_DISPLAY"
android:protectionLevel="signature" />
+ <!-- Must be required by a android.media.tv.ad.TvAdService to ensure that only the system can
+ bind to it.
+ <p>Protection level: signature|privileged
+ @FlaggedApi("android.media.tv.flags.enable_ad_service_fw")
+ -->
+ <permission android:name="android.permission.BIND_TV_AD_SERVICE"
+ android:protectionLevel="signature|privileged" />
+
<!-- Must be required by a {@link android.media.tv.TvInputService}
to ensure that only the system can bind to it.
<p>Protection level: signature|privileged
@@ -5665,7 +5694,7 @@
<!-- @SystemApi Allows an application to manage the holders of a role.
@hide -->
<permission android:name="android.permission.MANAGE_ROLE_HOLDERS"
- android:protectionLevel="signature|installer" />
+ android:protectionLevel="signature|installer|module" />
<!-- @SystemApi Allows an application to manage the holders of roles associated with default
applications.
@@ -5685,7 +5714,7 @@
<!-- @SystemApi Allows an application to observe role holder changes.
@hide -->
<permission android:name="android.permission.OBSERVE_ROLE_HOLDERS"
- android:protectionLevel="signature|installer" />
+ android:protectionLevel="signature|installer|module" />
<!-- Allows an application to manage the companion devices.
@hide -->
@@ -7888,6 +7917,26 @@
<permission android:name="android.permission.RECEIVE_SENSITIVE_NOTIFICATIONS"
android:protectionLevel="signature|role" />
+ <!-- @SystemApi
+ @FlaggedApi("android.app.bic_client")
+ Allows app to call BackgroundInstallControlManager API to retrieve silently installed apps
+ for all users on device.
+ <p>Apps with a BackgroundInstallControlManager client will not be able to call any API without
+ this permission.
+ <p>Protection level: signature|role
+ @hide
+ -->
+ <permission android:name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES"
+ android:protectionLevel="signature|role" />
+
+ <!-- @SystemApi Allows an application to read the system grammatical gender.
+ @FlaggedApi("android.app.system_terms_of_address_enabled")
+ <p>Protection level: signature|privileged|appop
+ @hide
+ -->
+ <permission android:name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"
+ android:protectionLevel="signature|privileged|appop"/>
+
<!-- Attribution for Geofencing service. -->
<attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
<!-- Attribution for Country Detector. -->
diff --git a/core/res/res/drawable-nodpi/platlogo.xml b/core/res/res/drawable-nodpi/platlogo.xml
index f3acab0..fe12f6e 100644
--- a/core/res/res/drawable-nodpi/platlogo.xml
+++ b/core/res/res/drawable-nodpi/platlogo.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2021 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.
@@ -20,179 +20,103 @@
android:viewportWidth="512"
android:viewportHeight="512">
<path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0">
+ android:pathData="M256.22,437.7c-26.38,0 -43.81,-18.3 -61.2,-48.42 -17.39,-30.12 -103.26,-178.86 -120.65,-208.98s-24.52,-54.37 -11.33,-77.21c13.19,-22.84 37.75,-28.79 72.53,-28.79 34.78,0 206.53,0 241.31,0 34.78,0 59.35,5.95 72.53,28.79 13.19,22.84 6.06,47.09 -11.33,77.21s-103.26,178.86 -120.65,208.98c-17.39,30.12 -34.83,48.42 -61.2,48.42Z"
+ android:strokeWidth="0">
<aapt:attr name="android:fillColor">
<gradient
- android:startX="256"
- android:startY="21.81"
- android:endX="256"
- android:endY="350.42"
+ android:startX="56.22"
+ android:startY="256"
+ android:endX="456.22"
+ android:endY="256"
android:type="linear">
<item android:offset="0" android:color="#FF073042"/>
<item android:offset="1" android:color="#FF073042"/>
</gradient>
</aapt:attr>
</path>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="m195.27,187.64l2.25,-6.69c13.91,78.13 50.84,284.39 50.84,50.33 0,-0.97 0.72,-1.81 1.62,-1.81h12.69c0.9,0 1.62,0.83 1.62,1.8 -0.2,409.91 -69.03,-43.64 -69.03,-43.64Z"
- android:fillColor="#3ddc84"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="m158.77,180.68l-33.17,57.45c-1.9,3.3 -0.77,7.52 2.53,9.42 3.3,1.9 7.52,0.77 9.42,-2.53l33.59,-58.17c54.27,24.33 116.34,24.33 170.61,0l33.59,58.17c1.97,3.26 6.21,4.3 9.47,2.33 3.17,-1.91 4.26,-5.99 2.47,-9.23l-33.16,-57.45c56.95,-30.97 95.91,-88.64 101.61,-156.76H57.17c5.7,68.12 44.65,125.79 101.61,156.76Z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M250.26,187.66L262.17,187.66A2.13,2.13 0,0 1,264.3 189.78L264.3,217.85A2.13,2.13 0,0 1,262.17 219.98L250.26,219.98A2.13,2.13 0,0 1,248.14 217.85L248.14,189.78A2.13,2.13 0,0 1,250.26 187.66z"
- android:fillColor="#3ddc84"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M250.12,170.29L262.32,170.29A1.98,1.98 0,0 1,264.3 172.26L264.3,176.39A1.98,1.98 0,0 1,262.32 178.37L250.12,178.37A1.98,1.98 0,0 1,248.14 176.39L248.14,172.26A1.98,1.98 0,0 1,250.12 170.29z"
- android:fillColor="#3ddc84"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M171.92,216.82h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M188.8,275.73h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M369.04,337.63h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M285.93,252.22h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M318.96,218.84h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M294.05,288.55h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M330.82,273.31h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M188.8,298.95h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M220.14,238.94h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M272.1,318.9h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M293.34,349.25h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M161.05,254.24h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M378.92,192h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M137.87,323.7h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
<path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"
- android:strokeWidth="56.561"
- android:fillColor="#00000000"
- android:strokeColor="#f86734"/>
- <path
- android:pathData="m256.22,126.57c-6.69,0 -12.12,5.27 -12.12,11.77v14.52c0,1.25 1.02,2.27 2.27,2.27h0c1.25,0 2.27,-1.02 2.27,-2.27v-3.91c0,-2.51 2.04,-4.55 4.55,-4.55h6.06c2.51,0 4.55,2.04 4.55,4.55v3.91c0,1.25 1.02,2.27 2.27,2.27s2.27,-1.02 2.27,-2.27v-14.52c0,-6.5 -5.43,-11.77 -12.12,-11.77Z"
+ android:pathData="M198.85,168.57l2.03,-6.03c12.55,70.48 45.87,256.56 45.87,45.41 0,-0.88 0.65,-1.63 1.46,-1.63h11.45c0.81,0 1.46,0.75 1.46,1.63 -0.18,369.8 -62.28,-39.37 -62.28,-39.37Z"
+ android:strokeWidth="0"
android:fillColor="#3ddc84"/>
<path
- android:pathData="m93.34,116.36l3.85,-4.36 29.64,9.76 -4.44,5.03 -6.23,-2.1 -7.86,8.91 2.86,5.92 -4.43,5.03 -13.39,-28.18ZM110.43,122.76l-8.86,-3.02 4.11,8.41 4.76,-5.39Z"
+ android:pathData="M186.69,167.97l-23.69,41.03c-1.36,2.36 -0.55,5.37 1.8,6.73 2.36,1.36 5.37,0.55 6.73,-1.8l23.99,-41.55c38.76,17.38 83.1,17.38 121.86,0l23.99,41.55c1.41,2.33 4.44,3.07 6.77,1.66 2.26,-1.37 3.04,-4.28 1.76,-6.59l-23.69,-41.03c40.68,-22.12 68.5,-63.31 72.57,-111.97H114.11c4.07,48.65 31.89,89.85 72.57,111.97Z"
+ android:strokeWidth="0"
android:fillColor="#fff"/>
<path
- android:pathData="m153.62,100.85l-21.71,-6.2 10.38,14.38 -5.21,3.76 -16.78,-23.26 4.49,-3.24 21.65,6.19 -10.35,-14.35 5.24,-3.78 16.78,23.26 -4.49,3.24Z"
+ android:pathData="M248.46,168.59L259.2,168.59A1.92,1.92 0,0 1,261.12 170.5L261.12,195.83A1.92,1.92 0,0 1,259.2 197.75L248.46,197.75A1.92,1.92 0,0 1,246.54 195.83L246.54,170.5A1.92,1.92 0,0 1,248.46 168.59z"
+ android:strokeWidth="0"
+ android:fillColor="#3ddc84"/>
+ <path
+ android:pathData="M248.32,152.92L259.34,152.92A1.78,1.78 0,0 1,261.12 154.7L261.12,158.43A1.78,1.78 0,0 1,259.34 160.21L248.32,160.21A1.78,1.78 0,0 1,246.54 158.43L246.54,154.7A1.78,1.78 0,0 1,248.32 152.92z"
+ android:strokeWidth="0"
+ android:fillColor="#3ddc84"/>
+ <path
+ android:pathData="M159.03,176.91h4.04v4.04h-4.04z"
+ android:strokeWidth="0"
android:fillColor="#fff"/>
<path
- android:pathData="m161.46,63.15l8.99,-3.84c7.43,-3.18 15.96,0.12 19.09,7.44 3.13,7.32 -0.38,15.76 -7.81,18.94l-8.99,3.84 -11.28,-26.38ZM179.41,80.26c4.46,-1.91 5.96,-6.72 4.15,-10.96 -1.81,-4.24 -6.33,-6.48 -10.79,-4.57l-3.08,1.32 6.64,15.53 3.08,-1.32Z"
+ android:pathData="M188.8,275.73h4.04v4.04h-4.04z"
+ android:strokeWidth="0"
android:fillColor="#fff"/>
<path
- android:pathData="m204.23,47.57l11.1,-2.2c5.31,-1.05 9.47,2.08 10.4,6.76 0.72,3.65 -0.76,6.37 -4.07,8.34l12.4,10.44 -7.57,1.5 -11.65,-9.76 -1.03,0.2 2.3,11.61 -6.3,1.25 -5.57,-28.14ZM216.78,56.7c1.86,-0.37 3,-1.71 2.68,-3.33 -0.34,-1.7 -1.88,-2.43 -3.74,-2.06l-4.04,0.8 1.07,5.39 4.04,-0.8Z"
+ android:pathData="M373.41,158.93h4.04v4.04h-4.04z"
+ android:strokeWidth="0"
android:fillColor="#fff"/>
<path
- android:pathData="m244.29,55.6c0.13,-8.16 6.86,-14.72 15.06,-14.58 8.16,0.13 14.72,6.9 14.58,15.06s-6.91,14.72 -15.06,14.58c-8.2,-0.13 -14.71,-6.9 -14.58,-15.06ZM267.44,55.98c0.08,-4.64 -3.54,-8.66 -8.18,-8.74 -4.68,-0.08 -8.42,3.82 -8.5,8.47 -0.08,4.65 3.54,8.66 8.22,8.74 4.64,0.08 8.39,-3.82 8.46,-8.47Z"
+ android:pathData="M112.1,129.34h4.04v4.04h-4.04z"
+ android:strokeWidth="0"
android:fillColor="#fff"/>
<path
- android:pathData="m294.39,44.84l6.31,1.23 -5.49,28.16 -6.31,-1.23 5.49,-28.16Z"
+ android:pathData="M285.93,252.22h4.04v4.04h-4.04z"
+ android:strokeWidth="0"
android:fillColor="#fff"/>
<path
- android:pathData="m321.94,51.41l9.14,3.48c7.55,2.88 11.39,11.17 8.56,18.61 -2.83,7.44 -11.22,11.07 -18.77,8.19l-9.14,-3.48 10.22,-26.8ZM322.96,76.19c4.53,1.73 8.95,-0.69 10.6,-5 1.64,-4.3 -0.05,-9.06 -4.58,-10.78l-3.13,-1.19 -6.01,15.78 3.13,1.19Z"
+ android:pathData="M318.96,218.84h4.04v4.04h-4.04z"
+ android:strokeWidth="0"
android:fillColor="#fff"/>
<path
- android:pathData="m381.41,89.24l-4.21,-3.21 3.65,-4.78 9.06,6.91 -17.4,22.81 -4.85,-3.7 13.75,-18.02Z"
+ android:pathData="M294.05,288.55h4.04v4.04h-4.04z"
+ android:strokeWidth="0"
android:fillColor="#fff"/>
<path
- android:pathData="m397.96,126.37l-9.56,-10.26 3.61,-3.36 22.8,-1.25 3.74,4.02 -12.35,11.51 2.51,2.69 -4.08,3.8 -2.51,-2.69 -4.55,4.24 -4.16,-4.46 4.55,-4.24ZM407.83,117.17l-10.28,0.58 4.49,4.82 5.79,-5.4Z"
+ android:pathData="M330.82,263.31h8.08v8.08h-8.08z"
+ android:strokeWidth="0"
android:fillColor="#fff"/>
+ <path
+ android:pathData="M188.8,298.95h4.04v4.04h-4.04z"
+ android:strokeWidth="0"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M224.18,216.82h8.08v8.08h-8.08z"
+ android:strokeWidth="0"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M272.1,318.9h8.08v8.08h-8.08z"
+ android:strokeWidth="0"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M293.34,339.25h8.08v8.08h-8.08z"
+ android:strokeWidth="0"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M165.09,236.28h8.08v8.08h-8.08z"
+ android:strokeWidth="0"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M378.92,192h8.08v8.08h-8.08z"
+ android:strokeWidth="0"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M204.28,314.86h8.08v8.08h-8.08z"
+ android:strokeWidth="0"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M253.83,118.47c-6.04,0 -10.93,4.76 -10.93,10.62v13.1c0,1.13 0.92,2.05 2.05,2.05h0c1.13,0 2.05,-0.92 2.05,-2.05v-3.53c0,-2.27 1.84,-4.1 4.1,-4.1h5.47c2.27,0 4.1,1.84 4.1,4.1v3.53c0,1.13 0.92,2.05 2.05,2.05s2.05,-0.92 2.05,-2.05v-13.1c0,-5.86 -4.9,-10.62 -10.93,-10.62Z"
+ android:strokeWidth="0"
+ android:fillColor="#3ddc84"/>
+ <path
+ android:pathData="M256,437.7c-26.38,0 -43.81,-18.3 -61.2,-48.42 -17.39,-30.12 -103.26,-178.86 -120.65,-208.98 -17.39,-30.12 -24.52,-54.37 -11.33,-77.21 13.19,-22.84 37.75,-28.79 72.53,-28.79 34.78,0 206.53,0 241.31,0 34.78,0 59.35,5.95 72.53,28.79 13.19,22.84 6.06,47.09 -11.33,77.21 -17.39,30.12 -103.26,178.86 -120.65,208.98 -17.39,30.12 -34.83,48.42 -61.2,48.42Z"
+ android:strokeWidth="55"
+ android:fillColor="#00000000"
+ android:strokeColor="#f86733"/>
</vector>
-
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 8281462..b60bd63 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Laat die app toe om die voorgronddienstipe “stelselvrystelling” te gebruik"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"gebruik voorgronddienstipe “lêerbestuur”"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Laat die app toe om die voorgronddienstipe “lêerbestuur” te gebruik"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"gebruik voorgronddienstipe “spesiale gebruik”"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Laat die app toe om die voorgronddienstipe “spesiale gebruik” te gebruik"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"meet programberging-ruimte"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Gebruik skermslot"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Voer jou skermslot in om voort te gaan"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Druk ferm op die sensor"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Kan nie vingerafdruk herken nie. Probeer weer."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Maak vingerafdruksensor skoon en probeer weer"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Maak sensor skoon en probeer weer"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Druk ferm op sensor"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Kan nie jou gesig sien nie. Hou jou foon op oogvlak."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Te veel beweging. Hou foon stil."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Skryf jou gesig asseblief weer in."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Kan nie gesig herken nie. Probeer weer."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Verander die posisie van jou kop effens"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Kyk meer reguit na jou foon"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Kyk meer reguit na jou foon"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index ff7dee8..5a6ef3c 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"መተግበሪያው የፊት አገልግሎትን በ«systemExempted» ዓይነት እንዲጠቀም ይፈቅዳል"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"የፊት አገልግሎትን በ«fileManagement» ዓይነት ማስሄድ"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"መተግበሪያው የፊት አገልግሎቶችን በ«fileManagement» ዓይነት እንዲጠቀም ያስችላል"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"የፊት አገልግሎትን በ«specialUse» ዓይነት ማስሄድ"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"መተግበሪያው የፊት አገልግሎትን በ«specialUse» ዓይነት እንዲጠቀም ይፈቅዳል"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"የመተግበሪያ ማከማቻ ቦታ ለካ"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"የማያ ገፅ መቆለፊን ይጠቀሙ"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"ለመቀጠል የማያ ገፅ ቁልፍዎን ያስገቡ"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"ዳሳሹን በደንብ ይጫኑት"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"የጣት አሻራን መለየት አልተቻለም። እንደገና ይሞክሩ።"</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"የጣት አሻራ ዳሳሽን ያጽዱ እና እንደገና ይሞክሩ"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"ዳሳሹን ያጽዱ እና እንደገና ይሞክሩ"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"ዳሳሹን ጠበቅ አድርገው ይጫኑት"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"የእርስዎን መልክ ማየት አይችልም። ስልክዎን በዓይን ትክክል ይያዙ።"</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"ከልክ በላይ ብዙ እንቅስቃሴ። ስልኩን ቀጥ አድርገው ይያዙት።"</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"እባክዎ ፊትዎን እንደገና ያስመዝግቡ"</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"መልክን መለየት አልተቻለም። እንደገና ይሞክሩ።"</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"የጭንቅላትዎን ቦታ በትንሹ ይለዋውጡ"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"ስልክዎን ይበልጥ በቀጥታ ይመልከቱ"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"ስልክዎን ይበልጥ በቀጥታ ይመልከቱ"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 0cc49a5..136ad53 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -438,6 +438,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"يسمح هذا الإذن للتطبيق بالاستفادة من الخدمات التي تعمل في المقدّمة ذات النوع \"systemExempted\"."</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"تشغيل الخدمة التي تعمل في المقدّمة ذات النوع \"fileManagement\""</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"يسمح هذا الإذن للتطبيق بالاستفادة من الخدمات التي تعمل في المقدّمة ذات النوع \"fileManagement\""</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"تشغيل الخدمة التي تعمل في المقدّمة ذات النوع \"specialUse\""</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"يسمح هذا الإذن للتطبيق بالاستفادة من الخدمات التي تعمل في المقدّمة ذات النوع \"specialUse\"."</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"قياس مساحة تخزين التطبيق"</string>
@@ -636,7 +640,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"استخدام قفل الشاشة"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"أدخِل قفل الشاشة للمتابعة"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"اضغط بقوة على أداة الاستشعار"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"يتعذّر التعرّف على بصمة الإصبع. يُرجى إعادة المحاولة."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"يُرجى تنظيف مستشعر بصمات الإصبع ثم إعادة المحاولة."</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"تنظيف المستشعر ثم إعادة المحاولة"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"اضغط بقوة على أداة الاستشعار"</string>
@@ -700,7 +705,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"ارفع هاتفك إلى مستوى العينَين لأنّه تتعذّر رؤية وجهك"</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"حركة أكثر من اللازم. يُرجى حمل الهاتف بثبات."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"يُرجى إعادة تسجيل وجهك."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"يتعذّر التعرّف على الوجه. يُرجى إعادة المحاولة."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"غيِّر موضع رأسك قليلاً."</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"يُرجى النظر إلى هاتفك مباشرةً"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"يُرجى النظر إلى هاتفك مباشرةً"</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index ddc2367..fa9e2d7 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"এপ্টোক \"systemExempted\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\"fileManagement\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ চলাওক"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"এপ্টোক \"fileManagement\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ চলাওক"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"এপ্টোক \"specialUse\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"এপৰ ষ্ট’ৰেজৰ খালী ঠাই হিচাপ কৰক"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"স্ক্ৰীন ল\'ক ব্যৱহাৰ কৰক"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"অব্যাহত ৰাখিবলৈ আপোনাৰ স্ক্ৰীন লক দিয়ক"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"ছেন্সৰটোত ভালকৈ টিপক"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"ফিংগাৰপ্ৰিণ্ট চিনাক্ত কৰিব পৰা নাই। পুনৰ চেষ্টা কৰক।"</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"ফিংগাৰপ্ৰিণ্ট ছেন্সৰটো মচি পুনৰ চেষ্টা কৰক"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"ছেন্সৰটো মচি পুনৰ চেষ্টা কৰক"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"ছেন্সৰটোত ভালকৈ টিপক"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"আপোনাৰ মুখাৱয়ব দেখা নাই। আপোনাৰ ফ’নটো চকুৰ স্তৰত ধৰি ৰাখক।"</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"বেছি লৰচৰ কৰি আছে। ফ’নটো স্থিৰকৈ ধৰক।"</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"আপোনাৰ মুখমণ্ডল পুনৰ পঞ্জীয়ন কৰক।"</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"মুখাৱয়ব চিনিব নোৱাৰি। পুনৰ চেষ্টা কৰক।"</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"আপোনাৰ মূৰটোৰ স্থান সামান্য সলনি কৰক"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"আপোনাৰ ফ’নটোলৈ আৰু পোনপটীয়াকৈ চাওক"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"আপোনাৰ ফ’নটোলৈ আৰু পোনপটীয়াকৈ চাওক"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 794e26a..d4df38e 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Tətbiqə \"systemExempted\" növü olan ön fon xidmətlərini işlətmək icazəsi verir"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\"fileManagement\" növü olan ön fon xidmətlərini işə salmaq"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Tətbiqə \"fileManagement\" növü olan ön fon xidmətlərini işlətmək icazəsi verir"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" növü olan ön fon xidmətləri işlətmək"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Tətbiqə \"specialUse\" növü olan ön fon xidmətlərini işlətmək icazəsi verir"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"tətbiq saxlama yaddaşını ölçmək"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Ekran kilidindən istifadə edin"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Davam etmək üçün ekran kilidinizi daxil edin"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Sensora basıb saxlayın"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Barmaq izini tanımaq olmur. Yenidən cəhd edin."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Barmaq izi sensorunu silib yenidən cəhd edin"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Sensoru silib yenidən cəhd edin"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Sensora basıb saxlayın"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Üzünüz görünmür. Telefonunuzu göz səviyyəsində saxlayın."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Cihaz stabil deyil. Telefonu tərpətməyin."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Üzünüzü yenidən qeydiyyatdan keçirin."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Üzü tanımaq olmur. Yenə cəhd edin."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Başınızın yerini bir az dəyişdirin"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Telefonunuza düz baxın"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Telefonunuza düz baxın"</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 8df8b87..e19a0b2 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -435,6 +435,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Dozvoljava aplikaciji da koristi usluge u prvom planu koje pripadaju tipu „systemExempted“"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"pokretanje usluge u prvom planu koja pripada tipu „fileManagement“"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Dozvoljava aplikaciji da koristi usluge u prvom planu koje pripadaju tipu „fileManagement“"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"pokretanje usluge u prvom planu koja pripada tipu „specialUse“"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Dozvoljava aplikaciji da koristi usluge u prvom planu koje pripadaju tipu „specialUse“"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"merenje memorijskog prostora u aplikaciji"</string>
@@ -633,7 +637,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Koristite zaključavanje ekrana"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Upotrebite zaključavanje ekrana da biste nastavili"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Čvrsto pritisnite senzor"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Prepoznavanje otiska prsta nije uspelo. Probajte ponovo."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Obrišite senzor za otisak prsta i probajte ponovo"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Obrišite senzor i probajte ponovo"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Čvrsto pritisnite senzor"</string>
@@ -697,7 +702,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Ne vidi se lice. Držite telefon u visini očiju."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Mnogo se pomerate. Držite telefon mirno."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Ponovo registrujte lice."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Lice nije prepoznato. Probajte ponovo."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Malo pomerite glavu"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Gledajte pravo u telefon"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Gledajte pravo u telefon"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index 13801d0..515a6a3 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -436,6 +436,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Дазваляе праграме выкарыстоўваць актыўныя сэрвісы тыпу \"systemExempted\""</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"запуск актыўнага сэрвісу тыпу \"fileManagement\""</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Праграма зможа выкарыстоўваць актыўныя сэрвісы тыпу \"fileManagement\""</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"запуск актыўнага сэрвісу тыпу \"specialUse\""</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Дазваляе праграме выкарыстоўваць актыўныя сэрвісы тыпу \"specialUse\""</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"вымерыць прастору для захоўвання прыкладання"</string>
@@ -634,7 +638,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Ужываць блакіроўку экрана"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Каб працягнуць, скарыстайце свой сродак блакіроўкі экрана"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Шчыльна прыкладзіце палец да сканера"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Не ўдалося распазнаць адбітак пальца. Паўтарыце спробу."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Ачысціце сканер адбіткаў пальцаў і паўтарыце спробу"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Ачысціце сканер і паўтарыце спробу"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Шчыльна прыкладзіце палец да сканера"</string>
@@ -698,7 +703,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Не відаць твару. Трымайце тэлефон на ўзроўні вачэй."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Трымайце прыладу нерухома. Трымайце тэлефон роўна."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Паўтарыце рэгістрацыю твару."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Твар не распазнаны. Паўтарыце спробу."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Крыху змяніце паставу галавы"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Глядзіце прама на экран тэлефона"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Глядзіце прама на экран тэлефона"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index e0e9b09..4689e32 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Разрешава на приложението да се възползва от услуги на преден план от тип systemExempted"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"Изпълнение на услуги на преден план от тип fileManagement"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Разрешава на приложението да се възползва от услуги на преден план от тип fileManagement"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"изпълнение на услуги на преден план от тип specialUse"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Разрешава на приложението да се възползва от услуги на преден план от тип specialUse"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"измерване на ползваното от приложението място в хранилището"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Ползване на заключв. на екрана"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Въведете опцията си за заключване на екрана, за да продължите"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Натиснете добре върху сензора"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Отпечатъкът не може да бъде разпознат. Опитайте отново."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Почистете сензора за отпечатъци и опитайте отново"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Почистете сензора и опитайте отново"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Натиснете добре върху сензора"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Лицето не се вижда. Задръжте на нивото на очите."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Твърде много движение. Дръжте телефона неподвижно."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Моля, регистрирайте лицето си отново."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Лицето не е разпознато. Опитайте отново."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Леко променете позицията на главата си"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Гледайте директно към телефона си"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Гледайте директно към телефона си"</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index cf8c64d..d615cbe 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"অ্যাপকে \"systemExempted\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা ব্যবহার করার অনুমতি দেয়"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\"fileManagement\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা রান করা"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"অ্যাপকে \"fileManagement\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা ব্যবহার করার অনুমতি দেয়"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা রান করান"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"অ্যাপকে \"specialUse\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা ব্যবহার করার অনুমতি দেয়"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"অ্যাপ্লিকেশন সঞ্চয়স্থানের জায়গা পরিমাপ করে"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"স্ক্রিন লক ব্যবহার করুন"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"চালিয়ে যেতে আপনার স্ক্রিন লক ব্যবহার করুন"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"সেন্সরে জোরে প্রেস করুন"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"ফিঙ্গারপ্রিন্ট শনাক্ত করা যায়নি। আবার চেষ্টা করুন।"</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"আঙ্গুলের ছাপের সেন্সর পরিষ্কার করে আবার চেষ্টা করুন"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"সেন্সর পরিষ্কার করে আবার চেষ্টা করুন"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"সেন্সরে জোরে প্রেস করুন"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"আপনার মুখ দেখা যাচ্ছে না। ফোন আপনার চোখের সোজাসুজি ধরুন।"</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"খুব বেশি নড়ছে। ফোনটি যাতে না কাঁপে সেইভাবে ধরুন।"</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"আপনার মুখের ছবি আবার নথিভুক্ত করুন।"</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"মুখ শনাক্ত করা যাচ্ছে না। আবার চেষ্টা করুন।"</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"আপনার মাথার পজিশন সামান্য পরিবর্তন করুন"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"আপনার ফোনের দিকে একদম সোজাসুজি তাকান"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"আপনার ফোনের দিকে একদম সোজাসুজি তাকান"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index ef941e19f..f110a24 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -435,6 +435,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Dozvoljava aplikaciji da koristi usluge u prvom planu s vrstom \"systemExempted\""</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"pokreni uslugu u prvom planu s vrstom \"fileManagement\""</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Dozvoljava aplikaciji da koristi usluge u prvom planu s vrstom \"fileManagement\""</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"pokreni uslugu u prvom planu s vrstom \"specialUse\""</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Dozvoljava aplikaciji da koristi usluge u prvom planu s vrstom \"specialUse\""</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"mjerenje prostora kojeg aplikacije zauzimaju u pohrani"</string>
@@ -633,7 +637,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Koristi zaključavanje ekrana"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Unesite zaključavanje ekrana da nastavite"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Čvrsto pritisnite senzor"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Otisak prsta nije prepoznat. Pokušajte ponovo."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Očistite senzor za otisak prsta i pokušajte ponovo"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Očistite senzor i pokušajte ponovo"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Čvrsto pritisnite senzor"</string>
@@ -697,7 +702,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Lice se ne vidi. Držite telefon u visini očiju."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Previše pokreta. Držite telefon mirno."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Ponovo registrirajte lice."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Nije moguće prepoznati lice. Pokušajte ponovo."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Malo pomjerite glavu"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Gledajte direktno u telefon"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Gledajte direktno u telefon"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index ec14677..d9744dd 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -435,6 +435,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Permet que l\'aplicació utilitzi serveis en primer pla amb el tipus \"systemExempted\""</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"executa serveis en primer pla amb el tipus \"fileManagement\""</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Permet que l\'aplicació utilitzi serveis en primer pla amb el tipus \"fileManagement\""</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"executa serveis en primer pla amb el tipus \"specialUse\""</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Permet que l\'aplicació utilitzi serveis en primer pla amb el tipus \"specialUse\""</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"mesura l\'espai d\'emmagatzematge d\'aplicacions"</string>
@@ -633,7 +637,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Utilitza el bloqueig de pantalla"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Introdueix el teu bloqueig de pantalla per continuar"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Prem el sensor de manera ferma"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"No es pot reconèixer l\'empremta digital. Torna-ho a provar."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Neteja el sensor d\'empremtes digitals i torna-ho a provar"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Neteja el sensor i torna-ho a provar"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Prem el sensor de manera ferma"</string>
@@ -697,7 +702,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"No se\'t veu la cara. Mantén el telèfon a l\'altura dels ulls."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Massa moviment. Subjecta bé el telèfon."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Torna a registrar la teva cara."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"No podem reconèixer la cara. Torna-ho a provar."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Canvia lleugerament la posició del cap"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Mira més directament al telèfon"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Mira més directament al telèfon"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index e518de4..c72f07d 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -436,6 +436,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Umožňuje aplikaci používat služby v popředí typu „systemExempted“"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"používat službu v popředí typu „fileManagement“"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Umožňuje aplikaci používat služby v popředí typu „fileManagement“"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"používat službu v popředí typu „specialUse“"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Umožňuje aplikaci používat služby v popředí typu „specialUse“"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"výpočet místa pro ukládání aplikací"</string>
@@ -634,7 +638,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Použít zámek obrazovky"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Pokračujte zadáním zámku obrazovky"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Pevně zatlačte na snímač"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Otisk prstu se nepodařilo rozpoznat. Zkuste to znovu."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Vyčistěte snímač otisků prstů a zkuste to znovu"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Vyčistěte senzor a zkuste to znovu"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Pevně přitiskněte prst na snímač"</string>
@@ -698,7 +703,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Obličej není vidět. Držte telefon v úrovni očí."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Příliš mnoho pohybu. Držte telefon nehybně."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Zaznamenejte obličej znovu."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Obličej se nepodařilo rozpoznat. Zkuste to znovu."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Mírně pohněte hlavou"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Dívejte se přímo na telefon"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Dívejte se přímo na telefon"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index d3f8550..f72c0c2 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Tillader, at appen benytter tjenester af typen \"systemExempted\" i forgrunden"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"kør tjenesten af typen \"fileManagement\" i forgrunden"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Tillader, at appen benytter tjenester af typen \"fileManagement\" i forgrunden"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"kør tjenesten af typen \"specialUse\" i forgrunden"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Tillader, at appen benytter tjenester af typen \"specialUse\" i forgrunden"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"måle appens lagerplads"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Brug skærmlås"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Angiv din skærmlås for at fortsætte"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Hold fingeren på sensoren"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Fingeraftrykket kan ikke genkendes. Prøv igen."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Rengør fingeraftrykssensoren, og prøv igen"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Rengør sensoren, og prøv igen"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Hold fingeren på sensoren"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Dit ansigt kan ikke registreres. Hold din telefon i øjenhøjde."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Der er for meget bevægelse. Hold telefonen stille."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Registrer dit ansigt igen."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Ansigtet kan ikke genkendes. Prøv igen."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Flyt dit hoved en smule"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Kig mere direkte på din telefon"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Kig mere direkte på din telefon"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 514d695..e03592c 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Ermöglicht der App, Vordergrunddienste mit dem Typ „systemExempted“ zu verwenden"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"Dienste im Vordergrund mit dem Typ „fileManagement“ ausführen"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Ermöglicht der App, Dienste im Vordergrund mit dem Typ „fileManagement“ zu verwenden"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"Vordergrunddienste mit dem Typ „specialUse“ ausführen"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Ermöglicht der App, Vordergrunddienste mit dem Typ „specialUse“ zu verwenden"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"Speicherplatz der App ermitteln"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Displaysperre verwenden"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Displaysperre eingeben, um fortzufahren"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Drücke fest auf den Sensor"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Fingerabdruck wurde nicht erkannt. Versuch es noch einmal."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Reinige den Fingerabdrucksensor und versuch es noch einmal"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Reinige den Sensor und versuche es noch einmal"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Drücke fest auf den Sensor"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Gesicht nicht erkannt. Smartphone auf Augenhöhe halten."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Zu viel Unruhe. Halte das Smartphone ruhig."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Bitte registriere dein Gesicht noch einmal."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Gesicht nicht erkannt. Versuche es noch einmal."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Ändere die Position deines Kopfes leicht"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Sieh direkt auf dein Smartphone"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Sieh direkt auf dein Smartphone"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index a392c0f..d9c9451 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Επιτρέπει στην εφαρμογή να χρησιμοποιεί τις υπηρεσίες στο προσκήνιο με τον τύπο \"systemExempted\""</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"εκτέλεση υπηρεσίας στο προσκήνιο με τον τύπο fileManagement"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Επιτρέπει στην εφαρμογή να χρησιμοποιεί τις υπηρεσίες στο προσκήνιο με τον τύπο fileManagement."</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"εκτέλεση υπηρεσίας στο προσκήνιο με τον τύπο \"specialUse\""</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Επιτρέπει στην εφαρμογή να χρησιμοποιεί τις υπηρεσίες στο προσκήνιο με τον τύπο \"specialUse\""</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"υπολογίζει τον αποθηκευτικό χώρο εφαρμογής"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Χρήση κλειδώματος οθόνης"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Χρησιμοποιήστε το κλείδωμα οθόνης για να συνεχίσετε"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Πιέστε σταθερά τον αισθητήρα"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"To δακτ. αποτύπωμα δεν αναγνωρίστηκε. Δοκιμάστε ξανά."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Καθαρίστε τον αισθητήρα δακτυλικών αποτυπωμάτων και δοκιμάστε ξανά"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Καθαρίστε τον αισθητήρα και δοκιμάστε ξανά"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Πιέστε σταθερά τον αισθητήρα"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Κρατήστε το τηλέφωνο στο ύψος των ματιών σας."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Πάρα πολλή κίνηση. Κρατήστε σταθερό το τηλέφωνο."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Καταχωρίστε ξανά το πρόσωπό σας."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Το πρόσωπο δεν αναγνωρίζεται. Δοκιμάστε ξανά."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Αλλάξτε ελαφρώς τη θέση του κεφαλιού σας"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Κοιτάξτε απευθείας το τηλέφωνό σας"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Κοιτάξτε απευθείας το τηλέφωνό σας"</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index 7498488..fd801b5 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Allows the app to make use of foreground services with the type \'systemExempted\'"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"run foreground service with the type \'fileManagement\'"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Allows the app to make use of foreground services with the type \'fileManagement\'"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"run foreground service with the type \'specialUse\'"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Allows the app to make use of foreground services with the type \'specialUse\'"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"measure app storage space"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Use screen lock"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Enter your screen lock to continue"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Press firmly on the sensor"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Can’t recognise fingerprint. Try again."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Clean fingerprint sensor and try again"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Clean sensor and try again"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Press firmly on the sensor"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Can’t see your face. Hold your phone at eye level."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Too much motion. Hold phone steady."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Please re-enroll your face."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Can’t recognise face. Try again."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Change the position of your head slightly"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Look more directly at your phone"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Look more directly at your phone"</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index fd76ce5..bf9acc1 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -434,6 +434,8 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Allows the app to make use of foreground services with the type \"systemExempted\""</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"run foreground service with the type \"fileManagement\""</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Allows the app to make use of foreground services with the type \"fileManagement\""</string>
+ <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"run foreground service with the type \"mediaProcessing\""</string>
+ <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Allows the app to make use of foreground services with the type \"mediaProcessing\""</string>
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"run foreground service with the type \"specialUse\""</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Allows the app to make use of foreground services with the type \"specialUse\""</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"measure app storage space"</string>
@@ -632,7 +634,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Use screen lock"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Enter your screen lock to continue"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Press firmly on the sensor"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Can’t recognize fingerprint. Try again."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Clean fingerprint sensor and try again"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Clean sensor and try again"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Press firmly on the sensor"</string>
@@ -696,7 +699,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Can’t see your face. Hold your phone at eye level."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Too much motion. Hold phone steady."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Please re-enroll your face."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Can’t recognize face. Try again."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Change the position of your head slightly"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Look more directly at your phone"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Look more directly at your phone"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 1f2ccce..662247e 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Allows the app to make use of foreground services with the type \'systemExempted\'"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"run foreground service with the type \'fileManagement\'"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Allows the app to make use of foreground services with the type \'fileManagement\'"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"run foreground service with the type \'specialUse\'"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Allows the app to make use of foreground services with the type \'specialUse\'"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"measure app storage space"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Use screen lock"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Enter your screen lock to continue"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Press firmly on the sensor"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Can’t recognise fingerprint. Try again."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Clean fingerprint sensor and try again"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Clean sensor and try again"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Press firmly on the sensor"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Can’t see your face. Hold your phone at eye level."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Too much motion. Hold phone steady."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Please re-enroll your face."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Can’t recognise face. Try again."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Change the position of your head slightly"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Look more directly at your phone"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Look more directly at your phone"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index d4fd01e..e7278d5 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Allows the app to make use of foreground services with the type \'systemExempted\'"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"run foreground service with the type \'fileManagement\'"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Allows the app to make use of foreground services with the type \'fileManagement\'"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"run foreground service with the type \'specialUse\'"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Allows the app to make use of foreground services with the type \'specialUse\'"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"measure app storage space"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Use screen lock"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Enter your screen lock to continue"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Press firmly on the sensor"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Can’t recognise fingerprint. Try again."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Clean fingerprint sensor and try again"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Clean sensor and try again"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Press firmly on the sensor"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Can’t see your face. Hold your phone at eye level."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Too much motion. Hold phone steady."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Please re-enroll your face."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Can’t recognise face. Try again."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Change the position of your head slightly"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Look more directly at your phone"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Look more directly at your phone"</string>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index 8a89ebd..51ab2ca 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -434,6 +434,8 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Allows the app to make use of foreground services with the type \"systemExempted\""</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"run foreground service with the type \"fileManagement\""</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Allows the app to make use of foreground services with the type \"fileManagement\""</string>
+ <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"run foreground service with the type \"mediaProcessing\""</string>
+ <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Allows the app to make use of foreground services with the type \"mediaProcessing\""</string>
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"run foreground service with the type \"specialUse\""</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Allows the app to make use of foreground services with the type \"specialUse\""</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"measure app storage space"</string>
@@ -632,7 +634,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Use screen lock"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Enter your screen lock to continue"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Press firmly on the sensor"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Can’t recognize fingerprint. Try again."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Clean fingerprint sensor and try again"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Clean sensor and try again"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Press firmly on the sensor"</string>
@@ -696,7 +699,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Can’t see your face. Hold your phone at eye level."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Too much motion. Hold phone steady."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Please re-enroll your face."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Can’t recognize face. Try again."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Change the position of your head slightly"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Look more directly at your phone"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Look more directly at your phone"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index f6117cd..8686e19 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -435,6 +435,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Permite que la app use servicios en primer plano con el tipo \"systemExempted\""</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"Ejecutar un servicio en primer plano con el tipo \"fileManagement\""</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Permite que la app use servicios en primer plano con el tipo \"fileManagement\""</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"Ejecuta un servicio en primer plano con el tipo \"specialUse\""</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Permite que la app use servicios en primer plano con el tipo \"specialUse\""</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"medir el espacio de almacenamiento de la aplicación"</string>
@@ -633,7 +637,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Usar bloqueo de pantalla"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Ingresa tu bloqueo de pantalla para continuar"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Presiona el sensor con firmeza"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"No se reconoce la huella dactilar. Vuelve a intentarlo."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Limpia el sensor de huellas dactilares y vuelve a intentarlo"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Limpia el sensor y vuelve a intentarlo"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Presiona el sensor con firmeza"</string>
@@ -697,7 +702,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"No se te ve el rostro. Sostén el teléfono a la altura de los ojos."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Te estás moviendo demasiado. No muevas el teléfono"</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Vuelve a registrar tu rostro."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"No se reconoce el rostro. Vuelve a intentarlo."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Cambia levemente la posición de la cabeza"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Mira directamente al teléfono"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Mira directamente al teléfono"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 406f879..8f57a07 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -435,6 +435,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Permite que la aplicación use servicios en primer plano con el tipo \"systemExempted\""</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"ejecutar un servicio en primer plano con el tipo \"fileManagement\""</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Permite que la aplicación use servicios en primer plano con el tipo \"fileManagement\""</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"ejecutar un servicio en primer plano con el tipo \"specialUse\""</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Permite que la aplicación use servicios en primer plano con el tipo \"specialUse\""</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"medir el espacio de almacenamiento de la aplicación"</string>
@@ -633,7 +637,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Usar bloqueo de pantalla"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Introduce tu bloqueo de pantalla para continuar"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Pulsa firmemente el sensor"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"No se puede reconocer la huella digital. Inténtalo de nuevo."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Limpia el sensor de huellas digitales e inténtalo de nuevo"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Limpia el sensor e inténtalo de nuevo"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Pulsa firmemente el sensor"</string>
@@ -697,7 +702,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"No se detecta tu cara. Sujeta el teléfono a la altura de los ojos."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"El teléfono se mueve demasiado. Mantenlo quieto."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Vuelve a registrar tu cara."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"No se reconoce la cara. Inténtalo de nuevo."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Cambia ligeramente la posición de tu cabeza"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Mira al teléfono de forma más directa"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Mira al teléfono de forma más directa"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index afcc618..e814c96 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Lubab rakendusel kasutada esiplaanil olevaid teenuseid, mille tüüp on „systemExempted“."</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"sellise esiplaanil oleva teenuse käitamine, mille tüüp on „fileManagement“"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Lubab rakendusel kasutada esiplaanil olevaid teenuseid, mille tüüp on „fileManagement“"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"sellise esiplaanil oleva teenuse käitamine, mille tüüp on „specialUse“"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Lubab rakendusel kasutada esiplaanil olevaid teenuseid, mille tüüp on „specialUse“."</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"Rakenduse mäluruumi mõõtmine"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Ekraaniluku kasutamine"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Jätkamiseks sisestage oma ekraanilukk"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Vajutage kindlalt andurile"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Sõrmejälge ei õnnestu tuvastada. Proovige uuesti."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Puhastage sõrmejäljeandur ja proovige uuesti"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Puhastage andur ja proovige uuesti"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Vajutage kindlalt andurile"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Teie nägu ei ole näha. Hoidke telefoni silmade kõrgusel."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Liiga palju liikumist. Hoidke telefoni paigal."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Registreerige oma nägu uuesti."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Nägu ei õnnestu tuvastada. Proovige uuesti."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Muutke pisut oma pea asendit"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Vaadake otse telefoni"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Vaadake otse telefoni"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index 2b9c555..feabc8f 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Aurreko planoko zerbitzuak (systemExempted motakoak) erabiltzeko baimena ematen dio aplikazioari"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"exekutatu aurreko planoko zerbitzu bat (fileManagement motakoa)"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Aurreko planoko zerbitzuak (fileManagement motakoak) erabiltzeko baimena ematen die aplikazioei."</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"exekutatu aurreko planoko zerbitzu bat (specialUse motakoa)"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Aurreko planoko zerbitzuak (specialUse motakoak) erabiltzeko baimena ematen dio aplikazioari"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"neurtu aplikazioen biltegiratzeko tokia"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Erabili pantailaren blokeoa"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Aurrera egiteko, desblokeatu pantailaren blokeoa"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Sakatu irmo sentsorea"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Ezin da hauteman hatz-marka. Saiatu berriro."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Garbitu hatz-marken sentsorea eta saiatu berriro"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Garbitu sentsorea eta saiatu berriro"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Sakatu irmo sentsorea"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Ezin da hauteman aurpegia. Eutsi telefonoari begien parean."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Mugimendu gehiegi dago. Eutsi tinko telefonoari."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Erregistratu berriro aurpegia."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Ezin da hauteman aurpegia. Saiatu berriro."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Aldatu buruaren posizioa apur bat"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Begiratu zuzenago telefonoari"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Begiratu zuzenago telefonoari"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index eb71a7d..c661e95 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"به برنامه اجازه میدهد از سرویسهای پیشنما از نوع «معافیت سیستم» استفاده کند"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"اجرای سرویس پیشنما از نوع «fileManagement»"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"به برنامه اجازه میدهد از سرویسهای پیشنما از نوع «fileManagement» استفاده کند"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"اجرای سرویس پیشنما از نوع «استفاده ویژه»"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"به برنامه اجازه میدهد از سرویسهای پیشنما از نوع «استفاده ویژه» استفاده کند"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"اندازهگیری اندازه فضای ذخیرهسازی برنامه"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"از قفل صفحه استفاده کنید"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"برای ادامه، قفل صفحهتان را وارد کنید"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"محکم روی حسگر فشار دهید"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"اثر انگشت شناسایی نشد. دوباره امتحان کنید."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"حسگر اثر انگشت را تمیز و دوباره امتحان کنید"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"حسگر را تمیز و دوباره امتحان کنید"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"محکم روی حسگر فشار دهید"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"چهره دیده نمیشود. تلفن را همسطح چشمانتان نگه دارید."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"حرکت خیلی زیاد است. تلفن را ثابت نگهدارید."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"لطفاً چهرهتان را مجدداً ثبت کنید."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"چهره شناسایی نشد. دوباره امتحان کنید."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"موقعیت سرتان را کمی تغییر دهید"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"مستقیمتر به تلفن نگاه کنید"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"مستقیمتر به تلفن نگاه کنید"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 8359bf6..9aad9fa6 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Sallii sovelluksen käyttää etualan palveluja, joiden tyyppi on \"systemExempted\""</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"käyttää etualan palveluja, joiden tyyppi on \"fileManagement\""</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Sallii sovelluksen käyttää etualan palveluja, joiden tyyppi on \"fileManagement\""</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"käyttää etualan palveluja, joiden tyyppi on \"specialUse\""</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Sallii sovelluksen käyttää etualan palveluja, joiden tyyppi on \"specialUse\""</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"sovellusten tallennustilan mittaaminen"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Käytä näytön lukitusta"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Jatka lisäämällä näytön lukituksen avaustapa"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Paina anturia voimakkaasti"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Sormenjälkeä ei voi tunnistaa. Yritä uudelleen."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Puhdista sormenjälkitunnistin ja yritä uudelleen"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Puhdista anturi ja yritä uudelleen"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Paina tunnistinta voimakkaasti"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Kasvoja ei näy. Pidä puhelinta silmien korkeudella."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Laite liikkui liikaa. Pidä puhelin vakaana."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Rekisteröi kasvot uudelleen."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Kasvoja ei voi tunnistaa. Yritä uudelleen."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Liikuta päätä hieman"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Katso suoremmin puhelimeen"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Katso suoremmin puhelimeen"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 8842ae8..7be3041 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -435,6 +435,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Autoriser l\'application à utiliser les services d\'avant-plan avec le type « système exempté »"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"exécuter le service d\'avant-plan avec le type « fileManagement »"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Autorise l\'application à utiliser les services d\'avant-plan avec le type « fileManagement »"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"exécuter le service d\'avant-plan avec le type « usage spécial »"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Autoriser l\'application à utiliser les services d\'avant-plan avec le type « usage spécial »"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"évaluer l\'espace de stockage de l\'application"</string>
@@ -633,7 +637,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Utiliser le verrouillage de l\'écran"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Entrez votre verrouillage d\'écran pour continuer"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Appuyez fermement sur le capteur"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Empreinte digitale non reconnue. Réessayez."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Nettoyez le capteur d\'empreintes digitales et réessayez"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Nettoyez le capteur et réessayez"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Appuyez fermement sur le capteur"</string>
@@ -697,7 +702,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Visage non détecté. Tenez votre téléphone à hauteur des yeux."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Trop de mouvement. Tenez le téléphone immobile."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Veuillez inscrire votre visage à nouveau."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Visage non reconnu. Réessayez."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Modifiez légèrement la position de votre tête"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Regardez droit dans le téléphone"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Regardez droit dans le téléphone"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 3b24230..f6e669c 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -435,6 +435,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Autorise l\'appli à utiliser les services de premier plan avec le type \"systemExempted\""</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"exécuter un service de premier plan avec le type \"fileManagement\""</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Autorise l\'appli à utiliser les services de premier plan avec le type \"fileManagement\""</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"exécuter un service de premier plan avec le type \"specialUse\""</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Autorise l\'appli à utiliser les services de premier plan avec le type \"specialUse\""</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"évaluer l\'espace de stockage de l\'application"</string>
@@ -633,7 +637,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Utiliser verrouillage écran"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Utilisez le verrouillage de l\'écran pour continuer"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Appuyez fermement sur le lecteur"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Impossible de reconnaître l\'empreinte digitale. Réessayez."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Nettoyez le lecteur d\'empreinte digitale et réessayez"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Nettoyez le lecteur et réessayez"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Appuyez fermement sur le lecteur"</string>
@@ -697,7 +702,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Visage non détecté. Tenez votre téléphone à hauteur des yeux."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Trop de mouvement. Ne bougez pas le téléphone."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Veuillez enregistrer à nouveau votre visage."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Visage non reconnu. Réessayez."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Déplacez légèrement votre tête."</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Mettez-vous bien de face et regardez le téléphone"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Mettez-vous bien de face et regardez le téléphone"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 703ed7c..98560f3 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Permite que a aplicación faga uso de servizos en primeiro plano co tipo \"systemExempted\""</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"executar servizo en primeiro plano co tipo \"fileManagement\""</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Permite que a aplicación faga uso de servizos en primeiro plano co tipo \"fileManagement\""</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"executar servizo en primeiro plano co tipo \"specialUse\""</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Permite que a aplicación faga uso de servizos en primeiro plano co tipo \"specialUse\""</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"medir o espazo de almacenamento da aplicación"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Usar credencial do dispositivo"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Desbloquea a pantalla para continuar"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Preme o sensor con firmeza"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Non se puido recoñecer a impresión dixital. Téntao de novo."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Limpa o sensor de impresión dixital e téntao de novo"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Limpa o sensor e téntao de novo"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Preme o sensor con firmeza"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Non se che ve a cara. Pon o teléfono diante dos ollos"</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Demasiado movemento. Non movas o teléfono."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Volve rexistrar a túa cara."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Non se recoñeceu a cara. Téntao de novo."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Cambia lixeiramente a posición da cabeza"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Mira o teléfono de forma máis directa"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Mira o teléfono de forma máis directa"</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 2e1f0e6..9d29201 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"ઍપને \"systemExempted\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવાઓનો ઉપયોગ કરવાની મંજૂરી આપે છે"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\"fileManagement\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવા ચલાવો"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"ઍપને \"fileManagement\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવાઓનો ઉપયોગ કરવાની મંજૂરી આપે છે"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવા ચલાવો"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"ઍપને \"specialUse\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવાઓનો ઉપયોગ કરવાની મંજૂરી આપે છે"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"ઍપ્લિકેશન સંગ્રહ સ્થાન માપો"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"સ્ક્રીન લૉકનો ઉપયોગ કરો"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"આગળ વધવા માટે તમારું સ્ક્રીન લૉક દાખલ કરો"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"સેન્સર પર જોરથી દબાવો"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"ફિંગરપ્રિન્ટ ઓળખી શકતા નથી. ફરી પ્રયાસ કરો."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"ફિંગરપ્રિન્ટ સેન્સર સાફ કરો અને ફરી પ્રયાસ કરો"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"સેન્સર સાફ કરો અને ફરી પ્રયાસ કરો"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"સેન્સર પર જોરથી દબાવો"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"તમારો ચહેરો દેખાતો નથી. તમારા ફોનને આંખના લેવલ પર પકડી રાખો."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"ડિવાઇસ અસ્થિર છે. ફોનને સ્થિર રાખો."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"કૃપા કરીને તમારા ચહેરાની ફરી નોંધણી કરાવો."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"ચહેરો ઓળખી શકતા નથી. ફરી પ્રયાસ કરો."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"તમારા માથાની સ્થિતિ સહેજ બદલો"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"વધારે પ્રમાણમાં સીધું તમારા ફોન તરફ જુઓ"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"વધારે પ્રમાણમાં સીધું તમારા ફોન તરફ જુઓ"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 336d998..084b956 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"इससे ऐप्लिकेशन, \"systemExempted\" टाइप वाली फ़ोरग्राउंड सेवाओं का इस्तेमाल कर पाता है"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\"fileManagement\" टाइप वाली फ़ोरग्राउंड सेवा को चलाने की अनुमति दें"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"इससे ऐप्लिकेशन को \"fileManagement\" टाइप वाली फ़ोरग्राउंड सेवाओं का इस्तेमाल करने की अनुमति मिलती है"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" टाइप वाली फ़ोरग्राउंड सेवा को चलाने की अनुमति दें"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"इससे ऐप्लिकेशन, \"specialUse\" टाइप वाली फ़ोरग्राउंड सेवाओं का इस्तेमाल कर पाता है"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"पता करें कि ऐप मेमोरी में कितनी जगह है"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"स्क्रीन लॉक का क्रेडेंशियल इस्तेमाल करें"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"जारी रखने के लिए, अपने स्क्रीन लॉक की पुष्टि करें"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"सेंसर को उंगली से ज़ोर से दबाएं"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"फ़िंगरप्रिंट की पहचान नहीं की जा सकी. फिर से कोशिश करें."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"फ़िंगरप्रिंट सेंसर को साफ़ करके फिर से कोशिश करें"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"फ़िंगरप्रिंट सेंसर को साफ़ करके फिर से कोशिश करें"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"सेंसर को उंगली से दबाकर रखें"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"आपका चेहरा नहीं दिख रहा है. फ़ोन को अपनी आंखों की सीध में रखें."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"डिवाइस बहुत ज़्यादा हिल रहा है. फ़ोन को बिना हिलाएं पकड़ें."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"कृपया फिर से अपने चेहरे की पहचान कराएं."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"चेहरे की पहचान नहीं हुई. फिर से कोशिश करें."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"अपने सिर की पोज़िशन को थोड़ा बदलें"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"अपने फ़ोन की तरफ़ बिलकुल सीधा देखें"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"अपने फ़ोन की तरफ़ बिलकुल सीधा देखें"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index ec5a77e..ca57b68 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -435,6 +435,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Omogućuje aplikaciji da iskoristi usluge u prednjem planu s vrstom \"izuzeo sustav\""</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"pokreni uslugu u prednjem planu s vrstom fileManagement"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Aplikaciji omogućuje da iskoristi usluge u prednjem planu s vrstom fileManagement"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"pokretanje usluge u prednjem planu s vrstom \"posebna upotreba\""</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Omogućuje aplikaciji da iskoristi usluge u prednjem planu s vrstom \"posebna upotreba\""</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"mjerenje prostora za pohranu aplikacije"</string>
@@ -633,7 +637,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Upotreba zaključavanja zaslona"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Upotrijebite zaključavanje zaslona da biste nastavili"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Čvrsto pritisnite senzor"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Prepoznavanje otiska prsta nije uspjelo. Pokušajte ponovo."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Očistite senzor otiska prsta i pokušajte ponovno"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Očistite senzor i pokušajte ponovno"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Čvrsto pritisnite senzor"</string>
@@ -697,7 +702,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Vaše se lice ne vidi. Držite telefon u razini očiju."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Previše kretanja. Držite telefon mirno."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Ponovo registrirajte svoje lice."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Prepoznavanje lica nije uspjelo. Pokušajte ponovo."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Malo pomaknite glavu"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Gledajte ravno u telefon"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Gledajte ravno u telefon"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 3bcd44b..2ccacff 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Engedélyezi az alkalmazásnak az előtérben lévő szolgáltatások használatát a következő típussal: „systemExempted”"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"előtérben lévő szolgáltatás futtatása a következő típussal: „fileManagement\""</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Lehetővé teszi az alkalmazásnak az előtérben futó szolgáltatások használatát a következő típussal: „fileManagement”"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"előtérben lévő szolgáltatás futtatása a következő típussal: „specialUse\""</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Engedélyezi az alkalmazásnak az előtérben lévő szolgáltatások használatát a következő típussal: „specialUse”"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"alkalmazás-tárhely felmérése"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Képernyőzár használata"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"A folytatáshoz adja meg a képernyőzár hitelesítési adatait"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Nyomja meg határozottan az érzékelőt"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Az ujjlenyomat nem ismerhető fel. Próbálkozzon újra."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Tisztítsa meg az ujjlenyomat-érzékelőt, majd próbálja újra"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Tisztítsa meg az érzékelőt, majd próbálja újra"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Nyomja meg határozottan az érzékelőt"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Nem látszik az arca. Tartsa szemmagasságban a telefonját."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Túl sok a mozgás. Tartsa stabilan a telefont."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Rögzítsen újra képet az arcáról."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Az arc nem felismerhető. Próbálja újra."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Egy kicsit mozdítsa el a fejét"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Nézzen egyenesen a telefonjára"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Nézzen egyenesen a telefonjára"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index 603d7c6..9480cc5 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Հավելվածին թույլ է տալիս օգտագործել systemExempted տեսակով ակտիվ ծառայությունները"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"fileManagement տեսակով ակտիվ ծառայությունների գործարկում"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Թույլատրում է հավելվածին օգտագործել fileManagement տեսակով ակտիվ ծառայությունները"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"գործարկել specialUse տեսակով ակտիվ ծառայությունները"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Հավելվածին թույլ է տալիս օգտագործել specialUse տեսակով ակտիվ ծառայությունները"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"չափել հավելվածի պահոցի տարածքը"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Էկրանի կողպում"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Շարունակելու համար ապակողպեք էկրանը"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Մատը ուժեղ սեղմեք սկաների վրա"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Մատնահետքը չի հաջողվում ճանաչել։ Նորից փորձեք։"</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Մաքրեք մատնահետքերի սկաները և նորից փորձեք"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Մաքրեք սկաները և նորից փորձեք"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Մատը ուժեղ սեղմեք սկաների վրա"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Դեմքը չի երևում։ Հեռախոսը պահեք աչքերի մակարդակում։"</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Շատ եք շարժում։ Հեռախոսն անշարժ պահեք։"</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Նորից փորձեք։"</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Դեմքը չի հաջողվում ճանաչել։ Նորից փորձեք։"</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Թեթևակի փոխեք գլխի դիրքը"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Նայեք ուղիղ էկրանին"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Նայեք ուղիղ էկրանին"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 7886280..50ef6a0 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Mengizinkan aplikasi menggunakan layanan latar depan dengan jenis \"systemExempted\""</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"menjalankan layanan latar depan dengan jenis \"fileManagement\""</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Mengizinkan aplikasi menggunakan layanan latar depan dengan jenis \"fileManagement\""</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"menjalankan layanan latar depan dengan jenis \"specialUse\""</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Mengizinkan aplikasi menggunakan layanan latar depan dengan jenis \"specialUse\""</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"mengukur ruang penyimpanan aplikasi"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Gunakan kunci layar"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Masukkan kunci layar untuk melanjutkan"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Tekan sensor dengan kuat"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Tidak dapat mengenali sidik jari. Coba lagi."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Bersihkan sensor sidik jari lalu coba lagi"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Bersihkan sensor lalu coba lagi"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Tekan sensor dengan kuat"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Wajah tidak terlihat. Pegang ponsel sejajar mata."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Terlalu banyak gerakan. Stabilkan ponsel."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Daftarkan ulang wajah Anda."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Tidak dapat mengenali wajah. Coba lagi."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Ubah sedikit posisi kepala"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Lihat lebih lurus ke arah ponsel"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Lihat lebih lurus ke arah ponsel"</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index ddf60c1..804f131 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Leyfir forritinu að nota forgrunnsþjónustu af gerðinni „systemExempted“"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"keyra forgrunnsþjónustu af gerðinni „fileManagement“"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Leyfir forritinu að nota forgrunnsþjónustur af gerðinni „fileManagement“"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"keyra forgrunnsþjónustu af gerðinni „specialUse“"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Leyfir forritinu að nota forgrunnsþjónustu af gerðinni „specialUse“"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"mæla geymslurými forrits"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Nota skjálás"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Sláðu inn skjálásinn þinn til að halda áfram"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Ýttu ákveðið á lesarann"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Fingrafar þekkist ekki. Reyndu aftur."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Hreinsaðu fingrafaralesarann og reyndu aftur"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Hreinsaðu lesarann og reyndu aftur"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Ýttu ákveðið á lesarann"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Sé ekki andlitið á þér. Haltu símanum í augnhæð."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Of mikil hreyfing. Haltu símanum stöðugum."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Skráðu nafnið þitt aftur."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Andlit þekkist ekki. Reyndu aftur."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Færðu höfuðið aðeins til"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Horfðu beint á símann"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Horfðu beint á símann"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 3877ac4..c1ef0ea 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -435,6 +435,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Consente all\'app di usare i servizi in primo piano con il tipo \"systemExempted\""</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"Eseguire servizi in primo piano con il tipo \"fileManagement\""</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Consente all\'app di usare i servizi in primo piano con il tipo \"fileManagement\""</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"Esecuzione di servizi in primo piano con il tipo \"specialUse\""</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Consente all\'app di usare i servizi in primo piano con il tipo \"specialUse\""</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"calcolo spazio di archiviazione applicazioni"</string>
@@ -633,7 +637,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Usa il blocco schermo"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Inserisci il blocco schermo per continuare"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Premi con decisione sul sensore"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Impossibile riconoscere l\'impronta. Riprova."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Pulisci il sensore di impronte digitali e riprova"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Pulisci il sensore e riprova"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Premi con decisione sul sensore"</string>
@@ -697,7 +702,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Volto non visibile. Tieni lo smartphone all\'altezza degli occhi."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Troppo movimento. Tieni fermo il telefono."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Ripeti l\'acquisizione del volto."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Impossibile riconoscere il volto. Riprova."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Cambia leggermente la posizione della testa"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Guarda dritto nello smartphone"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Guarda dritto nello smartphone"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index eb2f3b2..c189218 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -435,6 +435,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"ההרשאה הזו מאפשרת לאפליקציה להשתמש בשירותים שפועלים בחזית מסוג \"systemExempted\""</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"הפעלת שירות שפועל בחזית מסוג \"fileManagement\""</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"ההרשאה הזו מאפשרת לאפליקציה להתבסס על שירותים שפועלים בחזית מסוג \"fileManagement\""</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"הפעלת שירות שפועל בחזית מסוג \"specialUse\""</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"ההרשאה הזו מאפשרת לאפליקציה להשתמש בשירותים שפועלים בחזית מסוג \"specialUse\""</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"מדידת נפח האחסון של אפליקציות"</string>
@@ -633,7 +637,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"שימוש בנעילת מסך"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"יש לבטל את נעילת המסך כדי להמשיך"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"צריך ללחוץ לחיצה חזקה על החיישן"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"לא ניתן לזהות את טביעת האצבע. יש לנסות שוב."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"עליך לנקות את חיישן טביעות האצבע ולנסות שוב"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"עליך לנקות את החיישן ולנסות שוב"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"צריך ללחוץ חזק על החיישן"</string>
@@ -697,7 +702,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"לא רואים את הפנים שלך. יש להחזיק את הטלפון בגובה העיניים."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"יותר מדי תנועה. יש להחזיק את הטלפון בצורה יציבה."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"יש לסרוק שוב את הפנים."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"לא ניתן לזהות את הפנים. יש לנסות שוב."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"צריך לשנות מעט את תנוחת הראש"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"צריך להביט ישירות בטלפון"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"צריך להביט ישירות בטלפון"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index a2d40af..7bb322c 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"タイプが「systemExempted」のフォアグラウンド サービスの使用をアプリに許可します"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"タイプが「fileManagement」のフォアグラウンド サービスの実行"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"タイプが「fileManagement」のフォアグラウンド サービスの使用をアプリに許可します"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"タイプが「specialUse」のフォアグラウンド サービスの実行"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"タイプが「specialUse」のフォアグラウンド サービスの使用をアプリに許可します"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"アプリのストレージ容量の計測"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"画面ロックの使用"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"続行するには画面ロックを入力してください"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"センサーにしっかりと押し当ててください"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"指紋を認識できません。もう一度お試しください。"</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"指紋認証センサーの汚れを取り除いて、もう一度お試しください"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"センサーの汚れを取り除いて、もう一度お試しください"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"センサーにしっかりと押し当ててください"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"顔を確認できません。スマートフォンを目の高さに合わせます。"</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"あまり動かさないでください。安定させてください。"</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"顔を登録し直してください。"</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"顔を認識できません。もう一度お試しください。"</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"顔の位置を少し変えてください"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"もっとまっすぐスマートフォンに顔を向けてください"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"もっとまっすぐスマートフォンに顔を向けてください"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index dee1f1f..7b7f267 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"ნებას რთავს აპს, გამოიყენოს პრიორიტეტული სერვისები ტიპის „გათავისუფლებულისისტემა“ შემთხვევაში"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"პრიორიტეტული სერვისის გაშვება ტიპის „ფაილებისმართვა“ შემთხვევაში"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"საშუალებას აძლევს აპს, გამოიყენოს პრიორიტეტული სერვისები ტიპის „ფაილისმართვა“ შემთხვევაში"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"პრიორიტეტული სერვისის გაშვება ტიპის „სპეციალურიგამოყენება“ შემთხვევაში"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"ნებას რთავს აპს, გამოიყენოს პრიორიტეტული სერვისები ტიპის „სპეციალურიგამოყენება“ შემთხვევაში"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"აპის მეხსიერების სივრცის გაზომვა"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"გამოიყენეთ ეკრანის დაბლოკვა"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"გასაგრძელებლად შედით ეკრანის დაბლოკვაში"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"მაგრად დააჭირეთ სენსორს"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"თითის ანაბეჭდის ამოცნობა ვერ ხერხდება. ცადეთ ხელახლა."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"გაწმინდეთ თითის ანაბეჭდის სენსორი და ხელახლა ცადეთ"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"გაწმინდეთ სენსორი და ხელახლა ცადეთ"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"მაგრად დააჭირეთ სენსორს"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"სახე არ ჩანს. დაიჭირეთ ტელ. თვალის დონეზე."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"მეტისმეტად მოძრაობთ. მყარად დაიჭირეთ ტელეფონი."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"გთხოვთ, ხელახლა დაარეგისტრიროთ თქვენი სახე."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"სახის ამოცნობა ვერ ხერხდება. ცადეთ ხელახლა."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"ოდნავ შეცვალეთ თავის პოზიცია"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"პირდაპირ უყურეთ ტელეფონს"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"პირდაპირ უყურეთ ტელეფონს"</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 4cf61ac..fa2a798 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Қолданбаға \"systemExempted\" түріне жататын экрандық режимдегі қызметтерді пайдалануға рұқсат беріледі."</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\"fileManagement\" түрі бар экрандық режимдегі қызметті іске қосу"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Қолданбаға \"fileManagement\" түріне жататын экрандық режимдегі қызметтерді пайдалануға рұқсат беріледі."</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" түріне жататын экрандық режимдегі қызметті іске қосу"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Қолданбаға \"specialUse\" түріне жататын экрандық режимдегі қызметтерді пайдалануға рұқсат беріледі."</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"қолданба жадындағы бос орынды өлшеу"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Экран құлпын пайдалану"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Жалғастыру үшін экран құлпын енгізіңіз."</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Сканерді қатты басыңыз"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Саусақ ізін тану мүмкін емес. Қайталап көріңіз."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Саусақ ізін оқу сканерін тазалап, әрекетті қайталаңыз."</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Сканерді тазалап, әрекетті қайталаңыз."</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Сканерді қатты басыңыз"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Бетіңіз көрінбей тұр. Телефонды көз деңгейінде ұстаңыз."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Қозғалыс тым көп. Телефонды қозғалтпаңыз."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Қайта тіркеліңіз."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Бет танылмады. Қайталап көріңіз."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Басыңыздың қалпын сәл өзгертіңіз."</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Телефонға тура қараңыз"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Телефонға тура қараңыз"</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 7973f2a..ab51e6e 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"អនុញ្ញាតឱ្យកម្មវិធីប្រើប្រាស់សេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"systemExempted\""</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"ដំណើរការសេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"fileManagement\""</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"អនុញ្ញាតឱ្យកម្មវិធីប្រើប្រាស់សេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"fileManagement\""</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"ដំណើរការសេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"specialUse\""</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"អនុញ្ញាតឱ្យកម្មវិធីប្រើប្រាស់សេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"specialUse\""</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"វាស់ទំហំការផ្ទុកកម្មវិធី"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"ប្រើការចាក់សោអេក្រង់"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"បញ្ចូលការចាក់សោអេក្រង់របស់អ្នក ដើម្បីបន្ត"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"សង្កត់លើសេនស័រឱ្យណែន"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"មិនអាចសម្គាល់ស្នាមម្រាមដៃបានទេ។ សូមព្យាយាមម្ដងទៀត។"</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"សម្អាតឧបករណ៍ចាប់ស្នាមម្រាមដៃ រួចព្យាយាមម្ដងទៀត"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"សម្អាតឧបករណ៍ចាប់សញ្ញា រួចព្យាយាមម្ដងទៀត"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"សង្កត់លើសេនស័រឱ្យណែន"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"មើលមិនឃើញមុខរបស់អ្នកទេ។ កាន់ទូរសព្ទរបស់អ្នកដាក់ត្រឹមភ្នែក។"</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"មានចលនាខ្លាំងពេក។ សូមកាន់ទូរសព្ទឱ្យនឹង។"</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"សូមស្កេនបញ្ចូលមុខរបស់អ្នកម្ដងទៀត។"</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"មិនអាចសម្គាល់មុខបានទេ។ សូមព្យាយាមម្ដងទៀត។"</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"ប្ដូរទីតាំងក្បាលរបស់អ្នកតិចៗ"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"មើលទូរសព្ទរបស់អ្នកឱ្យចំជាងនេះ"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"មើលទូរសព្ទរបស់អ្នកឱ្យចំជាងនេះ"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index 7e5e5b5..625ade1 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"\"systemExempted\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಗಳನ್ನು ಬಳಸಲು ಆ್ಯಪ್ಗೆ ಅನುಮತಿಸುತ್ತದೆ"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\"fileManagement\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಯನ್ನು ರನ್ ಮಾಡಿ"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"\"fileManagement\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಗಳನ್ನು ಬಳಸಲು ಆ್ಯಪ್ಗೆ ಅನುಮತಿಸುತ್ತದೆ"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಯನ್ನು ರನ್ ಮಾಡಿ"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"\"specialUse\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಗಳನ್ನು ಬಳಸಲು ಆ್ಯಪ್ಗೆ ಅನುಮತಿಸುತ್ತದೆ"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"ಅಪ್ಲಿಕೇಶನ್ ಸಂಗ್ರಹ ಸ್ಥಳವನ್ನು ಅಳೆಯಿರಿ"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"ಸ್ಕ್ರೀನ್ ಲಾಕ್ ಬಳಸಿ"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"ಮುಂದುವರಿಯಲು ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ ಲಾಕ್ ಅನ್ನು ನಮೂದಿಸಿ"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"ಸೆನ್ಸರ್ ಮೇಲೆ ಗಟ್ಟಿಯಾಗಿ ಒತ್ತಿರಿ"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"ಫಿಂಗರ್ಪ್ರಿಂಟ್ ಅನ್ನು ಗುರುತಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ. ಪುನಃ ಪ್ರಯತ್ನಿಸಿ."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"ಫಿಂಗರ್ ಪ್ರಿಂಟ್ ಸೆನ್ಸರ್ ಸ್ವಚ್ಛಗೊಳಿಸಿ ಹಾಗೂ ಪುನಃ ಪ್ರಯತ್ನಿಸಿ"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"ಸೆನ್ಸರ್ ಸ್ವಚ್ಛಗೊಳಿಸಿ ಹಾಗೂ ಪುನಃ ಪ್ರಯತ್ನಿಸಿ"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"ಸೆನ್ಸರ್ ಮೇಲೆ ಗಟ್ಟಿಯಾಗಿ ಒತ್ತಿರಿ"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"ನಿಮ್ಮ ಮುಖ ಕಾಣಿಸುತ್ತಿಲ್ಲ. ನಿಮ್ಮ ಫೋನ್ ಅನ್ನು ಕಣ್ಣಿನ ನೇರಕ್ಕೆ ಹಿಡಿದುಕೊಳ್ಳಿ."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"ತುಂಬಾ ಅಲುಗಾಡುತ್ತಿದೆ ಫೋನ್ ಅನ್ನು ಸ್ಥಿರವಾಗಿ ಹಿಡಿಯಿರಿ."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"ನಿಮ್ಮ ಮುಖವನ್ನು ಮರುನೋಂದಣಿ ಮಾಡಿ."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"ಮುಖ ಗುರುತಿಸಲಾಗುತ್ತಿಲ್ಲ ಪುನಃ ಪ್ರಯತ್ನಿಸಿ."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"ನಿಮ್ಮ ತಲೆಯ ಸ್ಥಾನವನ್ನು ಸ್ವಲ್ಪ ಬದಲಾಯಿಸಿ"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"ನಿಮ್ಮ ಫೋನ್ ಅನ್ನು ನೇರವಾಗಿ ನೋಡಿ"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"ನಿಮ್ಮ ಫೋನ್ ಅನ್ನು ನೇರವಾಗಿ ನೋಡಿ"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index cfc7730..a40a7f4 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"앱에서 \'systemExempted\' 유형의 포그라운드 서비스를 사용하도록 허용합니다."</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\'fileManagement\' 유형의 포그라운드 서비스 실행"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"앱에서 \'fileManagement\' 유형의 포그라운드 서비스를 사용하도록 허용합니다."</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\'specialUse\' 유형의 포그라운드 서비스 실행"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"앱에서 \'specialUse\' 유형의 포그라운드 서비스를 사용하도록 허용합니다."</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"앱 저장공간 계산"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"화면 잠금 사용"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"계속하려면 화면 잠금용 사용자 인증 정보를 입력하세요"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"센서를 세게 누르세요"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"지문을 인식할 수 없습니다. 다시 시도해 주세요."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"지문 센서를 닦은 후 다시 시도해 보세요."</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"센서를 닦은 후 다시 시도해 보세요."</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"센서를 세게 누르세요"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"얼굴이 보이지 않습니다. 눈높이에 맞춰 휴대전화를 들어 주세요"</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"너무 많이 움직였습니다. 휴대전화를 흔들리지 않게 잡으세요."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"얼굴을 다시 등록해 주세요."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"얼굴을 인식할 수 없습니다. 다시 시도해 주세요."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"얼굴의 위치를 조금 변경해 주세요."</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"휴대전화를 좀 더 정면에서 바라보세요."</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"휴대전화를 좀 더 정면에서 바라보세요."</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index dcdfc80..cfa0983 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Колдонмолорго алдынкы пландагы \"systemExempted\" түрүндөгү кызматтарды колдонууга уруксат берет"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"алдынкы пландагы \"fileManagement\" түрүндөгү кызматты аткаруу"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Колдонмолорго алдынкы пландагы \"fileManagement\" түрүндөгү кызматтарды колдонууга уруксат берет"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"алдынкы пландагы \"specialUse\" түрүндөгү кызматты аткаруу"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Колдонмолорго алдынкы пландагы \"specialUse\" түрүндөгү кызматтарды колдонууга уруксат берет"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"колдонмо сактагычынын мейкиндигин өлчөө"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Экран кулпусун колдонуу"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Улантуу үчүн экрандын кулпусун киргизиңиз"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Сенсорду катуу басыңыз"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Манжа изи таанылбай жатат. Кайра аракет кылыңыз."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Манжа изинин сенсорун тазалап, кайра аракет кылыңыз"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Сенсорду тазалап, кайра аракет кылыңыз"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Сенсорду катуу басыңыз"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Жүзүңүз көрүнбөй жатат. Телефонду маңдайыңызга кармаңыз."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Кыймылдап жибердиңиз. Телефонду түз кармаңыз."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Жүзүңүздү кайра таанытыңыз."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Жүз таанылбай жатат. Кайталаңыз."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Башыңызды бир аз буруңуз"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Телефонуңузду караңыз"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Телефонуңузду караңыз"</string>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index 49266a4..653de36 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"ອະນຸຍາດໃຫ້ແອັບໃຊ້ປະໂຫຍດຈາກບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"ໄດ້ຮັບການຍົກເວັ້ນຈາກລະບົບ\""</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"ເອີ້ນໃຊ້ບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"ການຈັດການໄຟລ໌\""</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"ອະນຸຍາດໃຫ້ແອັບໃຊ້ປະໂຫຍດຈາກບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"ການຈັດການໄຟລ໌\""</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"ເອີ້ນໃຊ້ບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"ການນຳໃຊ້ພິເສດ\""</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"ອະນຸຍາດໃຫ້ແອັບໃຊ້ປະໂຫຍດຈາກບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"ການນຳໃຊ້ພິເສດ\""</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"ກວດສອບພື້ນທີ່ຈັດເກັບຂໍ້ມູນແອັບຯ"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"ໃຊ້ການລັອກໜ້າຈໍ"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"ໃສ່ການລັອກໜ້າຈໍຂອງທ່ານເພື່ອສືບຕໍ່"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"ກົດຢູ່ເຊັນເຊີໃຫ້ແໜ້ນ"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"ບໍ່ສາມາດຈຳແນກລາຍນິ້ວມືໄດ້. ກະລຸນາລອງໃໝ່."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"ໃຫ້ອະນາໄມເຊັນເຊີລາຍນິ້ວມືແລ້ວລອງໃໝ່"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"ໃຫ້ອະນາໄມເຊັນເຊີແລ້ວລອງໃໝ່"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"ກົດຢູ່ເຊັນເຊີໃຫ້ແໜ້ນ"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"ບໍ່ເຫັນໃບໜ້າຂອງທ່ານ. ຖືໂທລະສັບຂອງທ່ານໄວ້ໃນລະດັບສາຍຕາ."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"ເຄື່ອນໄຫວຫຼາຍເກີນໄປ. ກະລຸນາຖືໂທລະສັບໄວ້ຊື່ໆ."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"ກະລຸນາລົງທະບຽນອຸປະກອນຂອງທ່ານອີກເທື່ອໜຶ່ງ."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"ບໍ່ສາມາດຈຳແນກໃບໜ້າໄດ້. ກະລຸນາລອງໃໝ່."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"ປ່ຽນຕຳແໜ່ງຂອງຫົວທ່ານເລັກນ້ອຍ"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"ເບິ່ງຊື່ໆໄປຫາໂທລະສັບຂອງທ່ານໃຫ້ຫຼາຍຂຶ້ນ"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"ເບິ່ງຊື່ໆໄປຫາໂທລະສັບຂອງທ່ານໃຫ້ຫຼາຍຂຶ້ນ"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index aefda2e..4733552 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -436,6 +436,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Programai leidžiama naudoti priekinio plano paslaugas, kurių tipas „systemExempted“"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"Priekinio plano paslaugos, kurios tipas „fileManagement“, vykdymas"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Leidžiama programai naudoti priekinio plano paslaugas, kurių tipas „fileManagement“"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"paleisti priekinio plano paslaugą, kurios tipas „specialUse“"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Programai leidžiama naudoti priekinio plano paslaugas, kurių tipas „specialUse“"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"matuoti programos atmintinės vietą"</string>
@@ -634,7 +638,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Naudoti ekrano užraktą"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Jei norite tęsti, įveskite ekrano užraktą"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Stipriai paspauskite jutiklį"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Nepavyko atpažinti kontrolinio kodo. Bandykite dar kartą."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Nuvalykite kontrolinio kodo jutiklį ir bandykite dar kartą"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Nuvalykite jutiklį ir bandykite dar kartą"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Tvirtai paspauskite jutiklį"</string>
@@ -698,7 +703,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Nepavyko pamatyti jūsų veido. Laikykite telefoną akių lygyje."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Įrenginys per daug judinamas. Nejudink. telefono."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Užregistruokite veidą iš naujo."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Veidas neatpažintas. Bandykite dar kartą."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Kaskart šiek tiek pakeiskite galvos poziciją"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Žiūrėkite tiesiai į telefoną"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Žiūrėkite tiesiai į telefoną"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index f17f02f..0f4e3f9 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -435,6 +435,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Ļauj lietotnei izmantot šāda veida priekšplāna pakalpojumus: systemExempted"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"izpildīt šāda veida priekšplāna pakalpojumu: fileManagement"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Ļauj lietotnei izmantot šāda veida priekšplāna pakalpojumus: fileManagement"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"izpildīt šāda veida priekšplāna pakalpojumu: specialUse"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Ļauj lietotnei izmantot šāda veida priekšplāna pakalpojumus: specialUse"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"noteikt vietas apjomu lietotnes atmiņā"</string>
@@ -633,7 +637,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Ekrāna bloķēšanas metodes izmantošana"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Lai turpinātu, ievadiet ekrāna bloķēšanas informāciju"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Stingri spiediet pirkstu pie sensora"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Nevar atpazīt pirksta nospiedumu. Mēģiniet vēlreiz."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Notīriet pirkstu nospiedumu sensoru un mēģiniet vēlreiz"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Notīriet sensoru un mēģiniet vēlreiz"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Stingri spiediet pirkstu pie sensora"</string>
@@ -697,7 +702,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Seja nav redzama. Turiet tālruni acu līmenī."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Pārāk daudz kustību. Nekustīgi turiet tālruni."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Lūdzu, atkārtoti reģistrējiet savu seju."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Nevar atpazīt seju. Mēģiniet vēlreiz."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Nedaudz mainiet galvas pozīciju."</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Skatieties tieši uz tālruni"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Skatieties tieši uz tālruni"</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index dbcf11f..fd8daac 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Дозволува апликацијата да ги користи во преден план услугите со типот „systemExempted“"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"Извршување услуга во преден план со типот „fileManagement“"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Дозволува апликацијата да ги користи услугите во преден план со типот „fileManagement“"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"да извршува во преден план услуга со типот „specialUse“"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Дозволува апликацијата да ги користи во преден план услугите со типот „specialUse“"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"измери простор за складирање на апликацијата"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Користи заклучување екран"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Внесете го заклучувањето на екранот за да продолжите"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Цврсто притиснете на сензорот"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Не се препознава отпечатокот. Обидете се повторно."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Исчистете го сензорот за отпечатоци и обидете се повторно"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Исчистете го сензорот и обидете се повторно"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Цврсто притиснете на сензорот"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Не се гледа ликот. Држете го телефонот во висина на очите."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Премногу движење. Држете го телефонот стабилно."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Повторно регистрирајте го лицето."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Не се препознава ликот. Обидете се пак."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Малку сменете ја положбата на главата"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Гледајте подиректно во телефонот"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Гледајте подиректно во телефонот"</string>
@@ -1961,7 +1967,7 @@
<string name="locale_search_menu" msgid="6258090710176422934">"Пребарај"</string>
<string name="app_suspended_title" msgid="888873445010322650">"Апликацијата не е достапна"</string>
<string name="app_suspended_default_message" msgid="6451215678552004172">"Апликацијата <xliff:g id="APP_NAME_0">%1$s</xliff:g> не е достапна во моментов. Со ова управува <xliff:g id="APP_NAME_1">%2$s</xliff:g>."</string>
- <string name="app_suspended_more_details" msgid="211260942831587014">"Дознај повеќе"</string>
+ <string name="app_suspended_more_details" msgid="211260942831587014">"Дознајте повеќе"</string>
<string name="app_suspended_unsuspend_message" msgid="1665438589450555459">"Прекини ја паузата"</string>
<string name="work_mode_off_title" msgid="6367463960165135829">"Да се актив. работните аплик.?"</string>
<string name="work_mode_turn_on" msgid="5316648862401307800">"Прекини ја паузата"</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index 68c0749..a001de3 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -434,6 +434,8 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"\"systemExempted\" എന്ന തരം ഉപയോഗിച്ച് ഫോർഗ്രൗണ്ട് സേവനങ്ങൾ പ്രയോജനപ്പെടുത്താൻ ആപ്പിനെ അനുവദിക്കുന്നു"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\"fileManagement\" എന്ന തരത്തിലുള്ള ഫോർഗ്രൗണ്ട് സേവനം റൺ ചെയ്യുക"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"\"fileManagement\" എന്ന തരത്തിലുള്ള ഫോർഗ്രൗണ്ട് സേവനങ്ങൾ പ്രയോജനപ്പെടുത്താൻ ആപ്പിനെ അനുവദിക്കുന്നു"</string>
+ <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"\"mediaProcessing\" എന്ന തരം ഉപയോഗിച്ച് ഫോർഗ്രൗണ്ട് സേവനം റൺ ചെയ്യുക"</string>
+ <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"\"mediaProcessing\" എന്ന തരം ഉപയോഗിച്ച് ഫോർഗ്രൗണ്ട് സേവനങ്ങൾ പ്രയോജനപ്പെടുത്താൻ ആപ്പിനെ അനുവദിക്കുന്നു"</string>
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" എന്ന തരം ഉപയോഗിച്ച് ഫോർഗ്രൗണ്ട് സേവനം റൺ ചെയ്യുക"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"\"specialUse\" എന്ന തരം ഉപയോഗിച്ച് ഫോർഗ്രൗണ്ട് സേവനങ്ങൾ പ്രയോജനപ്പെടുത്താൻ ആപ്പിനെ അനുവദിക്കുന്നു"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"അപ്ലിക്കേഷൻ സംഭരണയിടം അളക്കുക"</string>
@@ -632,7 +634,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"സ്ക്രീൻ ലോക്ക് ഉപയോഗിക്കുക"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"തുടരാൻ നിങ്ങളുടെ സ്ക്രീൻ ലോക്ക് നൽകുക"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"സെൻസറിൽ നന്നായി അമർത്തുക"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"ഫിംഗർപ്രിന്റ് തിരിച്ചറിയാനാകുന്നില്ല. വീണ്ടും ശ്രമിക്കുക."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"ഫിംഗർപ്രിന്റ് സെൻസർ വൃത്തിയാക്കിയ ശേഷം വീണ്ടും ശ്രമിക്കുക"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"സെൻസർ വൃത്തിയാക്കിയ ശേഷം വീണ്ടും ശ്രമിക്കുക"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"സെൻസറിൽ നന്നായി അമർത്തുക"</string>
@@ -696,7 +699,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"മുഖം കാണുന്നില്ല. ഫോൺ കണ്ണിന് നേരെ പിടിക്കുക."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"വളരെയധികം ചലനം. ഫോൺ അനക്കാതെ നേരെ പിടിക്കുക."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"നിങ്ങളുടെ മുഖം വീണ്ടും എൻറോൾ ചെയ്യുക."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"മുഖം തിരിച്ചറിയാനാകുന്നില്ല. വീണ്ടും ശ്രമിക്കൂ."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"നിങ്ങളുടെ തലയുടെ സ്ഥാനം ചെറുതായി മാറ്റുക"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"കൂടുതൽ കൃത്യമായി ഫോണിന് നേരെ നോക്കുക"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"കൂടുതൽ കൃത്യമായി ഫോണിന് നേരെ നോക്കുക"</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index 97858ef2..16b5766 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Аппад \"systemExempted\" төрөлтэй нүүрэн талын үйлчилгээнүүдийг ашиглахыг зөвшөөрнө"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\"FileManagement\" төрөлтэй нүүрэн талын үйлчилгээг ажиллуулах"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Аппад \"fileManagement\" төрөлтэй нүүрэн талын үйлчилгээнүүдийг ашиглахыг зөвшөөрнө"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"SpecialUse\" төрөлтэй нүүрэн талын үйлчилгээг ажиллуулах"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Аппад \"specialUse\" төрөлтэй нүүрэн талын үйлчилгээнүүдийг ашиглахыг зөвшөөрнө"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"апп сангийн хэмжээг хэмжих"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Дэлгэцийн түгжээг ашиглах"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Үргэлжлүүлэхийн тулд дэлгэцийн түгжээгээ оруулна уу"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Мэдрэгч дээр чанга дарна уу"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Хурууны хээг таних боломжгүй. Дахин оролдоно уу."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Хурууны хээ мэдрэгчийг цэвэрлээд, дахин оролдоно уу"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Мэдрэгчийг цэвэрлээд, дахин оролдоно уу"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Мэдрэгч дээр чанга дарна уу"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Таны царай харагдахгүй байна. Утсаа нүднийхээ түвшинд барина уу."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Хэт их хөдөлгөөнтэй байна. Утсаа хөдөлгөөнгүй барина уу."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Нүүрээ дахин бүртгүүлнэ үү."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Царайг танихгүй байна. Дахин оролдоно уу."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Толгойныхоо байрлалыг бага зэрэг өөрчилнө үү"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Утас руугаа аль болох эгц харна уу"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Утас руугаа аль болох эгц харна уу"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index cd553a4..51752e3 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"\"systemExempted\" प्रकारासोबत अॅपला फोरग्राउंड सेवांचा वापर करण्याची अनुमती देते"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\"fileManagement\" प्रकारासोबत फोरग्राउंड सेवा रन करा"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"\"fileManagement\" प्रकारासोबत अॅपला फोरग्राउंड सेवांचा वापर करण्याची अनुमती देते"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" प्रकारासोबत फोरग्राउंड सेवा रन करा"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"\"specialUse\" प्रकारासोबत अॅपला फोरग्राउंड सेवांचा वापर करण्याची अनुमती देते"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"अॅप संचयन स्थान मोजा"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"स्क्रीन लॉक वापरा"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"पुढे सुरू ठेवण्यासाठी तुमचे स्क्रीन लॉक एंटर करा"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"सेन्सरवर जोरात प्रेस करा"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"फिंगरप्रिंट ओळखता आली नाही. पुन्हा प्रयत्न करा."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"फिंगरप्रिंट सेन्सर स्वच्छ करा आणि पुन्हा प्रयत्न करा"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"सेन्सर स्वच्छ करा आणि पुन्हा प्रयत्न करा"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"सेन्सरवर जोरात प्रेस करा"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"तुमचा चेहरा दिसत नाही. तुमचा फोन डोळ्याच्या पातळीवर धरा."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"खूप हलत आहे. फोन स्थिर धरून ठेवा."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"कृपया तुमच्या चेहऱ्याची पुन्हा नोंदणी करा."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"चेहरा ओळखू शकत नाही. पुन्हा प्रयत्न करा."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"तुमच्या डोक्याचे स्थान किंचित बदला"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"तुमच्या फोनकडे आणखी थेट पहा"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"तुमच्या फोनकडे आणखी थेट पहा"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 90ffb21..599083c 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Membenarkan apl menggunakan perkhidmatan latar depan dengan jenis \"systemExempted\""</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"jalankan perkhidmatan latar depan dengan jenis \"fileManagement\""</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Benarkan apl menggunakan perkhidmatan latar depan dengan jenis \"fileManagement\""</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"jalankan perkhidmatan latar depan dengan jenis \"specialUse\""</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Membenarkan apl menggunakan perkhidmatan latar depan dengan jenis \"specialUse\""</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"ukur ruang storan apl"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Gunakan kunci skrin"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Masukkan kunci skrin untuk teruskan"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Tekan penderia dengan kuat"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Tidak dapat mengecam cap jari. Cuba lagi."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Bersihkan penderia cap jari dan cuba lagi"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Bersihkan penderia dan cuba lagi"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Tekan penderia dengan kuat"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Wajah tidak kelihatan. Pegang telefon pada paras mata."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Terlalu bnyk gerakan. Pegang telefon dgn stabil."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Sila daftarkan semula wajah anda."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Tidak dapat mengecam wajah. Cuba lagi."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Tukar sedikit kedudukan kepala anda"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Lihat lebih lurus pada telefon"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Lihat lebih lurus pada telefon"</string>
@@ -1016,7 +1022,7 @@
<string name="lockscreen_forgot_pattern_button_text" msgid="8362442730606839031">"Lupa corak?"</string>
<string name="lockscreen_glogin_forgot_pattern" msgid="9218940117797602518">"Buka kunci akaun"</string>
<string name="lockscreen_glogin_too_many_attempts" msgid="3775904917743034195">"Terlalu banyak percubaan melukis corak"</string>
- <string name="lockscreen_glogin_instructions" msgid="4695162942525531700">"Untuk membuka kunci, log masuk dengan akaun Google anda."</string>
+ <string name="lockscreen_glogin_instructions" msgid="4695162942525531700">"Untuk membuka kunci, log masuk dengan Google Account anda."</string>
<string name="lockscreen_glogin_username_hint" msgid="6916101478673157045">"Nama Pengguna (E-mel)"</string>
<string name="lockscreen_glogin_password_hint" msgid="3031027901286812848">"Kata laluan"</string>
<string name="lockscreen_glogin_submit_button" msgid="3590556636347843733">"Log masuk"</string>
@@ -1667,7 +1673,7 @@
<string name="kg_invalid_puk" msgid="4809502818518963344">"Masukkan semula kod PIN yang betul. Percubaan berulang akan melumpuhkan SIM secara kekal."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="4705368340409816254">"Kod PIN tidak sepadan"</string>
<string name="kg_login_too_many_attempts" msgid="699292728290654121">"Terlalu banyak percubaan melukis corak"</string>
- <string name="kg_login_instructions" msgid="3619844310339066827">"Untuk membuka kunci, log masuk dengan akaun Google anda."</string>
+ <string name="kg_login_instructions" msgid="3619844310339066827">"Untuk membuka kunci, log masuk dengan Google Account anda."</string>
<string name="kg_login_username_hint" msgid="1765453775467133251">"Nama Pengguna (E-mel)"</string>
<string name="kg_login_password_hint" msgid="3330530727273164402">"Kata laluan"</string>
<string name="kg_login_submit_button" msgid="893611277617096870">"Log masuk"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index fa5efa6..cd7d43b 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"\"systemExempted\" အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှုများအား အကျိုးရှိရှိ အသုံးပြုနိုင်ရန် အက်ပ်ကို ခွင့်ပြုသည်"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"“fileManagement” အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှု လုပ်ဆောင်ခြင်း"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"“\"fileManagement” အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှုများအား အကျိုးရှိရှိ အသုံးပြုနိုင်ရန် အက်ပ်ကို ခွင့်ပြုသည်"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှု လုပ်ဆောင်ခြင်း"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"\"specialUse\" အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှုများအား အကျိုးရှိရှိ အသုံးပြုနိုင်ရန် အက်ပ်ကို ခွင့်ပြုသည်"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"အက်ပ်သိုလှောင်မှု နေရာကို တိုင်းထွာခြင်း"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"ဖန်သားပြင်လော့ခ်ချခြင်းကို သုံးခြင်း"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"ရှေ့ဆက်ရန် သင်၏ဖန်သားပြင် လော့ခ်ချခြင်းကို ထည့်ပါ"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"အာရုံခံကိရိယာပေါ်တွင် သေချာဖိပါ"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"လက်ဗွေကို မမှတ်မိပါ။ ထပ်စမ်းကြည့်ပါ။"</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"လက်ဗွေ အာရုံခံကိရိယာကို သန့်ရှင်းပြီး ထပ်စမ်းကြည့်ပါ"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"အာရုံခံကိရိယာကို သန့်ရှင်းပြီး ထပ်စမ်းကြည့်ပါ"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"အာရုံခံကိရိယာပေါ်တွင် သေချာဖိပါ"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"သင့်မျက်နှာ မမြင်ရပါ။ ဖုန်းနှင့် မျက်စိ တစ်တန်းတည်းထားပါ။"</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"လှုပ်လွန်းသည်။ ဖုန်းကို ငြိမ်ငြိမ်ကိုင်ပါ။"</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"သင့်မျက်နှာကို ပြန်စာရင်းသွင်းပါ။"</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"မျက်နှာကို မသိပါ။ ထပ်စမ်းကြည့်ပါ။"</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"ခေါင်းအနေအထားကို အနည်းငယ်ပြောင်းပါ"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"သင့်ဖုန်းကို တည့်တည့်ကြည့်ပါ"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"သင့်ဖုန်းကို တည့်တည့်ကြည့်ပါ"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index f97e437..b77d86a 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Lar appen bruke forgrunnstjenester med typen «systemExempted»"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"kjør forgrunnstjeneste med typen «fileManagement»"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Lar appen bruke forgrunnstjenester med typen «fileManagement»"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"kjøre forgrunnstjeneste med typen «specialUse»"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Lar appen bruke forgrunnstjenester med typen «specialUse»"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"måle lagringsplass for apper"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Bruk skjermlås"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Skriv inn skjermlåsen for å fortsette"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Trykk godt på sensoren"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Fingeravtrykket gjenkjennes ikke. Prøv på nytt."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Rengjør fingeravtrykkssensoren og prøv igjen"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Rengjør sensoren og prøv igjen"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Trykk godt på sensoren"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Kan ikke se ansiktet ditt. Hold telefonen i øyehøyde."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"For mye bevegelse. Hold telefonen stødig."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Registrer ansiktet ditt på nytt."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Ansiktet gjenkjennes ikke. Prøv på nytt."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Endre hodeposisjonen litt"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Se mer direkte på telefonen"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Se mer direkte på telefonen"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index 99dcae0..6dbeb40 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"यसले एपलाई \"systemExempted\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू प्रयोग गर्ने अनुमति दिन्छ"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"यस प्रकारको \"fileManagement\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू चलाउने अनुमति"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"यसले यो एपलाई यस प्रकारको \"fileManagement\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू प्रयोग गर्ने अनुमति दिन्छ"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू प्रयोग गर्ने अनुमति दिनुहोस्"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"यसले एपलाई \"specialUse\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू प्रयोग गर्ने अनुमति दिन्छ"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"एप भण्डारण ठाउँको मापन गर्नुहोस्"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"स्क्रिन लक प्रयोग गर्नुहोस्"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"जारी राख्न आफ्नो स्क्रिन लक हाल्नुहोस्"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"सेन्सरमा बेसरी थिच्नुहोस्"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"फिंगरप्रिन्ट पहिचान गर्न सकिएन। फेरि प्रयास गर्नुहोस्।"</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"फिंगरप्रिन्ट सेन्सर सफा गरेर फेरि प्रयास गर्नुहोस्"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"सेन्सर सफा गरेर फेरि प्रयास गर्नुहोस्"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"सेन्सरमा बेसरी थिच्नुहोस्"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"तपाईंको अनुहार देखिएन। तपाईंको फोन आफ्नो आँखाअघि राखी समात्नुहोस्।"</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"अत्यधिक हल्लियो। फोन स्थिर राख्नुहोस्।"</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"कृपया आफ्नो अनुहार पुनः दर्ता गर्नुहोस्।"</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"अनुहार पहिचान गर्न सकिएन। फेरि प्रयास गर्नुहोस्।"</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"आफ्नो टाउको थोरै यताउता सार्नुहोस्"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"आफ्नो फोनमा अझ सीधा हेर्नुहोस्"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"आफ्नो फोनमा अझ सीधा हेर्नुहोस्"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index f0d34a5..0bd0a59 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Hiermee kan de app gebruikmaken van services op de voorgrond van het type \'systemExempted\'"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"service op de voorgrond van het type \'fileManagement\' uitvoeren"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Hiermee kan de app gebruikmaken van services op de voorgrond van het type \'fileManagement\'."</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"service op de voorgrond van het type \'specialUse\' uitvoeren"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Hiermee kan de app gebruikmaken van services op de voorgrond van het type \'specialUse\'"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"opslagruimte van app meten"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Schermvergrendeling gebruiken"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Voer je schermvergrendeling in om door te gaan"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Druk stevig op de sensor"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Vingerafdruk niet herkend. Probeer het opnieuw."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Reinig de vingerafdruksensor en probeer het opnieuw"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Reinig de sensor en probeer het opnieuw"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Druk stevig op de sensor"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Je gezicht is niet te zien. Houd je telefoon op ooghoogte."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Te veel beweging. Houd je telefoon stil."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Registreer je gezicht opnieuw."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Gezicht niet herkend. Probeer het opnieuw."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Verander de positie van je hoofd een beetje"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Kijk goed recht naar je telefoon"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Kijk goed recht naar je telefoon"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index bfde6c9..58ab2c2 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"\"systemExempted\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ବ୍ୟବହାର କରିବା ପାଇଁ ଆପକୁ ଅନୁମତି ଦିଏ"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\"fileManagement\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ଚଲାଏ"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"\"fileManagement\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ବ୍ୟବହାର କରିବା ପାଇଁ ଆପକୁ ଅନୁମତି ଦିଏ"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ଚଲାଏ"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"\"specialUse\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ବ୍ୟବହାର କରିବା ପାଇଁ ଆପକୁ ଅନୁମତି ଦିଏ"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"ଆପ୍ ଷ୍ଟୋରେଜ୍ ସ୍ଥାନର ମାପ କରନ୍ତୁ"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"ସ୍କ୍ରିନ୍ ଲକ୍ ବ୍ୟବହାର କରନ୍ତୁ"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"ଜାରି ରଖିବାକୁ ଆପଣଙ୍କ ସ୍କ୍ରିନ୍ ଲକ୍ ଏଣ୍ଟର୍ କରନ୍ତୁ"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"ସେନ୍ସର ଉପରେ ଦୃଢ଼ ଭାବେ ଦବାନ୍ତୁ"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"ଟିପଚିହ୍ନକୁ ଚିହ୍ନଟ କରାଯାଇପାରିବ ନାହିଁ। ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ।"</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"ଟିପଚିହ୍ନ ସେନ୍ସରକୁ ପରିଷ୍କାର କରି ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"ସେନ୍ସରକୁ ପରିଷ୍କାର କରି ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"ସେନ୍ସର ଉପରେ ଦୃଢ଼ ଭାବେ ଦବାନ୍ତୁ"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"ଆପଣଙ୍କ ଫେସ ଦେଖାଯାଉନାହିଁ। ଆପଣଙ୍କ ଫୋନକୁ ଆଖି ସିଧାରେ ଧରି ରଖନ୍ତୁ।"</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"ଅତ୍ୟଧିକ ଅସ୍ଥିର। ଫୋନ୍କୁ ସ୍ଥିର ଭାବେ ଧରନ୍ତୁ।"</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"ଦୟାକରି ଆପଣଙ୍କର ମୁହଁ ପୁଣି-ଏନ୍ରୋଲ୍ କରନ୍ତୁ।"</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"ଫେସ ଚିହ୍ନଟ କରାଯାଇପାରିବ ନାହିଁ। ପୁଣି ଚେଷ୍ଟା କର।"</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"ଆପଣଙ୍କ ମୁଣ୍ଡର ସ୍ଥିତି ସାମାନ୍ୟ ବଦଳାନ୍ତୁ"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"ଆପଣଙ୍କ ଫୋନକୁ ସମ୍ପୂର୍ଣ୍ଣ ସିଧା ଦେଖନ୍ତୁ"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"ଆପଣଙ୍କ ଫୋନକୁ ସମ୍ପୂର୍ଣ୍ଣ ସିଧା ଦେଖନ୍ତୁ"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index d3901f7..c3e4134 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -85,7 +85,7 @@
<string name="NetworkPreferenceSwitchSummary" msgid="2086506181486324860">"ਤਰਜੀਹੀ ਨੈੱਟਵਰਕ ਨੂੰ ਬਦਲ ਕੇ ਦੇਖੋ। ਬਦਲਣ ਲਈ ਟੈਪ ਕਰੋ।"</string>
<string name="EmergencyCallWarningTitle" msgid="1615688002899152860">"ਸੰਕਟਕਾਲੀਨ ਕਾਲਿੰਗ ਉਪਲਬਧ ਨਹੀਂ"</string>
<string name="EmergencyCallWarningSummary" msgid="1194185880092805497">"ਵਾਈ-ਫਾਈ ਰਾਹੀਂ ਸੰਕਟਕਾਲੀਨ ਕਾਲਾਂ ਨਹੀਂ ਕਰ ਸਕਦੇ"</string>
- <string name="notification_channel_network_alert" msgid="4788053066033851841">"ਸੁਚੇਤਨਾਵਾਂ"</string>
+ <string name="notification_channel_network_alert" msgid="4788053066033851841">"ਅਲਰਟ"</string>
<string name="notification_channel_call_forward" msgid="8230490317314272406">"ਕਾਲ ਫਾਰਵਰਡਿੰਗ"</string>
<string name="notification_channel_emergency_callback" msgid="54074839059123159">"ਸੰਕਟਕਾਲੀਨ ਕਾਲਬੈਕ ਮੋਡ"</string>
<string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"ਮੋਬਾਈਲ ਡਾਟੇ ਦੀ ਸਥਿਤੀ"</string>
@@ -279,11 +279,11 @@
<string name="notification_channel_developer_important" msgid="7197281908918789589">"ਮਹੱਤਵਪੂਰਨ ਵਿਕਾਸਕਾਰ ਸੁਨੇਹੇ"</string>
<string name="notification_channel_updates" msgid="7907863984825495278">"ਅੱਪਡੇਟ"</string>
<string name="notification_channel_network_status" msgid="2127687368725272809">"ਨੈੱਟਵਰਕ ਅਵਸਥਾ"</string>
- <string name="notification_channel_network_alerts" msgid="6312366315654526528">"ਨੈੱਟਵਰਕ ਸੁਚੇਤਨਾਵਾਂ"</string>
+ <string name="notification_channel_network_alerts" msgid="6312366315654526528">"ਨੈੱਟਵਰਕ ਅਲਰਟ"</string>
<string name="notification_channel_network_available" msgid="6083697929214165169">"ਨੈੱਟਵਰਕ ਉਪਲਬਧ ਹੈ"</string>
<string name="notification_channel_vpn" msgid="1628529026203808999">"VPN ਅਵਸਥਾ"</string>
- <string name="notification_channel_device_admin" msgid="6384932669406095506">"ਤੁਹਾਡੇ ਆਈ.ਟੀ. ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਸੁਚੇਤਨਾਵਾਂ"</string>
- <string name="notification_channel_alerts" msgid="5070241039583668427">"ਸੁਚੇਤਨਾਵਾਂ"</string>
+ <string name="notification_channel_device_admin" msgid="6384932669406095506">"ਤੁਹਾਡੇ ਆਈ.ਟੀ. ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਅਲਰਟ"</string>
+ <string name="notification_channel_alerts" msgid="5070241039583668427">"ਅਲਰਟ"</string>
<string name="notification_channel_retail_mode" msgid="3732239154256431213">"ਪ੍ਰਚੂਨ ਸਟੋਰਾਂ ਲਈ ਡੈਮੋ"</string>
<string name="notification_channel_usb" msgid="1528280969406244896">"USB ਕਨੈਕਸ਼ਨ"</string>
<string name="notification_channel_heavy_weight_app" msgid="17455756500828043">"ਚੱਲ ਰਹੀ ਐਪ"</string>
@@ -367,7 +367,7 @@
<string name="permlab_receiveMms" msgid="4000650116674380275">"ਲਿਖਤ ਸੁਨੇਹੇ (MMS) ਪ੍ਰਾਪਤ ਕਰੋ"</string>
<string name="permdesc_receiveMms" msgid="958102423732219710">"ਐਪ ਨੂੰ MMS ਸੁਨੇਹੇ ਪ੍ਰਾਪਤ ਕਰਨ ਅਤੇ ਉਹਨਾਂ ਦੀ ਪ੍ਰਕਿਰਿਆ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ। ਇਸਦਾ ਮਤਲਬ ਹੈ ਕਿ ਐਪ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਤੇ ਭੇਜੇ ਗਏ ਸੁਨੇਹਿਆਂ ਨੂੰ ਤੁਹਾਨੂੰ ਦਿਖਾਏ ਬਿਨਾਂ ਨਿਰੀਖਣ ਕਰ ਸਕਦੀ ਹੈ ਜਾਂ ਮਿਟਾ ਸਕਦੀ ਹੈ।"</string>
<string name="permlab_bindCellBroadcastService" msgid="586746677002040651">"ਸੈੱਲ ਪ੍ਰਸਾਰਨ ਸੁਨੇਹਿਆਂ ਨੂੰ ਅੱਗੇ ਭੇਜੋ"</string>
- <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"ਐਪ ਨੂੰ ਸੈੱਲ ਪ੍ਰਸਾਰਨ ਸੁਨੇਹਿਆਂ ਦੇ ਪ੍ਰਾਪਤ ਹੁੰਦੇ ਹੀ ਉਹਨਾਂ ਨੂੰ ਅੱਗੇ ਭੇਜਣ ਲਈ ਸੈੱਲ ਪ੍ਰਸਾਰਨ ਮਾਡਿਊਲ ਨਾਲ ਜੋੜਨ ਦੀ ਇਜਾਜ਼ਤ ਦਿੱਤੀ ਜਾਂਦੀ ਹੈ। ਸੈੱਲ ਪ੍ਰਸਾਰਨ ਸੁਚੇਤਨਾਵਾਂ ਤੁਹਾਨੂੰ ਸੰਕਟਕਾਲੀ ਸਥਿਤੀਆਂ ਦੀ ਚਿਤਾਵਨੀ ਦੇਣ ਲਈ ਕੁਝ ਟਿਕਾਣਿਆਂ \'ਤੇ ਪ੍ਰਦਾਨ ਕੀਤੀਆਂ ਜਾਂਦੀਆਂ ਹਨ। ਭੈੜੀਆਂ ਐਪਾਂ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਦੀ ਕਾਰਗੁਜ਼ਾਰੀ ਜਾਂ ਓਪਰੇਸ਼ਨ ਵਿੱਚ ਵਿਘਨ ਪਾ ਸਕਦੀਆਂ ਹਨ ਜਦੋਂ ਇੱਕ ਸੰਕਟਕਾਲੀ ਸੈੱਲ ਪ੍ਰਸਾਰਨ ਪ੍ਰਾਪਤ ਕੀਤਾ ਜਾਂਦਾ ਹੈ।"</string>
+ <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"ਐਪ ਨੂੰ ਸੈੱਲ ਪ੍ਰਸਾਰਨ ਸੁਨੇਹਿਆਂ ਦੇ ਪ੍ਰਾਪਤ ਹੁੰਦੇ ਹੀ ਉਨ੍ਹਾਂ ਨੂੰ ਅੱਗੇ ਭੇਜਣ ਲਈ ਸੈੱਲ ਪ੍ਰਸਾਰਨ ਮਾਡਿਊਲ ਨਾਲ ਜੋੜਨ ਦੀ ਇਜਾਜ਼ਤ ਦਿੱਤੀ ਜਾਂਦੀ ਹੈ। ਸੈੱਲ ਪ੍ਰਸਾਰਨ ਅਲਰਟ ਤੁਹਾਨੂੰ ਐਮਰਜੈਂਸੀ ਸਥਿਤੀਆਂ ਦੀ ਚਿਤਾਵਨੀ ਦੇਣ ਲਈ ਕੁਝ ਟਿਕਾਣਿਆਂ \'ਤੇ ਪ੍ਰਦਾਨ ਕੀਤੀਆਂ ਜਾਂਦੀਆਂ ਹਨ। ਭੈੜੀਆਂ ਐਪਾਂ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਦੀ ਕਾਰਗੁਜ਼ਾਰੀ ਜਾਂ ਓਪਰੇਸ਼ਨ ਵਿੱਚ ਵਿਘਨ ਪਾ ਸਕਦੀਆਂ ਹਨ ਜਦੋਂ ਇੱਕ ਐਮਰਜੈਂਸੀ ਸੈੱਲ ਪ੍ਰਸਾਰਨ ਪ੍ਰਾਪਤ ਕੀਤਾ ਜਾਂਦਾ ਹੈ।"</string>
<string name="permlab_manageOngoingCalls" msgid="281244770664231782">"ਜਾਰੀ ਕਾਲਾਂ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰੋ"</string>
<string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"ਐਪ ਨੂੰ ਆਪਣੇ ਡੀਵਾਈਸ \'ਤੇ ਜਾਰੀ ਕਾਲਾਂ ਬਾਰੇ ਵੇਰਵੇ ਦੇਖਣ ਅਤੇ ਇਹਨਾਂ ਕਾਲਾਂ ਨੂੰ ਕੰਟਰੋਲ ਕਰਨ ਦਿਓ।"</string>
<string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"ਸੈਲ ਪ੍ਰਸਾਰਨ ਸੁਨੇਹੇ ਪੜ੍ਹੋ"</string>
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"ਐਪ ਨੂੰ \"systemExempted\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾਵਾਂ ਦੀ ਵਰਤੋਂ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\"fileManagement\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾ ਨੂੰ ਚਲਾਓ"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"ਐਪ ਨੂੰ \"fileManagement\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾਵਾਂ ਦੀ ਵਰਤਣ ਦੀ ਆਗਿਆ ਮਿਲਦੀ ਹੈ"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾ ਨੂੰ ਚਲਾਉਂਦੀ ਹੈ"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"ਐਪ ਨੂੰ \"specialUse\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾਵਾਂ ਦੀ ਵਰਤੋਂ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"ਐਪ ਸਟੋਰੇਜ ਜਗ੍ਹਾ ਮਾਪੋ"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"ਸਕ੍ਰੀਨ ਲਾਕ ਦੀ ਵਰਤੋਂ ਕਰੋ"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"ਜਾਰੀ ਰੱਖਣ ਲਈ ਆਪਣਾ ਸਕ੍ਰੀਨ ਲਾਕ ਦਾਖਲ ਕਰੋ"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"ਸੈਂਸਰ ਨੂੰ ਜ਼ੋਰ ਨਾਲ ਦਬਾਓ"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"ਫਿੰਗਰਪ੍ਰਿੰਟ ਦੀ ਪਛਾਣ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"ਫਿੰਗਰਪ੍ਰਿੰਟ ਸੈਂਸਰ ਨੂੰ ਸਾਫ਼ ਕਰੋ ਅਤੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"ਸੈਂਸਰ ਨੂੰ ਸਾਫ਼ ਕਰੋ ਅਤੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"ਸੈਂਸਰ ਨੂੰ ਜ਼ੋਰ ਨਾਲ ਦਬਾਓ"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"ਤੁਹਾਡਾ ਚਿਹਰਾ ਨਹੀਂ ਦਿਸ ਰਿਹਾ। ਆਪਣੇ ਫ਼ੋਨ ਨੂੰ ਅੱਖਾਂ ਦੀ ਸੀਧ ਵਿੱਚ ਰੱਖੋ।"</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"ਬਹੁਤ ਜ਼ਿਆਦਾ ਹਿਲਜੁਲ। ਫ਼ੋਨ ਨੂੰ ਸਥਿਰ ਰੱਖੋ।"</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"ਕਿਰਪਾ ਕਰਕੇ ਆਪਣਾ ਚਿਹਰਾ ਦੁਬਾਰਾ ਦਰਜ ਕਰੋ।"</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"ਚਿਹਰੇ ਦੀ ਪਛਾਣ ਨਹੀਂ ਹੋਈ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"ਆਪਣੇ ਸਿਰ ਨੂੰ ਥੋੜ੍ਹਾ ਹਿਲਾਓ"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"ਸਿੱਧਾ ਆਪਣੇ ਫ਼ੋਨ ਵੱਲ ਦੇਖੋ"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"ਸਿੱਧਾ ਆਪਣੇ ਫ਼ੋਨ ਵੱਲ ਦੇਖੋ"</string>
@@ -1922,7 +1928,7 @@
<string name="stk_cc_ss_to_ss" msgid="132040645206514450">"ਨਵੀਂ SS ਬੇਨਤੀ ਵਿੱਚ ਬਦਲਿਆ ਗਿਆ"</string>
<string name="notification_phishing_alert_content_description" msgid="494227305355958790">"ਫ਼ਿਸ਼ਿੰਗ ਸੰਬੰਧੀ ਅਲਰਟ"</string>
<string name="notification_work_profile_content_description" msgid="5296477955677725799">"ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ"</string>
- <string name="notification_alerted_content_description" msgid="6139691253611265992">"ਸੁਚੇਤਨਾਵਾਂ"</string>
+ <string name="notification_alerted_content_description" msgid="6139691253611265992">"ਅਲਰਟ"</string>
<string name="notification_verified_content_description" msgid="6401483602782359391">"ਪੁਸ਼ਟੀਕਿਰਤ"</string>
<string name="expand_button_content_description_collapsed" msgid="3873368935659010279">"ਵਿਸਤਾਰ ਕਰੋ"</string>
<string name="expand_button_content_description_expanded" msgid="7484217944948667489">"ਸਮੇਟੋ"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index e03679b..07a69e6 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -436,6 +436,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Zezwala na wykorzystywanie przez aplikację usług działających na pierwszym planie typu „systemExempted”"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"Uruchamianie usług działających na pierwszym planie typu „fileManagement”"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Zezwala na wykorzystywanie przez aplikację usług działających na pierwszym planie typu „fileManagement”."</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"uruchamianie usług działających na pierwszym planie typu „specialUse”"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Zezwala na wykorzystywanie przez aplikację usług działających na pierwszym planie typu „specialUse”"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"mierzenie rozmiaru pamięci aplikacji"</string>
@@ -634,7 +638,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Używaj blokady ekranu"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Użyj blokady ekranu, aby kontynuować"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Mocno naciśnij czujnik"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Nie rozpoznaję odcisku palca. Spróbuj ponownie."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Wyczyść czytnik linii papilarnych i spróbuj ponownie"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Wyczyść czujnik i spróbuj ponownie"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Mocno naciśnij czujnik"</string>
@@ -698,7 +703,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Nie widać twarzy – trzymaj telefon na wysokości oczu"</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Telefon się porusza. Trzymaj go nieruchomo."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Zarejestruj swoją twarz ponownie."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Nie rozpoznaję twarzy. Spróbuj ponownie."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Lekko zmień położenie głowy"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Patrz prosto na telefon"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Patrz prosto na telefon"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 2888c5f..e29502a 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -435,6 +435,8 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Permite que o app use serviços em primeiro plano com o tipo \"systemExempted\""</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"executar serviços em primeiro plano com o tipo \"fileManagement\""</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Permite que o app use serviços em primeiro plano com o tipo \"fileManagement\""</string>
+ <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"executar serviços em primeiro plano com o tipo \"mediaProcessing\""</string>
+ <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Permite que o app use serviços em primeiro plano com o tipo \"mediaProcessing\""</string>
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"executar serviços em primeiro plano com o tipo \"specialUse\""</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Permite que o app use serviços em primeiro plano com o tipo \"specialUse\""</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"medir o espaço de armazenamento do app"</string>
@@ -633,7 +635,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Usar bloqueio de tela"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Insira seu bloqueio de tela para continuar"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Pressione o sensor com firmeza"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Impressão digital não reconhecida. Tente de novo."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Limpe o sensor de impressão digital e tente novamente"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Limpe o sensor e tente novamente"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Pressione o sensor com firmeza"</string>
@@ -697,7 +700,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Rosto não detectado. Segure o smartphone na altura dos olhos."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Muito movimento. Não mova o smartphone."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Registre seu rosto novamente."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Não foi possível reconhecer o rosto. Tente de novo."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Mude a posição da cabeça ligeiramente"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Olhe diretamente para o smartphone"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Olhe diretamente para o smartphone"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 7a3201f..cae02fc 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -435,6 +435,8 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Permite que a app use serviços em primeiro plano com o tipo \"systemExempted\""</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"executar o serviço em primeiro plano com o tipo \"fileManagement\""</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Permite que a app use serviços em primeiro plano com o tipo \"fileManagement\""</string>
+ <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"executar o serviço em primeiro plano com o tipo \"mediaProcessing\""</string>
+ <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Permite que a app use serviços em primeiro plano com o tipo \"mediaProcessing\""</string>
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"executar o serviço em primeiro plano com o tipo \"specialUse\""</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Permite que a app use serviços em primeiro plano com o tipo \"specialUse\""</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"medir espaço de armazenamento da app"</string>
@@ -633,7 +635,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Usar o bloqueio de ecrã"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Introduza o bloqueio de ecrã para continuar"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Prima firmemente o sensor"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Impossível reconhecer impressão digital. Volte a tentar."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Limpe o sensor de impressões digitais e tente novamente"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Limpe o sensor e tente novamente"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Prima firmemente o sensor"</string>
@@ -697,7 +700,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Rosto não detetado. Segure o telemóvel ao nível dos olhos"</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Demasiado movimento. Mantenha o telemóvel firme."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Volte a inscrever o rosto."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Impossível reconhecer o rosto. Tente novamente."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Altere ligeiramente a posição da sua cabeça"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Olhe mais diretamente para o telemóvel"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Olhe mais diretamente para o telemóvel"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 2888c5f..e29502a 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -435,6 +435,8 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Permite que o app use serviços em primeiro plano com o tipo \"systemExempted\""</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"executar serviços em primeiro plano com o tipo \"fileManagement\""</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Permite que o app use serviços em primeiro plano com o tipo \"fileManagement\""</string>
+ <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"executar serviços em primeiro plano com o tipo \"mediaProcessing\""</string>
+ <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Permite que o app use serviços em primeiro plano com o tipo \"mediaProcessing\""</string>
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"executar serviços em primeiro plano com o tipo \"specialUse\""</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Permite que o app use serviços em primeiro plano com o tipo \"specialUse\""</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"medir o espaço de armazenamento do app"</string>
@@ -633,7 +635,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Usar bloqueio de tela"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Insira seu bloqueio de tela para continuar"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Pressione o sensor com firmeza"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Impressão digital não reconhecida. Tente de novo."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Limpe o sensor de impressão digital e tente novamente"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Limpe o sensor e tente novamente"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Pressione o sensor com firmeza"</string>
@@ -697,7 +700,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Rosto não detectado. Segure o smartphone na altura dos olhos."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Muito movimento. Não mova o smartphone."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Registre seu rosto novamente."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Não foi possível reconhecer o rosto. Tente de novo."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Mude a posição da cabeça ligeiramente"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Olhe diretamente para o smartphone"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Olhe diretamente para o smartphone"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 082fbb2..2beb190 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -435,6 +435,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Permite aplicației să folosească serviciile în prim-plan cu tipul „systemExempted”"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"să folosească serviciile în prim-plan cu tipul „fileManagement”"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Permite aplicației să folosească serviciile în prim-plan cu tipul „fileManagement”"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"să folosească serviciile în prim-plan cu tipul „specialUse”"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Permite aplicației să folosească serviciile în prim-plan cu tipul „specialUse”"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"măsurare spațiu de stocare al aplicației"</string>
@@ -633,7 +637,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Folosește blocarea ecranului"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Introdu blocarea ecranului pentru a continua"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Apasă ferm pe senzor"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Amprenta nu a fost recunoscută. Încearcă din nou."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Curăță senzorul de amprentă și încearcă din nou"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Curăță senzorul și încearcă din nou"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Apasă ferm pe senzor"</string>
@@ -697,7 +702,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Nu ți se vede fața. Ține telefonul la nivelul ochilor."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Prea multă mișcare. Ține telefonul nemișcat."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Reînregistrează-ți chipul."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Chipul nu a fost recunoscut. Reîncearcă."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Schimbă ușor poziția capului"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Privește mai direct spre telefon"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Privește mai direct spre telefon"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 8a03a6b..299e445 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -436,6 +436,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Разрешить приложению использовать активные службы с типом systemExempted"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"Запуск активных служб с типом fileManagement"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Приложение сможет использовать активные службы с типом fileManagement."</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"запускать активные службы с типом specialUse"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Разрешить приложению использовать активные службы с типом specialUse"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"Вычисление объема памяти приложений"</string>
@@ -634,7 +638,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Использовать блокировку экрана"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Чтобы продолжить, разблокируйте экран."</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Плотно прижмите палец к сканеру"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Не удалось распознать отпечаток. Повторите попытку."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Очистите сканер отпечатков пальцев и повторите попытку."</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Очистите сканер и повторите попытку."</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Плотно прижмите палец к сканеру."</string>
@@ -698,7 +703,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Вашего лица не видно. Держите телефон на уровне глаз"</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Не перемещайте устройство. Держите его неподвижно."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Повторите попытку."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Не удалось распознать лицо. Повторите попытку."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Немного измените положение головы"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Смотрите прямо на телефон"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Смотрите прямо на телефон"</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 89ee9f9..e19231e 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"\"systemExempted\" වර්ගය සමග පෙරබිම් සේවා භාවිතා කිරීමට යෙදුමට ඉඩ දෙයි"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\"fileManagement\" වර්ගය සමග පෙරබිම් සේවාව ධාවනය කරන්න"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"\"fileManagement\" වර්ගය සමග පෙරබිම් සේවා භාවිතා කිරීමට යෙදුමට ඉඩ දෙයි"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" වර්ගය සමග පෙරබිම් සේවාව ධාවනය කරන්න"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"\"specialUse\" වර්ගය සමග පෙරබිම් සේවා භාවිතා කිරීමට යෙදුමට ඉඩ දෙයි"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"යෙදුම් ආචයනයේ ඉඩ ප්රමාණය මැනීම"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"තිර අගුල භාවිත කරන්න"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"ඉදිරියට යාමට ඔබගේ තිර අගුල ඇතුළත් කරන්න"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"සංවේදකය මත තදින් ඔබන්න"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"ඇඟිලි සලකුණ හඳුනා ගත නොහැක. නැවත උත්සාහ කරන්න."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"ඇඟිලි සලකුණු සංවේදකය පිරිසිදු කර නැවත උත්සාහ කරන්න"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"සංවේදකය පිරිසිදු කර නැවත උත්සාහ කරන්න"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"සංවේදකය මත තදින් ඔබන්න"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"ඔබගේ මුහුණ දැකිය නොහැකිය. ඔබගේ දුරකථනය ඇස් මට්ටමින් අල්ලා ගන්න."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"චලනය ඉතා වැඩියි. දුරකථනය ස්ථිරව අල්ලා සිටින්න."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"ඔබේ මුහුණ යළි ලියාපදිංචි කරන්න."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"මුහුණ හඳුනා ගත නොහැකිය. නැවත උත්සාහ කරන්න."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"ඔබගේ හිසෙහි පිහිටීම මදක් වෙනස් කරන්න"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"ඔබගේ දුරකථනය දෙස වඩාත් ඍජුව බලන්න"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"ඔබගේ දුරකථනය දෙස වඩාත් ඍජුව බලන්න"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index bbfefd1..c9508ba 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -436,6 +436,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Umožňuje aplikácii využívať služby na popredí s typom dataSync systemExempted"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"spúšťať službu na popredí s typom remoteMessaging"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Umožňuje aplikácii využívať služby na popredí s typom fileManagement"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"spustiť službu na popredí s typom specialUse"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Umožňuje aplikácii využívať služby na popredí s typom specialUse"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"zistiť veľkosť ukladacieho priestoru aplikácie"</string>
@@ -634,7 +638,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Použiť zámku obrazovky"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Pokračujte zadaním zámky obrazovky"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Pevne pritlačte prst na senzor"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Odtlačok prsta sa nedá rozpoznať. Skúste to znova."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Vyčistite senzor odtlačkov prstov a skúste to znova"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Vyčistite senzor a skúste to znova"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Pevne pritlačte prst na senzor"</string>
@@ -698,7 +703,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Nie je vidieť vašu tvár. Držte telefón na úrovni očí."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Priveľa pohybu. Nehýbte telefónom."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Znova zaregistrujte svoju tvár."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Tvár sa nedá rozpoznať. Skúste to znova."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Trocha zmeňte pozíciu hlavy"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Pozrite sa na telefón priamejšie"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Pozrite sa na telefón priamejšie"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index e4e9a37..c459fdb 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -436,6 +436,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Aplikaciji dovoljuje, da uporablja storitve v ospredju vrste »systemExempted«."</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"izvajanje storitve v ospredju vrste »fileManagement«"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Aplikaciji dovoljuje, da uporablja storitve v ospredju vrste »fileManagement«."</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"izvajanje storitve v ospredju vrste »specialUse«"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Aplikaciji dovoljuje, da uporablja storitve v ospredju vrste »specialUse«."</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"izračunavanje prostora za shranjevanje aplikacije"</string>
@@ -634,7 +638,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Uporaba odklepanja s poverilnico"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Odklenite zaslon, če želite nadaljevati."</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Prst dobro pridržite na tipalu"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Prstnega odtisa ni mogoče prepoznati. Poskusite znova."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Očistite tipalo prstnih odtisov in poskusite znova."</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Očistite tipalo in poskusite znova."</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Prst dobro pridržite na tipalu"</string>
@@ -698,7 +703,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Obraz ni viden. Držite telefon v višini oči."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Preveč se premikate. Držite telefon pri miru."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Znova registrirajte svoj obraz."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Obraza ni mogoče prepoznati. Poskusite znova."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Nekoliko spremenite položaj glave."</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Glejte bolj naravnost v telefon"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Glejte bolj naravnost v telefon"</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index 288d8bf..a62dddb 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Lejon që aplikacioni të përdorë shërbimet në plan të parë me llojin \"systemExempted\""</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"ekzekuto shërbimin në plan të parë me llojin \"fileManagement\""</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Lejon aplikacionin të përdorë shërbimet në plan të parë me llojin \"fileManagement\""</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"të ekzekutojë shërbimin në plan të parë me llojin \"specialUse\""</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Lejon që aplikacioni të përdorë shërbimet në plan të parë me llojin \"specialUse\""</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"mat hapësirën ruajtëse të aplikacionit"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Përdor kyçjen e ekranit"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Fut kyçjen e ekranit për të vazhduar"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Shtyp fort te sensori"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Nuk mund ta dallojë gjurmën e gishtit. Provo përsëri."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Pastro sensorin e gjurmës së gishtit dhe provo sërish"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Pastro sensorin dhe provo sërish"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Shtyp fort te sensori"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Fytyra s\'mund të shihet. Mbaje telefonin në nivelin e syve."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Ka shumë lëvizje. Mbaje telefonin të palëvizur."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Regjistroje përsëri fytyrën tënde."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Fytyra nuk mund të njihet. Provo sërish."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Ndrysho pak pozicionin e kokës"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Shiko më drejtpërdrejt telefonin"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Shiko më drejtpërdrejt telefonin"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 479a1db..048d826 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -435,6 +435,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Дозвољава апликацији да користи услуге у првом плану које припадају типу „systemExempted“"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"покретање услуге у првом плану која припада типу „fileManagement“"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Дозвољава апликацији да користи услуге у првом плану које припадају типу „fileManagement“"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"покретање услуге у првом плану која припада типу „specialUse“"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Дозвољава апликацији да користи услуге у првом плану које припадају типу „specialUse“"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"мерење меморијског простора у апликацији"</string>
@@ -633,7 +637,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Користите закључавање екрана"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Употребите закључавање екрана да бисте наставили"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Чврсто притисните сензор"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Препознавање отиска прста није успело. Пробајте поново."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Обришите сензор за отисак прста и пробајте поново"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Обришите сензор и пробајте поново"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Чврсто притисните сензор"</string>
@@ -697,7 +702,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Не види се лице. Држите телефон у висини очију."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Много се померате. Држите телефон мирно."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Поново региструјте лице."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Лице није препознато. Пробајте поново."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Мало померите главу"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Гледајте право у телефон"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Гледајте право у телефон"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index a30aa0a..d10cbe6 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Tillåter att appen använder förgrundstjänster av typen systemExempted"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"kör förgrundstjänst av typen fileManagement"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Tillåter att appen använder förgrundstjänster av typen fileManagement"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"kör förgrundstjänst av typen specialUse"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Tillåter att appen använder förgrundstjänster av typen specialUse"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"mäta appens lagringsplats"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Använd skärmlåset"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Fortsätt med hjälp av ditt skärmlås"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Tryck hårt på sensorn"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Fingeravtrycket kändes inte igen. Försök igen."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Rengör fingeravtryckssensorn och försök igen"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Rengör sensorn och försök igen"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Tryck hårt på sensorn"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Ansiktet syns inte. Håll telefonen i ögonhöjd."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"För mycket rörelse. Håll mobilen stilla."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Registrera ansiktet på nytt."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Ansiktet kändes inte igen. Försök igen."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Rör lite på huvudet"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Titta rakt på telefonen"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Titta rakt på telefonen"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 9a063fb..8e1348f 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Huruhusu programu itumie huduma zinazoonekana kwenye skrini zinazohusiana na \"systemExempted\""</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"kutekeleza huduma inayoonekana kwenye skrini inayohusiana na \"fileManagement\""</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Huiruhusu programu itumie huduma zinazoonekana kwenye skrini zinazohusiana na \"fileManagement\""</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"kutekeleza huduma inayoonekana kwenye skrini inayohusiana na \"specialUse\""</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Huruhusu programu itumie huduma zinazoonekana kwenye skrini zinazohusiana na \"specialUse\""</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"Pima nafasi ya hifadhi ya programu"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Tumia mbinu ya kufunga skrini"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Weka mbinu yako ya kufunga skrini ili uendelee"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Bonyeza kwa uthabiti kwenye kitambuzi"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Imeshindwa kutambua alama ya kidole. Jaribu tena."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Safisha kitambua alama ya kidole kisha ujaribu tena"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Safisha kitambuzi kisha ujaribu tena"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Bonyeza kwa nguvu kwenye kitambuzi"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Imeshindwa kuona uso wako. Shikilia simu ikilingana na macho."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Inatikisika sana. Ishike simu iwe thabiti."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Tafadhali sajili uso wako tena."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Imeshindwa kutambua uso. Jaribu tena."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Badilisha nafasi ya kichwa chako kidogo"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Angalia simu yako moja kwa moja"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Angalia simu yako moja kwa moja"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 23402f7..32fb6a4 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"\"systemExempted\" எனும் வகையைக் கொண்ட முன்புலச் சேவைகளைப் பயன்படுத்த ஆப்ஸை அனுமதிக்கும்"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\"fileManagement\" எனும் வகையைக் கொண்ட முன்புலச் சேவையை இயக்குதல்"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"\"fileManagement\" எனும் வகையைக் கொண்ட முன்புலச் சேவைகளைப் பயன்படுத்த ஆப்ஸை அனுமதிக்கும்"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" எனும் வகையைக் கொண்ட முன்புலச் சேவையை இயக்குதல்"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"\"specialUse\" எனும் வகையைக் கொண்ட முன்புலச் சேவைகளைப் பயன்படுத்த ஆப்ஸை அனுமதிக்கும்"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"ஆப்ஸ் சேமிப்பு இடத்தை அளவிடல்"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"திரைப் பூட்டைப் பயன்படுத்து"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"தொடர்வதற்கு உங்கள் திரைப் பூட்டை உள்ளிடுங்கள்"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"சென்சாரின் மீது நன்றாக அழுத்தவும்"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"கைரேகையை அடையாளம் காண முடியவில்லை. மீண்டும் முயலவும்."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"கைரேகை சென்சாரைச் சுத்தம் செய்துவிட்டு மீண்டும் முயலவும்"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"சென்சாரைச் சுத்தம் செய்துவிட்டு மீண்டும் முயலவும்"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"சென்சாரின் மீது நன்றாக அழுத்தவும்"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"முகம் சரியாகத் தெரியவில்லை. மொபைலைக் கண்களுக்கு நேராகப் பிடிக்கவும்."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"அதிகமாக அசைகிறது. மொபைலை அசைக்காமல் பிடிக்கவும்."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"உங்கள் முகத்தை மீண்டும் பதிவுசெய்யுங்கள்."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"முகத்தை அடையாளம் காண இயலவில்லை. மீண்டும் முயலவும்."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"தலையின் நிலையைச் சிறிதளவு மாற்றவும்"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"உங்கள் மொபைலை நேராகப் பார்க்கவும்"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"உங்கள் மொபைலை நேராகப் பார்க்கவும்"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 30697b3..3f98b25 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -434,6 +434,8 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"\"systemExempted\" అనే రకంతో ఫోర్గ్రౌండ్ సర్వీస్లను ఉపయోగించడానికి యాప్ను అనుమతిస్తుంది"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\"fileManagement\" రకంతో ఫోర్గ్రౌండ్ సర్వీస్ను రన్ చేయండి"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"\"fileManagement\" అనే రకంతో ఫోర్గ్రౌండ్ సర్వీస్లను ఉపయోగించుకోవడానికి యాప్ను అనుమతిస్తుంది"</string>
+ <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"\"mediaProcessing\" రకంతో ఫోర్గ్రౌండ్ సర్వీస్ను రన్ చేయండి"</string>
+ <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"\"mediaProcessing\" అనే రకంతో ఫోర్గ్రౌండ్ సర్వీస్లను ఉపయోగించుకోవడానికి యాప్ను అనుమతిస్తుంది"</string>
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" రకంతో ఫోర్గ్రౌండ్ సర్వీస్ను రన్ చేయండి"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"\"specialUse\" అనే రకంతో ఫోర్గ్రౌండ్ సర్వీస్లను ఉపయోగించడానికి యాప్ను అనుమతిస్తుంది"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"యాప్ స్టోరేజ్ స్థలాన్ని అంచనా వేయడం"</string>
@@ -632,7 +634,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"స్క్రీన్ లాక్ను ఉపయోగించండి"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"కొనసాగించడానికి మీ స్క్రీన్ లాక్ను ఎంటర్ చేయండి"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"సెన్సార్ మీద గట్టిగా నొక్కండి"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"వేలిముద్రను గుర్తించడం సాధ్యపడదు. మళ్లీ ట్రై చేయండి."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"వేలిముద్ర సెన్సార్ను క్లీన్ చేసి, మళ్లీ ట్రై చేయండి"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"సెన్సార్ను క్లీన్ చేసి, మళ్లీ ట్రై చేయండి"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"సెన్సార్ మీద గట్టిగా నొక్కండి"</string>
@@ -696,7 +699,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"మీ ముఖం కనిపించడం లేదు. మీ ఫోన్ను కళ్లకు ఎదురుగా పట్టుకోండి."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"బాగా కదుపుతున్నారు. ఫోన్ను స్థిరంగా పట్టుకోండి"</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"దయచేసి మీ ముఖాన్ని మళ్లీ నమోదు చేయండి."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"ముఖం గుర్తించబడలేదు. మళ్లీ ట్రై చేయండి."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"మీ తల స్థానాన్ని కొద్దిగా మార్చండి"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"మీ ఫోన్ వైపు మరింత నేరుగా చూడండి"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"మీ ఫోన్ వైపు మరింత నేరుగా చూడండి"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 66ed053..81fa177 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"อนุญาตให้แอปใช้ประโยชน์จากบริการที่ทำงานอยู่เบื้องหน้าโดยมีประเภทเป็น \"ได้รับการยกเว้นจากระบบ\""</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"เรียกใช้บริการที่ทำงานอยู่เบื้องหน้าโดยมีประเภทเป็น \"การจัดการไฟล์\""</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"อนุญาตให้แอปใช้ประโยชน์จากบริการที่ทำงานอยู่เบื้องหน้าโดยมีประเภทเป็น \"การจัดการไฟล์\""</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"เรียกใช้บริการที่ทำงานอยู่เบื้องหน้าโดยมีประเภทเป็น \"การใช้งานพิเศษ\""</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"อนุญาตให้แอปใช้ประโยชน์จากบริการที่ทำงานอยู่เบื้องหน้าโดยมีประเภทเป็น \"การใช้งานพิเศษ\""</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"วัดพื้นที่เก็บข้อมูลของแอปพลิเคชัน"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"ใช้การล็อกหน้าจอ"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"ป้อนข้อมูลการล็อกหน้าจอเพื่อดำเนินการต่อ"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"กดเซ็นเซอร์ให้แน่น"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"ไม่รู้จักลายนิ้วมือ โปรดลองอีกครั้ง"</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"ทำความสะอาดเซ็นเซอร์ลายนิ้วมือแล้วลองอีกครั้ง"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"ทำความสะอาดเซ็นเซอร์แล้วลองอีกครั้ง"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"กดเซ็นเซอร์ให้แน่น"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"ไม่เห็นใบหน้า ถือโทรศัพท์ไว้ที่ระดับสายตา"</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"มีการเคลื่อนไหวมากเกินไป ถือโทรศัพท์นิ่งๆ"</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"โปรดลงทะเบียนใบหน้าอีกครั้ง"</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"ไม่รู้จักใบหน้า โปรดลองอีกครั้ง"</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"เปลี่ยนตำแหน่งของศีรษะเล็กน้อย"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"โปรดมองตรงมาที่โทรศัพท์"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"โปรดมองตรงมาที่โทรศัพท์"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index c83daa3..8009e0c 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Nagbibigay-daan sa app na gamitin ang mga serbisyo sa foreground na may uring \"systemExempted\""</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"Magpatakbo ng serbisyo sa foreground na may uring \"fileManagement\""</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Nagbibigay-daan sa app na gamitin ang mga serbisyo sa foreground na may uring \"fileManagement\""</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"magpagana ng serbisyo sa foreground na may uring \"specialUse\""</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Nagbibigay-daan sa app na gamitin ang mga serbisyo sa foreground na may uring \"specialUse\""</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"sukatin ang espasyo ng storage ng app"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Gumamit ng lock ng screen"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Ilagay ang iyong lock ng screen para magpatuloy"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Pumindot nang madiin sa sensor"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Hindi makilala ang fingerprint. Subukan ulit."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Linisin ang sensor para sa fingerprint at subukan ulit"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Linisin ang sensor at subukan ulit"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Pumindot nang madiin sa sensor"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Hindi makita ang mukha mo. Hawakan ang telepono kapantay ng mata."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Masyadong magalaw. Hawakang mabuti ang telepono."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Paki-enroll muli ang iyong mukha."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Hindi makilala ang mukha. Subukan ulit."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Bahagyang baguhin ang posisyon ng iyong ulo"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Tumingin nang mas direkta sa iyong telepono"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Tumingin nang mas direkta sa iyong telepono"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index c2c8ec6..18f46ee 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Uygulamanın \"systemExempted\" türüyle ön plan hizmetlerini kullanmasına izin verir"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\"fileManagement\" türündeki ön plan hizmetini çalıştırma"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Uygulamanın \"fileManagement\" türündeki ön plan hizmetlerini kullanmasına izin verir."</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" türüyle ön plan hizmetini çalıştırma"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Uygulamanın \"specialUse\" türüyle ön plan hizmetlerini kullanmasına izin verir"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"uygulama depolama alanını ölç"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Ekran kilidi kullan"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Devam etmek için ekran kilidinizi girin"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Sensöre sıkıca bastırın"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Parmak izi tanınamadı. Tekrar deneyin."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Parmak izi sensörünü temizleyip tekrar deneyin"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Sensörü temizleyip tekrar deneyin"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Sensöre sıkıca bastırın"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Yüzünüz görünmüyor. Telefonunuzu göz hizasında tutun."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Çok fazla hareket ediyorsunuz. Telefonu sabit tutun."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Lütfen yüzünüzü yeniden kaydedin."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Yüz tanınamadı. Tekrar deneyin."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Başınızın konumunu hafifçe değiştirin"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Telefonunuza daha doğrudan bakın"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Telefonunuza daha doğrudan bakın"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 00139a6..88a3b2b 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -436,6 +436,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Дозволяє додатку використовувати активні сервіси типу systemExempted"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"запускати активний сервіс типу fileManagement"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Дозволяє додатку використовувати активні сервіси типу fileManagement"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"запускати сервіс типу specialUse в активному режимі"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Дозволяє додатку використовувати активні сервіси типу specialUse"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"визначати об’єм пам’яті програми"</string>
@@ -634,7 +638,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Доступ розблокуванням екрана"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Щоб продовжити, введіть дані для розблокування екрана"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Міцно притисніть палець до сканера"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Відбиток пальця не розпізнано. Повторіть спробу."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Очистьте сканер відбитків пальців і повторіть спробу"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Очистьте сканер і повторіть спробу"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Міцно притисніть палець до сканера"</string>
@@ -698,7 +703,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Обличчя не видно. Утримуйте телефон на рівні очей."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Забагато рухів. Тримайте телефон нерухомо."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Повторно проскануйте обличчя."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Обличчя не розпізнано. Повторіть спробу."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Трохи змініть положення голови"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Дивіться на телефон прямо"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Дивіться на телефон прямо"</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index dc3ca47..267351a 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"ایپ کو \"systemExempted\" کی قسم کے ساتھ پیش منظر کی سروسز کے استعمال کی اجازت دیتی ہے"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\"fileManagement\" کی قسم کے ساتھ پیش منظر کی سروس چلائیں"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"ایپ کو \"fileManagement\" کی قسم کے ساتھ پیش منظر کی سروسز کے استعمال کی اجازت دیتی ہے"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" کی قسم کے ساتھ پیش منظر کی سروس چلائیں"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"ایپ کو \"specialUse\" کی قسم کے ساتھ پیش منظر کی سروسز کے استعمال کی اجازت دیتی ہے"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"ایپ اسٹوریج کی جگہ کی پیمائش کریں"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"اسکرین لاک استعمال کریں"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"جاری رکھنے کے لیے اپنا اسکرین لاک درج کریں"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"سینسر پر اچھی طرح دبائیں"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"فنگر پرنٹ کی شناخت نہیں کی جا سکی۔ دوبارہ کوشش کریں۔"</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"فنگر پرنٹ سینسر صاف کریں اور دوبارہ کوشش کریں"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"سینسر صاف کریں اور دوبارہ کوشش کریں"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"سینسر پر اچھی طرح دبائیں"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"آپ کا چہرہ دکھائی نہیں دے رہا۔ اپنے فون کو آنکھ کی سطح پر پکڑیں۔"</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"کافی حرکت ہو رہی ہے۔ فون کو مضبوطی سے پکڑیں۔"</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"براہ کرم اپنے چہرے کو دوبارہ مندرج کریں۔"</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"چہرے کی شناخت نہیں ہو سکی۔ پھر کوشش کریں۔"</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"اپنے سر کی پوزیشن کو تھوڑا تبدیل کریں"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"اپنے فون کی طرف چہرے کو سیدھا رکھیں"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"اپنے فون کی طرف چہرے کو سیدھا رکھیں"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index 23ef54c..d739e8b 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Ilovaga “systemExempted” turidagi faol xizmatlardan foydalanishga ruxsat beradi"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"“fileManagement” turidagi faol xizmatni ishga tushirish"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Ilovaga “fileManagement” turidagi faol xizmatlardan foydalanishga ruxsat beradi"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"“specialUse” turidagi faol xizmatni ishga tushirish"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Ilovaga “specialUse” turidagi faol xizmatlardan foydalanishga ruxsat beradi"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"ilovalar egallagan xotira joyini hisoblash"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Ekran qulfi"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Ekran qulfini kiritish bilan davom eting"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Sensorni mahkam bosing"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Bu barmoq izi notanish. Qayta urining."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Barmoq izi skanerini tozalang va qayta urining"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Sensorni tozalang va qayta urining"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Sensorni mahkam bosing"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Yuz koʻrinmayapti. Telefonni koʻz darajasida tuting."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Ortiqcha harakatlanmoqda. Qimirlatmasdan ushlang."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Yuzingizni qaytadan qayd qildiring."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Yuz aniqlanmadi. Qayta urining."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Boshingiz holatini biroz oʻzgartiring"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Telefonga tik qarab turing"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Telefonga tik qarab turing"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 22d9f06..15f7f8f 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Cho phép ứng dụng dùng các dịch vụ trên nền trước thuộc loại \"systemExempted\""</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"chạy dịch vụ trên nền trước thuộc loại \"fileManagement\""</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Cho phép ứng dụng dùng các dịch vụ trên nền trước thuộc loại \"fileManagement\""</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"chạy dịch vụ trên nền trước thuộc loại \"specialUse\""</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Cho phép ứng dụng dùng các dịch vụ trên nền trước thuộc loại \"specialUse\""</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"đo dung lượng lưu trữ ứng dụng"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Dùng phương thức khóa màn hình"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Hãy nhập phương thức khóa màn hình của bạn để tiếp tục"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Ấn mạnh lên cảm biến"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Không nhận dạng được vân tay. Hãy thử lại."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Hãy vệ sinh cảm biến vân tay rồi thử lại"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Vệ sinh cảm biến rồi thử lại"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Ấn mạnh lên cảm biến"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Không thấy khuôn mặt. Hãy cầm điện thoại ngang tầm mắt."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Thiết bị di chuyển quá nhiều. Giữ yên thiết bị."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Vui lòng đăng ký lại khuôn mặt của bạn."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Không thể nhận dạng khuôn mặt. Hãy thử lại."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Nghiêng đầu của bạn một chút"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Nhìn thẳng vào điện thoại"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Nhìn thẳng vào điện thoại"</string>
diff --git a/core/res/res/values-watch/config.xml b/core/res/res/values-watch/config.xml
index af30532..31acd9a 100644
--- a/core/res/res/values-watch/config.xml
+++ b/core/res/res/values-watch/config.xml
@@ -90,4 +90,7 @@
{@link MotionEvent#AXIS_SCROLL} generated by {@link InputDevice#SOURCE_ROTARY_ENCODER}
devices. -->
<bool name="config_viewRotaryEncoderHapticScrollFedbackEnabled">true</bool>
+
+ <!-- If this is true, allow wake from theater mode from motion. -->
+ <bool name="config_allowTheaterModeWakeFromMotion">true</bool>
</resources>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index dec4705..91fdc94 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"允许该应用使用“systemExempted”类型的前台服务"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"运行“fileManagement”类型的前台服务"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"允许该应用使用“fileManagement”类型的前台服务"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"运行“specialUse”类型的前台服务"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"允许该应用使用“specialUse”类型的前台服务"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"计算应用存储空间"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"使用屏幕锁定凭据"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"输入您的屏幕锁定凭据才能继续"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"请用力按住传感器"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"无法识别指纹,请重试。"</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"请清洁指纹传感器,然后重试"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"请清洁传感器,然后重试"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"请用力按住传感器"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"看不到您的脸,请将手机举到与眼睛齐平的位置。"</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"摄像头过于晃动。请将手机拿稳。"</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"请重新注册您的面孔。"</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"无法识别人脸,请重试。"</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"请略微调整头部的位置"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"请尽量直视手机"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"请尽量直视手机"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 769c274..3fe3061 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"允許應用程式配搭「systemExempted」類型使用前景服務"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"配搭「fileManagement」類型執行前景服務"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"允許應用程式配搭「fileManagement」類型使用前景服務"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"配搭「specialUse」類型執行前景服務"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"允許應用程式配搭「specialUse」類型使用前景服務"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"測量應用程式儲存空間"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"使用螢幕鎖定"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"如要繼續操作,請輸入螢幕鎖定解鎖憑證"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"請用力按住感應器"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"無法辨識指紋,請再試一次。"</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"請清潔指紋感應器,然後再試一次"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"請清潔感應器,然後再試一次"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"請用力按住感應器"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"看不到面孔,請將手機放在視線水平。"</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"裝置不夠穩定。請拿穩手機。"</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"請重新註冊面孔。"</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"無法辨識面孔,請再試一次。"</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"請稍為轉換頭部的位置"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"正面望向手機"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"正面望向手機"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 2c8c046..4f6ef28 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"允許應用程式搭配「systemExempted」類型使用前景服務"</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"搭配「fileManagement」類型執行前景服務"</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"允許應用程式搭配「fileManagement」類型使用前景服務"</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"搭配「specialUse」類型執行前景服務"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"允許應用程式搭配「specialUse」類型使用前景服務"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"測量應用程式儲存空間"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"使用螢幕鎖定功能"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"如要繼續操作,請輸入螢幕鎖定憑證"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"請確實按住感應器"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"無法辨識指紋,請再試一次。"</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"請清潔指紋感應器,然後再試一次"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"清潔感應器,然後再試一次"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"請確實按住感應器"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"未偵測到你的臉,請將手機舉到與眼睛同高的位置。"</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"鏡頭過度晃動,請拿穩手機。"</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"請重新註冊你的臉孔。"</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"無法辨識這張臉,請再試一次。"</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"請稍微改變頭部位置"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"請盡可能直視手機"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"請盡可能直視手機"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 42f0b3ff..d5ce2ad 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -434,6 +434,10 @@
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Kuvumela i-app ukusebenzisa amasevisi aphambili ngohlobo lokuthi \"systemExempted\""</string>
<string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"qalisa isevisi ephambili ngohlobo lokuthi \"Ikholi yefoni\""</string>
<string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Kuvumela i-app ukusebenzisa amasevisi aphambili ngohlobo lwe-\"systemExempted\""</string>
+ <!-- no translation found for permlab_foregroundServiceMediaProcessing (3045295152245381864) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProcessing (8303086172106677312) -->
+ <skip />
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"qalisa isevisi ephambili ngohlobo lokuthi \"specialUse\""</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Kuvumela i-app ukusebenzisa amasevisi aphambili ngohlobo lokuthi \"specialUse\""</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"linganisa isikhala sokugcina uhlelo lokusebenza"</string>
@@ -632,7 +636,8 @@
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Sebenzisa isikhiya sesikrini"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Faka ukukhiya isikrini kwakho ukuze uqhubeke"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Cindezela inzwa uqinise"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Ayisazi isigxivizo somunwe. Zama futhi."</string>
+ <!-- no translation found for fingerprint_acquired_insufficient (2410176550915730974) -->
+ <skip />
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Hlanza inzwa yesigxivizo somunwe bese uzame futhi"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Hlanza inzwa bese uzame futhi"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Cindezela inzwa uqinise"</string>
@@ -696,7 +701,8 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Ayikwazi ukubona ubuso bakho. Bamba ifoni yakho iqondane namehlo"</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Ukunyakaza okuningi kakhulu. Bamba ifoni iqine."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Sicela uphinde ubhalise ubuso bakho."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Ayikwazi ukubona ubuso. Zama futhi."</string>
+ <!-- no translation found for face_acquired_too_different (4505278456634706967) -->
+ <skip />
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Shintsha indawo yekhanda lakho kancane"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Bheka ngqo kakhulu kufoni yakho"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Bheka ngqo kakhulu kufoni yakho"</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 5cfb1a3..2eb28eb 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4372,6 +4372,14 @@
<attr name="name" />
</declare-styleable>
+ <!-- Specify one or more <code>polling-loop-filter</code> elements inside a
+ <code>host-apdu-service</code> to indicate polling loop frames that
+ your service can handle. -->
+ <declare-styleable name="PollingLoopFilter">
+ <!-- The polling loop frame. This attribute is mandatory. -->
+ <attr name="name" />
+ </declare-styleable>
+
<!-- Use <code>host-nfcf-service</code> as the root tag of the XML resource that
describes an {@link android.nfc.cardemulation.HostNfcFService} service, which
is referenced from its {@link android.nfc.cardemulation.HostNfcFService#SERVICE_META_DATA}
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 6019524..29086a45 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -506,12 +506,6 @@
receivers, and providers; it can not be used with activities. -->
<attr name="singleUser" format="boolean" />
- <!-- If set to true, only a single instance of this component will
- run and be available for the SYSTEM user. Non SYSTEM users will not be
- allowed to access the component if this flag is enabled.
- This flag can be used with services, receivers, providers and activities. -->
- <attr name="systemUserOnly" format="boolean" />
-
<!-- Specify a specific process that the associated code is to run in.
Use with the application tag (to supply a default process for all
application components), or with the activity, receiver, service,
@@ -1819,6 +1813,12 @@
<attr name="allowUpdateOwnership" format="boolean" />
+ <!-- This attribute can be applied to any tag in the manifest. The system uses its value to
+ determine whether the element (e.g., a permission) should be enabled or disabled
+ depending on the state of feature flags.
+ @hide @SystemApi @FlaggedApi("android.content.res.manifest_flagging") -->
+ <attr name="featureFlag" format="string" />
+
<!-- The <code>manifest</code> tag is the root of an
<code>AndroidManifest.xml</code> file,
describing the contents of an Android package (.apk) file. One
@@ -2865,7 +2865,6 @@
Context.createAttributionContext() using the first attribution tag
contained here. -->
<attr name="attributionTags" />
- <attr name="systemUserOnly" format="boolean" />
</declare-styleable>
<!-- Attributes that can be supplied in an AndroidManifest.xml
@@ -3024,7 +3023,6 @@
ignored when the process is bound into a shared isolated process by a client.
-->
<attr name="allowSharedIsolatedProcess" format="boolean" />
- <attr name="systemUserOnly" format="boolean" />
</declare-styleable>
<!-- @hide The <code>apex-system-service</code> tag declares an apex system service
@@ -3152,7 +3150,7 @@
<attr name="uiOptions" />
<attr name="parentActivityName" />
<attr name="singleUser" />
- <!-- This broadcast receiver or activity will only receive broadcasts for the
+ <!-- @hide This broadcast receiver or activity will only receive broadcasts for the
system user-->
<attr name="systemUserOnly" format="boolean" />
<attr name="persistableMode" />
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 5346454..d0216b3 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -192,8 +192,13 @@
<!-- The identifier of the satellite's SIM profile. The identifier is composed of MCC and MNC
of the satellite PLMN with the format "mccmnc". -->
- <string name="config_satellite_sim_identifier" translatable="false"></string>
- <java-symbol type="string" name="config_satellite_sim_identifier" />
+ <string name="config_satellite_sim_plmn_identifier" translatable="false"></string>
+ <java-symbol type="string" name="config_satellite_sim_plmn_identifier" />
+
+ <!-- The identifier for the satellite's SIM profile. The identifier is the service provider name
+ (spn) from the profile metadata. -->
+ <string name="config_satellite_sim_spn_identifier" translatable="false"></string>
+ <java-symbol type="string" name="config_satellite_sim_spn_identifier" />
<!-- The app to which the emergency call will be handed over for OEM-enabled satellite
messaging. The format of the config string is "package_name;class_name". -->
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 7b5c49c..7d22885 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -119,8 +119,8 @@
<public name="optional"/>
<!-- @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") -->
<public name="adServiceTypes" />
- <!-- @FlaggedApi("android.multiuser.enable_system_user_only_for_services_and_providers") -->
- <public name="systemUserOnly"/>
+ <!-- @hide @SystemApi @FlaggedApi("android.content.res.manifest_flagging") -->
+ <public name="featureFlag"/>
</staging-public-group>
<staging-public-group type="id" first-id="0x01bc0000">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 542e9d6..bd3a5e4 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1075,6 +1075,12 @@
<string name="permdesc_manageOngoingCalls">Allows an app to see details about ongoing calls
on your device and to control these calls.</string>
+ <!-- Title for an application permission, listed so that the user can access the last known cell id provided by telephony. -->
+ <string name="permlab_accessLastKnownCellId">Access last known cell identity.</string>
+ <!-- Description on an application permission, listed so that the user can access the last known cell id provided by telephony -->
+ <string name="permdesc_accessLastKnownCellId">Allows an app to access to the last known
+ cell identity provided by telephony.</string>
+
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_readCellBroadcasts">read cell broadcast messages</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@@ -1825,7 +1831,7 @@
<!-- Message shown during fingerprint acquisision when the fingerprint cannot be recognized -->
<string name="fingerprint_acquired_partial">Press firmly on the sensor</string>
<!-- Message shown during fingerprint acquisision when the fingerprint cannot be recognized -->
- <string name="fingerprint_acquired_insufficient">Can\u2019t recognize fingerprint. Try again.</string>
+ <string name="fingerprint_acquired_insufficient">Fingerprint not recognized. Try again.</string>
<!-- Message shown during fingerprint acquisision when the fingerprint sensor needs cleaning -->
<string name="fingerprint_acquired_imager_dirty">Clean fingerprint sensor and try again</string>
<string name="fingerprint_acquired_imager_dirty_alt">Clean sensor and try again</string>
@@ -1959,7 +1965,7 @@
<!-- Message shown during face acquisition when the sensor needs to be recalibrated [CHAR LIMIT=50] -->
<string name="face_acquired_recalibrate">Please re-enroll your face.</string>
<!-- Message shown during face enrollment when a different person's face is detected [CHAR LIMIT=50] -->
- <string name="face_acquired_too_different">Can\u2019t recognize face. Try again.</string>
+ <string name="face_acquired_too_different">Face not recognized. Try again.</string>
<!-- Message shown during face enrollment when the face is too similar to a previous acquisition [CHAR LIMIT=50] -->
<string name="face_acquired_too_similar">Change the position of your head slightly</string>
<!-- Message shown during acqusition when the user's face is turned too far left or right [CHAR LIMIT=50] -->
diff --git a/core/tests/BroadcastRadioTests/TEST_MAPPING b/core/tests/BroadcastRadioTests/TEST_MAPPING
new file mode 100644
index 0000000..b085a27
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "postsubmit": [
+ {
+ "name": "BroadcastRadioTests"
+ }
+ ]
+}
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramSelectorTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramSelectorTest.java
index b1cf9c2..bb69fa4 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramSelectorTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramSelectorTest.java
@@ -124,6 +124,17 @@
}
@Test
+ public void construct_withNullInSecondaryIdsForSelector() {
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+ new ProgramSelector(DAB_PROGRAM_TYPE, DAB_DMB_SID_EXT_IDENTIFIER_1,
+ new ProgramSelector.Identifier[]{null}, /* vendorIds= */ null);
+ });
+
+ assertWithMessage("Exception for null secondary id")
+ .that(thrown).hasMessageThat().contains("secondaryIds list must not contain nulls");
+ }
+
+ @Test
public void getProgramType() {
ProgramSelector selector = getFmSelector(/* secondaryIds= */ null, /* vendorIds= */ null);
@@ -269,11 +280,11 @@
ProgramSelector selector = ProgramSelector.createAmFmSelector(band, (int) AM_FREQUENCY);
- assertWithMessage("Program type")
+ assertWithMessage("AM program type without subchannel")
.that(selector.getProgramType()).isEqualTo(ProgramSelector.PROGRAM_TYPE_AM);
- assertWithMessage("Primary identifiers")
+ assertWithMessage("AM primary identifiers without subchannel")
.that(selector.getPrimaryId()).isEqualTo(primaryIdExpected);
- assertWithMessage("Secondary identifiers")
+ assertWithMessage("AM secondary identifiers without subchannel")
.that(selector.getSecondaryIds()).isEmpty();
}
@@ -285,15 +296,29 @@
ProgramSelector selector = ProgramSelector.createAmFmSelector(
RadioManager.BAND_INVALID, (int) FM_FREQUENCY);
- assertWithMessage("Program type")
+ assertWithMessage("FM program type without band and subchannel")
.that(selector.getProgramType()).isEqualTo(ProgramSelector.PROGRAM_TYPE_FM);
- assertWithMessage("Primary identifiers")
+ assertWithMessage("FM primary identifiers without band and subchannel")
.that(selector.getPrimaryId()).isEqualTo(primaryIdExpected);
- assertWithMessage("Secondary identifiers")
+ assertWithMessage("FM secondary identifiers without band and subchannel")
.that(selector.getSecondaryIds()).isEmpty();
}
@Test
+ public void createAmFmSelector_withValidFrequency() {
+ ProgramSelector.Identifier primaryIdExpected = new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, AM_FREQUENCY);
+
+ ProgramSelector selector = ProgramSelector.createAmFmSelector(RadioManager.BAND_INVALID,
+ (int) AM_FREQUENCY);
+
+ assertWithMessage("AM program type with valid frequency")
+ .that(selector.getProgramType()).isEqualTo(ProgramSelector.PROGRAM_TYPE_AM);
+ assertWithMessage("AM primary identifiers with valid frequency")
+ .that(selector.getPrimaryId()).isEqualTo(primaryIdExpected);
+ }
+
+ @Test
public void createAmFmSelector_withValidFrequencyAndSubChannel() {
int band = RadioManager.BAND_AM_HD;
int subChannel = 2;
@@ -307,15 +332,26 @@
ProgramSelector selector = ProgramSelector.createAmFmSelector(band, (int) AM_FREQUENCY,
subChannel);
- assertWithMessage("Program type")
+ assertWithMessage("AM program type with valid frequency and subchannel")
.that(selector.getProgramType()).isEqualTo(ProgramSelector.PROGRAM_TYPE_AM);
- assertWithMessage("Primary identifiers")
+ assertWithMessage("AM primary identifiers with valid frequency and subchannel")
.that(selector.getPrimaryId()).isEqualTo(primaryIdExpected);
- assertWithMessage("Secondary identifiers")
+ assertWithMessage("AM secondary identifiers with valid frequency and subchannel")
.that(selector.getSecondaryIds()).isEqualTo(secondaryIdExpected);
}
@Test
+ public void createAmFmSelector_withInvalidBand_throwsIllegalArgumentException() {
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+ ProgramSelector.createAmFmSelector(/* band= */ 1000, (int) AM_FREQUENCY);
+ });
+
+ assertWithMessage("Exception for using invalid band")
+ .that(thrown).hasMessageThat().contains(
+ "Unknown band");
+ }
+
+ @Test
public void createAmFmSelector_withInvalidFrequency_throwsIllegalArgumentException() {
int invalidFrequency = 50000;
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java
index 89464d1..bbac69f 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java
@@ -169,6 +169,16 @@
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Test
+ public void constructor_withUnsupportedTypeForBandDescriptor_throwsException() {
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
+ () -> new RadioManager.AmBandDescriptor(REGION, /* type= */ 100, AM_LOWER_LIMIT,
+ AM_UPPER_LIMIT, AM_SPACING, STEREO_SUPPORTED));
+
+ assertWithMessage("Unsupported band type exception")
+ .that(thrown).hasMessageThat().contains("Unsupported band");
+ }
+
+ @Test
public void getType_forBandDescriptor() {
RadioManager.BandDescriptor bandDescriptor = createAmBandDescriptor();
@@ -363,6 +373,18 @@
}
@Test
+ public void equals_withAmBandDescriptorsAndOtherTypeObject() {
+ assertWithMessage("AM Band Descriptor")
+ .that(AM_BAND_DESCRIPTOR).isNotEqualTo(FM_BAND_DESCRIPTOR);
+ }
+
+ @Test
+ public void equals_withFmBandDescriptorsAndOtherTypeObject() {
+ assertWithMessage("FM Band Descriptor")
+ .that(FM_BAND_DESCRIPTOR).isNotEqualTo(AM_BAND_DESCRIPTOR);
+ }
+
+ @Test
public void equals_withAmBandDescriptorsOfDifferentUpperLimits_returnsFalse() {
RadioManager.AmBandDescriptor amBandDescriptorCompared =
new RadioManager.AmBandDescriptor(REGION, RadioManager.BAND_AM, AM_LOWER_LIMIT,
@@ -373,9 +395,83 @@
}
@Test
- public void equals_withAmAndFmBandDescriptors_returnsFalse() {
- assertWithMessage("AM Band Descriptor")
- .that(AM_BAND_DESCRIPTOR).isNotEqualTo(FM_BAND_DESCRIPTOR);
+ public void equals_withAmBandDescriptorsOfDifferentStereoSupportValues() {
+ RadioManager.AmBandDescriptor amBandDescriptorCompared =
+ new RadioManager.AmBandDescriptor(REGION, RadioManager.BAND_AM, AM_LOWER_LIMIT,
+ AM_UPPER_LIMIT, AM_SPACING, !STEREO_SUPPORTED);
+
+ assertWithMessage("AM Band Descriptor of different stereo support values")
+ .that(AM_BAND_DESCRIPTOR).isNotEqualTo(amBandDescriptorCompared);
+ }
+
+ @Test
+ public void equals_withFmBandDescriptorsOfDifferentSpacingValues() {
+ RadioManager.FmBandDescriptor fmBandDescriptorCompared = new RadioManager.FmBandDescriptor(
+ REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT, FM_UPPER_LIMIT, FM_SPACING * 2,
+ STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED, AF_SUPPORTED, EA_SUPPORTED);
+
+ assertWithMessage("FM Band Descriptors of different support limit values")
+ .that(FM_BAND_DESCRIPTOR).isNotEqualTo(fmBandDescriptorCompared);
+ }
+
+ @Test
+ public void equals_withFmBandConfigsOfDifferentLowerLimitValues() {
+ RadioManager.FmBandDescriptor fmBandDescriptorCompared = new RadioManager.FmBandDescriptor(
+ REGION + 1, RadioManager.BAND_AM_HD, AM_LOWER_LIMIT, AM_UPPER_LIMIT, AM_SPACING,
+ STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED, AF_SUPPORTED, EA_SUPPORTED);
+
+ assertWithMessage("FM Band Descriptors of different region values")
+ .that(FM_BAND_DESCRIPTOR).isNotEqualTo(fmBandDescriptorCompared);
+ }
+
+ @Test
+ public void equals_withFmBandDescriptorsOfDifferentStereoSupportValues() {
+ RadioManager.FmBandDescriptor fmBandDescriptorCompared = new RadioManager.FmBandDescriptor(
+ REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT, FM_UPPER_LIMIT, FM_SPACING,
+ !STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED, AF_SUPPORTED, EA_SUPPORTED);
+
+ assertWithMessage("FM Band Descriptors of different stereo support values")
+ .that(fmBandDescriptorCompared).isNotEqualTo(FM_BAND_DESCRIPTOR);
+ }
+
+ @Test
+ public void equals_withFmBandDescriptorsOfDifferentRdsSupportValues() {
+ RadioManager.FmBandDescriptor fmBandDescriptorCompared = new RadioManager.FmBandDescriptor(
+ REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT, FM_UPPER_LIMIT, FM_SPACING,
+ STEREO_SUPPORTED, !RDS_SUPPORTED, TA_SUPPORTED, AF_SUPPORTED, EA_SUPPORTED);
+
+ assertWithMessage("FM Band Descriptors of different rds support values")
+ .that(fmBandDescriptorCompared).isNotEqualTo(FM_BAND_DESCRIPTOR);
+ }
+
+ @Test
+ public void equals_withFmBandDescriptorsOfDifferentTaSupportValues() {
+ RadioManager.FmBandDescriptor fmBandDescriptorCompared = new RadioManager.FmBandDescriptor(
+ REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT, FM_UPPER_LIMIT, FM_SPACING,
+ STEREO_SUPPORTED, RDS_SUPPORTED, !TA_SUPPORTED, AF_SUPPORTED, EA_SUPPORTED);
+
+ assertWithMessage("FM Band Descriptors of different ta support values")
+ .that(fmBandDescriptorCompared).isNotEqualTo(FM_BAND_DESCRIPTOR);
+ }
+
+ @Test
+ public void equals_withFmBandDescriptorsOfDifferentAfSupportValues() {
+ RadioManager.FmBandDescriptor fmBandDescriptorCompared = new RadioManager.FmBandDescriptor(
+ REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT, FM_UPPER_LIMIT, FM_SPACING,
+ STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED, !AF_SUPPORTED, EA_SUPPORTED);
+
+ assertWithMessage("FM Band Descriptors of different af support values")
+ .that(fmBandDescriptorCompared).isNotEqualTo(FM_BAND_DESCRIPTOR);
+ }
+
+ @Test
+ public void equals_withFmBandDescriptorsOfDifferentEaSupportValues() {
+ RadioManager.FmBandDescriptor fmBandDescriptorCompared = new RadioManager.FmBandDescriptor(
+ REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT, FM_UPPER_LIMIT, FM_SPACING,
+ STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED, AF_SUPPORTED, !EA_SUPPORTED);
+
+ assertWithMessage("FM Band Descriptors of different ea support values")
+ .that(fmBandDescriptorCompared).isNotEqualTo(FM_BAND_DESCRIPTOR);
}
@Test
@@ -587,18 +683,79 @@
}
@Test
- public void equals_withFmBandConfigsOfDifferentAfs_returnsFalse() {
- RadioManager.FmBandConfig.Builder builder = new RadioManager.FmBandConfig.Builder(
- createFmBandDescriptor()).setStereo(STEREO_SUPPORTED).setRds(RDS_SUPPORTED)
- .setTa(TA_SUPPORTED).setAf(!AF_SUPPORTED).setEa(EA_SUPPORTED);
- RadioManager.FmBandConfig fmBandConfigFromBuilder = builder.build();
+ public void equals_withFmBandConfigsOfDifferentRegionValues() {
+ RadioManager.FmBandConfig fmBandConfigCompared = new RadioManager.FmBandConfig(
+ new RadioManager.FmBandDescriptor(REGION + 1, RadioManager.BAND_AM_HD,
+ AM_LOWER_LIMIT, AM_UPPER_LIMIT, AM_SPACING, STEREO_SUPPORTED, RDS_SUPPORTED,
+ TA_SUPPORTED, AF_SUPPORTED, EA_SUPPORTED));
- assertWithMessage("FM Band Config of different af value")
- .that(FM_BAND_CONFIG).isNotEqualTo(fmBandConfigFromBuilder);
+ assertWithMessage("FM Band Config of different regions")
+ .that(FM_BAND_CONFIG).isNotEqualTo(fmBandConfigCompared);
}
@Test
- public void equals_withFmAndAmBandConfigs_returnsFalse() {
+ public void equals_withFmBandConfigsOfDifferentStereoSupportValues() {
+ RadioManager.FmBandConfig fmBandConfigCompared = new RadioManager.FmBandConfig(
+ new RadioManager.FmBandDescriptor(REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT,
+ FM_UPPER_LIMIT, FM_SPACING, !STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED,
+ AF_SUPPORTED, EA_SUPPORTED));
+
+ assertWithMessage("FM Band Config with different stereo support values")
+ .that(fmBandConfigCompared).isNotEqualTo(FM_BAND_CONFIG);
+ }
+
+ @Test
+ public void equals_withFmBandConfigsOfDifferentRdsSupportValues() {
+ RadioManager.FmBandConfig fmBandConfigCompared = new RadioManager.FmBandConfig(
+ new RadioManager.FmBandDescriptor(REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT,
+ FM_UPPER_LIMIT, FM_SPACING, STEREO_SUPPORTED, !RDS_SUPPORTED, TA_SUPPORTED,
+ AF_SUPPORTED, EA_SUPPORTED));
+
+ assertWithMessage("FM Band Config with different RDS support values")
+ .that(fmBandConfigCompared).isNotEqualTo(FM_BAND_CONFIG);
+ }
+
+ @Test
+ public void equals_withFmBandConfigsOfDifferentTaSupportValues() {
+ RadioManager.FmBandConfig fmBandConfigCompared = new RadioManager.FmBandConfig(
+ new RadioManager.FmBandDescriptor(REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT,
+ FM_UPPER_LIMIT, FM_SPACING, STEREO_SUPPORTED, RDS_SUPPORTED, !TA_SUPPORTED,
+ AF_SUPPORTED, EA_SUPPORTED));
+
+ assertWithMessage("FM Band Configs with different ta values")
+ .that(fmBandConfigCompared).isNotEqualTo(FM_BAND_CONFIG);
+ }
+
+ @Test
+ public void equals_withFmBandConfigsOfDifferentAfSupportValues() {
+ RadioManager.FmBandConfig fmBandConfigCompared = new RadioManager.FmBandConfig(
+ new RadioManager.FmBandDescriptor(REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT,
+ FM_UPPER_LIMIT, FM_SPACING, STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED,
+ !AF_SUPPORTED, EA_SUPPORTED));
+
+ assertWithMessage("FM Band Config of different af support value")
+ .that(FM_BAND_CONFIG).isNotEqualTo(fmBandConfigCompared);
+ }
+
+ @Test
+ public void equals_withFmBandConfigsOfDifferentEaSupportValues() {
+ RadioManager.FmBandConfig fmBandConfigCompared = new RadioManager.FmBandConfig(
+ new RadioManager.FmBandDescriptor(REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT,
+ FM_UPPER_LIMIT, FM_SPACING, STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED,
+ AF_SUPPORTED, !EA_SUPPORTED));
+
+ assertWithMessage("FM Band Configs with different ea support values")
+ .that(fmBandConfigCompared).isNotEqualTo(FM_BAND_CONFIG);
+ }
+
+ @Test
+ public void equals_withAmBandConfigsAndOtherTypeObject() {
+ assertWithMessage("AM Band Config")
+ .that(AM_BAND_CONFIG).isNotEqualTo(FM_BAND_CONFIG);
+ }
+
+ @Test
+ public void equals_withFmBandConfigsAndOtherTypeObject() {
assertWithMessage("FM Band Config")
.that(FM_BAND_CONFIG).isNotEqualTo(AM_BAND_CONFIG);
}
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java
index 7b9121e..fddfd39 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java
@@ -16,14 +16,20 @@
package android.hardware.radio;
-import static com.google.common.truth.Truth.assertWithMessage;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.mock;
import android.graphics.Bitmap;
import android.os.Parcel;
import android.platform.test.flag.junit.SetFlagsRule;
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+import com.android.server.broadcastradio.ExtendedRadioMockitoTestCase;
+
+import com.google.common.truth.Expect;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -34,7 +40,7 @@
import java.util.Set;
@RunWith(MockitoJUnitRunner.class)
-public final class RadioMetadataTest {
+public final class RadioMetadataTest extends ExtendedRadioMockitoTestCase {
private static final int CREATOR_ARRAY_SIZE = 3;
private static final int INT_KEY_VALUE = 1;
@@ -49,14 +55,21 @@
private Bitmap mBitmapValue;
@Rule
+ public final Expect mExpect = Expect.create();
+ @Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Override
+ protected void initializeSession(StaticMockitoSessionBuilder builder) {
+ builder.spyStatic(Bitmap.class);
+ }
+
@Test
public void describeContents_forClock() {
RadioMetadata.Clock clock = new RadioMetadata.Clock(TEST_UTC_SECOND_SINCE_EPOCH,
TEST_TIME_ZONE_OFFSET_MINUTES);
- assertWithMessage("Describe contents for metadata clock")
+ mExpect.withMessage("Describe contents for metadata clock")
.that(clock.describeContents()).isEqualTo(0);
}
@@ -64,7 +77,7 @@
public void newArray_forClockCreator() {
RadioMetadata.Clock[] clocks = RadioMetadata.Clock.CREATOR.newArray(CREATOR_ARRAY_SIZE);
- assertWithMessage("Clock array size").that(clocks.length).isEqualTo(CREATOR_ARRAY_SIZE);
+ mExpect.withMessage("Clock array size").that(clocks.length).isEqualTo(CREATOR_ARRAY_SIZE);
}
@Test
@@ -77,9 +90,9 @@
parcel.setDataPosition(0);
RadioMetadata.Clock clockFromParcel = RadioMetadata.Clock.CREATOR.createFromParcel(parcel);
- assertWithMessage("UTC second since epoch of metadata clock created from parcel")
+ mExpect.withMessage("UTC second since epoch of metadata clock created from parcel")
.that(clockFromParcel.getUtcEpochSeconds()).isEqualTo(TEST_UTC_SECOND_SINCE_EPOCH);
- assertWithMessage("Time zone offset minutes of metadata clock created from parcel")
+ mExpect.withMessage("Time zone offset minutes of metadata clock created from parcel")
.that(clockFromParcel.getTimezoneOffsetMinutes())
.isEqualTo(TEST_TIME_ZONE_OFFSET_MINUTES);
}
@@ -92,7 +105,7 @@
mBuilder.putString(invalidStringKey, "value");
});
- assertWithMessage("Exception for putting illegal string-value key %s", invalidStringKey)
+ mExpect.withMessage("Exception for putting illegal string-value key %s", invalidStringKey)
.that(thrown).hasMessageThat()
.matches(".*" + invalidStringKey + ".*cannot.*String.*?");
}
@@ -105,12 +118,25 @@
mBuilder.putInt(invalidIntKey, INT_KEY_VALUE);
});
- assertWithMessage("Exception for putting illegal int-value for key %s", invalidIntKey)
+ mExpect.withMessage("Exception for putting illegal int-value for key %s", invalidIntKey)
.that(thrown).hasMessageThat()
.matches(".*" + invalidIntKey + ".*cannot.*int.*?");
}
@Test
+ public void putBitmap_withIllegalKey() {
+ String invalidIntKey = RadioMetadata.METADATA_KEY_GENRE;
+
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+ mBuilder.putBitmap(invalidIntKey, mBitmapValue);
+ });
+
+ mExpect.withMessage("Exception for putting illegal bitmap-value for key %s", invalidIntKey)
+ .that(thrown).hasMessageThat()
+ .matches(".*" + invalidIntKey + ".*cannot.*Bitmap.*?");
+ }
+
+ @Test
public void putClock_withIllegalKey() {
String invalidClockKey = RadioMetadata.METADATA_KEY_ALBUM;
@@ -119,7 +145,7 @@
/* timezoneOffsetMinutes= */ 1);
});
- assertWithMessage("Exception for putting illegal clock-value key %s", invalidClockKey)
+ mExpect.withMessage("Exception for putting illegal clock-value key %s", invalidClockKey)
.that(thrown).hasMessageThat()
.matches(".*" + invalidClockKey + ".*cannot.*Clock.*?");
}
@@ -133,7 +159,7 @@
mBuilder.putStringArray(invalidStringArrayKey, UFIDS_VALUE);
});
- assertWithMessage("Exception for putting illegal string-array-value for key %s",
+ mExpect.withMessage("Exception for putting illegal string-array-value for key %s",
invalidStringArrayKey).that(thrown).hasMessageThat()
.matches(".*" + invalidStringArrayKey + ".*cannot.*Array.*?");
}
@@ -146,7 +172,7 @@
mBuilder.putStringArray(/* key= */ null, UFIDS_VALUE);
});
- assertWithMessage("Exception for putting string-array with null key")
+ mExpect.withMessage("Exception for putting string-array with null key")
.that(thrown).hasMessageThat().contains("can not be null");
}
@@ -158,7 +184,7 @@
mBuilder.putStringArray(RadioMetadata.METADATA_KEY_UFIDS, /* value= */ null);
});
- assertWithMessage("Exception for putting null string-array")
+ mExpect.withMessage("Exception for putting null string-array")
.that(thrown).hasMessageThat().contains("can not be null");
}
@@ -167,7 +193,7 @@
String key = RadioMetadata.METADATA_KEY_RDS_PI;
RadioMetadata metadata = mBuilder.putInt(key, INT_KEY_VALUE).build();
- assertWithMessage("Whether metadata contains %s in metadata", key)
+ mExpect.withMessage("Whether metadata contains %s in metadata", key)
.that(metadata.containsKey(key)).isTrue();
}
@@ -176,7 +202,7 @@
String key = RadioMetadata.METADATA_KEY_RDS_PI;
RadioMetadata metadata = mBuilder.putInt(key, INT_KEY_VALUE).build();
- assertWithMessage("Whether metadata contains key %s not in metadata", key)
+ mExpect.withMessage("Whether metadata contains key %s not in metadata", key)
.that(metadata.containsKey(RadioMetadata.METADATA_KEY_ARTIST)).isFalse();
}
@@ -185,7 +211,7 @@
String key = RadioMetadata.METADATA_KEY_RDS_PI;
RadioMetadata metadata = mBuilder.putInt(key, INT_KEY_VALUE).build();
- assertWithMessage("Int value for key %s in metadata", key)
+ mExpect.withMessage("Int value for key %s in metadata", key)
.that(metadata.getInt(key)).isEqualTo(INT_KEY_VALUE);
}
@@ -195,7 +221,7 @@
RadioMetadata metadata =
mBuilder.putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE).build();
- assertWithMessage("Int value for key %s in metadata", key)
+ mExpect.withMessage("Int value for key %s in metadata", key)
.that(metadata.getInt(key)).isEqualTo(0);
}
@@ -204,7 +230,7 @@
String key = RadioMetadata.METADATA_KEY_ARTIST;
RadioMetadata metadata = mBuilder.putString(key, ARTIST_KEY_VALUE).build();
- assertWithMessage("String value for key %s in metadata", key)
+ mExpect.withMessage("String value for key %s in metadata", key)
.that(metadata.getString(key)).isEqualTo(ARTIST_KEY_VALUE);
}
@@ -213,7 +239,7 @@
String key = RadioMetadata.METADATA_KEY_ARTIST;
RadioMetadata metadata = mBuilder.build();
- assertWithMessage("String value for key %s not in metadata", key)
+ mExpect.withMessage("String value for key %s not in metadata", key)
.that(metadata.getString(key)).isNull();
}
@@ -222,7 +248,7 @@
String key = RadioMetadata.METADATA_KEY_ICON;
RadioMetadata metadata = mBuilder.putBitmap(key, mBitmapValue).build();
- assertWithMessage("Bitmap value for key %s in metadata", key)
+ mExpect.withMessage("Bitmap value for key %s in metadata", key)
.that(metadata.getBitmap(key)).isEqualTo(mBitmapValue);
}
@@ -231,16 +257,26 @@
String key = RadioMetadata.METADATA_KEY_ICON;
RadioMetadata metadata = mBuilder.build();
- assertWithMessage("Bitmap value for key %s not in metadata", key)
+ mExpect.withMessage("Bitmap value for key %s not in metadata", key)
.that(metadata.getBitmap(key)).isNull();
}
@Test
+ public void getBitmap_withIllegalKey() {
+ String illegalKey = RadioMetadata.METADATA_KEY_ARTIST;
+ RadioMetadata metadata = mBuilder.putInt(RadioMetadata.METADATA_KEY_ICON, INT_KEY_VALUE)
+ .build();
+
+ mExpect.withMessage("Bitmap id value with non-bitmap-type key %s", illegalKey)
+ .that(metadata.getBitmap(illegalKey)).isNull();
+ }
+
+ @Test
public void getBitmapId_withKeyInMetadata() {
String key = RadioMetadata.METADATA_KEY_ART;
RadioMetadata metadata = mBuilder.putInt(key, INT_KEY_VALUE).build();
- assertWithMessage("Bitmap id value for key %s in metadata", key)
+ mExpect.withMessage("Bitmap id value for key %s in metadata", key)
.that(metadata.getBitmapId(key)).isEqualTo(INT_KEY_VALUE);
}
@@ -249,11 +285,21 @@
String key = RadioMetadata.METADATA_KEY_ART;
RadioMetadata metadata = mBuilder.build();
- assertWithMessage("Bitmap id value for key %s not in metadata", key)
+ mExpect.withMessage("Bitmap id value for key %s not in metadata", key)
.that(metadata.getBitmapId(key)).isEqualTo(0);
}
@Test
+ public void getBitmapId_withIllegalKey() {
+ String illegalKey = RadioMetadata.METADATA_KEY_ARTIST;
+ RadioMetadata metadata = mBuilder.putInt(RadioMetadata.METADATA_KEY_ART, INT_KEY_VALUE)
+ .build();
+
+ mExpect.withMessage("Bitmap id value with non-bitmap-id-type key %s", illegalKey)
+ .that(metadata.getBitmapId(illegalKey)).isEqualTo(0);
+ }
+
+ @Test
public void getClock_withKeyInMetadata() {
String key = RadioMetadata.METADATA_KEY_CLOCK;
RadioMetadata metadata = mBuilder
@@ -262,10 +308,10 @@
RadioMetadata.Clock clockExpected = metadata.getClock(key);
- assertWithMessage("Number of seconds since epoch of value for key %s in metadata", key)
+ mExpect.withMessage("Number of seconds since epoch of value for key %s in metadata", key)
.that(clockExpected.getUtcEpochSeconds())
.isEqualTo(TEST_UTC_SECOND_SINCE_EPOCH);
- assertWithMessage("Offset of timezone in minutes of value for key %s in metadata", key)
+ mExpect.withMessage("Offset of timezone in minutes of value for key %s in metadata", key)
.that(clockExpected.getTimezoneOffsetMinutes())
.isEqualTo(TEST_TIME_ZONE_OFFSET_MINUTES);
}
@@ -275,17 +321,27 @@
String key = RadioMetadata.METADATA_KEY_CLOCK;
RadioMetadata metadata = mBuilder.build();
- assertWithMessage("Clock value for key %s not in metadata", key)
+ mExpect.withMessage("Clock value for key %s not in metadata", key)
.that(metadata.getClock(key)).isNull();
}
@Test
+ public void getClock_withIllegalKey() {
+ String illegalKey = RadioMetadata.METADATA_KEY_ARTIST;
+ RadioMetadata metadata = mBuilder.putClock(RadioMetadata.METADATA_KEY_CLOCK,
+ TEST_UTC_SECOND_SINCE_EPOCH, TEST_TIME_ZONE_OFFSET_MINUTES).build();
+
+ mExpect.withMessage("Clock value for non-clock-type key %s", illegalKey)
+ .that(metadata.getClock(illegalKey)).isNull();
+ }
+
+ @Test
public void getStringArray_withKeyInMetadata() {
mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
String key = RadioMetadata.METADATA_KEY_UFIDS;
RadioMetadata metadata = mBuilder.putStringArray(key, UFIDS_VALUE).build();
- assertWithMessage("String-array value for key %s not in metadata", key)
+ mExpect.withMessage("String-array value for key %s not in metadata", key)
.that(metadata.getStringArray(key)).asList().isEqualTo(Arrays.asList(UFIDS_VALUE));
}
@@ -299,7 +355,7 @@
metadata.getStringArray(key);
});
- assertWithMessage("Exception for getting string array for string-array value for key %s "
+ mExpect.withMessage("Exception for getting string array for string-array value for key %s "
+ "not in metadata", key).that(thrown).hasMessageThat().contains("not found");
}
@@ -312,7 +368,7 @@
metadata.getStringArray(/* key= */ null);
});
- assertWithMessage("Exception for getting string array with null key")
+ mExpect.withMessage("Exception for getting string array with null key")
.that(thrown).hasMessageThat().contains("can not be null");
}
@@ -326,7 +382,7 @@
metadata.getStringArray(invalidClockKey);
});
- assertWithMessage("Exception for getting string array for key %s not of string-array type",
+ mExpect.withMessage("Exception for getting string array for non-string-array type key %s",
invalidClockKey).that(thrown).hasMessageThat()
.contains("string array");
}
@@ -338,7 +394,7 @@
.putString(RadioMetadata.METADATA_KEY_ARTIST, ARTIST_KEY_VALUE)
.build();
- assertWithMessage("Size of fields in non-empty metadata")
+ mExpect.withMessage("Size of fields in non-empty metadata")
.that(metadata.size()).isEqualTo(2);
}
@@ -346,7 +402,7 @@
public void size_withEmptyMetadata() {
RadioMetadata metadata = mBuilder.build();
- assertWithMessage("Size of fields in empty metadata")
+ mExpect.withMessage("Size of fields in empty metadata")
.that(metadata.size()).isEqualTo(0);
}
@@ -360,7 +416,7 @@
Set<String> metadataSet = metadata.keySet();
- assertWithMessage("Metadata set of non-empty metadata")
+ mExpect.withMessage("Metadata set of non-empty metadata")
.that(metadataSet).containsExactly(RadioMetadata.METADATA_KEY_ICON,
RadioMetadata.METADATA_KEY_RDS_PI, RadioMetadata.METADATA_KEY_ARTIST);
}
@@ -371,7 +427,7 @@
Set<String> metadataSet = metadata.keySet();
- assertWithMessage("Metadata set of empty metadata")
+ mExpect.withMessage("Metadata set of empty metadata")
.that(metadataSet).isEmpty();
}
@@ -380,7 +436,7 @@
int nativeKey = 0;
String key = RadioMetadata.getKeyFromNativeKey(nativeKey);
- assertWithMessage("Key for native key %s", nativeKey)
+ mExpect.withMessage("Key for native key %s", nativeKey)
.that(key).isEqualTo(RadioMetadata.METADATA_KEY_RDS_PI);
}
@@ -393,7 +449,7 @@
RadioMetadata.Builder copyBuilder = new RadioMetadata.Builder(metadata);
RadioMetadata metadataCopied = copyBuilder.build();
- assertWithMessage("Metadata with the same contents")
+ mExpect.withMessage("Metadata with the same contents")
.that(metadataCopied).isEqualTo(metadata);
}
@@ -401,14 +457,15 @@
public void describeContents_forMetadata() {
RadioMetadata metadata = mBuilder.build();
- assertWithMessage("Metadata contents").that(metadata.describeContents()).isEqualTo(0);
+ mExpect.withMessage("Metadata contents").that(metadata.describeContents()).isEqualTo(0);
}
@Test
public void newArray_forRadioMetadataCreator() {
RadioMetadata[] metadataArray = RadioMetadata.CREATOR.newArray(CREATOR_ARRAY_SIZE);
- assertWithMessage("Radio metadata array").that(metadataArray).hasLength(CREATOR_ARRAY_SIZE);
+ mExpect.withMessage("Radio metadata array").that(metadataArray)
+ .hasLength(CREATOR_ARRAY_SIZE);
}
@Test
@@ -423,7 +480,7 @@
parcel.setDataPosition(0);
RadioMetadata metadataFromParcel = RadioMetadata.CREATOR.createFromParcel(parcel);
- assertWithMessage("Radio metadata created from parcel")
+ mExpect.withMessage("Radio metadata created from parcel")
.that(metadataFromParcel).isEqualTo(metadataExpected);
}
@@ -441,7 +498,43 @@
parcel.setDataPosition(0);
RadioMetadata metadataFromParcel = RadioMetadata.CREATOR.createFromParcel(parcel);
- assertWithMessage("Radio metadata created from parcel with string array type metadata")
+ mExpect.withMessage("Radio metadata created from parcel with string array type metadata")
.that(metadataFromParcel).isEqualTo(metadataExpected);
}
+
+ @Test
+ public void build_withRadioMetadataMeetingSizeRequirement() {
+ int maxBitmapSize = 10;
+ doReturn(maxBitmapSize - 1).when(mBitmapValue).getHeight();
+ doReturn(maxBitmapSize - 1).when(mBitmapValue).getWidth();
+ RadioMetadata metadataSource = mBuilder.putBitmap(
+ RadioMetadata.METADATA_KEY_ICON, mBitmapValue).build();
+
+ RadioMetadata metadata = new RadioMetadata.Builder(metadataSource, maxBitmapSize).build();
+
+ mExpect.withMessage("Bitmap without resizing")
+ .that(metadata.getBitmap(RadioMetadata.METADATA_KEY_ICON)).isEqualTo(mBitmapValue);
+ }
+
+ @Test
+ public void build_withRadioMetadataAboveSizeRequirement() {
+ int maxBitmapSize = 10;
+ int bitmapHeight = 10;
+ int bitmapWidth = 20;
+ int bitmapWidthResized = maxBitmapSize;
+ int bitmapHeightResized = (bitmapHeight * bitmapWidthResized) / bitmapWidth;
+ Bitmap bitmapResized = mock(Bitmap.class);
+ doReturn(bitmapHeight).when(mBitmapValue).getHeight();
+ doReturn(bitmapWidth).when(mBitmapValue).getWidth();
+ doReturn(bitmapResized).when(() -> Bitmap.createScaledBitmap(
+ mBitmapValue, bitmapWidthResized, bitmapHeightResized, /* filter= */ true));
+ RadioMetadata metadataSource = mBuilder.putBitmap(
+ RadioMetadata.METADATA_KEY_ICON, mBitmapValue).build();
+
+ RadioMetadata metadata = new RadioMetadata.Builder(metadataSource, maxBitmapSize).build();
+
+ mExpect.withMessage("Bitmap with resizing")
+ .that(metadata.getBitmap(RadioMetadata.METADATA_KEY_ICON))
+ .isEqualTo(bitmapResized);
+ }
}
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/UniqueProgramIdentifierTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/UniqueProgramIdentifierTest.java
index b36367b..e68a0b8 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/UniqueProgramIdentifierTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/UniqueProgramIdentifierTest.java
@@ -16,6 +16,8 @@
package android.hardware.radio;
+import static org.junit.Assert.assertThrows;
+
import android.annotation.Nullable;
import android.os.Parcel;
@@ -45,6 +47,24 @@
public final Expect expect = Expect.create();
@Test
+ public void requireCriticalSecondaryIds_forDab() {
+ expect.withMessage("Critical secondary Id required for DAB")
+ .that(UniqueProgramIdentifier.requireCriticalSecondaryIds(
+ ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT)).isTrue();
+ }
+
+ @Test
+ public void constructor_withNullSelector() {
+ ProgramSelector nullSelector = null;
+
+ NullPointerException thrown = assertThrows(NullPointerException.class,
+ () -> new UniqueProgramIdentifier(nullSelector));
+
+ expect.withMessage("Null pointer exception for unique program identifier")
+ .that(thrown).hasMessageThat().contains("can not be null");
+ }
+
+ @Test
public void getPrimaryId_forUniqueProgramIdentifier() {
ProgramSelector dabSelector = getDabSelector(new ProgramSelector.Identifier[]{
DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER}, /* vendorIds= */ null);
@@ -67,6 +87,27 @@
}
@Test
+ public void getCriticalSecondaryIds_forDabUniqueProgramIdentifierWithoutEnsemble() {
+ ProgramSelector dabSelector = getDabSelector(new ProgramSelector.Identifier[]{
+ DAB_FREQUENCY_IDENTIFIER}, /* vendorIds= */ null);
+ UniqueProgramIdentifier dabIdentifier = new UniqueProgramIdentifier(dabSelector);
+
+ expect.withMessage("Critical secondary ids of DAB unique identifier without ensemble")
+ .that(dabIdentifier.getCriticalSecondaryIds())
+ .containsExactly(DAB_FREQUENCY_IDENTIFIER);
+ }
+
+ @Test
+ public void getCriticalSecondaryIds_forDabUniqueProgramIdentifierWithoutSecondaryIds() {
+ ProgramSelector dabSelector = getDabSelector(new ProgramSelector.Identifier[]{},
+ /* vendorIds= */ null);
+ UniqueProgramIdentifier dabIdentifier = new UniqueProgramIdentifier(dabSelector);
+
+ expect.withMessage("Critical secondary ids of DAB unique identifier")
+ .that(dabIdentifier.getCriticalSecondaryIds()).isEmpty();
+ }
+
+ @Test
public void getCriticalSecondaryIds_forFmUniqueProgramIdentifier() {
UniqueProgramIdentifier fmUniqueIdentifier = new UniqueProgramIdentifier(
new ProgramSelector(ProgramSelector.PROGRAM_TYPE_FM, FM_IDENTIFIER,
@@ -147,6 +188,19 @@
}
@Test
+ public void equals_withMissingSecondaryIdsForUniqueProgramIdentifier() {
+ ProgramSelector dabSelector1 = getDabSelector(new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER}, /* vendorIds= */ null);
+ UniqueProgramIdentifier dabIdentifier1 = new UniqueProgramIdentifier(dabSelector1);
+ ProgramSelector dabSelector2 = getDabSelector(new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER}, /* vendorIds= */ null);
+ UniqueProgramIdentifier dabIdentifier2 = new UniqueProgramIdentifier(dabSelector2);
+
+ expect.withMessage("DAB unique identifier with missing secondary ids")
+ .that(dabIdentifier1).isNotEqualTo(dabIdentifier2);
+ }
+
+ @Test
public void describeContents_forUniqueProgramIdentifier() {
UniqueProgramIdentifier fmUniqueIdentifier = new UniqueProgramIdentifier(FM_IDENTIFIER);
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index c058174..d1a90ae 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -30,6 +30,8 @@
android_test {
name: "FrameworksCoreTests",
+ // FrameworksCoreTestsRavenwood references the .aapt.srcjar
+ use_resource_processor: false,
srcs: [
"src/**/*.java",
@@ -60,6 +62,7 @@
"frameworks-core-util-lib",
"mockwebserver",
"guava",
+ "android.app.usage.flags-aconfig-java",
"android.view.accessibility.flags-aconfig-java",
"androidx.core_core",
"androidx.core_core-ktx",
@@ -83,6 +86,10 @@
"com.android.text.flags-aconfig-java",
"flag-junit",
"ravenwood-junit",
+ "perfetto_trace_java_protos",
+ "flickerlib-parsers",
+ "flickerlib-trace_processor_shell",
+ "mockito-target-extended-minus-junit4",
],
libs: [
diff --git a/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java b/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
index 9d85b65..1925588 100644
--- a/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
+++ b/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
@@ -16,8 +16,6 @@
package android.app;
-import static com.google.common.truth.Truth.assertThat;
-
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.fail;
@@ -28,8 +26,6 @@
import android.os.Parcel;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
-import android.service.notification.ZenDeviceEffects;
-import android.service.notification.ZenPolicy;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -230,66 +226,4 @@
assertThrows(IllegalArgumentException.class, () -> builder.setType(100));
}
-
- @Test
- @EnableFlags(Flags.FLAG_MODES_API)
- public void testCanUpdate_nullPolicyAndDeviceEffects() {
- AutomaticZenRule.Builder builder = new AutomaticZenRule.Builder("name",
- Uri.parse("uri://short"));
-
- AutomaticZenRule rule = builder.setUserModifiedFields(0)
- .setZenPolicy(null)
- .setDeviceEffects(null)
- .build();
-
- assertThat(rule.canUpdate()).isTrue();
-
- rule = builder.setUserModifiedFields(1).build();
- assertThat(rule.canUpdate()).isFalse();
- }
-
- @Test
- @EnableFlags(Flags.FLAG_MODES_API)
- public void testCanUpdate_policyModified() {
- ZenPolicy.Builder policyBuilder = new ZenPolicy.Builder().setUserModifiedFields(0);
- ZenPolicy policy = policyBuilder.build();
-
- AutomaticZenRule.Builder builder = new AutomaticZenRule.Builder("name",
- Uri.parse("uri://short"));
- AutomaticZenRule rule = builder.setUserModifiedFields(0)
- .setZenPolicy(policy)
- .setDeviceEffects(null).build();
-
- // Newly created ZenPolicy is not user modified.
- assertThat(policy.getUserModifiedFields()).isEqualTo(0);
- assertThat(rule.canUpdate()).isTrue();
-
- policy = policyBuilder.setUserModifiedFields(1).build();
- assertThat(policy.getUserModifiedFields()).isEqualTo(1);
- rule = builder.setZenPolicy(policy).build();
- assertThat(rule.canUpdate()).isFalse();
- }
-
- @Test
- @EnableFlags(Flags.FLAG_MODES_API)
- public void testCanUpdate_deviceEffectsModified() {
- ZenDeviceEffects.Builder deviceEffectsBuilder =
- new ZenDeviceEffects.Builder().setUserModifiedFields(0);
- ZenDeviceEffects deviceEffects = deviceEffectsBuilder.build();
-
- AutomaticZenRule.Builder builder = new AutomaticZenRule.Builder("name",
- Uri.parse("uri://short"));
- AutomaticZenRule rule = builder.setUserModifiedFields(0)
- .setZenPolicy(null)
- .setDeviceEffects(deviceEffects).build();
-
- // Newly created ZenDeviceEffects is not user modified.
- assertThat(deviceEffects.getUserModifiedFields()).isEqualTo(0);
- assertThat(rule.canUpdate()).isTrue();
-
- deviceEffects = deviceEffectsBuilder.setUserModifiedFields(1).build();
- assertThat(deviceEffects.getUserModifiedFields()).isEqualTo(1);
- rule = builder.setDeviceEffects(deviceEffects).build();
- assertThat(rule.canUpdate()).isFalse();
- }
}
diff --git a/core/tests/coretests/src/android/app/usage/UsageEventsQueryTest.java b/core/tests/coretests/src/android/app/usage/UsageEventsQueryTest.java
index 839b645..4565978 100644
--- a/core/tests/coretests/src/android/app/usage/UsageEventsQueryTest.java
+++ b/core/tests/coretests/src/android/app/usage/UsageEventsQueryTest.java
@@ -15,24 +15,34 @@
*/
package android.app.usage;
+import static android.app.usage.Flags.FLAG_FILTER_BASED_EVENT_QUERY_API;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import android.app.usage.UsageEvents.Event;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Random;
-import java.util.Set;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class UsageEventsQueryTest {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Test
+ @RequiresFlagsEnabled(FLAG_FILTER_BASED_EVENT_QUERY_API)
public void testQueryDuration() {
// Test with negative beginTimeMillis.
long beginTimeMillis = -100;
@@ -97,6 +107,7 @@
}
@Test
+ @RequiresFlagsEnabled(FLAG_FILTER_BASED_EVENT_QUERY_API)
public void testQueryEventTypes() {
Random rnd = new Random();
UsageEventsQuery.Builder queryBuilder = new UsageEventsQuery.Builder(1000, 2000);
@@ -104,7 +115,7 @@
// Test with invalid event type.
int eventType = Event.NONE - 1;
try {
- queryBuilder.addEventTypes(eventType);
+ queryBuilder.setEventTypes(eventType);
fail("Invalid event type: " + eventType);
} catch (IllegalArgumentException e) {
// Expected, fall through.
@@ -112,7 +123,7 @@
eventType = Event.MAX_EVENT_TYPE + 1;
try {
- queryBuilder.addEventTypes(eventType);
+ queryBuilder.setEventTypes(eventType);
fail("Invalid event type: " + eventType);
} catch (IllegalArgumentException e) {
// Expected, fall through.
@@ -121,11 +132,11 @@
// Test with valid and duplicate event types.
eventType = rnd.nextInt(Event.MAX_EVENT_TYPE + 1);
try {
- UsageEventsQuery query = queryBuilder.addEventTypes(eventType, eventType, eventType)
+ UsageEventsQuery query = queryBuilder.setEventTypes(eventType, eventType, eventType)
.build();
- Set<Integer> eventTypeSet = query.getEventTypes();
- assertEquals(eventTypeSet.size(), 1);
- int type = eventTypeSet.iterator().next();
+ final int[] eventTypesArray = query.getEventTypes();
+ assertEquals(eventTypesArray.length, 1);
+ int type = eventTypesArray[0];
assertEquals(type, eventType);
} catch (IllegalArgumentException e) {
fail("Valid event type: " + eventType);
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
index 1617eda..e32a57b 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
@@ -48,7 +48,7 @@
@get:Rule
val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
- private lateinit var defaultLookupTables: SparseArray<FontScaleConverter>
+ private var defaultLookupTables: SparseArray<FontScaleConverter>? = null
@Before
fun setup() {
@@ -58,7 +58,9 @@
@After
fun teardown() {
// Restore the default tables (since some tests will have added extras to the cache)
- FontScaleConverterFactory.sLookupTables = defaultLookupTables
+ if (defaultLookupTables != null) {
+ FontScaleConverterFactory.sLookupTables = defaultLookupTables!!
+ }
}
@Test
diff --git a/core/tests/coretests/src/android/graphics/PaintTest.java b/core/tests/coretests/src/android/graphics/PaintTest.java
index bf56df1..0dec756 100644
--- a/core/tests/coretests/src/android/graphics/PaintTest.java
+++ b/core/tests/coretests/src/android/graphics/PaintTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertNotEquals;
import android.test.InstrumentationTestCase;
+import android.text.TextUtils;
import androidx.test.filters.SmallTest;
@@ -362,4 +363,44 @@
// = 30
assertEquals(30.0f, p.getUnderlineThickness(), 0.5f);
}
+
+ private int getClusterCount(Paint p, String text) {
+ Paint.RunInfo runInfo = new Paint.RunInfo();
+ p.getRunCharacterAdvance(text, 0, text.length(), 0, text.length(), false, 0, null, 0, null,
+ runInfo);
+ int ccByString = runInfo.getClusterCount();
+ runInfo.setClusterCount(0);
+ char[] buf = new char[text.length()];
+ TextUtils.getChars(text, 0, text.length(), buf, 0);
+ p.getRunCharacterAdvance(buf, 0, buf.length, 0, buf.length, false, 0, null, 0, null,
+ runInfo);
+ int ccByChars = runInfo.getClusterCount();
+ assertEquals(ccByChars, ccByString);
+ return ccByChars;
+ }
+
+ public void testCluster() {
+ final Paint p = new Paint();
+ p.setTextSize(100);
+
+ // Regular String
+ assertEquals(1, getClusterCount(p, "A"));
+ assertEquals(2, getClusterCount(p, "AB"));
+
+ // Ligature is in the same cluster
+ assertEquals(1, getClusterCount(p, "fi")); // Ligature
+ p.setFontFeatureSettings("'liga' off");
+ assertEquals(2, getClusterCount(p, "fi")); // Ligature is disabled
+ p.setFontFeatureSettings("");
+
+ // Combining character
+ assertEquals(1, getClusterCount(p, "\u0061\u0300")); // A + COMBINING GRAVE ACCENT
+
+ // BiDi
+ final String rtlStr = "\u05D0\u05D1\u05D2";
+ final String ltrStr = "abc";
+ assertEquals(3, getClusterCount(p, rtlStr));
+ assertEquals(6, getClusterCount(p, rtlStr + ltrStr));
+ assertEquals(9, getClusterCount(p, ltrStr + rtlStr + ltrStr));
+ }
}
diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
index 12a2844..a28bb69 100644
--- a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
+++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
@@ -195,9 +195,30 @@
Session s = createSession();
assumeNotNull(s);
s.updateTargetWorkDuration(16);
- s.reportActualWorkDuration(new WorkDuration(1, 12, 8, 6));
- s.reportActualWorkDuration(new WorkDuration(1, 33, 14, 20));
- s.reportActualWorkDuration(new WorkDuration(1, 14, 10, 6));
+ {
+ WorkDuration workDuration = new WorkDuration();
+ workDuration.setWorkPeriodStartTimestampNanos(1);
+ workDuration.setActualTotalDurationNanos(12);
+ workDuration.setActualCpuDurationNanos(8);
+ workDuration.setActualGpuDurationNanos(6);
+ s.reportActualWorkDuration(workDuration);
+ }
+ {
+ WorkDuration workDuration = new WorkDuration();
+ workDuration.setWorkPeriodStartTimestampNanos(1);
+ workDuration.setActualTotalDurationNanos(33);
+ workDuration.setActualCpuDurationNanos(14);
+ workDuration.setActualGpuDurationNanos(20);
+ s.reportActualWorkDuration(workDuration);
+ }
+ {
+ WorkDuration workDuration = new WorkDuration();
+ workDuration.setWorkPeriodStartTimestampNanos(1);
+ workDuration.setActualTotalDurationNanos(14);
+ workDuration.setActualCpuDurationNanos(10);
+ workDuration.setActualGpuDurationNanos(6);
+ s.reportActualWorkDuration(workDuration);
+ }
}
@Test
@@ -206,25 +227,25 @@
assumeNotNull(s);
s.updateTargetWorkDuration(16);
assertThrows(IllegalArgumentException.class, () -> {
- s.reportActualWorkDuration(new WorkDuration(-1, 12, 8, 6));
+ s.reportActualWorkDuration(new WorkDuration(-1, 12, 8, 6, 1));
});
assertThrows(IllegalArgumentException.class, () -> {
- s.reportActualWorkDuration(new WorkDuration(0, 12, 8, 6));
+ s.reportActualWorkDuration(new WorkDuration(0, 12, 8, 6, 1));
});
assertThrows(IllegalArgumentException.class, () -> {
- s.reportActualWorkDuration(new WorkDuration(1, -1, 8, 6));
+ s.reportActualWorkDuration(new WorkDuration(1, -1, 8, 6, 1));
});
assertThrows(IllegalArgumentException.class, () -> {
- s.reportActualWorkDuration(new WorkDuration(1, 0, 8, 6));
+ s.reportActualWorkDuration(new WorkDuration(1, 0, 8, 6, 1));
});
assertThrows(IllegalArgumentException.class, () -> {
- s.reportActualWorkDuration(new WorkDuration(1, 12, -1, 6));
+ s.reportActualWorkDuration(new WorkDuration(1, 12, -1, 6, 1));
});
assertThrows(IllegalArgumentException.class, () -> {
- s.reportActualWorkDuration(new WorkDuration(1, 12, 0, 6));
+ s.reportActualWorkDuration(new WorkDuration(1, 12, 0, 6, 1));
});
assertThrows(IllegalArgumentException.class, () -> {
- s.reportActualWorkDuration(new WorkDuration(1, 12, 8, -1));
+ s.reportActualWorkDuration(new WorkDuration(1, 12, 8, -1, 1));
});
}
}
diff --git a/core/tests/coretests/src/android/os/WorkDurationUnitTest.java b/core/tests/coretests/src/android/os/WorkDurationUnitTest.java
new file mode 100644
index 0000000..c70da6e
--- /dev/null
+++ b/core/tests/coretests/src/android/os/WorkDurationUnitTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import static org.junit.Assert.assertThrows;
+
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@IgnoreUnderRavenwood(blockedBy = WorkDuration.class)
+public class WorkDurationUnitTest {
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+ // Required for RequiresFlagsEnabled and RequiresFlagsDisabled annotations to take effect.
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isUnderRavenwood() ? null
+ : DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION)
+ public void testWorkDurationSetters_IllegalArgument() {
+ WorkDuration workDuration = new WorkDuration();
+ assertThrows(IllegalArgumentException.class, () -> {
+ workDuration.setWorkPeriodStartTimestampNanos(-1);
+ });
+ assertThrows(IllegalArgumentException.class, () -> {
+ workDuration.setWorkPeriodStartTimestampNanos(0);
+ });
+ assertThrows(IllegalArgumentException.class, () -> {
+ workDuration.setActualTotalDurationNanos(-1);
+ });
+ assertThrows(IllegalArgumentException.class, () -> {
+ workDuration.setActualTotalDurationNanos(0);
+ });
+ assertThrows(IllegalArgumentException.class, () -> {
+ workDuration.setActualCpuDurationNanos(-1);
+ });
+ assertThrows(IllegalArgumentException.class, () -> {
+ workDuration.setActualCpuDurationNanos(0);
+ });
+ assertThrows(IllegalArgumentException.class, () -> {
+ workDuration.setActualGpuDurationNanos(-1);
+ });
+ }
+}
diff --git a/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt b/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt
new file mode 100644
index 0000000..27869bb
--- /dev/null
+++ b/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt
@@ -0,0 +1,224 @@
+/*
+ * 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.text
+
+import android.graphics.Paint
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+const val LEFT_EDGE = Paint.TEXT_RUN_FLAG_LEFT_EDGE
+const val RIGHT_EDGE = Paint.TEXT_RUN_FLAG_RIGHT_EDGE
+const val MIDDLE_OF_LINE = 0
+const val WHOLE_LINE = LEFT_EDGE or RIGHT_EDGE
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TextLineLetterSpacingTest {
+
+ @Rule
+ @JvmField
+ val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+ @RequiresFlagsEnabled(FLAG_INTER_CHARACTER_JUSTIFICATION)
+ @Test
+ fun calculateRunFlagTest() {
+ // Only one Bidi run
+ assertThat(TextLine.calculateRunFlag(0, 1, Layout.DIR_LEFT_TO_RIGHT))
+ .isEqualTo(WHOLE_LINE)
+ assertThat(TextLine.calculateRunFlag(0, 1, Layout.DIR_RIGHT_TO_LEFT))
+ .isEqualTo(WHOLE_LINE)
+
+ // Two BiDi Runs.
+ // If the layout is LTR, the first run is the left most run.
+ assertThat(TextLine.calculateRunFlag(0, 2, Layout.DIR_LEFT_TO_RIGHT))
+ .isEqualTo(LEFT_EDGE)
+ // If the layout is LTR, the last run is the right most run.
+ assertThat(TextLine.calculateRunFlag(1, 2, Layout.DIR_LEFT_TO_RIGHT))
+ .isEqualTo(RIGHT_EDGE)
+ // If the layout is RTL, the first run is the right most run.
+ assertThat(TextLine.calculateRunFlag(0, 2, Layout.DIR_RIGHT_TO_LEFT))
+ .isEqualTo(RIGHT_EDGE)
+ // If the layout is RTL, the last run is the left most run.
+ assertThat(TextLine.calculateRunFlag(1, 2, Layout.DIR_RIGHT_TO_LEFT))
+ .isEqualTo(LEFT_EDGE)
+
+ // Three BiDi Runs.
+ // If the layout is LTR, the first run is the left most run.
+ assertThat(TextLine.calculateRunFlag(0, 3, Layout.DIR_LEFT_TO_RIGHT))
+ .isEqualTo(LEFT_EDGE)
+ // Regardless of the context direction, the middle run must not have any flags.
+ assertThat(TextLine.calculateRunFlag(1, 3, Layout.DIR_LEFT_TO_RIGHT))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // If the layout is LTR, the last run is the right most run.
+ assertThat(TextLine.calculateRunFlag(2, 3, Layout.DIR_LEFT_TO_RIGHT))
+ .isEqualTo(RIGHT_EDGE)
+ // If the layout is RTL, the first run is the right most run.
+ assertThat(TextLine.calculateRunFlag(0, 3, Layout.DIR_RIGHT_TO_LEFT))
+ .isEqualTo(RIGHT_EDGE)
+ // Regardless of the context direction, the middle run must not have any flags.
+ assertThat(TextLine.calculateRunFlag(1, 3, Layout.DIR_RIGHT_TO_LEFT))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // If the layout is RTL, the last run is the left most run.
+ assertThat(TextLine.calculateRunFlag(2, 3, Layout.DIR_RIGHT_TO_LEFT))
+ .isEqualTo(LEFT_EDGE)
+ }
+
+ @RequiresFlagsEnabled(FLAG_INTER_CHARACTER_JUSTIFICATION)
+ @Test
+ fun resolveRunFlagForSubSequenceTest() {
+ val runStart = 5
+ val runEnd = 15
+ // Regardless of the run directions, if the span covers entire Bidi run, the same runFlag
+ // should be returned.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ LEFT_EDGE, false, runStart, runEnd, runStart, runEnd))
+ .isEqualTo(LEFT_EDGE)
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ LEFT_EDGE, true, runStart, runEnd, runStart, runEnd))
+ .isEqualTo(LEFT_EDGE)
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ RIGHT_EDGE, false, runStart, runEnd, runStart, runEnd))
+ .isEqualTo(RIGHT_EDGE)
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ RIGHT_EDGE, true, runStart, runEnd, runStart, runEnd))
+ .isEqualTo(RIGHT_EDGE)
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ WHOLE_LINE, false, runStart, runEnd, runStart, runEnd))
+ .isEqualTo(WHOLE_LINE)
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ WHOLE_LINE, true, runStart, runEnd, runStart, runEnd))
+ .isEqualTo(WHOLE_LINE)
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ MIDDLE_OF_LINE, false, runStart, runEnd, runStart, runEnd))
+ .isEqualTo(MIDDLE_OF_LINE)
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ MIDDLE_OF_LINE, true, runStart, runEnd, runStart, runEnd))
+ .isEqualTo(MIDDLE_OF_LINE)
+
+
+
+ // Left edge of LTR text, span start from run start offset but not cover the run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ LEFT_EDGE, false, runStart, runEnd, runStart, runEnd - 1))
+ .isEqualTo(LEFT_EDGE)
+ // Left edge of RTL text, span start from run start offset but not cover the run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ LEFT_EDGE, true, runStart, runEnd, runStart, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Right edge of LTR text, span start from run start offset but not cover the run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ RIGHT_EDGE, false, runStart, runEnd, runStart, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Right edge of RTL text, span start from run start offset but not cover the run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ RIGHT_EDGE, true, runStart, runEnd, runStart, runEnd - 1))
+ .isEqualTo(RIGHT_EDGE)
+ // Whole line of LTR text, span start from run start offset but not cover the run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ WHOLE_LINE, false, runStart, runEnd, runStart, runEnd - 1))
+ .isEqualTo(LEFT_EDGE)
+ // Whole line of RTL text, span start from run start offset but not cover the run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ WHOLE_LINE, true, runStart, runEnd, runStart, runEnd - 1))
+ .isEqualTo(RIGHT_EDGE)
+ // Middle of LTR text, span start from run start offset but not cover the run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ MIDDLE_OF_LINE, false, runStart, runEnd, runStart, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Middle of RTL text, span start from run start offset but not cover the run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ MIDDLE_OF_LINE, true, runStart, runEnd, runStart, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+
+
+
+ // Left edge of LTR text, span start from middle of run start offset until end of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ LEFT_EDGE, false, runStart, runEnd, runStart + 1, runEnd))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Left edge of RTL text, span start from middle of run start offset until end of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ LEFT_EDGE, true, runStart, runEnd, runStart + 1, runEnd))
+ .isEqualTo(LEFT_EDGE)
+ // Right edge of LTR text, span start from middle of run start offset until end of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ RIGHT_EDGE, false, runStart, runEnd, runStart + 1, runEnd))
+ .isEqualTo(RIGHT_EDGE)
+ // Right edge of RTL text, span start from middle of run start offset until end of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ RIGHT_EDGE, true, runStart, runEnd, runStart + 1, runEnd))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Whole line of LTR text, span start from middle of run start offset until end of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ WHOLE_LINE, false, runStart, runEnd, runStart + 1, runEnd))
+ .isEqualTo(RIGHT_EDGE)
+ // Whole line of RTL text, span start from middle of run start offset until end of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ WHOLE_LINE, true, runStart, runEnd, runStart + 1, runEnd))
+ .isEqualTo(LEFT_EDGE)
+ // Middle of LTR text, span start from middle of run start offset until end of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ MIDDLE_OF_LINE, false, runStart, runEnd, runStart + 1, runEnd))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Middle of RTL text, span start from middle of run start offset until end of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ MIDDLE_OF_LINE, true, runStart, runEnd, runStart + 1, runEnd))
+ .isEqualTo(MIDDLE_OF_LINE)
+
+
+
+ // Left edge of LTR text, span start from middle of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ LEFT_EDGE, false, runStart, runEnd, runStart + 1, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Left edge of RTL text, span start from middle of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ LEFT_EDGE, true, runStart, runEnd, runStart + 1, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Right edge of LTR text, span start from middle of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ RIGHT_EDGE, false, runStart, runEnd, runStart + 1, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Right edge of RTL text, span start from middle of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ RIGHT_EDGE, true, runStart, runEnd, runStart + 1, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Whole line of LTR text, span start from middle of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ WHOLE_LINE, false, runStart, runEnd, runStart + 1, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Whole line of RTL text, span start from middle of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ WHOLE_LINE, true, runStart, runEnd, runStart + 1, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Middle of LTR text, span start from middle of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ MIDDLE_OF_LINE, false, runStart, runEnd, runStart + 1, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Middle of RTL text, span start from middle of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ MIDDLE_OF_LINE, true, runStart, runEnd, runStart + 1, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+ }
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/text/TextLineTest.java b/core/tests/coretests/src/android/text/TextLineTest.java
index 34842a0..a31992c 100644
--- a/core/tests/coretests/src/android/text/TextLineTest.java
+++ b/core/tests/coretests/src/android/text/TextLineTest.java
@@ -50,11 +50,11 @@
tl.set(paint, line, 0, line.length(), Layout.DIR_LEFT_TO_RIGHT,
Layout.DIRS_ALL_LEFT_TO_RIGHT, false /* hasTabs */, null /* tabStops */,
0, 0 /* no ellipsis */, false /* useFallbackLinespace */);
- final float originalWidth = tl.metrics(null, null, false);
+ final float originalWidth = tl.metrics(null, null, false, null);
final float expandedWidth = 2 * originalWidth;
tl.justify(expandedWidth);
- final float newWidth = tl.metrics(null, null, false);
+ final float newWidth = tl.metrics(null, null, false, null);
TextLine.recycle(tl);
return Math.abs(newWidth - expandedWidth) < 0.5;
}
@@ -128,7 +128,7 @@
private void assertMeasurements(final TextLine tl, final int length, boolean trailing,
final float[] expected) {
for (int offset = 0; offset <= length; ++offset) {
- assertEquals(expected[offset], tl.measure(offset, trailing, null, null), 0.0f);
+ assertEquals(expected[offset], tl.measure(offset, trailing, null, null, null), 0.0f);
}
final boolean[] trailings = new boolean[length + 1];
@@ -318,7 +318,7 @@
tl.set(new TextPaint(), text, 0, text.length(), 1, Layout.DIRS_ALL_LEFT_TO_RIGHT,
false /* hasTabs */, null /* tabStops */, 9, 12,
false /* useFallbackLineSpacing */);
- tl.measure(text.length(), false /* trailing */, null /* fmi */, null);
+ tl.measure(text.length(), false /* trailing */, null /* fmi */, null, null);
assertFalse(span.mIsUsed);
}
@@ -335,7 +335,7 @@
tl.set(new TextPaint(), text, 0, text.length(), 1, Layout.DIRS_ALL_LEFT_TO_RIGHT,
false /* hasTabs */, null /* tabStops */, 9, 12,
false /* useFallbackLineSpacing */);
- tl.measure(text.length(), false /* trailing */, null /* fmi */, null);
+ tl.measure(text.length(), false /* trailing */, null /* fmi */, null, null);
assertTrue(span.mIsUsed);
}
@@ -352,7 +352,7 @@
tl.set(new TextPaint(), text, 0, text.length(), 1, Layout.DIRS_ALL_LEFT_TO_RIGHT,
false /* hasTabs */, null /* tabStops */, 9, 12,
false /* useFallbackLineSpacing */);
- tl.measure(text.length(), false /* trailing */, null /* fmi */, null);
+ tl.measure(text.length(), false /* trailing */, null /* fmi */, null, null);
assertTrue(span.mIsUsed);
}
diff --git a/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java b/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java
new file mode 100644
index 0000000..bd2f36f
--- /dev/null
+++ b/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java
@@ -0,0 +1,664 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.tracing.perfetto;
+
+import static android.internal.perfetto.protos.PerfettoTrace.TestEvent.PAYLOAD;
+import static android.internal.perfetto.protos.PerfettoTrace.TestEvent.TestPayload.SINGLE_INT;
+import static android.internal.perfetto.protos.PerfettoTrace.TracePacket.FOR_TESTING;
+
+import static java.io.File.createTempFile;
+import static java.nio.file.Files.createTempDirectory;
+
+import android.internal.perfetto.protos.PerfettoTrace;
+import android.tools.common.ScenarioBuilder;
+import android.tools.common.Tag;
+import android.tools.common.io.TraceType;
+import android.tools.device.traces.TraceConfig;
+import android.tools.device.traces.TraceConfigs;
+import android.tools.device.traces.io.ResultReader;
+import android.tools.device.traces.io.ResultWriter;
+import android.tools.device.traces.monitors.PerfettoTraceMonitor;
+import android.tools.device.traces.monitors.TraceMonitor;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.common.truth.Truth;
+import com.google.protobuf.InvalidProtocolBufferException;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import perfetto.protos.PerfettoConfig;
+import perfetto.protos.TracePacketOuterClass;
+
+@RunWith(AndroidJUnit4.class)
+public class DataSourceTest {
+ private final File mTracingDirectory = createTempDirectory("temp").toFile();
+
+ private final ResultWriter mWriter = new ResultWriter()
+ .forScenario(new ScenarioBuilder()
+ .forClass(createTempFile("temp", "").getName()).build())
+ .withOutputDir(mTracingDirectory)
+ .setRunComplete();
+
+ private final TraceConfigs mTraceConfig = new TraceConfigs(
+ new TraceConfig(false, true, false),
+ new TraceConfig(false, true, false),
+ new TraceConfig(false, true, false),
+ new TraceConfig(false, true, false)
+ );
+
+ private static TestDataSource sTestDataSource;
+
+ private static TestDataSource.DataSourceInstanceProvider sInstanceProvider;
+ private static TestDataSource.TlsStateProvider sTlsStateProvider;
+ private static TestDataSource.IncrementalStateProvider sIncrementalStateProvider;
+
+ public DataSourceTest() throws IOException {}
+
+ @BeforeClass
+ public static void beforeAll() {
+ Producer.init(InitArguments.DEFAULTS);
+ setupProviders();
+ sTestDataSource = new TestDataSource(
+ (ds, idx, configStream) -> sInstanceProvider.provide(ds, idx, configStream),
+ args -> sTlsStateProvider.provide(args),
+ args -> sIncrementalStateProvider.provide(args));
+ sTestDataSource.register(DataSourceParams.DEFAULTS);
+ }
+
+ private static void setupProviders() {
+ sInstanceProvider = (ds, idx, configStream) ->
+ new TestDataSource.TestDataSourceInstance(ds, idx);
+ sTlsStateProvider = args -> new TestDataSource.TestTlsState();
+ sIncrementalStateProvider = args -> new TestDataSource.TestIncrementalState();
+ }
+
+ @Before
+ public void setup() {
+ setupProviders();
+ }
+
+ @Test
+ public void canTraceData() throws InvalidProtocolBufferException {
+ final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+
+ try {
+ traceMonitor.start();
+
+ sTestDataSource.trace((ctx) -> {
+ final ProtoOutputStream protoOutputStream = ctx.newTracePacket();
+ long forTestingToken = protoOutputStream.start(FOR_TESTING);
+ long payloadToken = protoOutputStream.start(PAYLOAD);
+ protoOutputStream.write(SINGLE_INT, 10);
+ protoOutputStream.end(payloadToken);
+ protoOutputStream.end(forTestingToken);
+ });
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL);
+ assert rawProtoFromFile != null;
+ final perfetto.protos.TraceOuterClass.Trace trace = perfetto.protos.TraceOuterClass.Trace
+ .parseFrom(rawProtoFromFile);
+
+ Truth.assertThat(trace.getPacketCount()).isGreaterThan(0);
+ final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList()
+ .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList();
+ final List<TracePacketOuterClass.TracePacket> matchingPackets = tracePackets.stream()
+ .filter(it -> it.getForTesting().getPayload().getSingleInt() == 10).toList();
+ Truth.assertThat(matchingPackets).hasSize(1);
+ }
+
+ @Test
+ public void canUseTlsStateForCustomState() {
+ final int expectedStateTestValue = 10;
+ final AtomicInteger actualStateTestValue = new AtomicInteger();
+
+ final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+
+ try {
+ traceMonitor.start();
+
+ sTestDataSource.trace((ctx) -> {
+ TestDataSource.TestTlsState state = ctx.getCustomTlsState();
+ state.testStateValue = expectedStateTestValue;
+ });
+
+ sTestDataSource.trace((ctx) -> {
+ TestDataSource.TestTlsState state = ctx.getCustomTlsState();
+ actualStateTestValue.set(state.testStateValue);
+ });
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ Truth.assertThat(actualStateTestValue.get()).isEqualTo(expectedStateTestValue);
+ }
+
+ @Test
+ public void eachInstanceHasOwnTlsState() {
+ final int[] expectedStateTestValues = new int[] { 1, 2 };
+ final int[] actualStateTestValues = new int[] { 0, 0 };
+
+ final TraceMonitor traceMonitor1 = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+ final TraceMonitor traceMonitor2 = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+
+ try {
+ traceMonitor1.start();
+ try {
+ traceMonitor2.start();
+
+ AtomicInteger index = new AtomicInteger(0);
+ sTestDataSource.trace((ctx) -> {
+ TestDataSource.TestTlsState state = ctx.getCustomTlsState();
+ state.testStateValue = expectedStateTestValues[index.getAndIncrement()];
+ });
+
+ index.set(0);
+ sTestDataSource.trace((ctx) -> {
+ TestDataSource.TestTlsState state = ctx.getCustomTlsState();
+ actualStateTestValues[index.getAndIncrement()] = state.testStateValue;
+ });
+ } finally {
+ traceMonitor1.stop(mWriter);
+ }
+ } finally {
+ traceMonitor2.stop(mWriter);
+ }
+
+ Truth.assertThat(actualStateTestValues[0]).isEqualTo(expectedStateTestValues[0]);
+ Truth.assertThat(actualStateTestValues[1]).isEqualTo(expectedStateTestValues[1]);
+ }
+
+ @Test
+ public void eachThreadHasOwnTlsState() throws InterruptedException {
+ final int thread1ExpectedStateValue = 1;
+ final int thread2ExpectedStateValue = 2;
+
+ final AtomicInteger thread1ActualStateValue = new AtomicInteger();
+ final AtomicInteger thread2ActualStateValue = new AtomicInteger();
+
+ final CountDownLatch setUpLatch = new CountDownLatch(2);
+ final CountDownLatch setStateLatch = new CountDownLatch(2);
+ final CountDownLatch setOutStateLatch = new CountDownLatch(2);
+
+ final RunnableCreator createTask = (stateValue, stateOut) -> () -> {
+ Producer.init(InitArguments.DEFAULTS);
+
+ setUpLatch.countDown();
+
+ try {
+ setUpLatch.await(3, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+
+ sTestDataSource.trace((ctx) -> {
+ TestDataSource.TestTlsState state = ctx.getCustomTlsState();
+ state.testStateValue = stateValue;
+ setStateLatch.countDown();
+ });
+
+ try {
+ setStateLatch.await(3, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+
+ sTestDataSource.trace((ctx) -> {
+ stateOut.set(ctx.getCustomTlsState().testStateValue);
+ setOutStateLatch.countDown();
+ });
+ };
+
+ final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+
+ try {
+ traceMonitor.start();
+
+ new Thread(
+ createTask.create(thread1ExpectedStateValue, thread1ActualStateValue)).start();
+ new Thread(
+ createTask.create(thread2ExpectedStateValue, thread2ActualStateValue)).start();
+
+ setOutStateLatch.await(3, TimeUnit.SECONDS);
+
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ Truth.assertThat(thread1ActualStateValue.get()).isEqualTo(thread1ExpectedStateValue);
+ Truth.assertThat(thread2ActualStateValue.get()).isEqualTo(thread2ExpectedStateValue);
+ }
+
+ @Test
+ public void incrementalStateIsReset() throws InterruptedException {
+
+ final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build())
+ .setIncrementalTimeout(10)
+ .build();
+
+ final AtomicInteger testStateValue = new AtomicInteger();
+ try {
+ traceMonitor.start();
+
+ sTestDataSource.trace(ctx -> ctx.getIncrementalState().testStateValue = 1);
+
+ // Timeout to make sure the incremental state is cleared.
+ Thread.sleep(1000);
+
+ sTestDataSource.trace(ctx ->
+ testStateValue.set(ctx.getIncrementalState().testStateValue));
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ Truth.assertThat(testStateValue.get()).isNotEqualTo(1);
+ }
+
+ @Test
+ public void getInstanceConfigOnCreateInstance() throws IOException {
+ final int expectedDummyIntValue = 10;
+ AtomicReference<ProtoInputStream> configStream = new AtomicReference<>();
+ sInstanceProvider = (ds, idx, config) -> {
+ configStream.set(config);
+ return new TestDataSource.TestDataSourceInstance(ds, idx);
+ };
+
+ final TraceMonitor monitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name)
+ .setForTesting(PerfettoConfig.TestConfig.newBuilder().setDummyFields(
+ PerfettoConfig.TestConfig.DummyFields.newBuilder()
+ .setFieldInt32(expectedDummyIntValue)
+ .build())
+ .build())
+ .build())
+ .build();
+
+ try {
+ monitor.start();
+ } finally {
+ monitor.stop(mWriter);
+ }
+
+ int configDummyIntValue = 0;
+ while (configStream.get().nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ if (configStream.get().getFieldNumber()
+ == (int) PerfettoTrace.DataSourceConfig.FOR_TESTING) {
+ final long forTestingToken = configStream.get()
+ .start(PerfettoTrace.DataSourceConfig.FOR_TESTING);
+ while (configStream.get().nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ if (configStream.get().getFieldNumber()
+ == (int) PerfettoTrace.TestConfig.DUMMY_FIELDS) {
+ final long dummyFieldsToken = configStream.get()
+ .start(PerfettoTrace.TestConfig.DUMMY_FIELDS);
+ while (configStream.get().nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ if (configStream.get().getFieldNumber()
+ == (int) PerfettoTrace.TestConfig.DummyFields.FIELD_INT32) {
+ int val = configStream.get().readInt(
+ PerfettoTrace.TestConfig.DummyFields.FIELD_INT32);
+ if (val != 0) {
+ configDummyIntValue = val;
+ break;
+ }
+ }
+ }
+ configStream.get().end(dummyFieldsToken);
+ break;
+ }
+ }
+ configStream.get().end(forTestingToken);
+ break;
+ }
+ }
+
+ Truth.assertThat(configDummyIntValue).isEqualTo(expectedDummyIntValue);
+ }
+
+ @Test
+ public void multipleTraceInstances() throws IOException, InterruptedException {
+ final int instanceCount = 3;
+
+ final List<TraceMonitor> monitors = new ArrayList<>();
+ final List<ResultWriter> writers = new ArrayList<>();
+
+ for (int i = 0; i < instanceCount; i++) {
+ final ResultWriter writer = new ResultWriter()
+ .forScenario(new ScenarioBuilder()
+ .forClass(createTempFile("temp", "").getName()).build())
+ .withOutputDir(mTracingDirectory)
+ .setRunComplete();
+ writers.add(writer);
+ }
+
+ // Start at 1 because 0 is considered null value so payload will be ignored in that case
+ TestDataSource.TestTlsState.lastIndex = 1;
+
+ final AtomicInteger traceCallCount = new AtomicInteger();
+ final CountDownLatch latch = new CountDownLatch(instanceCount);
+
+ try {
+ // Start instances
+ for (int i = 0; i < instanceCount; i++) {
+ final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+ monitors.add(traceMonitor);
+ traceMonitor.start();
+ }
+
+ // Trace the stateIndex of the tracing instance.
+ sTestDataSource.trace(ctx -> {
+ final int testIntValue = ctx.getCustomTlsState().stateIndex;
+ traceCallCount.incrementAndGet();
+
+ final ProtoOutputStream os = ctx.newTracePacket();
+ long forTestingToken = os.start(FOR_TESTING);
+ long payloadToken = os.start(PAYLOAD);
+ os.write(SINGLE_INT, testIntValue);
+ os.end(payloadToken);
+ os.end(forTestingToken);
+
+ latch.countDown();
+ });
+ } finally {
+ // Stop instances
+ for (int i = 0; i < instanceCount; i++) {
+ final TraceMonitor monitor = monitors.get(i);
+ final ResultWriter writer = writers.get(i);
+ monitor.stop(writer);
+ }
+ }
+
+ latch.await(3, TimeUnit.SECONDS);
+ Truth.assertThat(traceCallCount.get()).isEqualTo(instanceCount);
+
+ for (int i = 0; i < instanceCount; i++) {
+ final int expectedTracedValue = i + 1;
+ final ResultWriter writer = writers.get(i);
+ final ResultReader reader = new ResultReader(writer.write(), mTraceConfig);
+ final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL);
+ assert rawProtoFromFile != null;
+ final perfetto.protos.TraceOuterClass.Trace trace =
+ perfetto.protos.TraceOuterClass.Trace.parseFrom(rawProtoFromFile);
+
+ Truth.assertThat(trace.getPacketCount()).isGreaterThan(0);
+ final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList()
+ .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList();
+ Truth.assertWithMessage("One packet has for testing data")
+ .that(tracePackets).hasSize(1);
+
+ final List<TracePacketOuterClass.TracePacket> matchingPackets =
+ tracePackets.stream()
+ .filter(it -> it.getForTesting().getPayload()
+ .getSingleInt() == expectedTracedValue).toList();
+ Truth.assertWithMessage(
+ "One packet has testing data with a payload with the expected value")
+ .that(matchingPackets).hasSize(1);
+ }
+ }
+
+ @Test
+ public void onStartCallbackTriggered() throws InterruptedException {
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ final AtomicBoolean callbackCalled = new AtomicBoolean(false);
+ sInstanceProvider = (ds, idx, config) -> new TestDataSource.TestDataSourceInstance(
+ ds,
+ idx,
+ (args) -> {
+ callbackCalled.set(true);
+ latch.countDown();
+ },
+ (args) -> {},
+ (args) -> {}
+ );
+
+ final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+
+ Truth.assertThat(callbackCalled.get()).isFalse();
+ try {
+ traceMonitor.start();
+ latch.await(3, TimeUnit.SECONDS);
+ Truth.assertThat(callbackCalled.get()).isTrue();
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+ }
+
+ @Test
+ public void onFlushCallbackTriggered() throws InterruptedException {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final AtomicBoolean callbackCalled = new AtomicBoolean(false);
+ sInstanceProvider = (ds, idx, config) ->
+ new TestDataSource.TestDataSourceInstance(
+ ds,
+ idx,
+ (args) -> {},
+ (args) -> {
+ callbackCalled.set(true);
+ latch.countDown();
+ },
+ (args) -> {}
+ );
+
+ final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+
+ try {
+ traceMonitor.start();
+ Truth.assertThat(callbackCalled.get()).isFalse();
+ sTestDataSource.trace((ctx) -> {
+ final ProtoOutputStream protoOutputStream = ctx.newTracePacket();
+ long forTestingToken = protoOutputStream.start(FOR_TESTING);
+ long payloadToken = protoOutputStream.start(PAYLOAD);
+ protoOutputStream.write(SINGLE_INT, 10);
+ protoOutputStream.end(payloadToken);
+ protoOutputStream.end(forTestingToken);
+ });
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ latch.await(3, TimeUnit.SECONDS);
+ Truth.assertThat(callbackCalled.get()).isTrue();
+ }
+
+ @Test
+ public void onStopCallbackTriggered() throws InterruptedException {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final AtomicBoolean callbackCalled = new AtomicBoolean(false);
+ sInstanceProvider = (ds, idx, config) ->
+ new TestDataSource.TestDataSourceInstance(
+ ds,
+ idx,
+ (args) -> {},
+ (args) -> {},
+ (args) -> {
+ callbackCalled.set(true);
+ latch.countDown();
+ }
+ );
+
+ final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+
+ try {
+ traceMonitor.start();
+ Truth.assertThat(callbackCalled.get()).isFalse();
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ latch.await(3, TimeUnit.SECONDS);
+ Truth.assertThat(callbackCalled.get()).isTrue();
+ }
+
+ @Test
+ public void canUseDataSourceInstanceToCreateTlsState() throws InvalidProtocolBufferException {
+ final Object testObject = new Object();
+
+ sInstanceProvider = (ds, idx, configStream) -> {
+ final TestDataSource.TestDataSourceInstance dsInstance =
+ new TestDataSource.TestDataSourceInstance(ds, idx);
+ dsInstance.testObject = testObject;
+ return dsInstance;
+ };
+
+ sTlsStateProvider = args -> {
+ final TestDataSource.TestTlsState tlsState = new TestDataSource.TestTlsState();
+
+ try (TestDataSource.TestDataSourceInstance dataSourceInstance =
+ args.getDataSourceInstanceLocked()) {
+ if (dataSourceInstance != null) {
+ tlsState.testStateValue = dataSourceInstance.testObject.hashCode();
+ }
+ }
+
+ return tlsState;
+ };
+
+ final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+
+ try {
+ traceMonitor.start();
+ sTestDataSource.trace((ctx) -> {
+ final ProtoOutputStream protoOutputStream = ctx.newTracePacket();
+ long forTestingToken = protoOutputStream.start(FOR_TESTING);
+ long payloadToken = protoOutputStream.start(PAYLOAD);
+ protoOutputStream.write(SINGLE_INT, ctx.getCustomTlsState().testStateValue);
+ protoOutputStream.end(payloadToken);
+ protoOutputStream.end(forTestingToken);
+ });
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL);
+ assert rawProtoFromFile != null;
+ final perfetto.protos.TraceOuterClass.Trace trace = perfetto.protos.TraceOuterClass.Trace
+ .parseFrom(rawProtoFromFile);
+
+ Truth.assertThat(trace.getPacketCount()).isGreaterThan(0);
+ final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList()
+ .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList();
+ final List<TracePacketOuterClass.TracePacket> matchingPackets = tracePackets.stream()
+ .filter(it -> it.getForTesting().getPayload().getSingleInt()
+ == testObject.hashCode()).toList();
+ Truth.assertThat(matchingPackets).hasSize(1);
+ }
+
+ @Test
+ public void canUseDataSourceInstanceToCreateIncrementalState()
+ throws InvalidProtocolBufferException {
+ final Object testObject = new Object();
+
+ sInstanceProvider = (ds, idx, configStream) -> {
+ final TestDataSource.TestDataSourceInstance dsInstance =
+ new TestDataSource.TestDataSourceInstance(ds, idx);
+ dsInstance.testObject = testObject;
+ return dsInstance;
+ };
+
+ sIncrementalStateProvider = args -> {
+ final TestDataSource.TestIncrementalState incrementalState =
+ new TestDataSource.TestIncrementalState();
+
+ try (TestDataSource.TestDataSourceInstance dataSourceInstance =
+ args.getDataSourceInstanceLocked()) {
+ if (dataSourceInstance != null) {
+ incrementalState.testStateValue = dataSourceInstance.testObject.hashCode();
+ }
+ }
+
+ return incrementalState;
+ };
+
+ final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+
+ try {
+ traceMonitor.start();
+ sTestDataSource.trace((ctx) -> {
+ final ProtoOutputStream protoOutputStream = ctx.newTracePacket();
+ long forTestingToken = protoOutputStream.start(FOR_TESTING);
+ long payloadToken = protoOutputStream.start(PAYLOAD);
+ protoOutputStream.write(SINGLE_INT, ctx.getIncrementalState().testStateValue);
+ protoOutputStream.end(payloadToken);
+ protoOutputStream.end(forTestingToken);
+ });
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL);
+ assert rawProtoFromFile != null;
+ final perfetto.protos.TraceOuterClass.Trace trace = perfetto.protos.TraceOuterClass.Trace
+ .parseFrom(rawProtoFromFile);
+
+ Truth.assertThat(trace.getPacketCount()).isGreaterThan(0);
+ final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList()
+ .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList();
+ final List<TracePacketOuterClass.TracePacket> matchingPackets = tracePackets.stream()
+ .filter(it -> it.getForTesting().getPayload().getSingleInt()
+ == testObject.hashCode()).toList();
+ Truth.assertThat(matchingPackets).hasSize(1);
+ }
+
+ interface RunnableCreator {
+ Runnable create(int state, AtomicInteger stateOut);
+ }
+}
diff --git a/core/tests/coretests/src/android/tracing/perfetto/TestDataSource.java b/core/tests/coretests/src/android/tracing/perfetto/TestDataSource.java
new file mode 100644
index 0000000..d78f78b
--- /dev/null
+++ b/core/tests/coretests/src/android/tracing/perfetto/TestDataSource.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.tracing.perfetto;
+
+import android.util.proto.ProtoInputStream;
+
+import java.util.UUID;
+import java.util.function.Consumer;
+
+public class TestDataSource extends DataSource<TestDataSource.TestDataSourceInstance,
+ TestDataSource.TestTlsState, TestDataSource.TestIncrementalState> {
+ private final DataSourceInstanceProvider mDataSourceInstanceProvider;
+ private final TlsStateProvider mTlsStateProvider;
+ private final IncrementalStateProvider mIncrementalStateProvider;
+
+ interface DataSourceInstanceProvider {
+ TestDataSourceInstance provide(
+ TestDataSource dataSource, int instanceIndex, ProtoInputStream configStream);
+ }
+
+ interface TlsStateProvider {
+ TestTlsState provide(CreateTlsStateArgs<TestDataSourceInstance> args);
+ }
+
+ interface IncrementalStateProvider {
+ TestIncrementalState provide(CreateIncrementalStateArgs<TestDataSourceInstance> args);
+ }
+
+ public TestDataSource() {
+ this((ds, idx, config) -> new TestDataSourceInstance(ds, idx),
+ args -> new TestTlsState(), args -> new TestIncrementalState());
+ }
+
+ public TestDataSource(
+ DataSourceInstanceProvider dataSourceInstanceProvider,
+ TlsStateProvider tlsStateProvider,
+ IncrementalStateProvider incrementalStateProvider
+ ) {
+ super("android.tracing.perfetto.TestDataSource#" + UUID.randomUUID().toString());
+ this.mDataSourceInstanceProvider = dataSourceInstanceProvider;
+ this.mTlsStateProvider = tlsStateProvider;
+ this.mIncrementalStateProvider = incrementalStateProvider;
+ }
+
+ @Override
+ public TestDataSourceInstance createInstance(ProtoInputStream configStream, int instanceIndex) {
+ return mDataSourceInstanceProvider.provide(this, instanceIndex, configStream);
+ }
+
+ @Override
+ public TestTlsState createTlsState(CreateTlsStateArgs args) {
+ return mTlsStateProvider.provide(args);
+ }
+
+ @Override
+ public TestIncrementalState createIncrementalState(CreateIncrementalStateArgs args) {
+ return mIncrementalStateProvider.provide(args);
+ }
+
+ public static class TestTlsState {
+ public int testStateValue;
+ public int stateIndex = lastIndex++;
+
+ public static int lastIndex = 0;
+ }
+
+ public static class TestIncrementalState {
+ public int testStateValue;
+ }
+
+ public static class TestDataSourceInstance extends DataSourceInstance {
+ public Object testObject;
+ Consumer<StartCallbackArguments> mStartCallback;
+ Consumer<FlushCallbackArguments> mFlushCallback;
+ Consumer<StopCallbackArguments> mStopCallback;
+
+ public TestDataSourceInstance(DataSource dataSource, int instanceIndex) {
+ this(dataSource, instanceIndex, args -> {}, args -> {}, args -> {});
+ }
+
+ public TestDataSourceInstance(
+ DataSource dataSource,
+ int instanceIndex,
+ Consumer<StartCallbackArguments> startCallback,
+ Consumer<FlushCallbackArguments> flushCallback,
+ Consumer<StopCallbackArguments> stopCallback) {
+ super(dataSource, instanceIndex);
+ this.mStartCallback = startCallback;
+ this.mFlushCallback = flushCallback;
+ this.mStopCallback = stopCallback;
+ }
+
+ @Override
+ public void onStart(StartCallbackArguments args) {
+ this.mStartCallback.accept(args);
+ }
+
+ @Override
+ public void onFlush(FlushCallbackArguments args) {
+ this.mFlushCallback.accept(args);
+ }
+
+ @Override
+ public void onStop(StopCallbackArguments args) {
+ this.mStopCallback.accept(args);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index cf3eb12..cfbda84 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.Surface.FRAME_RATE_CATEGORY_HIGH;
+import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
@@ -575,8 +576,13 @@
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW);
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
+ viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT);
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+ FRAME_RATE_CATEGORY_HIGH_HINT);
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH);
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT);
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW);
diff --git a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
index 0742052..ec4c563 100644
--- a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
@@ -18,39 +18,67 @@
import static com.google.common.truth.Truth.assertThat;
-import android.util.Xml;
-
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileWriter;
import java.io.IOException;
+import java.nio.file.Files;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class MonotonicClockTest {
private final MockClock mClock = new MockClock();
+ private File mFile;
+
+ @Before
+ public void setup() throws IOException {
+ File systemDir = Files.createTempDirectory("MonotonicClockTest").toFile();
+ mFile = new File(systemDir, "test_monotonic_clock.xml");
+ if (mFile.exists()) {
+ assertThat(mFile.delete()).isTrue();
+ }
+ }
@Test
public void persistence() throws IOException {
- MonotonicClock monotonicClock = new MonotonicClock(1000, mClock);
+ MonotonicClock monotonicClock = new MonotonicClock(mFile, 1000, mClock);
mClock.realtime = 234;
assertThat(monotonicClock.monotonicTime()).isEqualTo(1234);
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- monotonicClock.writeXml(out, Xml.newBinarySerializer());
+ monotonicClock.write();
mClock.realtime = 42;
- MonotonicClock newMonotonicClock = new MonotonicClock(0, mClock);
- ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
- newMonotonicClock.readXml(in, Xml.newBinaryPullParser());
+ MonotonicClock newMonotonicClock = new MonotonicClock(mFile, 0, mClock);
mClock.realtime = 2000;
assertThat(newMonotonicClock.monotonicTime()).isEqualTo(1234 - 42 + 2000);
}
+
+ @Test
+ public void constructor() {
+ MonotonicClock monotonicClock = new MonotonicClock(null, 1000, mClock);
+ mClock.realtime = 234;
+
+ assertThat(monotonicClock.monotonicTime()).isEqualTo(1234);
+ }
+
+ @Test
+ public void corruptedFile() throws IOException {
+ // Create an invalid binary XML file to cause IOException: "Unexpected magic number"
+ try (FileWriter w = new FileWriter(mFile)) {
+ w.write("garbage");
+ }
+
+ MonotonicClock monotonicClock = new MonotonicClock(mFile, 1000, mClock);
+ mClock.realtime = 234;
+
+ assertThat(monotonicClock.monotonicTime()).isEqualTo(1234);
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/os/StoragedUidIoStatsReaderTest.java b/core/tests/coretests/src/com/android/internal/os/StoragedUidIoStatsReaderTest.java
index 84c93c2..80061a5 100644
--- a/core/tests/coretests/src/com/android/internal/os/StoragedUidIoStatsReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/StoragedUidIoStatsReaderTest.java
@@ -18,7 +18,6 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import android.os.FileUtils;
@@ -73,8 +72,7 @@
@Test
public void testReadNonexistentFile() throws Exception {
mStoragedUidIoStatsReader.readAbsolute(mCallback);
- verifyZeroInteractions(mCallback);
-
+ verifyNoMoreInteractions(mCallback);
}
/**
diff --git a/core/tests/nfctests/OWNERS b/core/tests/nfctests/OWNERS
deleted file mode 100644
index 34b095c..0000000
--- a/core/tests/nfctests/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /core/java/android/nfc/OWNERS
diff --git a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
index 0df5b0a..dcaf676 100644
--- a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
@@ -19,8 +19,10 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED;
import static com.google.common.truth.Truth.assertThat;
@@ -76,12 +78,15 @@
@Rule
public final RavenwoodRule mRavenwood = new RavenwoodRule();
+ private ILockSettings mLockSettings;
+ private static final int USER_ID = 1;
private static final int DEMO_USER_ID = 5;
private LockPatternUtils mLockPatternUtils;
private void configureTest(boolean isSecure, boolean isDemoUser, int deviceDemoMode)
throws Exception {
+ mLockSettings = Mockito.mock(ILockSettings.class);
final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
final MockContentResolver cr = new MockContentResolver(context);
@@ -89,15 +94,14 @@
when(context.getContentResolver()).thenReturn(cr);
Settings.Global.putInt(cr, Settings.Global.DEVICE_DEMO_MODE, deviceDemoMode);
- final ILockSettings ils = Mockito.mock(ILockSettings.class);
- when(ils.getCredentialType(DEMO_USER_ID)).thenReturn(
+ when(mLockSettings.getCredentialType(DEMO_USER_ID)).thenReturn(
isSecure ? LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
: LockPatternUtils.CREDENTIAL_TYPE_NONE);
- when(ils.getLong("lockscreen.password_type", PASSWORD_QUALITY_UNSPECIFIED, DEMO_USER_ID))
- .thenReturn((long) PASSWORD_QUALITY_MANAGED);
+ when(mLockSettings.getLong("lockscreen.password_type", PASSWORD_QUALITY_UNSPECIFIED,
+ DEMO_USER_ID)).thenReturn((long) PASSWORD_QUALITY_MANAGED);
// TODO(b/63758238): stop spying the class under test
mLockPatternUtils = spy(new LockPatternUtils(context));
- when(mLockPatternUtils.getLockSettings()).thenReturn(ils);
+ when(mLockPatternUtils.getLockSettings()).thenReturn(mLockSettings);
doReturn(true).when(mLockPatternUtils).hasSecureLockScreen();
final UserInfo userInfo = Mockito.mock(UserInfo.class);
@@ -108,6 +112,31 @@
}
@Test
+ public void isUserInLockDown() throws Exception {
+ configureTest(true, false, 2);
+
+ // GIVEN strong auth not required
+ when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(STRONG_AUTH_NOT_REQUIRED);
+
+ // THEN user isn't in lockdown
+ assertFalse(mLockPatternUtils.isUserInLockdown(USER_ID));
+
+ // GIVEN lockdown
+ when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(
+ STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
+
+ // THEN user is in lockdown
+ assertTrue(mLockPatternUtils.isUserInLockdown(USER_ID));
+
+ // GIVEN lockdown and lockout
+ when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(
+ STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN | STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
+
+ // THEN user is in lockdown
+ assertTrue(mLockPatternUtils.isUserInLockdown(USER_ID));
+ }
+
+ @Test
public void isLockScreenDisabled_isDemoUser_true() throws Exception {
configureTest(false, true, 2);
assertTrue(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
diff --git a/data/etc/com.android.documentsui.xml b/data/etc/com.android.documentsui.xml
index d32cbec..2e521e3 100644
--- a/data/etc/com.android.documentsui.xml
+++ b/data/etc/com.android.documentsui.xml
@@ -23,5 +23,7 @@
<permission name="android.permission.MODIFY_QUIET_MODE"/>
<permission name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
<permission name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND"/>
+ <!-- Permissions required for reading device configs -->
+ <permission name="android.permission.READ_DEVICE_CONFIG" />
</privapp-permissions>
</permissions>
diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml
index dcc9686..fbe1b8e 100644
--- a/data/etc/com.android.settings.xml
+++ b/data/etc/com.android.settings.xml
@@ -48,6 +48,7 @@
<permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
<permission name="android.permission.READ_SEARCH_INDEXABLES"/>
<permission name="android.permission.REBOOT"/>
+ <permission name="android.permission.RECOVERY"/>
<permission name="android.permission.STATUS_BAR"/>
<permission name="android.permission.SUGGEST_MANUAL_TIME_AND_ZONE"/>
<permission name="android.permission.TETHER_PRIVILEGED"/>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index a1ea2b8..d24720d 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -121,6 +121,7 @@
<permission name="android.permission.BIND_CARRIER_MESSAGING_SERVICE"/>
<permission name="android.permission.BIND_CARRIER_SERVICES"/>
<permission name="android.permission.BIND_CELL_BROADCAST_SERVICE"/>
+ <permission name="android.permission.BIND_DOMAIN_SELECTION_SERVICE"/>
<permission name="android.permission.BIND_IMS_SERVICE"/>
<permission name="android.permission.BIND_SATELLITE_GATEWAY_SERVICE"/>
<permission name="android.permission.BIND_SATELLITE_SERVICE"/>
@@ -426,6 +427,7 @@
<!-- Permissions required for CTS test - android.server.biometrics -->
<permission name="android.permission.USE_BIOMETRIC" />
<permission name="android.permission.TEST_BIOMETRIC" />
+ <permission name="android.permission.MANAGE_BIOMETRIC_DIALOG" />
<!-- Permissions required for CTS test - CtsContactsProviderTestCases -->
<permission name="android.contacts.permission.MANAGE_SIM_ACCOUNTS" />
<!-- Permissions required for CTS test - CtsHdmiCecHostTestCases -->
@@ -511,6 +513,7 @@
<permission name="android.permission.READ_SAFETY_CENTER_STATUS" />
<!-- Permission required for CTS test - CtsTelephonyTestCases -->
<permission name="android.permission.BIND_TELECOM_CONNECTION_SERVICE" />
+ <permission name="android.permission.ACCESS_LAST_KNOWN_CELL_ID" />
<!-- Permission required for CTS test - CtsAppTestCases -->
<permission name="android.permission.CAPTURE_MEDIA_OUTPUT" />
<permission name="android.permission.CAPTURE_TUNER_AUDIO_INPUT" />
@@ -541,6 +544,8 @@
<permission name="android.permission.GET_BINDING_UID_IMPORTANCE"/>
<!-- Permission required for CTS test NotificationManagerZenTest -->
<permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" />
+ <!-- Permission required for BinaryTransparencyService shell API and host test -->
+ <permission name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES" />
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 917a300..c77004d 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -535,6 +535,12 @@
"group": "WM_DEBUG_TASKS",
"at": "com\/android\/server\/wm\/ActivityStarter.java"
},
+ "-1583619037": {
+ "message": "Failed to register MediaProjectionWatcherCallback",
+ "level": "ERROR",
+ "group": "WM_ERROR",
+ "at": "com\/android\/server\/wm\/ScreenRecordingCallbackController.java"
+ },
"-1582845629": {
"message": "Starting animation on %s",
"level": "VERBOSE",
@@ -2983,12 +2989,6 @@
"group": "WM_DEBUG_SCREEN_ON",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "466506262": {
- "message": "Clear freezing of %s: visible=%b freezing=%b",
- "level": "VERBOSE",
- "group": "WM_DEBUG_ORIENTATION",
- "at": "com\/android\/server\/wm\/ActivityRecord.java"
- },
"485170982": {
"message": "Not finishing noHistory %s on stop because we're just sleeping",
"level": "DEBUG",
@@ -4333,6 +4333,12 @@
"group": "WM_DEBUG_ANIM",
"at": "com\/android\/server\/wm\/WindowStateAnimator.java"
},
+ "1810872941": {
+ "message": "setWallpaperCropHints: non-existent wallpaper token: %s",
+ "level": "WARN",
+ "group": "WM_ERROR",
+ "at": "com\/android\/server\/wm\/WindowManagerService.java"
+ },
"1820873642": {
"message": "SyncGroup %d: Unfinished dependencies: %s",
"level": "VERBOSE",
diff --git a/data/fonts/Android.bp b/data/fonts/Android.bp
index 3dd9ba9..471acaa 100644
--- a/data/fonts/Android.bp
+++ b/data/fonts/Android.bp
@@ -48,12 +48,35 @@
// Copies the font configuration file into system/etc for the product as fonts.xml.
// Additional fonts should be installed to /product/fonts/ alongside a corresponding
// fonts_customiztion.xml in /product/etc/
-prebuilt_etc {
- name: "fonts.xml",
- src: "fonts.xml",
+
+soong_config_bool_variable {
+ name: "use_var_font",
}
-prebuilt_etc {
+soong_config_module_type {
+ name: "prebuilt_fonts_xml",
+ module_type: "prebuilt_etc",
+ config_namespace: "noto_sans_cjk_config",
+ bool_variables: ["use_var_font"],
+ properties: ["src"],
+}
+
+prebuilt_fonts_xml {
+ name: "fonts.xml",
+ src: "fonts.xml",
+ soong_config_variables: {
+ use_var_font: {
+ src: "fonts_cjkvf.xml",
+ },
+ },
+}
+
+prebuilt_fonts_xml {
name: "font_fallback.xml",
src: "font_fallback.xml",
+ soong_config_variables: {
+ use_var_font: {
+ src: "font_fallback_cjkvf.xml",
+ },
+ },
}
diff --git a/data/fonts/font_fallback_cjkvf.xml b/data/fonts/font_fallback_cjkvf.xml
new file mode 100644
index 0000000..70474ba
--- /dev/null
+++ b/data/fonts/font_fallback_cjkvf.xml
@@ -0,0 +1,1015 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ In this file, all fonts without names are added to the default list.
+ Fonts are chosen based on a match: full BCP-47 language tag including
+ script, then just language, and finally order (the first font containing
+ the glyph).
+
+ Order of appearance is also the tiebreaker for weight matching. This is
+ the reason why the 900 weights of Roboto precede the 700 weights - we
+ prefer the former when an 800 weight is requested. Since bold spans
+ effectively add 300 to the weight, this ensures that 900 is the bold
+ paired with the 500 weight, ensuring adequate contrast.
+
+ TODO(rsheeter) update comment; ordering to match 800 to 900 is no longer required
+-->
+<familyset version="23">
+ <!-- first font is default -->
+ <family name="sans-serif" varFamilyType="2">
+ <font>Roboto-Regular.ttf
+ <axis tag="wdth" stylevalue="100" />
+ </font>
+ </family>
+
+
+ <!-- Note that aliases must come after the fonts they reference. -->
+ <alias name="sans-serif-thin" to="sans-serif" weight="100" />
+ <alias name="sans-serif-light" to="sans-serif" weight="300" />
+ <alias name="sans-serif-medium" to="sans-serif" weight="500" />
+ <alias name="sans-serif-black" to="sans-serif" weight="900" />
+ <alias name="arial" to="sans-serif" />
+ <alias name="helvetica" to="sans-serif" />
+ <alias name="tahoma" to="sans-serif" />
+ <alias name="verdana" to="sans-serif" />
+
+ <family name="sans-serif-condensed" varFamilyType="2">
+ <font>Roboto-Regular.ttf
+ <axis tag="wdth" stylevalue="75" />
+ </font>
+ </family>
+ <alias name="sans-serif-condensed-light" to="sans-serif-condensed" weight="300" />
+ <alias name="sans-serif-condensed-medium" to="sans-serif-condensed" weight="500" />
+
+ <family name="serif">
+ <font weight="400" style="normal" postScriptName="NotoSerif">NotoSerif-Regular.ttf</font>
+ <font weight="700" style="normal">NotoSerif-Bold.ttf</font>
+ <font weight="400" style="italic">NotoSerif-Italic.ttf</font>
+ <font weight="700" style="italic">NotoSerif-BoldItalic.ttf</font>
+ </family>
+ <alias name="serif-bold" to="serif" weight="700" />
+ <alias name="times" to="serif" />
+ <alias name="times new roman" to="serif" />
+ <alias name="palatino" to="serif" />
+ <alias name="georgia" to="serif" />
+ <alias name="baskerville" to="serif" />
+ <alias name="goudy" to="serif" />
+ <alias name="fantasy" to="serif" />
+ <alias name="ITC Stone Serif" to="serif" />
+
+ <family name="monospace">
+ <font weight="400" style="normal">DroidSansMono.ttf</font>
+ </family>
+ <alias name="sans-serif-monospace" to="monospace" />
+ <alias name="monaco" to="monospace" />
+
+ <family name="serif-monospace">
+ <font weight="400" style="normal" postScriptName="CutiveMono-Regular">CutiveMono.ttf</font>
+ </family>
+ <alias name="courier" to="serif-monospace" />
+ <alias name="courier new" to="serif-monospace" />
+
+ <family name="casual">
+ <font weight="400" style="normal" postScriptName="ComingSoon-Regular">ComingSoon.ttf</font>
+ </family>
+
+ <family name="cursive" varFamilyType="1">
+ <font>DancingScript-Regular.ttf</font>
+ </family>
+
+ <family name="sans-serif-smallcaps">
+ <font weight="400" style="normal">CarroisGothicSC-Regular.ttf</font>
+ </family>
+
+ <family name="source-sans-pro">
+ <font weight="400" style="normal">SourceSansPro-Regular.ttf</font>
+ <font weight="400" style="italic">SourceSansPro-Italic.ttf</font>
+ <font weight="600" style="normal">SourceSansPro-SemiBold.ttf</font>
+ <font weight="600" style="italic">SourceSansPro-SemiBoldItalic.ttf</font>
+ <font weight="700" style="normal">SourceSansPro-Bold.ttf</font>
+ <font weight="700" style="italic">SourceSansPro-BoldItalic.ttf</font>
+ </family>
+ <alias name="source-sans-pro-semi-bold" to="source-sans-pro" weight="600"/>
+
+ <family name="roboto-flex" varFamilyType="2">
+ <font>RobotoFlex-Regular.ttf
+ <axis tag="wdth" stylevalue="100" />
+ </font>
+ </family>
+
+ <!-- fallback fonts -->
+ <family lang="und-Arab" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoNaskhArabic">
+ NotoNaskhArabic-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoNaskhArabic-Bold.ttf</font>
+ </family>
+ <family lang="und-Arab" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoNaskhArabicUI">
+ NotoNaskhArabicUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Ethi" varFamilyType="1">
+ <font postScriptName="NotoSansEthiopic-Regular">
+ NotoSansEthiopic-VF.ttf
+ </font>
+ <font fallbackFor="serif" postScriptName="NotoSerifEthiopic-Regular">
+ NotoSerifEthiopic-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Hebr">
+ <font weight="400" style="normal" postScriptName="NotoSansHebrew">
+ NotoSansHebrew-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansHebrew-Bold.ttf</font>
+ <font weight="400" style="normal" fallbackFor="serif">NotoSerifHebrew-Regular.ttf</font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifHebrew-Bold.ttf</font>
+ </family>
+ <family lang="und-Thai" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansThai">NotoSansThai-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansThai-Bold.ttf</font>
+ <font weight="400" style="normal" fallbackFor="serif">
+ NotoSerifThai-Regular.ttf
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifThai-Bold.ttf</font>
+ </family>
+ <family lang="und-Thai" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansThaiUI">
+ NotoSansThaiUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansThaiUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Armn" varFamilyType="1">
+ <font postScriptName="NotoSansArmenian-Regular">
+ NotoSansArmenian-VF.ttf
+ </font>
+ <font fallbackFor="serif" postScriptName="NotoSerifArmenian-Regular">
+ NotoSerifArmenian-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Geor,und-Geok" varFamilyType="1">
+ <font postScriptName="NotoSansGeorgian-Regular">
+ NotoSansGeorgian-VF.ttf
+ </font>
+ <font fallbackFor="serif" postScriptName="NotoSerifGeorgian-Regular">
+ NotoSerifGeorgian-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Deva" variant="elegant" varFamilyType="1">
+ <font postScriptName="NotoSansDevanagari-Regular">
+ NotoSansDevanagari-VF.ttf
+ </font>
+ <font fallbackFor="serif" postScriptName="NotoSerifDevanagari-Regular">
+ NotoSerifDevanagari-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Deva" variant="compact" varFamilyType="1">
+ <font postScriptName="NotoSansDevanagariUI-Regular">
+ NotoSansDevanagariUI-VF.ttf
+ </font>
+ </family>
+
+ <!-- All scripts of India should come after Devanagari, due to shared
+ danda characters.
+ -->
+ <family lang="und-Gujr" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansGujarati">
+ NotoSansGujarati-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansGujarati-Bold.ttf</font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Gujr" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansGujaratiUI">
+ NotoSansGujaratiUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansGujaratiUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Guru" variant="elegant" varFamilyType="1">
+ <font postScriptName="NotoSansGurmukhi-Regular">
+ NotoSansGurmukhi-VF.ttf
+ </font>
+ <font fallbackFor="serif" postScriptName="NotoSerifGurmukhi-Regular">
+ NotoSerifGurmukhi-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Guru" variant="compact" varFamilyType="1">
+ <font postScriptName="NotoSansGurmukhiUI-Regular">
+ NotoSansGurmukhiUI-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Taml" variant="elegant" varFamilyType="1">
+ <font postScriptName="NotoSansTamil-Regular">
+ NotoSansTamil-VF.ttf
+ </font>
+ <font fallbackFor="serif" postScriptName="NotoSerifTamil-Regular">
+ NotoSerifTamil-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Taml" variant="compact" varFamilyType="1">
+ <font postScriptName="NotoSansTamilUI-Regular">
+ NotoSansTamilUI-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Mlym" variant="elegant" varFamilyType="1">
+ <font postScriptName="NotoSansMalayalam-Regular">
+ NotoSansMalayalam-VF.ttf
+ </font>
+ <font fallbackFor="serif" postScriptName="NotoSerifMalayalam-Regular">
+ NotoSerifMalayalam-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Mlym" variant="compact" varFamilyType="1">
+ <font postScriptName="NotoSansMalayalamUI-Regular">
+ NotoSansMalayalamUI-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Beng" variant="elegant" varFamilyType="1">
+ <font postScriptName="NotoSansBengali-Regular">
+ NotoSansBengali-VF.ttf
+ </font>
+ <font fallbackFor="serif" postScriptName="NotoSerifBengali-Regular">
+ NotoSerifBengali-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Beng" variant="compact" varFamilyType="1">
+ <font postScriptName="NotoSansBengaliUI-Regular">
+ NotoSansBengaliUI-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Telu" variant="elegant" varFamilyType="1">
+ <font postScriptName="NotoSansTelugu-Regular">
+ NotoSansTelugu-VF.ttf
+ </font>
+ <font fallbackFor="serif" postScriptName="NotoSerifTelugu-Regular">
+ NotoSerifTelugu-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Telu" variant="compact" varFamilyType="1">
+ <font postScriptName="NotoSansTeluguUI-Regular">
+ NotoSansTeluguUI-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Knda" variant="elegant" varFamilyType="1">
+ <font postScriptName="NotoSansKannada-Regular">
+ NotoSansKannada-VF.ttf
+ </font>
+ <font fallbackFor="serif" postScriptName="NotoSerifKannada-Regular">
+ NotoSerifKannada-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Knda" variant="compact" varFamilyType="1">
+ <font postScriptName="NotoSansKannadaUI-Regular">
+ NotoSansKannadaUI-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Orya" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansOriya">NotoSansOriya-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansOriya-Bold.ttf</font>
+ </family>
+ <family lang="und-Orya" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansOriyaUI">
+ NotoSansOriyaUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansOriyaUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Sinh" variant="elegant" varFamilyType="1">
+ <font postScriptName="NotoSansSinhala-Regular">
+ NotoSansSinhala-VF.ttf
+ </font>
+ <font fallbackFor="serif" postScriptName="NotoSerifSinhala-Regular">
+ NotoSerifSinhala-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Sinh" variant="compact" varFamilyType="1">
+ <font postScriptName="NotoSansSinhalaUI-Regular">
+ NotoSansSinhalaUI-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Khmr" variant="elegant">
+ <font weight="100" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="26.0"/>
+ </font>
+ <font weight="200" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="39.0"/>
+ </font>
+ <font weight="300" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="58.0"/>
+ </font>
+ <font weight="400" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="90.0"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="108.0"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="128.0"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="151.0"/>
+ </font>
+ <font weight="800" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="169.0"/>
+ </font>
+ <font weight="900" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="190.0"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif">NotoSerifKhmer-Regular.otf</font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifKhmer-Bold.otf</font>
+ </family>
+ <family lang="und-Khmr" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansKhmerUI">
+ NotoSansKhmerUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansKhmerUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Laoo" variant="elegant">
+ <font weight="400" style="normal">NotoSansLao-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansLao-Bold.ttf</font>
+ <font weight="400" style="normal" fallbackFor="serif">
+ NotoSerifLao-Regular.ttf
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifLao-Bold.ttf</font>
+ </family>
+ <family lang="und-Laoo" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansLaoUI">NotoSansLaoUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Mymr" variant="elegant">
+ <font weight="400" style="normal">NotoSansMyanmar-Regular.otf</font>
+ <font weight="500" style="normal">NotoSansMyanmar-Medium.otf</font>
+ <font weight="700" style="normal">NotoSansMyanmar-Bold.otf</font>
+ <font weight="400" style="normal" fallbackFor="serif">NotoSerifMyanmar-Regular.otf</font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifMyanmar-Bold.otf</font>
+ </family>
+ <family lang="und-Mymr" variant="compact">
+ <font weight="400" style="normal">NotoSansMyanmarUI-Regular.otf</font>
+ <font weight="500" style="normal">NotoSansMyanmarUI-Medium.otf</font>
+ <font weight="700" style="normal">NotoSansMyanmarUI-Bold.otf</font>
+ </family>
+ <family lang="und-Thaa">
+ <font weight="400" style="normal" postScriptName="NotoSansThaana">
+ NotoSansThaana-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansThaana-Bold.ttf</font>
+ </family>
+ <family lang="und-Cham">
+ <font weight="400" style="normal" postScriptName="NotoSansCham">NotoSansCham-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansCham-Bold.ttf</font>
+ </family>
+ <family lang="und-Ahom">
+ <font weight="400" style="normal">NotoSansAhom-Regular.otf</font>
+ </family>
+ <family lang="und-Adlm" varFamilyType="1">
+ <font postScriptName="NotoSansAdlam-Regular">
+ NotoSansAdlam-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Avst">
+ <font weight="400" style="normal" postScriptName="NotoSansAvestan">
+ NotoSansAvestan-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Bali">
+ <font weight="400" style="normal" postScriptName="NotoSansBalinese">
+ NotoSansBalinese-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Bamu">
+ <font weight="400" style="normal" postScriptName="NotoSansBamum">NotoSansBamum-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Batk">
+ <font weight="400" style="normal" postScriptName="NotoSansBatak">NotoSansBatak-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Brah">
+ <font weight="400" style="normal" postScriptName="NotoSansBrahmi">
+ NotoSansBrahmi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Bugi">
+ <font weight="400" style="normal" postScriptName="NotoSansBuginese">
+ NotoSansBuginese-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Buhd">
+ <font weight="400" style="normal" postScriptName="NotoSansBuhid">NotoSansBuhid-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Cans">
+ <font weight="400" style="normal">
+ NotoSansCanadianAboriginal-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Cari">
+ <font weight="400" style="normal" postScriptName="NotoSansCarian">
+ NotoSansCarian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Cakm">
+ <font weight="400" style="normal">NotoSansChakma-Regular.otf</font>
+ </family>
+ <family lang="und-Cher">
+ <font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font>
+ </family>
+ <family lang="und-Copt">
+ <font weight="400" style="normal" postScriptName="NotoSansCoptic">
+ NotoSansCoptic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Xsux">
+ <font weight="400" style="normal" postScriptName="NotoSansCuneiform">
+ NotoSansCuneiform-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Cprt">
+ <font weight="400" style="normal" postScriptName="NotoSansCypriot">
+ NotoSansCypriot-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Dsrt">
+ <font weight="400" style="normal" postScriptName="NotoSansDeseret">
+ NotoSansDeseret-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Egyp">
+ <font weight="400" style="normal" postScriptName="NotoSansEgyptianHieroglyphs">
+ NotoSansEgyptianHieroglyphs-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Elba">
+ <font weight="400" style="normal">NotoSansElbasan-Regular.otf</font>
+ </family>
+ <family lang="und-Glag">
+ <font weight="400" style="normal" postScriptName="NotoSansGlagolitic">
+ NotoSansGlagolitic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Goth">
+ <font weight="400" style="normal" postScriptName="NotoSansGothic">
+ NotoSansGothic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Hano">
+ <font weight="400" style="normal" postScriptName="NotoSansHanunoo">
+ NotoSansHanunoo-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Armi">
+ <font weight="400" style="normal" postScriptName="NotoSansImperialAramaic">
+ NotoSansImperialAramaic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Phli">
+ <font weight="400" style="normal" postScriptName="NotoSansInscriptionalPahlavi">
+ NotoSansInscriptionalPahlavi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Prti">
+ <font weight="400" style="normal" postScriptName="NotoSansInscriptionalParthian">
+ NotoSansInscriptionalParthian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Java">
+ <font weight="400" style="normal">NotoSansJavanese-Regular.otf</font>
+ </family>
+ <family lang="und-Kthi">
+ <font weight="400" style="normal" postScriptName="NotoSansKaithi">
+ NotoSansKaithi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Kali">
+ <font weight="400" style="normal" postScriptName="NotoSansKayahLi">
+ NotoSansKayahLi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Khar">
+ <font weight="400" style="normal" postScriptName="NotoSansKharoshthi">
+ NotoSansKharoshthi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lepc">
+ <font weight="400" style="normal" postScriptName="NotoSansLepcha">
+ NotoSansLepcha-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Limb">
+ <font weight="400" style="normal" postScriptName="NotoSansLimbu">NotoSansLimbu-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Linb">
+ <font weight="400" style="normal" postScriptName="NotoSansLinearB">
+ NotoSansLinearB-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lisu">
+ <font weight="400" style="normal" postScriptName="NotoSansLisu">NotoSansLisu-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lyci">
+ <font weight="400" style="normal" postScriptName="NotoSansLycian">
+ NotoSansLycian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lydi">
+ <font weight="400" style="normal" postScriptName="NotoSansLydian">
+ NotoSansLydian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Mand">
+ <font weight="400" style="normal" postScriptName="NotoSansMandaic">
+ NotoSansMandaic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Mtei">
+ <font weight="400" style="normal" postScriptName="NotoSansMeeteiMayek">
+ NotoSansMeeteiMayek-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Talu">
+ <font weight="400" style="normal" postScriptName="NotoSansNewTaiLue">
+ NotoSansNewTaiLue-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Nkoo">
+ <font weight="400" style="normal" postScriptName="NotoSansNKo">NotoSansNKo-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Ogam">
+ <font weight="400" style="normal" postScriptName="NotoSansOgham">NotoSansOgham-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Olck">
+ <font weight="400" style="normal" postScriptName="NotoSansOlChiki">
+ NotoSansOlChiki-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Ital">
+ <font weight="400" style="normal" postScriptName="NotoSansOldItalic">
+ NotoSansOldItalic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Xpeo">
+ <font weight="400" style="normal" postScriptName="NotoSansOldPersian">
+ NotoSansOldPersian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Sarb">
+ <font weight="400" style="normal" postScriptName="NotoSansOldSouthArabian">
+ NotoSansOldSouthArabian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Orkh">
+ <font weight="400" style="normal" postScriptName="NotoSansOldTurkic">
+ NotoSansOldTurkic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Osge">
+ <font weight="400" style="normal">NotoSansOsage-Regular.ttf</font>
+ </family>
+ <family lang="und-Osma">
+ <font weight="400" style="normal" postScriptName="NotoSansOsmanya">
+ NotoSansOsmanya-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Phnx">
+ <font weight="400" style="normal" postScriptName="NotoSansPhoenician">
+ NotoSansPhoenician-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Rjng">
+ <font weight="400" style="normal" postScriptName="NotoSansRejang">
+ NotoSansRejang-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Runr">
+ <font weight="400" style="normal" postScriptName="NotoSansRunic">NotoSansRunic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Samr">
+ <font weight="400" style="normal" postScriptName="NotoSansSamaritan">
+ NotoSansSamaritan-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Saur">
+ <font weight="400" style="normal" postScriptName="NotoSansSaurashtra">
+ NotoSansSaurashtra-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Shaw">
+ <font weight="400" style="normal" postScriptName="NotoSansShavian">
+ NotoSansShavian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Sund">
+ <font weight="400" style="normal" postScriptName="NotoSansSundanese">
+ NotoSansSundanese-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Sylo">
+ <font weight="400" style="normal" postScriptName="NotoSansSylotiNagri">
+ NotoSansSylotiNagri-Regular.ttf
+ </font>
+ </family>
+ <!-- Esrangela should precede Eastern and Western Syriac, since it's our default form. -->
+ <family lang="und-Syre">
+ <font weight="400" style="normal" postScriptName="NotoSansSyriacEstrangela">
+ NotoSansSyriacEstrangela-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Syrn">
+ <font weight="400" style="normal" postScriptName="NotoSansSyriacEastern">
+ NotoSansSyriacEastern-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Syrj">
+ <font weight="400" style="normal" postScriptName="NotoSansSyriacWestern">
+ NotoSansSyriacWestern-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Tglg">
+ <font weight="400" style="normal" postScriptName="NotoSansTagalog">
+ NotoSansTagalog-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Tagb">
+ <font weight="400" style="normal" postScriptName="NotoSansTagbanwa">
+ NotoSansTagbanwa-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lana">
+ <font weight="400" style="normal" postScriptName="NotoSansTaiTham">
+ NotoSansTaiTham-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Tavt">
+ <font weight="400" style="normal" postScriptName="NotoSansTaiViet">
+ NotoSansTaiViet-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Tibt" varFamilyType="1">
+ <font postScriptName="NotoSerifTibetan-Regular">
+ NotoSerifTibetan-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Tfng">
+ <font weight="400" style="normal">NotoSansTifinagh-Regular.otf</font>
+ </family>
+ <family lang="und-Ugar">
+ <font weight="400" style="normal" postScriptName="NotoSansUgaritic">
+ NotoSansUgaritic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Vaii">
+ <font weight="400" style="normal" postScriptName="NotoSansVai">NotoSansVai-Regular.ttf
+ </font>
+ </family>
+ <family>
+ <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font>
+ </family>
+ <family lang="zh-Hans">
+ <font weight="100" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="100"/>
+ </font>
+ <font weight="200" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="200"/>
+ </font>
+ <font weight="300" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="300"/>
+ </font>
+ <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="800" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="800"/>
+ </font>
+ <font weight="900" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="900"/>
+ </font>
+ <font weight="400" style="normal" index="2" fallbackFor="serif"
+ postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+ </font>
+ </family>
+ <family lang="zh-Hant,zh-Bopo">
+ <font weight="100" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="100"/>
+ </font>
+ <font weight="200" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="200"/>
+ </font>
+ <font weight="300" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="300"/>
+ </font>
+ <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="800" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="800"/>
+ </font>
+ <font weight="900" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="900"/>
+ </font>
+ <font weight="400" style="normal" index="3" fallbackFor="serif"
+ postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+ </font>
+ </family>
+ <family lang="ja">
+ <font weight="100" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="100"/>
+ </font>
+ <font weight="200" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="200"/>
+ </font>
+ <font weight="300" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="300"/>
+ </font>
+ <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="800" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="800"/>
+ </font>
+ <font weight="900" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="900"/>
+ </font>
+ <font weight="400" style="normal" index="0" fallbackFor="serif"
+ postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+ </font>
+ </family>
+ <family lang="ko">
+ <font weight="100" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="100"/>
+ </font>
+ <font weight="200" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="200"/>
+ </font>
+ <font weight="300" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="300"/>
+ </font>
+ <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="800" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="800"/>
+ </font>
+ <font weight="900" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="900"/>
+ </font>
+ <font weight="400" style="normal" index="1" fallbackFor="serif"
+ postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+ </font>
+ </family>
+ <family lang="und-Zsye">
+ <font weight="400" style="normal">NotoColorEmoji.ttf</font>
+ </family>
+ <family lang="und-Zsye">
+ <font weight="400" style="normal">NotoColorEmojiFlags.ttf</font>
+ </family>
+ <family lang="und-Zsym">
+ <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted2.ttf</font>
+ </family>
+ <!--
+ Tai Le, Yi, Mongolian, and Phags-pa are intentionally kept last, to make sure they don't
+ override the East Asian punctuation for Chinese.
+ -->
+ <family lang="und-Tale">
+ <font weight="400" style="normal" postScriptName="NotoSansTaiLe">NotoSansTaiLe-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Yiii">
+ <font weight="400" style="normal" postScriptName="NotoSansYi">NotoSansYi-Regular.ttf</font>
+ </family>
+ <family lang="und-Mong">
+ <font weight="400" style="normal" postScriptName="NotoSansMongolian">
+ NotoSansMongolian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Phag">
+ <font weight="400" style="normal" postScriptName="NotoSansPhagsPa">
+ NotoSansPhagsPa-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Hluw">
+ <font weight="400" style="normal">NotoSansAnatolianHieroglyphs-Regular.otf</font>
+ </family>
+ <family lang="und-Bass">
+ <font weight="400" style="normal">NotoSansBassaVah-Regular.otf</font>
+ </family>
+ <family lang="und-Bhks">
+ <font weight="400" style="normal">NotoSansBhaiksuki-Regular.otf</font>
+ </family>
+ <family lang="und-Hatr">
+ <font weight="400" style="normal">NotoSansHatran-Regular.otf</font>
+ </family>
+ <family lang="und-Lina">
+ <font weight="400" style="normal">NotoSansLinearA-Regular.otf</font>
+ </family>
+ <family lang="und-Mani">
+ <font weight="400" style="normal">NotoSansManichaean-Regular.otf</font>
+ </family>
+ <family lang="und-Marc">
+ <font weight="400" style="normal">NotoSansMarchen-Regular.otf</font>
+ </family>
+ <family lang="und-Merc">
+ <font weight="400" style="normal">NotoSansMeroitic-Regular.otf</font>
+ </family>
+ <family lang="und-Plrd">
+ <font weight="400" style="normal">NotoSansMiao-Regular.otf</font>
+ </family>
+ <family lang="und-Mroo">
+ <font weight="400" style="normal">NotoSansMro-Regular.otf</font>
+ </family>
+ <family lang="und-Mult">
+ <font weight="400" style="normal">NotoSansMultani-Regular.otf</font>
+ </family>
+ <family lang="und-Nbat">
+ <font weight="400" style="normal">NotoSansNabataean-Regular.otf</font>
+ </family>
+ <family lang="und-Newa">
+ <font weight="400" style="normal">NotoSansNewa-Regular.otf</font>
+ </family>
+ <family lang="und-Narb">
+ <font weight="400" style="normal">NotoSansOldNorthArabian-Regular.otf</font>
+ </family>
+ <family lang="und-Perm">
+ <font weight="400" style="normal">NotoSansOldPermic-Regular.otf</font>
+ </family>
+ <family lang="und-Hmng">
+ <font weight="400" style="normal">NotoSansPahawhHmong-Regular.otf</font>
+ </family>
+ <family lang="und-Palm">
+ <font weight="400" style="normal">NotoSansPalmyrene-Regular.otf</font>
+ </family>
+ <family lang="und-Pauc">
+ <font weight="400" style="normal">NotoSansPauCinHau-Regular.otf</font>
+ </family>
+ <family lang="und-Shrd">
+ <font weight="400" style="normal">NotoSansSharada-Regular.otf</font>
+ </family>
+ <family lang="und-Sora">
+ <font weight="400" style="normal">NotoSansSoraSompeng-Regular.otf</font>
+ </family>
+ <family lang="und-Gong">
+ <font weight="400" style="normal">NotoSansGunjalaGondi-Regular.otf</font>
+ </family>
+ <family lang="und-Rohg">
+ <font weight="400" style="normal">NotoSansHanifiRohingya-Regular.otf</font>
+ </family>
+ <family lang="und-Khoj">
+ <font weight="400" style="normal">NotoSansKhojki-Regular.otf</font>
+ </family>
+ <family lang="und-Gonm">
+ <font weight="400" style="normal">NotoSansMasaramGondi-Regular.otf</font>
+ </family>
+ <family lang="und-Wcho">
+ <font weight="400" style="normal">NotoSansWancho-Regular.otf</font>
+ </family>
+ <family lang="und-Wara">
+ <font weight="400" style="normal">NotoSansWarangCiti-Regular.otf</font>
+ </family>
+ <family lang="und-Gran">
+ <font weight="400" style="normal">NotoSansGrantha-Regular.ttf</font>
+ </family>
+ <family lang="und-Modi">
+ <font weight="400" style="normal">NotoSansModi-Regular.ttf</font>
+ </family>
+ <family lang="und-Dogr">
+ <font weight="400" style="normal">NotoSerifDogra-Regular.ttf</font>
+ </family>
+ <family lang="und-Medf" varFamilyType="1">
+ <font postScriptName="NotoSansMedefaidrin-Regular">
+ NotoSansMedefaidrin-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Soyo" varFamilyType="1">
+ <font postScriptName="NotoSansSoyombo-Regular">
+ NotoSansSoyombo-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Takr" varFamilyType="1">
+ <font postScriptName="NotoSansTakri-Regular">
+ NotoSansTakri-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Hmnp" varFamilyType="1">
+ <font postScriptName="NotoSerifHmongNyiakeng-Regular">
+ NotoSerifNyiakengPuachueHmong-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Yezi" varFamilyType="1">
+ <font postScriptName="NotoSerifYezidi-Regular">
+ NotoSerifYezidi-VF.ttf
+ </font>
+ </family>
+</familyset>
diff --git a/data/fonts/fonts_cjkvf.xml b/data/fonts/fonts_cjkvf.xml
new file mode 100644
index 0000000..8d91edd
--- /dev/null
+++ b/data/fonts/fonts_cjkvf.xml
@@ -0,0 +1,1785 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ DEPRECATED: This XML file is no longer a source of the font files installed
+ in the system.
+
+ For the device vendors: please add your font configurations to the
+ platform/frameworks/base/data/font_fallback.xml and also add it to this XML
+ file as much as possible for apps that reads this XML file.
+
+ For the application developers: please stop reading this XML file and use
+ android.graphics.fonts.SystemFonts#getAvailableFonts Java API or
+ ASystemFontIterator_open NDK API for getting list of system installed
+ font files.
+
+ WARNING: Parsing of this file by third-party apps is not supported. The
+ file, and the font files it refers to, will be renamed and/or moved out
+ from their respective location in the next Android release, and/or the
+ format or syntax of the file may change significantly. If you parse this
+ file for information about system fonts, do it at your own risk. Your
+ application will almost certainly break with the next major Android
+ release.
+
+ In this file, all fonts without names are added to the default list.
+ Fonts are chosen based on a match: full BCP-47 language tag including
+ script, then just language, and finally order (the first font containing
+ the glyph).
+
+ Order of appearance is also the tiebreaker for weight matching. This is
+ the reason why the 900 weights of Roboto precede the 700 weights - we
+ prefer the former when an 800 weight is requested. Since bold spans
+ effectively add 300 to the weight, this ensures that 900 is the bold
+ paired with the 500 weight, ensuring adequate contrast.
+
+ TODO(rsheeter) update comment; ordering to match 800 to 900 is no longer required
+-->
+<familyset version="23">
+ <!-- first font is default -->
+ <family name="sans-serif">
+ <font weight="100" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="100" />
+ </font>
+ <font weight="200" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="200" />
+ </font>
+ <font weight="300" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="300" />
+ </font>
+ <font weight="400" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="400" />
+ </font>
+ <font weight="500" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="500" />
+ </font>
+ <font weight="600" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="600" />
+ </font>
+ <font weight="700" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="700" />
+ </font>
+ <font weight="800" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="800" />
+ </font>
+ <font weight="900" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="900" />
+ </font>
+ <font weight="100" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="100" />
+ </font>
+ <font weight="200" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="200" />
+ </font>
+ <font weight="300" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="300" />
+ </font>
+ <font weight="400" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="400" />
+ </font>
+ <font weight="500" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="500" />
+ </font>
+ <font weight="600" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="600" />
+ </font>
+ <font weight="700" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="700" />
+ </font>
+ <font weight="800" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="800" />
+ </font>
+ <font weight="900" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="900" />
+ </font>
+ </family>
+
+
+ <!-- Note that aliases must come after the fonts they reference. -->
+ <alias name="sans-serif-thin" to="sans-serif" weight="100" />
+ <alias name="sans-serif-light" to="sans-serif" weight="300" />
+ <alias name="sans-serif-medium" to="sans-serif" weight="500" />
+ <alias name="sans-serif-black" to="sans-serif" weight="900" />
+ <alias name="arial" to="sans-serif" />
+ <alias name="helvetica" to="sans-serif" />
+ <alias name="tahoma" to="sans-serif" />
+ <alias name="verdana" to="sans-serif" />
+
+ <family name="sans-serif-condensed">
+ <font weight="100" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="100" />
+ </font>
+ <font weight="200" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="200" />
+ </font>
+ <font weight="300" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="300" />
+ </font>
+ <font weight="400" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="400" />
+ </font>
+ <font weight="500" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="500" />
+ </font>
+ <font weight="600" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="600" />
+ </font>
+ <font weight="700" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="700" />
+ </font>
+ <font weight="800" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="800" />
+ </font>
+ <font weight="900" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="900" />
+ </font>
+ <font weight="100" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="100" />
+ </font>
+ <font weight="200" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="200" />
+ </font>
+ <font weight="300" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="300" />
+ </font>
+ <font weight="400" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="400" />
+ </font>
+ <font weight="500" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="500" />
+ </font>
+ <font weight="600" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="600" />
+ </font>
+ <font weight="700" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="700" />
+ </font>
+ <font weight="800" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="800" />
+ </font>
+ <font weight="900" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="900" />
+ </font>
+ </family>
+ <alias name="sans-serif-condensed-light" to="sans-serif-condensed" weight="300" />
+ <alias name="sans-serif-condensed-medium" to="sans-serif-condensed" weight="500" />
+
+ <family name="serif">
+ <font weight="400" style="normal" postScriptName="NotoSerif">NotoSerif-Regular.ttf</font>
+ <font weight="700" style="normal">NotoSerif-Bold.ttf</font>
+ <font weight="400" style="italic">NotoSerif-Italic.ttf</font>
+ <font weight="700" style="italic">NotoSerif-BoldItalic.ttf</font>
+ </family>
+ <alias name="serif-bold" to="serif" weight="700" />
+ <alias name="times" to="serif" />
+ <alias name="times new roman" to="serif" />
+ <alias name="palatino" to="serif" />
+ <alias name="georgia" to="serif" />
+ <alias name="baskerville" to="serif" />
+ <alias name="goudy" to="serif" />
+ <alias name="fantasy" to="serif" />
+ <alias name="ITC Stone Serif" to="serif" />
+
+ <family name="monospace">
+ <font weight="400" style="normal">DroidSansMono.ttf</font>
+ </family>
+ <alias name="sans-serif-monospace" to="monospace" />
+ <alias name="monaco" to="monospace" />
+
+ <family name="serif-monospace">
+ <font weight="400" style="normal" postScriptName="CutiveMono-Regular">CutiveMono.ttf</font>
+ </family>
+ <alias name="courier" to="serif-monospace" />
+ <alias name="courier new" to="serif-monospace" />
+
+ <family name="casual">
+ <font weight="400" style="normal" postScriptName="ComingSoon-Regular">ComingSoon.ttf</font>
+ </family>
+
+ <family name="cursive">
+ <font weight="400" style="normal">DancingScript-Regular.ttf
+ <axis tag="wght" stylevalue="400" />
+ </font>
+ <font weight="700" style="normal">DancingScript-Regular.ttf
+ <axis tag="wght" stylevalue="700" />
+ </font>
+ </family>
+
+ <family name="sans-serif-smallcaps">
+ <font weight="400" style="normal">CarroisGothicSC-Regular.ttf</font>
+ </family>
+
+ <family name="source-sans-pro">
+ <font weight="400" style="normal">SourceSansPro-Regular.ttf</font>
+ <font weight="400" style="italic">SourceSansPro-Italic.ttf</font>
+ <font weight="600" style="normal">SourceSansPro-SemiBold.ttf</font>
+ <font weight="600" style="italic">SourceSansPro-SemiBoldItalic.ttf</font>
+ <font weight="700" style="normal">SourceSansPro-Bold.ttf</font>
+ <font weight="700" style="italic">SourceSansPro-BoldItalic.ttf</font>
+ </family>
+ <alias name="source-sans-pro-semi-bold" to="source-sans-pro" weight="600"/>
+
+ <family name="roboto-flex">
+ <font weight="100" style="normal">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="100" />
+ </font>
+ <font weight="200" style="normal">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="200" />
+ </font>
+ <font weight="300" style="normal">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="300" />
+ </font>
+ <font weight="400" style="normal">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="400" />
+ </font>
+ <font weight="500" style="normal">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="500" />
+ </font>
+ <font weight="600" style="normal">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="600" />
+ </font>
+ <font weight="700" style="normal">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="700" />
+ </font>
+ <font weight="800" style="normal">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="800" />
+ </font>
+ <font weight="900" style="normal">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="900" />
+ </font>
+ <font weight="100" style="italic">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="-10" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="100" />
+ </font>
+ <font weight="200" style="italic">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="-10" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="200" />
+ </font>
+ <font weight="300" style="italic">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="-10" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="300" />
+ </font>
+ <font weight="400" style="italic">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="-10" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="400" />
+ </font>
+ <font weight="500" style="italic">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="-10" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="500" />
+ </font>
+ <font weight="600" style="italic">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="-10" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="600" />
+ </font>
+ <font weight="700" style="italic">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="-10" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="700" />
+ </font>
+ <font weight="800" style="italic">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="-10" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="800" />
+ </font>
+ <font weight="900" style="italic">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="-10" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="900" />
+ </font>
+ </family>
+
+ <!-- fallback fonts -->
+ <family lang="und-Arab" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoNaskhArabic">
+ NotoNaskhArabic-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoNaskhArabic-Bold.ttf</font>
+ </family>
+ <family lang="und-Arab" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoNaskhArabicUI">
+ NotoNaskhArabicUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Ethi">
+ <font weight="400" style="normal" postScriptName="NotoSansEthiopic-Regular">
+ NotoSansEthiopic-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansEthiopic-Regular">
+ NotoSansEthiopic-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansEthiopic-Regular">
+ NotoSansEthiopic-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansEthiopic-Regular">
+ NotoSansEthiopic-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Hebr">
+ <font weight="400" style="normal" postScriptName="NotoSansHebrew">
+ NotoSansHebrew-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansHebrew-Bold.ttf</font>
+ <font weight="400" style="normal" fallbackFor="serif">NotoSerifHebrew-Regular.ttf</font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifHebrew-Bold.ttf</font>
+ </family>
+ <family lang="und-Thai" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansThai">NotoSansThai-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansThai-Bold.ttf</font>
+ <font weight="400" style="normal" fallbackFor="serif">
+ NotoSerifThai-Regular.ttf
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifThai-Bold.ttf</font>
+ </family>
+ <family lang="und-Thai" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansThaiUI">
+ NotoSansThaiUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansThaiUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Armn">
+ <font weight="400" style="normal" postScriptName="NotoSansArmenian-Regular">
+ NotoSansArmenian-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansArmenian-Regular">
+ NotoSansArmenian-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansArmenian-Regular">
+ NotoSansArmenian-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansArmenian-Regular">
+ NotoSansArmenian-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Geor,und-Geok">
+ <font weight="400" style="normal" postScriptName="NotoSansGeorgian-Regular">
+ NotoSansGeorgian-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansGeorgian-Regular">
+ NotoSansGeorgian-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansGeorgian-Regular">
+ NotoSansGeorgian-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansGeorgian-Regular">
+ NotoSansGeorgian-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Deva" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansDevanagari-Regular">
+ NotoSansDevanagari-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansDevanagari-Regular">
+ NotoSansDevanagari-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansDevanagari-Regular">
+ NotoSansDevanagari-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansDevanagari-Regular">
+ NotoSansDevanagari-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Deva" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansDevanagariUI-Regular">
+ NotoSansDevanagariUI-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansDevanagariUI-Regular">
+ NotoSansDevanagariUI-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansDevanagariUI-Regular">
+ NotoSansDevanagariUI-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansDevanagariUI-Regular">
+ NotoSansDevanagariUI-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+
+ <!-- All scripts of India should come after Devanagari, due to shared
+ danda characters.
+ -->
+ <family lang="und-Gujr" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansGujarati">
+ NotoSansGujarati-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansGujarati-Bold.ttf</font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Gujr" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansGujaratiUI">
+ NotoSansGujaratiUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansGujaratiUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Guru" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansGurmukhi-Regular">
+ NotoSansGurmukhi-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansGurmukhi-Regular">
+ NotoSansGurmukhi-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansGurmukhi-Regular">
+ NotoSansGurmukhi-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansGurmukhi-Regular">
+ NotoSansGurmukhi-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Guru" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
+ NotoSansGurmukhiUI-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
+ NotoSansGurmukhiUI-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
+ NotoSansGurmukhiUI-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
+ NotoSansGurmukhiUI-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Taml" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansTamil-Regular">
+ NotoSansTamil-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansTamil-Regular">
+ NotoSansTamil-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansTamil-Regular">
+ NotoSansTamil-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansTamil-Regular">
+ NotoSansTamil-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Taml" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansTamilUI-Regular">
+ NotoSansTamilUI-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansTamilUI-Regular">
+ NotoSansTamilUI-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansTamilUI-Regular">
+ NotoSansTamilUI-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansTamilUI-Regular">
+ NotoSansTamilUI-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Mlym" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansMalayalam-Regular">
+ NotoSansMalayalam-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansMalayalam-Regular">
+ NotoSansMalayalam-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansMalayalam-Regular">
+ NotoSansMalayalam-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansMalayalam-Regular">
+ NotoSansMalayalam-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Mlym" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
+ NotoSansMalayalamUI-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
+ NotoSansMalayalamUI-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
+ NotoSansMalayalamUI-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
+ NotoSansMalayalamUI-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Beng" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansBengali-Regular">
+ NotoSansBengali-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansBengali-Regular">
+ NotoSansBengali-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansBengali-Regular">
+ NotoSansBengali-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansBengali-Regular">
+ NotoSansBengali-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Beng" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansBengaliUI-Regular">
+ NotoSansBengaliUI-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansBengaliUI-Regular">
+ NotoSansBengaliUI-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansBengaliUI-Regular">
+ NotoSansBengaliUI-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansBengaliUI-Regular">
+ NotoSansBengaliUI-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Telu" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansTelugu-Regular">
+ NotoSansTelugu-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansTelugu-Regular">
+ NotoSansTelugu-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansTelugu-Regular">
+ NotoSansTelugu-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansTelugu-Regular">
+ NotoSansTelugu-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Telu" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansTeluguUI-Regular">
+ NotoSansTeluguUI-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansTeluguUI-Regular">
+ NotoSansTeluguUI-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansTeluguUI-Regular">
+ NotoSansTeluguUI-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansTeluguUI-Regular">
+ NotoSansTeluguUI-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Knda" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansKannada-Regular">
+ NotoSansKannada-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansKannada-Regular">
+ NotoSansKannada-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansKannada-Regular">
+ NotoSansKannada-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansKannada-Regular">
+ NotoSansKannada-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Knda" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansKannadaUI-Regular">
+ NotoSansKannadaUI-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansKannadaUI-Regular">
+ NotoSansKannadaUI-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansKannadaUI-Regular">
+ NotoSansKannadaUI-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansKannadaUI-Regular">
+ NotoSansKannadaUI-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Orya" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansOriya">NotoSansOriya-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansOriya-Bold.ttf</font>
+ </family>
+ <family lang="und-Orya" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansOriyaUI">
+ NotoSansOriyaUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansOriyaUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Sinh" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansSinhala-Regular">
+ NotoSansSinhala-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansSinhala-Regular">
+ NotoSansSinhala-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansSinhala-Regular">
+ NotoSansSinhala-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansSinhala-Regular">
+ NotoSansSinhala-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Sinh" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
+ NotoSansSinhalaUI-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
+ NotoSansSinhalaUI-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
+ NotoSansSinhalaUI-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
+ NotoSansSinhalaUI-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Khmr" variant="elegant">
+ <font weight="100" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="26.0"/>
+ </font>
+ <font weight="200" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="39.0"/>
+ </font>
+ <font weight="300" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="58.0"/>
+ </font>
+ <font weight="400" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="90.0"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="108.0"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="128.0"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="151.0"/>
+ </font>
+ <font weight="800" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="169.0"/>
+ </font>
+ <font weight="900" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="190.0"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif">NotoSerifKhmer-Regular.otf</font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifKhmer-Bold.otf</font>
+ </family>
+ <family lang="und-Khmr" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansKhmerUI">
+ NotoSansKhmerUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansKhmerUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Laoo" variant="elegant">
+ <font weight="400" style="normal">NotoSansLao-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansLao-Bold.ttf</font>
+ <font weight="400" style="normal" fallbackFor="serif">
+ NotoSerifLao-Regular.ttf
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifLao-Bold.ttf</font>
+ </family>
+ <family lang="und-Laoo" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansLaoUI">NotoSansLaoUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Mymr" variant="elegant">
+ <font weight="400" style="normal">NotoSansMyanmar-Regular.otf</font>
+ <font weight="500" style="normal">NotoSansMyanmar-Medium.otf</font>
+ <font weight="700" style="normal">NotoSansMyanmar-Bold.otf</font>
+ <font weight="400" style="normal" fallbackFor="serif">NotoSerifMyanmar-Regular.otf</font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifMyanmar-Bold.otf</font>
+ </family>
+ <family lang="und-Mymr" variant="compact">
+ <font weight="400" style="normal">NotoSansMyanmarUI-Regular.otf</font>
+ <font weight="500" style="normal">NotoSansMyanmarUI-Medium.otf</font>
+ <font weight="700" style="normal">NotoSansMyanmarUI-Bold.otf</font>
+ </family>
+ <family lang="und-Thaa">
+ <font weight="400" style="normal" postScriptName="NotoSansThaana">
+ NotoSansThaana-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansThaana-Bold.ttf</font>
+ </family>
+ <family lang="und-Cham">
+ <font weight="400" style="normal" postScriptName="NotoSansCham">NotoSansCham-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansCham-Bold.ttf</font>
+ </family>
+ <family lang="und-Ahom">
+ <font weight="400" style="normal">NotoSansAhom-Regular.otf</font>
+ </family>
+ <family lang="und-Adlm">
+ <font weight="400" style="normal" postScriptName="NotoSansAdlam-Regular">
+ NotoSansAdlam-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansAdlam-Regular">
+ NotoSansAdlam-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansAdlam-Regular">
+ NotoSansAdlam-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansAdlam-Regular">
+ NotoSansAdlam-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Avst">
+ <font weight="400" style="normal" postScriptName="NotoSansAvestan">
+ NotoSansAvestan-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Bali">
+ <font weight="400" style="normal" postScriptName="NotoSansBalinese">
+ NotoSansBalinese-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Bamu">
+ <font weight="400" style="normal" postScriptName="NotoSansBamum">NotoSansBamum-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Batk">
+ <font weight="400" style="normal" postScriptName="NotoSansBatak">NotoSansBatak-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Brah">
+ <font weight="400" style="normal" postScriptName="NotoSansBrahmi">
+ NotoSansBrahmi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Bugi">
+ <font weight="400" style="normal" postScriptName="NotoSansBuginese">
+ NotoSansBuginese-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Buhd">
+ <font weight="400" style="normal" postScriptName="NotoSansBuhid">NotoSansBuhid-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Cans">
+ <font weight="400" style="normal">
+ NotoSansCanadianAboriginal-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Cari">
+ <font weight="400" style="normal" postScriptName="NotoSansCarian">
+ NotoSansCarian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Cakm">
+ <font weight="400" style="normal">NotoSansChakma-Regular.otf</font>
+ </family>
+ <family lang="und-Cher">
+ <font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font>
+ </family>
+ <family lang="und-Copt">
+ <font weight="400" style="normal" postScriptName="NotoSansCoptic">
+ NotoSansCoptic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Xsux">
+ <font weight="400" style="normal" postScriptName="NotoSansCuneiform">
+ NotoSansCuneiform-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Cprt">
+ <font weight="400" style="normal" postScriptName="NotoSansCypriot">
+ NotoSansCypriot-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Dsrt">
+ <font weight="400" style="normal" postScriptName="NotoSansDeseret">
+ NotoSansDeseret-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Egyp">
+ <font weight="400" style="normal" postScriptName="NotoSansEgyptianHieroglyphs">
+ NotoSansEgyptianHieroglyphs-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Elba">
+ <font weight="400" style="normal">NotoSansElbasan-Regular.otf</font>
+ </family>
+ <family lang="und-Glag">
+ <font weight="400" style="normal" postScriptName="NotoSansGlagolitic">
+ NotoSansGlagolitic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Goth">
+ <font weight="400" style="normal" postScriptName="NotoSansGothic">
+ NotoSansGothic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Hano">
+ <font weight="400" style="normal" postScriptName="NotoSansHanunoo">
+ NotoSansHanunoo-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Armi">
+ <font weight="400" style="normal" postScriptName="NotoSansImperialAramaic">
+ NotoSansImperialAramaic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Phli">
+ <font weight="400" style="normal" postScriptName="NotoSansInscriptionalPahlavi">
+ NotoSansInscriptionalPahlavi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Prti">
+ <font weight="400" style="normal" postScriptName="NotoSansInscriptionalParthian">
+ NotoSansInscriptionalParthian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Java">
+ <font weight="400" style="normal">NotoSansJavanese-Regular.otf</font>
+ </family>
+ <family lang="und-Kthi">
+ <font weight="400" style="normal" postScriptName="NotoSansKaithi">
+ NotoSansKaithi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Kali">
+ <font weight="400" style="normal" postScriptName="NotoSansKayahLi">
+ NotoSansKayahLi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Khar">
+ <font weight="400" style="normal" postScriptName="NotoSansKharoshthi">
+ NotoSansKharoshthi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lepc">
+ <font weight="400" style="normal" postScriptName="NotoSansLepcha">
+ NotoSansLepcha-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Limb">
+ <font weight="400" style="normal" postScriptName="NotoSansLimbu">NotoSansLimbu-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Linb">
+ <font weight="400" style="normal" postScriptName="NotoSansLinearB">
+ NotoSansLinearB-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lisu">
+ <font weight="400" style="normal" postScriptName="NotoSansLisu">NotoSansLisu-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lyci">
+ <font weight="400" style="normal" postScriptName="NotoSansLycian">
+ NotoSansLycian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lydi">
+ <font weight="400" style="normal" postScriptName="NotoSansLydian">
+ NotoSansLydian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Mand">
+ <font weight="400" style="normal" postScriptName="NotoSansMandaic">
+ NotoSansMandaic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Mtei">
+ <font weight="400" style="normal" postScriptName="NotoSansMeeteiMayek">
+ NotoSansMeeteiMayek-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Talu">
+ <font weight="400" style="normal" postScriptName="NotoSansNewTaiLue">
+ NotoSansNewTaiLue-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Nkoo">
+ <font weight="400" style="normal" postScriptName="NotoSansNKo">NotoSansNKo-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Ogam">
+ <font weight="400" style="normal" postScriptName="NotoSansOgham">NotoSansOgham-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Olck">
+ <font weight="400" style="normal" postScriptName="NotoSansOlChiki">
+ NotoSansOlChiki-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Ital">
+ <font weight="400" style="normal" postScriptName="NotoSansOldItalic">
+ NotoSansOldItalic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Xpeo">
+ <font weight="400" style="normal" postScriptName="NotoSansOldPersian">
+ NotoSansOldPersian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Sarb">
+ <font weight="400" style="normal" postScriptName="NotoSansOldSouthArabian">
+ NotoSansOldSouthArabian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Orkh">
+ <font weight="400" style="normal" postScriptName="NotoSansOldTurkic">
+ NotoSansOldTurkic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Osge">
+ <font weight="400" style="normal">NotoSansOsage-Regular.ttf</font>
+ </family>
+ <family lang="und-Osma">
+ <font weight="400" style="normal" postScriptName="NotoSansOsmanya">
+ NotoSansOsmanya-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Phnx">
+ <font weight="400" style="normal" postScriptName="NotoSansPhoenician">
+ NotoSansPhoenician-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Rjng">
+ <font weight="400" style="normal" postScriptName="NotoSansRejang">
+ NotoSansRejang-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Runr">
+ <font weight="400" style="normal" postScriptName="NotoSansRunic">NotoSansRunic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Samr">
+ <font weight="400" style="normal" postScriptName="NotoSansSamaritan">
+ NotoSansSamaritan-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Saur">
+ <font weight="400" style="normal" postScriptName="NotoSansSaurashtra">
+ NotoSansSaurashtra-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Shaw">
+ <font weight="400" style="normal" postScriptName="NotoSansShavian">
+ NotoSansShavian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Sund">
+ <font weight="400" style="normal" postScriptName="NotoSansSundanese">
+ NotoSansSundanese-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Sylo">
+ <font weight="400" style="normal" postScriptName="NotoSansSylotiNagri">
+ NotoSansSylotiNagri-Regular.ttf
+ </font>
+ </family>
+ <!-- Esrangela should precede Eastern and Western Syriac, since it's our default form. -->
+ <family lang="und-Syre">
+ <font weight="400" style="normal" postScriptName="NotoSansSyriacEstrangela">
+ NotoSansSyriacEstrangela-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Syrn">
+ <font weight="400" style="normal" postScriptName="NotoSansSyriacEastern">
+ NotoSansSyriacEastern-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Syrj">
+ <font weight="400" style="normal" postScriptName="NotoSansSyriacWestern">
+ NotoSansSyriacWestern-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Tglg">
+ <font weight="400" style="normal" postScriptName="NotoSansTagalog">
+ NotoSansTagalog-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Tagb">
+ <font weight="400" style="normal" postScriptName="NotoSansTagbanwa">
+ NotoSansTagbanwa-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lana">
+ <font weight="400" style="normal" postScriptName="NotoSansTaiTham">
+ NotoSansTaiTham-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Tavt">
+ <font weight="400" style="normal" postScriptName="NotoSansTaiViet">
+ NotoSansTaiViet-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Tibt">
+ <font weight="400" style="normal" postScriptName="NotoSerifTibetan-Regular">
+ NotoSerifTibetan-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSerifTibetan-Regular">
+ NotoSerifTibetan-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSerifTibetan-Regular">
+ NotoSerifTibetan-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSerifTibetan-Regular">
+ NotoSerifTibetan-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Tfng">
+ <font weight="400" style="normal">NotoSansTifinagh-Regular.otf</font>
+ </family>
+ <family lang="und-Ugar">
+ <font weight="400" style="normal" postScriptName="NotoSansUgaritic">
+ NotoSansUgaritic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Vaii">
+ <font weight="400" style="normal" postScriptName="NotoSansVai">NotoSansVai-Regular.ttf
+ </font>
+ </family>
+ <family>
+ <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font>
+ </family>
+ <family lang="zh-Hans">
+ <font weight="100" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="100"/>
+ </font>
+ <font weight="200" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="200"/>
+ </font>
+ <font weight="300" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="300"/>
+ </font>
+ <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="800" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="800"/>
+ </font>
+ <font weight="900" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="900"/>
+ </font>
+ <font weight="400" style="normal" index="2" fallbackFor="serif"
+ postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+ </font>
+ </family>
+ <family lang="zh-Hant,zh-Bopo">
+ <font weight="100" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="100"/>
+ </font>
+ <font weight="200" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="200"/>
+ </font>
+ <font weight="300" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="300"/>
+ </font>
+ <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="800" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="800"/>
+ </font>
+ <font weight="900" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="900"/>
+ </font>
+ <font weight="400" style="normal" index="3" fallbackFor="serif"
+ postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+ </font>
+ </family>
+ <family lang="ja">
+ <font weight="100" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="100"/>
+ </font>
+ <font weight="200" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="200"/>
+ </font>
+ <font weight="300" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="300"/>
+ </font>
+ <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="800" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="800"/>
+ </font>
+ <font weight="900" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="900"/>
+ </font>
+ <font weight="400" style="normal" index="0" fallbackFor="serif"
+ postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+ </font>
+ </family>
+ <family lang="ko">
+ <font weight="100" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="100"/>
+ </font>
+ <font weight="200" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="200"/>
+ </font>
+ <font weight="300" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="300"/>
+ </font>
+ <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="800" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="800"/>
+ </font>
+ <font weight="900" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="900"/>
+ </font>
+ <font weight="400" style="normal" index="1" fallbackFor="serif"
+ postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+ </font>
+ </family>
+ <family lang="und-Zsye" ignore="true">
+ <font weight="400" style="normal">NotoColorEmojiLegacy.ttf</font>
+ </family>
+ <family lang="und-Zsye">
+ <font weight="400" style="normal">NotoColorEmoji.ttf</font>
+ </family>
+ <family lang="und-Zsye">
+ <font weight="400" style="normal">NotoColorEmojiFlags.ttf</font>
+ </family>
+ <family lang="und-Zsym">
+ <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted2.ttf</font>
+ </family>
+ <!--
+ Tai Le, Yi, Mongolian, and Phags-pa are intentionally kept last, to make sure they don't
+ override the East Asian punctuation for Chinese.
+ -->
+ <family lang="und-Tale">
+ <font weight="400" style="normal" postScriptName="NotoSansTaiLe">NotoSansTaiLe-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Yiii">
+ <font weight="400" style="normal" postScriptName="NotoSansYi">NotoSansYi-Regular.ttf</font>
+ </family>
+ <family lang="und-Mong">
+ <font weight="400" style="normal" postScriptName="NotoSansMongolian">
+ NotoSansMongolian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Phag">
+ <font weight="400" style="normal" postScriptName="NotoSansPhagsPa">
+ NotoSansPhagsPa-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Hluw">
+ <font weight="400" style="normal">NotoSansAnatolianHieroglyphs-Regular.otf</font>
+ </family>
+ <family lang="und-Bass">
+ <font weight="400" style="normal">NotoSansBassaVah-Regular.otf</font>
+ </family>
+ <family lang="und-Bhks">
+ <font weight="400" style="normal">NotoSansBhaiksuki-Regular.otf</font>
+ </family>
+ <family lang="und-Hatr">
+ <font weight="400" style="normal">NotoSansHatran-Regular.otf</font>
+ </family>
+ <family lang="und-Lina">
+ <font weight="400" style="normal">NotoSansLinearA-Regular.otf</font>
+ </family>
+ <family lang="und-Mani">
+ <font weight="400" style="normal">NotoSansManichaean-Regular.otf</font>
+ </family>
+ <family lang="und-Marc">
+ <font weight="400" style="normal">NotoSansMarchen-Regular.otf</font>
+ </family>
+ <family lang="und-Merc">
+ <font weight="400" style="normal">NotoSansMeroitic-Regular.otf</font>
+ </family>
+ <family lang="und-Plrd">
+ <font weight="400" style="normal">NotoSansMiao-Regular.otf</font>
+ </family>
+ <family lang="und-Mroo">
+ <font weight="400" style="normal">NotoSansMro-Regular.otf</font>
+ </family>
+ <family lang="und-Mult">
+ <font weight="400" style="normal">NotoSansMultani-Regular.otf</font>
+ </family>
+ <family lang="und-Nbat">
+ <font weight="400" style="normal">NotoSansNabataean-Regular.otf</font>
+ </family>
+ <family lang="und-Newa">
+ <font weight="400" style="normal">NotoSansNewa-Regular.otf</font>
+ </family>
+ <family lang="und-Narb">
+ <font weight="400" style="normal">NotoSansOldNorthArabian-Regular.otf</font>
+ </family>
+ <family lang="und-Perm">
+ <font weight="400" style="normal">NotoSansOldPermic-Regular.otf</font>
+ </family>
+ <family lang="und-Hmng">
+ <font weight="400" style="normal">NotoSansPahawhHmong-Regular.otf</font>
+ </family>
+ <family lang="und-Palm">
+ <font weight="400" style="normal">NotoSansPalmyrene-Regular.otf</font>
+ </family>
+ <family lang="und-Pauc">
+ <font weight="400" style="normal">NotoSansPauCinHau-Regular.otf</font>
+ </family>
+ <family lang="und-Shrd">
+ <font weight="400" style="normal">NotoSansSharada-Regular.otf</font>
+ </family>
+ <family lang="und-Sora">
+ <font weight="400" style="normal">NotoSansSoraSompeng-Regular.otf</font>
+ </family>
+ <family lang="und-Gong">
+ <font weight="400" style="normal">NotoSansGunjalaGondi-Regular.otf</font>
+ </family>
+ <family lang="und-Rohg">
+ <font weight="400" style="normal">NotoSansHanifiRohingya-Regular.otf</font>
+ </family>
+ <family lang="und-Khoj">
+ <font weight="400" style="normal">NotoSansKhojki-Regular.otf</font>
+ </family>
+ <family lang="und-Gonm">
+ <font weight="400" style="normal">NotoSansMasaramGondi-Regular.otf</font>
+ </family>
+ <family lang="und-Wcho">
+ <font weight="400" style="normal">NotoSansWancho-Regular.otf</font>
+ </family>
+ <family lang="und-Wara">
+ <font weight="400" style="normal">NotoSansWarangCiti-Regular.otf</font>
+ </family>
+ <family lang="und-Gran">
+ <font weight="400" style="normal">NotoSansGrantha-Regular.ttf</font>
+ </family>
+ <family lang="und-Modi">
+ <font weight="400" style="normal">NotoSansModi-Regular.ttf</font>
+ </family>
+ <family lang="und-Dogr">
+ <font weight="400" style="normal">NotoSerifDogra-Regular.ttf</font>
+ </family>
+ <family lang="und-Medf">
+ <font weight="400" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
+ NotoSansMedefaidrin-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
+ NotoSansMedefaidrin-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
+ NotoSansMedefaidrin-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
+ NotoSansMedefaidrin-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Soyo">
+ <font weight="400" style="normal" postScriptName="NotoSansSoyombo-Regular">
+ NotoSansSoyombo-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansSoyombo-Regular">
+ NotoSansSoyombo-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansSoyombo-Regular">
+ NotoSansSoyombo-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansSoyombo-Regular">
+ NotoSansSoyombo-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Takr">
+ <font weight="400" style="normal" postScriptName="NotoSansTakri-Regular">
+ NotoSansTakri-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansTakri-Regular">
+ NotoSansTakri-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansTakri-Regular">
+ NotoSansTakri-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansTakri-Regular">
+ NotoSansTakri-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Hmnp">
+ <font weight="400" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
+ NotoSerifNyiakengPuachueHmong-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
+ NotoSerifNyiakengPuachueHmong-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
+ NotoSerifNyiakengPuachueHmong-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
+ NotoSerifNyiakengPuachueHmong-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Yezi">
+ <font weight="400" style="normal" postScriptName="NotoSerifYezidi-Regular">
+ NotoSerifYezidi-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSerifYezidi-Regular">
+ NotoSerifYezidi-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSerifYezidi-Regular">
+ NotoSerifYezidi-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSerifYezidi-Regular">
+ NotoSerifYezidi-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+</familyset>
diff --git a/framework-jarjar-rules.txt b/framework-jarjar-rules.txt
index 03b268d..6339a87 100644
--- a/framework-jarjar-rules.txt
+++ b/framework-jarjar-rules.txt
@@ -8,3 +8,6 @@
# for modules-utils-build dependency
rule com.android.modules.utils.build.** android.internal.modules.utils.build.@1
+
+# For Perfetto proto dependencies
+rule perfetto.protos.** android.internal.perfetto.protos.@1
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index f10cdb8..ae61a2d 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -17,6 +17,7 @@
package android.graphics;
import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
+import static com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION;
import android.annotation.ColorInt;
import android.annotation.ColorLong;
@@ -133,7 +134,9 @@
FAKE_BOLD_TEXT_FLAG,
LINEAR_TEXT_FLAG,
SUBPIXEL_TEXT_FLAG,
- EMBEDDED_BITMAP_TEXT_FLAG
+ EMBEDDED_BITMAP_TEXT_FLAG,
+ TEXT_RUN_FLAG_LEFT_EDGE,
+ TEXT_RUN_FLAG_RIGHT_EDGE
})
public @interface PaintFlag{}
@@ -264,6 +267,66 @@
/** @hide bit mask for the flag enabling vertical rendering for text */
public static final int VERTICAL_TEXT_FLAG = 0x1000;
+ /**
+ * A text run flag that indicates the run is located the visually most left segment of the line.
+ * <p>
+ * This flag is used for telling the underlying text layout engine that the text is located at
+ * the most left of the line. This flag is used for controlling the amount letter spacing
+ * added. If the text is in the middle of the line, the text layout engine assigns additional
+ * letter spacing to the both side of each letter. On the other hand, the letter spacing should
+ * not be added to the visually most left and right of the line. By setting this flag, text
+ * layout engine calculates the layout as it is located at the most visually left of the line
+ * and doesn't add letter spacing to the left of this run.
+ * <p>
+ * Note that the caller must resolve BiDi runs and reorder them visually and set this flag only
+ * if the target run is located visually most left position. This left does not always mean the
+ * beginning of the text.
+ * <p>
+ * If the run covers entire line, caller should set {@link #TEXT_RUN_FLAG_RIGHT_EDGE} as well.
+ * <p>
+ * Note that this flag is only effective for run based APIs. For example, this flag works for
+ * {@link Canvas#drawTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)}
+ * and
+ * {@link Paint#getRunCharacterAdvance(char[], int, int, int, int, boolean, int, float[], int)}.
+ * However, this doesn't work for
+ * {@link Canvas#drawText(CharSequence, int, int, float, float, Paint)} or
+ * {@link Paint#measureText(CharSequence, int, int)}. The non-run based APIs works as both
+ * {@link #TEXT_RUN_FLAG_LEFT_EDGE} and {@link #TEXT_RUN_FLAG_RIGHT_EDGE} are specified.
+ */
+ @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+ public static final int TEXT_RUN_FLAG_LEFT_EDGE = 0x2000;
+
+
+ /**
+ * A text run flag that indicates the run is located the visually most right segment of the
+ * line.
+ * <p>
+ * This flag is used for telling the underlying text layout engine that the text is located at
+ * the most right of the line. This flag is used for controlling the amount letter spacing
+ * added. If the text is in the middle of the line, the text layout engine assigns additional
+ * letter spacing to the both side of each letter. On the other hand, the letter spacing should
+ * not be added to the visually most left and right of the line. By setting this flag, text
+ * layout engine calculates the layout as it is located at the most visually left of the line
+ * and doesn't add letter spacing to the left of this run.
+ * <p>
+ * Note that the caller must resolve BiDi runs and reorder them visually and set this flag only
+ * if the target run is located visually most right position. This right does not always mean
+ * the end of the text.
+ * <p>
+ * If the run covers entire line, caller should set {@link #TEXT_RUN_FLAG_LEFT_EDGE} as well.
+ * <p>
+ * Note that this flag is only effective for run based APIs. For example, this flag works for
+ * {@link Canvas#drawTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)}
+ * and
+ * {@link Paint#getRunCharacterAdvance(char[], int, int, int, int, boolean, int, float[], int)}.
+ * However, this doesn't work for
+ * {@link Canvas#drawText(CharSequence, int, int, float, float, Paint)} or
+ * {@link Paint#measureText(CharSequence, int, int)}. The non-run based APIs works as both
+ * {@link #TEXT_RUN_FLAG_LEFT_EDGE} and {@link #TEXT_RUN_FLAG_RIGHT_EDGE} are specified.
+ */
+ @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+ public static final int TEXT_RUN_FLAG_RIGHT_EDGE = 0x4000;
+
// These flags are always set on a new/reset paint, even if flags 0 is passed.
static final int HIDDEN_DEFAULT_PAINT_FLAGS = DEV_KERN_TEXT_FLAG | EMBEDDED_BITMAP_TEXT_FLAG
| FILTER_BITMAP_FLAG;
@@ -2474,6 +2537,19 @@
nGetFontMetricsInt(mNativePaint, metrics, true);
}
+ /** @hide */
+ public static final class RunInfo {
+ private int mClusterCount = 0;
+
+ public int getClusterCount() {
+ return mClusterCount;
+ }
+
+ public void setClusterCount(int clusterCount) {
+ mClusterCount = clusterCount;
+ }
+ }
+
/**
* Return the recommend line spacing based on the current typeface and
* text size.
@@ -2507,17 +2583,24 @@
if (text.length == 0 || count == 0) {
return 0f;
}
- if (!mHasCompatScaling) {
- return (float) Math.ceil(nGetTextAdvances(mNativePaint, text,
- index, count, index, count, mBidiFlags, null, 0));
- }
+ int oldFlag = getFlags();
+ setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE));
+ try {
- final float oldSize = getTextSize();
- setTextSize(oldSize * mCompatScaling);
- final float w = nGetTextAdvances(mNativePaint, text, index, count, index, count,
- mBidiFlags, null, 0);
- setTextSize(oldSize);
- return (float) Math.ceil(w*mInvCompatScaling);
+ if (!mHasCompatScaling) {
+ return (float) Math.ceil(nGetTextAdvances(mNativePaint, text,
+ index, count, index, count, mBidiFlags, null, 0));
+ }
+
+ final float oldSize = getTextSize();
+ setTextSize(oldSize * mCompatScaling);
+ final float w = nGetTextAdvances(mNativePaint, text, index, count, index, count,
+ mBidiFlags, null, 0);
+ setTextSize(oldSize);
+ return (float) Math.ceil(w * mInvCompatScaling);
+ } finally {
+ setFlags(oldFlag);
+ }
}
/**
@@ -2539,16 +2622,22 @@
if (text.length() == 0 || start == end) {
return 0f;
}
- if (!mHasCompatScaling) {
- return (float) Math.ceil(nGetTextAdvances(mNativePaint, text,
- start, end, start, end, mBidiFlags, null, 0));
+ int oldFlag = getFlags();
+ setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE));
+ try {
+ if (!mHasCompatScaling) {
+ return (float) Math.ceil(nGetTextAdvances(mNativePaint, text,
+ start, end, start, end, mBidiFlags, null, 0));
+ }
+ final float oldSize = getTextSize();
+ setTextSize(oldSize * mCompatScaling);
+ final float w = nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags,
+ null, 0);
+ setTextSize(oldSize);
+ return (float) Math.ceil(w * mInvCompatScaling);
+ } finally {
+ setFlags(oldFlag);
}
- final float oldSize = getTextSize();
- setTextSize(oldSize * mCompatScaling);
- final float w = nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags,
- null, 0);
- setTextSize(oldSize);
- return (float) Math.ceil(w * mInvCompatScaling);
}
/**
@@ -2753,19 +2842,26 @@
if (text.length == 0 || count == 0) {
return 0;
}
- if (!mHasCompatScaling) {
- nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths, 0);
- return count;
- }
+ int oldFlag = getFlags();
+ setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE));
+ try {
+ if (!mHasCompatScaling) {
+ nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths,
+ 0);
+ return count;
+ }
- final float oldSize = getTextSize();
- setTextSize(oldSize * mCompatScaling);
- nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths, 0);
- setTextSize(oldSize);
- for (int i = 0; i < count; i++) {
- widths[i] *= mInvCompatScaling;
+ final float oldSize = getTextSize();
+ setTextSize(oldSize * mCompatScaling);
+ nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths, 0);
+ setTextSize(oldSize);
+ for (int i = 0; i < count; i++) {
+ widths[i] *= mInvCompatScaling;
+ }
+ return count;
+ } finally {
+ setFlags(oldFlag);
}
- return count;
}
/**
@@ -2836,19 +2932,25 @@
if (text.length() == 0 || start == end) {
return 0;
}
- if (!mHasCompatScaling) {
- nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0);
- return end - start;
- }
+ int oldFlag = getFlags();
+ setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE));
+ try {
+ if (!mHasCompatScaling) {
+ nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0);
+ return end - start;
+ }
- final float oldSize = getTextSize();
- setTextSize(oldSize * mCompatScaling);
- nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0);
- setTextSize(oldSize);
- for (int i = 0; i < end - start; i++) {
- widths[i] *= mInvCompatScaling;
+ final float oldSize = getTextSize();
+ setTextSize(oldSize * mCompatScaling);
+ nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0);
+ setTextSize(oldSize);
+ for (int i = 0; i < end - start; i++) {
+ widths[i] *= mInvCompatScaling;
+ }
+ return end - start;
+ } finally {
+ setFlags(oldFlag);
}
- return end - start;
}
/**
@@ -3320,7 +3422,7 @@
int contextEnd, boolean isRtl, int offset,
@Nullable float[] advances, int advancesIndex) {
return getRunCharacterAdvance(text, start, end, contextStart, contextEnd, isRtl, offset,
- advances, advancesIndex, null);
+ advances, advancesIndex, null, null);
}
/**
@@ -3339,12 +3441,14 @@
* @param advances the array that receives the computed character advances
* @param advancesIndex the start index from which the advances array is filled
* @param drawBounds the output parameter for the bounding box of drawing text, optional
+ * @param runInfo the output parameter for storing run information.
* @return width measurement between start and offset
- * @hide
+ * @hide TODO: Reorganize APIs
*/
public float getRunCharacterAdvance(@NonNull char[] text, int start, int end, int contextStart,
int contextEnd, boolean isRtl, int offset,
- @Nullable float[] advances, int advancesIndex, @Nullable RectF drawBounds) {
+ @Nullable float[] advances, int advancesIndex, @Nullable RectF drawBounds,
+ @Nullable RunInfo runInfo) {
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -3370,11 +3474,14 @@
}
if (end == start) {
+ if (runInfo != null) {
+ runInfo.setClusterCount(0);
+ }
return 0.0f;
}
return nGetRunCharacterAdvance(mNativePaint, text, start, end, contextStart, contextEnd,
- isRtl, offset, advances, advancesIndex, drawBounds);
+ isRtl, offset, advances, advancesIndex, drawBounds, runInfo);
}
/**
@@ -3402,7 +3509,7 @@
int contextStart, int contextEnd, boolean isRtl, int offset,
@Nullable float[] advances, int advancesIndex) {
return getRunCharacterAdvance(text, start, end, contextStart, contextEnd, isRtl, offset,
- advances, advancesIndex, null);
+ advances, advancesIndex, null, null);
}
/**
@@ -3418,12 +3525,14 @@
* @param advances the array that receives the computed character advances
* @param advancesIndex the start index from which the advances array is filled
* @param drawBounds the output parameter for the bounding box of drawing text, optional
+ * @param runInfo an optional output parameter for filling run information.
* @return width measurement between start and offset
- * @hide
+ * @hide TODO: Reorganize APIs
*/
public float getRunCharacterAdvance(@NonNull CharSequence text, int start, int end,
int contextStart, int contextEnd, boolean isRtl, int offset,
- @Nullable float[] advances, int advancesIndex, @Nullable RectF drawBounds) {
+ @Nullable float[] advances, int advancesIndex, @Nullable RectF drawBounds,
+ @Nullable RunInfo runInfo) {
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -3456,7 +3565,7 @@
TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
final float result = getRunCharacterAdvance(buf, start - contextStart, end - contextStart,
0, contextEnd - contextStart, isRtl, offset - contextStart,
- advances, advancesIndex, drawBounds);
+ advances, advancesIndex, drawBounds, runInfo);
TemporaryBuffer.recycle(buf);
return result;
}
@@ -3574,7 +3683,7 @@
int contextStart, int contextEnd, boolean isRtl, int offset);
private static native float nGetRunCharacterAdvance(long paintPtr, char[] text, int start,
int end, int contextStart, int contextEnd, boolean isRtl, int offset, float[] advances,
- int advancesIndex, RectF drawingBounds);
+ int advancesIndex, RectF drawingBounds, RunInfo runInfo);
private static native int nGetOffsetForAdvance(long paintPtr, char[] text, int start, int end,
int contextStart, int contextEnd, boolean isRtl, float advance);
private static native void nGetFontMetricsIntForText(long paintPtr, char[] text,
@@ -3729,4 +3838,11 @@
private static native void nSetTextSize(long paintPtr, float textSize);
@CriticalNative
private static native boolean nEqualsForTextMeasurement(long leftPaintPtr, long rightPaintPtr);
+
+
+ // Following Native methods are kept for old Robolectric JNI signature used by
+ // SystemUIGoogleRoboRNGTests
+ private static native float nGetRunCharacterAdvance(long paintPtr, char[] text,
+ int start, int end, int contextStart, int contextEnd, boolean isRtl, int offset,
+ float[] advances, int advancesIndex, RectF drawingBounds);
}
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
index ddae673..b21bf11 100644
--- a/graphics/java/android/graphics/text/LineBreakConfig.java
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -23,9 +23,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.compat.CompatChanges;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledSince;
+import android.app.ActivityThread;
import android.os.Build;
import android.os.LocaleList;
import android.os.Parcel;
@@ -43,15 +41,6 @@
* line-break property</a> for more information.
*/
public final class LineBreakConfig implements Parcelable {
-
- /**
- * A feature ID for automatic line break word style.
- * @hide
- */
- @ChangeId
- @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
- public static final long WORD_STYLE_AUTO = 280005585L;
-
/**
* No hyphenation preference is specified.
*
@@ -487,8 +476,15 @@
* @hide
*/
public static @LineBreakStyle int getResolvedLineBreakStyle(@Nullable LineBreakConfig config) {
- final int defaultStyle = CompatChanges.isChangeEnabled(WORD_STYLE_AUTO)
- ? LINE_BREAK_STYLE_AUTO : LINE_BREAK_STYLE_NONE;
+ final int targetSdkVersion = ActivityThread.currentApplication().getApplicationInfo()
+ .targetSdkVersion;
+ final int defaultStyle;
+ final int vicVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM;
+ if (targetSdkVersion >= vicVersion) {
+ defaultStyle = LINE_BREAK_STYLE_AUTO;
+ } else {
+ defaultStyle = LINE_BREAK_STYLE_NONE;
+ }
if (config == null) {
return defaultStyle;
}
@@ -515,8 +511,15 @@
*/
public static @LineBreakWordStyle int getResolvedLineBreakWordStyle(
@Nullable LineBreakConfig config) {
- final int defaultWordStyle = CompatChanges.isChangeEnabled(WORD_STYLE_AUTO)
- ? LINE_BREAK_WORD_STYLE_AUTO : LINE_BREAK_WORD_STYLE_NONE;
+ final int targetSdkVersion = ActivityThread.currentApplication().getApplicationInfo()
+ .targetSdkVersion;
+ final int defaultWordStyle;
+ final int vicVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM;
+ if (targetSdkVersion >= vicVersion) {
+ defaultWordStyle = LINE_BREAK_WORD_STYLE_AUTO;
+ } else {
+ defaultWordStyle = LINE_BREAK_WORD_STYLE_NONE;
+ }
if (config == null) {
return defaultWordStyle;
}
diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java
index 2d33e8d..6da0719 100644
--- a/graphics/java/android/graphics/text/MeasuredText.java
+++ b/graphics/java/android/graphics/text/MeasuredText.java
@@ -269,6 +269,10 @@
* offset is zero. After the style is applied the internal offset is moved to {@code offset
* + length}, and next call will start from this new position.
*
+ * <p>
+ * {@link Paint#TEXT_RUN_FLAG_RIGHT_EDGE} and {@link Paint#TEXT_RUN_FLAG_LEFT_EDGE} are
+ * ignored and treated as both of them are set.
+ *
* @param paint a paint
* @param length a length to be applied with a given paint, can not exceed the length of the
* text
diff --git a/keystore/java/android/security/Authorization.java b/keystore/java/android/security/Authorization.java
index 4ec5e1b..6404c4b 100644
--- a/keystore/java/android/security/Authorization.java
+++ b/keystore/java/android/security/Authorization.java
@@ -100,12 +100,14 @@
*
* @param userId - the user's Android user ID
* @param unlockingSids - list of biometric SIDs with which the device may be unlocked again
+ * @param weakUnlockEnabled - true if non-strong biometric or trust agent unlock is enabled
* @return 0 if successful or a {@code ResponseCode}.
*/
- public static int onDeviceLocked(int userId, @NonNull long[] unlockingSids) {
+ public static int onDeviceLocked(int userId, @NonNull long[] unlockingSids,
+ boolean weakUnlockEnabled) {
StrictMode.noteDiskWrite();
try {
- getService().onDeviceLocked(userId, unlockingSids);
+ getService().onDeviceLocked(userId, unlockingSids, weakUnlockEnabled);
return 0;
} catch (RemoteException | NullPointerException e) {
Log.w(TAG, "Can not connect to keystore", e);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 066f38b..83d555c 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -822,11 +822,6 @@
// Checks if container should be updated before apply new parentInfo.
final boolean shouldUpdateContainer = taskContainer.shouldUpdateContainer(parentInfo);
taskContainer.updateTaskFragmentParentInfo(parentInfo);
- if (!taskContainer.isVisible()) {
- // Don't update containers if the task is not visible. We only update containers when
- // parentInfo#isVisibleRequested is true.
- return;
- }
// If the last direct activity of the host task is dismissed and the overlay container is
// the only taskFragment, the overlay container should also be dismissed.
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
index bc92101..4e7b760 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -475,8 +475,10 @@
@Test
public void testOnTaskFragmentParentInfoChanged_positionOnlyChange_earlyReturn() {
final TaskFragmentContainer overlayContainer = createTestOverlayContainer(TASK_ID, "test");
-
final TaskContainer taskContainer = overlayContainer.getTaskContainer();
+
+ assertThat(taskContainer.getOverlayContainer()).isEqualTo(overlayContainer);
+
spyOn(taskContainer);
final TaskContainer.TaskProperties taskProperties = taskContainer.getTaskProperties();
final TaskFragmentParentInfo parentInfo = new TaskFragmentParentInfo(
@@ -495,6 +497,30 @@
.that(taskContainer.getOverlayContainer()).isNull();
}
+ @Test
+ public void testOnTaskFragmentParentInfoChanged_invisibleTask_callDismissOverlayContainer() {
+ final TaskFragmentContainer overlayContainer = createTestOverlayContainer(TASK_ID, "test");
+ final TaskContainer taskContainer = overlayContainer.getTaskContainer();
+
+ assertThat(taskContainer.getOverlayContainer()).isEqualTo(overlayContainer);
+
+ spyOn(taskContainer);
+ final TaskContainer.TaskProperties taskProperties = taskContainer.getTaskProperties();
+ final TaskFragmentParentInfo parentInfo = new TaskFragmentParentInfo(
+ new Configuration(taskProperties.getConfiguration()), taskProperties.getDisplayId(),
+ false /* visible */, false /* hasDirectActivity */, null /* decorSurface */);
+
+ mSplitController.onTaskFragmentParentInfoChanged(mTransaction, TASK_ID, parentInfo);
+
+ // The parent info must be applied to the task container
+ verify(taskContainer).updateTaskFragmentParentInfo(parentInfo);
+ verify(mSplitController, never()).updateContainer(any(), any());
+
+ assertWithMessage("The overlay container must still be dismissed even if "
+ + "#updateContainer is not called")
+ .that(taskContainer.getOverlayContainer()).isNull();
+ }
+
/**
* A simplified version of {@link SplitController.ActivityStartMonitor
* #createOrUpdateOverlayTaskFragmentIfNeeded}
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 5ad144d..4cdc06a 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -162,6 +162,7 @@
"com_android_wm_shell_flags_lib",
"com.android.window.flags.window-aconfig-java",
"WindowManager-Shell-proto",
+ "perfetto_trace_java_protos",
"dagger2",
"jsr330",
],
@@ -175,3 +176,74 @@
plugins: ["dagger2-compiler"],
use_resource_processor: true,
}
+
+android_app {
+ name: "WindowManagerShellRobolectric",
+ platform_apis: true,
+ static_libs: [
+ "WindowManager-Shell",
+ ],
+ manifest: "multivalentTests/AndroidManifestRobolectric.xml",
+ use_resource_processor: true,
+}
+
+android_robolectric_test {
+ name: "WMShellRobolectricTests",
+ instrumentation_for: "WindowManagerShellRobolectric",
+ upstream: true,
+ java_resource_dirs: [
+ "multivalentTests/robolectric/config",
+ ],
+ srcs: [
+ "multivalentTests/src/**/*.kt",
+ ],
+ static_libs: [
+ "junit",
+ "androidx.test.runner",
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "mockito-robolectric-prebuilt",
+ "mockito-kotlin2",
+ "truth",
+ ],
+}
+
+android_test {
+ name: "WMShellMultivalentTestsOnDevice",
+ srcs: [
+ "multivalentTests/src/**/*.kt",
+ ],
+ static_libs: [
+ "WindowManager-Shell",
+ "junit",
+ "androidx.test.runner",
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "frameworks-base-testutils",
+ "mockito-kotlin2",
+ "mockito-target-extended-minus-junit4",
+ "truth",
+ "platform-test-annotations",
+ "platform-test-rules",
+ ],
+ libs: [
+ "android.test.base",
+ "android.test.runner",
+ ],
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
+ ],
+ kotlincflags: ["-Xjvm-default=all"],
+ optimize: {
+ enabled: false,
+ },
+ test_suites: ["device-tests"],
+ platform_apis: true,
+ certificate: "platform",
+ aaptflags: [
+ "--extra-packages",
+ "com.android.wm.shell",
+ ],
+ manifest: "multivalentTests/AndroidManifest.xml",
+}
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 901d5fa..0e04658 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -1,13 +1,6 @@
package: "com.android.wm.shell"
flag {
- name: "example_flag"
- namespace: "multitasking"
- description: "An Example Flag"
- bug: "300136750"
-}
-
-flag {
name: "enable_app_pairs"
namespace: "multitasking"
description: "Enables the ability to create and save app pairs to the Home screen"
diff --git a/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml b/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml
new file mode 100644
index 0000000..f8f8338
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml
@@ -0,0 +1,13 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.wm.shell.multivalenttests">
+
+ <application android:debuggable="true" android:supportsRtl="true" >
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="Multivalent tests for WindowManager-Shell"
+ android:targetPackage="com.android.wm.shell.multivalenttests">
+ </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/multivalentTests/AndroidManifestRobolectric.xml b/libs/WindowManager/Shell/multivalentTests/AndroidManifestRobolectric.xml
new file mode 100644
index 0000000..ffcd7d4
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/AndroidManifestRobolectric.xml
@@ -0,0 +1,3 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.wm.shell.multivalenttests">
+</manifest>
diff --git a/libs/WindowManager/Shell/multivalentTests/AndroidTest.xml b/libs/WindowManager/Shell/multivalentTests/AndroidTest.xml
new file mode 100644
index 0000000..36fe8ec
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs Tests for WindowManagerShellLib">
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="install-arg" value="-t" />
+ <option name="test-file-name" value="WMShellMultivalentTestsOnDevice.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="framework-base-presubmit" />
+ <option name="test-tag" value="WMShellMultivalentTestsOnDevice" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.wm.shell.multivalenttests" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/libs/WindowManager/Shell/multivalentTests/robolectric/config/robolectric.properties b/libs/WindowManager/Shell/multivalentTests/robolectric/config/robolectric.properties
new file mode 100644
index 0000000..7a0527c
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/robolectric/config/robolectric.properties
@@ -0,0 +1,2 @@
+sdk=NEWEST_SDK
+
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
new file mode 100644
index 0000000..ea7c6ed
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
@@ -0,0 +1,481 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ShortcutInfo
+import android.graphics.Insets
+import android.graphics.PointF
+import android.graphics.Rect
+import android.os.UserHandle
+import android.view.WindowManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.R
+import com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT
+import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.MoreExecutors.directExecutor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Tests operations and the resulting state managed by [BubblePositioner]. */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BubblePositionerTest {
+
+ private lateinit var positioner: BubblePositioner
+ private val context = ApplicationProvider.getApplicationContext<Context>()
+ private val defaultDeviceConfig =
+ DeviceConfig(
+ windowBounds = Rect(0, 0, 1000, 2000),
+ isLargeScreen = false,
+ isSmallTablet = false,
+ isLandscape = false,
+ isRtl = false,
+ insets = Insets.of(0, 0, 0, 0)
+ )
+
+ @Before
+ fun setUp() {
+ val windowManager = context.getSystemService(WindowManager::class.java)
+ positioner = BubblePositioner(context, windowManager)
+ }
+
+ @Test
+ fun testUpdate() {
+ val insets = Insets.of(10, 20, 5, 15)
+ val screenBounds = Rect(0, 0, 1000, 1200)
+ val availableRect = Rect(screenBounds)
+ availableRect.inset(insets)
+ positioner.update(defaultDeviceConfig.copy(insets = insets, windowBounds = screenBounds))
+ assertThat(positioner.availableRect).isEqualTo(availableRect)
+ assertThat(positioner.isLandscape).isFalse()
+ assertThat(positioner.isLargeScreen).isFalse()
+ assertThat(positioner.insets).isEqualTo(insets)
+ }
+
+ @Test
+ fun testShowBubblesVertically_phonePortrait() {
+ positioner.update(defaultDeviceConfig)
+ assertThat(positioner.showBubblesVertically()).isFalse()
+ }
+
+ @Test
+ fun testShowBubblesVertically_phoneLandscape() {
+ positioner.update(defaultDeviceConfig.copy(isLandscape = true))
+ assertThat(positioner.isLandscape).isTrue()
+ assertThat(positioner.showBubblesVertically()).isTrue()
+ }
+
+ @Test
+ fun testShowBubblesVertically_tablet() {
+ positioner.update(defaultDeviceConfig.copy(isLargeScreen = true))
+ assertThat(positioner.showBubblesVertically()).isTrue()
+ }
+
+ /** If a resting position hasn't been set, calling it will return the default position. */
+ @Test
+ fun testGetRestingPosition_returnsDefaultPosition() {
+ positioner.update(defaultDeviceConfig)
+ val restingPosition = positioner.getRestingPosition()
+ val defaultPosition = positioner.defaultStartPosition
+ assertThat(restingPosition).isEqualTo(defaultPosition)
+ }
+
+ /** If a resting position has been set, it'll return that instead of the default position. */
+ @Test
+ fun testGetRestingPosition_returnsRestingPosition() {
+ positioner.update(defaultDeviceConfig)
+ val restingPosition = PointF(100f, 100f)
+ positioner.restingPosition = restingPosition
+ assertThat(positioner.getRestingPosition()).isEqualTo(restingPosition)
+ }
+
+ /** Test that the default resting position on phone is in upper left. */
+ @Test
+ fun testGetRestingPosition_bubble_onPhone() {
+ positioner.update(defaultDeviceConfig)
+ val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
+ val restingPosition = positioner.getRestingPosition()
+ assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left)
+ assertThat(restingPosition.y).isEqualTo(defaultYPosition)
+ }
+
+ @Test
+ fun testGetRestingPosition_bubble_onPhone_RTL() {
+ positioner.update(defaultDeviceConfig.copy(isRtl = true))
+ val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
+ val restingPosition = positioner.getRestingPosition()
+ assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right)
+ assertThat(restingPosition.y).isEqualTo(defaultYPosition)
+ }
+
+ /** Test that the default resting position on tablet is middle left. */
+ @Test
+ fun testGetRestingPosition_chatBubble_onTablet() {
+ positioner.update(defaultDeviceConfig.copy(isLargeScreen = true))
+ val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
+ val restingPosition = positioner.getRestingPosition()
+ assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left)
+ assertThat(restingPosition.y).isEqualTo(defaultYPosition)
+ }
+
+ @Test
+ fun testGetRestingPosition_chatBubble_onTablet_RTL() {
+ positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true))
+ val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
+ val restingPosition = positioner.getRestingPosition()
+ assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right)
+ assertThat(restingPosition.y).isEqualTo(defaultYPosition)
+ }
+
+ /** Test that the default resting position on tablet is middle right. */
+ @Test
+ fun testGetDefaultPosition_appBubble_onTablet() {
+ positioner.update(defaultDeviceConfig.copy(isLargeScreen = true))
+ val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
+ val startPosition = positioner.getDefaultStartPosition(true /* isAppBubble */)
+ assertThat(startPosition.x).isEqualTo(allowableStackRegion.right)
+ assertThat(startPosition.y).isEqualTo(defaultYPosition)
+ }
+
+ @Test
+ fun testGetRestingPosition_appBubble_onTablet_RTL() {
+ positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true))
+ val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
+ val startPosition = positioner.getDefaultStartPosition(true /* isAppBubble */)
+ assertThat(startPosition.x).isEqualTo(allowableStackRegion.left)
+ assertThat(startPosition.y).isEqualTo(defaultYPosition)
+ }
+
+ @Test
+ fun testHasUserModifiedDefaultPosition_false() {
+ positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true))
+ assertThat(positioner.hasUserModifiedDefaultPosition()).isFalse()
+ positioner.restingPosition = positioner.defaultStartPosition
+ assertThat(positioner.hasUserModifiedDefaultPosition()).isFalse()
+ }
+
+ @Test
+ fun testHasUserModifiedDefaultPosition_true() {
+ positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true))
+ assertThat(positioner.hasUserModifiedDefaultPosition()).isFalse()
+ positioner.restingPosition = PointF(0f, 100f)
+ assertThat(positioner.hasUserModifiedDefaultPosition()).isTrue()
+ }
+
+ @Test
+ fun testGetExpandedViewHeight_max() {
+ val deviceConfig =
+ defaultDeviceConfig.copy(
+ isLargeScreen = true,
+ insets = Insets.of(10, 20, 5, 15),
+ windowBounds = Rect(0, 0, 1800, 2600)
+ )
+ positioner.update(deviceConfig)
+ val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
+ val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+
+ assertThat(positioner.getExpandedViewHeight(bubble)).isEqualTo(MAX_HEIGHT)
+ }
+
+ @Test
+ fun testGetExpandedViewHeight_customHeight_valid() {
+ val deviceConfig =
+ defaultDeviceConfig.copy(
+ isLargeScreen = true,
+ insets = Insets.of(10, 20, 5, 15),
+ windowBounds = Rect(0, 0, 1800, 2600)
+ )
+ positioner.update(deviceConfig)
+ val minHeight =
+ context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_default_height)
+ val bubble =
+ Bubble(
+ "key",
+ ShortcutInfo.Builder(context, "id").build(),
+ minHeight + 100 /* desiredHeight */,
+ 0 /* desiredHeightResId */,
+ "title",
+ 0 /* taskId */,
+ null /* locus */,
+ true /* isDismissable */,
+ directExecutor()) {}
+
+ // Ensure the height is the same as the desired value
+ assertThat(positioner.getExpandedViewHeight(bubble))
+ .isEqualTo(bubble.getDesiredHeight(context))
+ }
+
+ @Test
+ fun testGetExpandedViewHeight_customHeight_tooSmall() {
+ val deviceConfig =
+ defaultDeviceConfig.copy(
+ isLargeScreen = true,
+ insets = Insets.of(10, 20, 5, 15),
+ windowBounds = Rect(0, 0, 1800, 2600)
+ )
+ positioner.update(deviceConfig)
+
+ val bubble =
+ Bubble(
+ "key",
+ ShortcutInfo.Builder(context, "id").build(),
+ 10 /* desiredHeight */,
+ 0 /* desiredHeightResId */,
+ "title",
+ 0 /* taskId */,
+ null /* locus */,
+ true /* isDismissable */,
+ directExecutor()) {}
+
+ // Ensure the height is the same as the desired value
+ val minHeight =
+ context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_default_height)
+ assertThat(positioner.getExpandedViewHeight(bubble)).isEqualTo(minHeight)
+ }
+
+ @Test
+ fun testGetMaxExpandedViewHeight_onLargeTablet() {
+ val deviceConfig =
+ defaultDeviceConfig.copy(
+ isLargeScreen = true,
+ insets = Insets.of(10, 20, 5, 15),
+ windowBounds = Rect(0, 0, 1800, 2600)
+ )
+ positioner.update(deviceConfig)
+
+ val manageButtonHeight =
+ context.resources.getDimensionPixelSize(R.dimen.bubble_manage_button_height)
+ val pointerWidth = context.resources.getDimensionPixelSize(R.dimen.bubble_pointer_width)
+ val expandedViewPadding =
+ context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding)
+ val expectedHeight =
+ 1800 - 2 * 20 - manageButtonHeight - pointerWidth - expandedViewPadding * 2
+ assertThat(positioner.getMaxExpandedViewHeight(false /* isOverflow */))
+ .isEqualTo(expectedHeight)
+ }
+
+ @Test
+ fun testAreBubblesBottomAligned_largeScreen_true() {
+ val deviceConfig =
+ defaultDeviceConfig.copy(
+ isLargeScreen = true,
+ insets = Insets.of(10, 20, 5, 15),
+ windowBounds = Rect(0, 0, 1800, 2600)
+ )
+ positioner.update(deviceConfig)
+
+ assertThat(positioner.areBubblesBottomAligned()).isTrue()
+ }
+
+ @Test
+ fun testAreBubblesBottomAligned_largeScreen_landscape_false() {
+ val deviceConfig =
+ defaultDeviceConfig.copy(
+ isLargeScreen = true,
+ isLandscape = true,
+ insets = Insets.of(10, 20, 5, 15),
+ windowBounds = Rect(0, 0, 1800, 2600)
+ )
+ positioner.update(deviceConfig)
+
+ assertThat(positioner.areBubblesBottomAligned()).isFalse()
+ }
+
+ @Test
+ fun testAreBubblesBottomAligned_smallTablet_false() {
+ val deviceConfig =
+ defaultDeviceConfig.copy(
+ isLargeScreen = true,
+ isSmallTablet = true,
+ insets = Insets.of(10, 20, 5, 15),
+ windowBounds = Rect(0, 0, 1800, 2600)
+ )
+ positioner.update(deviceConfig)
+
+ assertThat(positioner.areBubblesBottomAligned()).isFalse()
+ }
+
+ @Test
+ fun testAreBubblesBottomAligned_phone_false() {
+ val deviceConfig =
+ defaultDeviceConfig.copy(
+ insets = Insets.of(10, 20, 5, 15),
+ windowBounds = Rect(0, 0, 1800, 2600)
+ )
+ positioner.update(deviceConfig)
+
+ assertThat(positioner.areBubblesBottomAligned()).isFalse()
+ }
+
+ @Test
+ fun testExpandedViewY_phoneLandscape() {
+ val deviceConfig =
+ defaultDeviceConfig.copy(
+ isLandscape = true,
+ insets = Insets.of(10, 20, 5, 15),
+ windowBounds = Rect(0, 0, 1800, 2600)
+ )
+ positioner.update(deviceConfig)
+
+ val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
+ val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+
+ // This bubble will have max height so it'll always be top aligned
+ assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+ .isEqualTo(positioner.getExpandedViewYTopAligned())
+ }
+
+ @Test
+ fun testExpandedViewY_phonePortrait() {
+ val deviceConfig =
+ defaultDeviceConfig.copy(
+ insets = Insets.of(10, 20, 5, 15),
+ windowBounds = Rect(0, 0, 1800, 2600)
+ )
+ positioner.update(deviceConfig)
+
+ val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
+ val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+
+ // Always top aligned in phone portrait
+ assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+ .isEqualTo(positioner.getExpandedViewYTopAligned())
+ }
+
+ @Test
+ fun testExpandedViewY_smallTabletLandscape() {
+ val deviceConfig =
+ defaultDeviceConfig.copy(
+ isSmallTablet = true,
+ isLandscape = true,
+ insets = Insets.of(10, 20, 5, 15),
+ windowBounds = Rect(0, 0, 1800, 2600)
+ )
+ positioner.update(deviceConfig)
+
+ val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
+ val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+
+ // This bubble will have max height which is always top aligned on small tablets
+ assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+ .isEqualTo(positioner.getExpandedViewYTopAligned())
+ }
+
+ @Test
+ fun testExpandedViewY_smallTabletPortrait() {
+ val deviceConfig =
+ defaultDeviceConfig.copy(
+ isSmallTablet = true,
+ insets = Insets.of(10, 20, 5, 15),
+ windowBounds = Rect(0, 0, 1800, 2600)
+ )
+ positioner.update(deviceConfig)
+
+ val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
+ val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+
+ // This bubble will have max height which is always top aligned on small tablets
+ assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+ .isEqualTo(positioner.getExpandedViewYTopAligned())
+ }
+
+ @Test
+ fun testExpandedViewY_largeScreenLandscape() {
+ val deviceConfig =
+ defaultDeviceConfig.copy(
+ isLargeScreen = true,
+ isLandscape = true,
+ insets = Insets.of(10, 20, 5, 15),
+ windowBounds = Rect(0, 0, 1800, 2600)
+ )
+ positioner.update(deviceConfig)
+
+ val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
+ val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+
+ // This bubble will have max height which is always top aligned on landscape, large tablet
+ assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+ .isEqualTo(positioner.getExpandedViewYTopAligned())
+ }
+
+ @Test
+ fun testExpandedViewY_largeScreenPortrait() {
+ val deviceConfig =
+ defaultDeviceConfig.copy(
+ isLargeScreen = true,
+ insets = Insets.of(10, 20, 5, 15),
+ windowBounds = Rect(0, 0, 1800, 2600)
+ )
+ positioner.update(deviceConfig)
+
+ val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
+ val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+
+ val manageButtonHeight =
+ context.resources.getDimensionPixelSize(R.dimen.bubble_manage_button_height)
+ val manageButtonPlusMargin =
+ manageButtonHeight +
+ 2 * context.resources.getDimensionPixelSize(R.dimen.bubble_manage_button_margin)
+ val pointerWidth = context.resources.getDimensionPixelSize(R.dimen.bubble_pointer_width)
+
+ val expectedExpandedViewY =
+ positioner.availableRect.bottom -
+ manageButtonPlusMargin -
+ positioner.getExpandedViewHeightForLargeScreen() -
+ pointerWidth
+
+ // Bubbles are bottom aligned on portrait, large tablet
+ assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+ .isEqualTo(expectedExpandedViewY)
+ }
+
+ private val defaultYPosition: Float
+ /**
+ * Calculates the Y position bubbles should be placed based on the config. Based on the
+ * calculations in [BubblePositioner.getDefaultStartPosition] and
+ * [BubbleStackView.RelativeStackPosition].
+ */
+ get() {
+ val isTablet = positioner.isLargeScreen
+
+ // On tablet the position is centered, on phone it is an offset from the top.
+ val desiredY =
+ if (isTablet) {
+ positioner.screenRect.height() / 2f - positioner.bubbleSize / 2f
+ } else {
+ context.resources
+ .getDimensionPixelOffset(R.dimen.bubble_stack_starting_offset_y)
+ .toFloat()
+ }
+ // Since we're visually centering the bubbles on tablet, use total screen height rather
+ // than the available height.
+ val height =
+ if (isTablet) {
+ positioner.screenRect.height()
+ } else {
+ positioner.availableRect.height()
+ }
+ val offsetPercent = (desiredY / height).coerceIn(0f, 1f)
+ val allowableStackRegion =
+ positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
+ return allowableStackRegion.top + allowableStackRegion.height() * offsetPercent
+ }
+}
diff --git a/libs/WindowManager/Shell/multivalentTestsForDevice b/libs/WindowManager/Shell/multivalentTestsForDevice
new file mode 120000
index 0000000..20ee34a
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTestsForDevice
@@ -0,0 +1 @@
+multivalentTests
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/multivalentTestsForDeviceless b/libs/WindowManager/Shell/multivalentTestsForDeviceless
new file mode 120000
index 0000000..20ee34a
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTestsForDeviceless
@@ -0,0 +1 @@
+multivalentTests
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index e4abae4..9854e58 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -134,6 +134,13 @@
<!-- Whether the additional education about reachability is enabled -->
<bool name="config_letterboxIsReachabilityEducationEnabled">false</bool>
+ <!-- The minimum tolerance of the percentage of activity bounds within its task to hide
+ size compat restart button. Value lower than 0 or higher than 100 will be ignored.
+ 100 is the default value where the activity has to fit exactly within the task to allow
+ size compat restart button to be hidden. 0 means size compat restart button will always
+ be hidden. -->
+ <integer name="config_letterboxRestartButtonHideTolerance">100</integer>
+
<!-- Whether DragAndDrop capability is enabled -->
<bool name="config_enableShellDragDrop">true</bool>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 81d9638..e7f6f0d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -17,7 +17,7 @@
package com.android.wm.shell.back;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_HOME;
-import static com.android.window.flags.Flags.predictiveBackSystemAnimations;
+import static com.android.window.flags.Flags.predictiveBackSystemAnims;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
@@ -176,6 +176,10 @@
private StatusBarCustomizer mCustomizer;
private boolean mTrackingLatency;
+ // Keep previous navigation type before remove mBackNavigationInfo.
+ @BackNavigationInfo.BackTargetType
+ private int mPreviousNavigationType;
+
public BackAnimationController(
@NonNull ShellInit shellInit,
@NonNull ShellController shellController,
@@ -240,7 +244,7 @@
private void setupAnimationDeveloperSettingsObserver(
@NonNull ContentResolver contentResolver,
@NonNull @ShellBackgroundThread final Handler backgroundHandler) {
- if (predictiveBackSystemAnimations()) {
+ if (predictiveBackSystemAnims()) {
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation aconfig flag is enabled, therefore "
+ "developer settings flag is ignored and no content observer registered");
return;
@@ -263,7 +267,7 @@
*/
@ShellBackgroundThread
private void updateEnableAnimationFromFlags() {
- boolean isEnabled = predictiveBackSystemAnimations() || isDeveloperSettingEnabled();
+ boolean isEnabled = predictiveBackSystemAnims() || isDeveloperSettingEnabled();
mEnableAnimations.set(isEnabled);
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation enabled=%s", isEnabled);
}
@@ -871,6 +875,7 @@
mShellBackAnimationRegistry.resetDefaultCrossActivity();
cancelLatencyTracking();
if (mBackNavigationInfo != null) {
+ mPreviousNavigationType = mBackNavigationInfo.getType();
mBackNavigationInfo.onBackNavigationFinished(triggerBack);
mBackNavigationInfo = null;
}
@@ -983,7 +988,9 @@
mShellExecutor.execute(
() -> {
if (!mShellBackAnimationRegistry.cancel(
- mBackNavigationInfo.getType())) {
+ mBackNavigationInfo != null
+ ? mBackNavigationInfo.getType()
+ : mPreviousNavigationType)) {
return;
}
if (!mBackGestureStarted) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index 80fc3a8..ac2a1c8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -136,6 +136,9 @@
mStartTaskRect.set(mClosingTarget.windowConfiguration.getBounds());
mStartTaskRect.offsetTo(0, 0);
+ // inset bottom in case of pinned taskbar being present
+ mStartTaskRect.inset(0, 0, 0, mClosingTarget.contentInsets.bottom);
+
// Draw background.
mBackground.ensureBackground(mClosingTarget.windowConfiguration.getBounds(),
BACKGROUNDCOLOR, mTransaction);
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 896bcaf..e23e15f 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
@@ -1249,6 +1249,7 @@
}
String appBubbleKey = Bubble.getAppBubbleKeyForApp(intent.getPackage(), user);
+ Log.v(TAG, "showOrHideAppBubble, with key: " + appBubbleKey);
PackageManager packageManager = getPackageManagerForUser(mContext, user.getIdentifier());
if (!isResizableActivity(intent, packageManager, appBubbleKey)) return;
@@ -1258,18 +1259,22 @@
if (isStackExpanded()) {
if (selectedBubble != null && appBubbleKey.equals(selectedBubble.getKey())) {
// App bubble is expanded, lets collapse
+ Log.v(TAG, " showOrHideAppBubble, selected bubble is app bubble, collapsing");
collapseStack();
} else {
// App bubble is not selected, select it
+ Log.v(TAG, " showOrHideAppBubble, expanded, selecting existing app bubble");
mBubbleData.setSelectedBubble(existingAppBubble);
}
} else {
// App bubble is not selected, select it & expand
+ Log.v(TAG, " showOrHideAppBubble, expand and select existing app bubble");
mBubbleData.setSelectedBubble(existingAppBubble);
mBubbleData.setExpanded(true);
}
} else {
// App bubble does not exist, lets add and expand it
+ Log.v(TAG, " showOrHideAppBubble, creating and expanding app bubble");
Bubble b = Bubble.createAppBubble(intent, user, icon, mMainExecutor);
b.setShouldAutoExpand(true);
inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index e487328..bb0dd95 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -769,8 +769,10 @@
boolean swapped = false;
for (int newIndex = 0; newIndex < bubbleViews.size(); newIndex++) {
View view = bubbleViews.get(newIndex);
- final int oldIndex = mLayout.indexOfChild(view);
- swapped |= animateSwap(view, oldIndex, newIndex, updateAllIcons, after);
+ if (view != null) {
+ final int oldIndex = mLayout.indexOfChild(view);
+ swapped |= animateSwap(view, oldIndex, newIndex, updateAllIcons, after);
+ }
}
if (!swapped) {
// All bubbles were at the right position. Make sure badges and z order is correct.
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 1211451..bd8ce80 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
@@ -26,6 +26,7 @@
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.ColorDrawable;
+import android.view.Gravity;
import android.view.TouchDelegate;
import android.view.View;
import android.view.ViewTreeObserver;
@@ -74,10 +75,6 @@
private DismissView mDismissView;
private @Nullable Consumer<String> mUnBubbleConversationCallback;
- // TODO(b/273310265) - currently the view is always on the right, need to update for RTL.
- /** Whether the expanded view is displaying on the left of the screen or not. */
- private boolean mOnLeft = false;
-
/** Whether a bubble is expanded. */
private boolean mIsExpanded = false;
@@ -154,10 +151,10 @@
return mIsExpanded;
}
- // (TODO: b/273310265): BubblePositioner should be source of truth when this work is done.
+ // 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 mOnLeft;
+ return getLayoutDirection() == LAYOUT_DIRECTION_RTL;
}
/** Shows the expanded view of the provided bubble. */
@@ -216,7 +213,7 @@
return Unit.INSTANCE;
});
- addView(mExpandedView, new FrameLayout.LayoutParams(width, height));
+ addView(mExpandedView, new LayoutParams(width, height, Gravity.LEFT));
}
if (mEducationViewController.isEducationVisible()) {
@@ -311,7 +308,7 @@
lp.width = width;
lp.height = height;
mExpandedView.setLayoutParams(lp);
- if (mOnLeft) {
+ if (isOnLeft()) {
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/compatui/CompatUIConfiguration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
index 09d99b2..fa2e236 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
@@ -71,6 +71,8 @@
private static final String HAS_SEEN_VERTICAL_REACHABILITY_EDUCATION_KEY_PREFIX =
"has_seen_vertical_reachability_education";
+ private static final int MAX_PERCENTAGE_VAL = 100;
+
/**
* The {@link SharedPreferences} instance for the restart dialog and the reachability
* education.
@@ -82,6 +84,12 @@
*/
private final SharedPreferences mLetterboxEduSharedPreferences;
+ /**
+ * The minimum tolerance of the percentage of activity bounds within its task to hide
+ * size compat restart button.
+ */
+ private final int mHideSizeCompatRestartButtonTolerance;
+
// Whether the extended restart dialog is enabled
private boolean mIsRestartDialogEnabled;
@@ -106,6 +114,9 @@
R.bool.config_letterboxIsRestartDialogEnabled);
mIsReachabilityEducationEnabled = context.getResources().getBoolean(
R.bool.config_letterboxIsReachabilityEducationEnabled);
+ final int tolerance = context.getResources().getInteger(
+ R.integer.config_letterboxRestartButtonHideTolerance);
+ mHideSizeCompatRestartButtonTolerance = getHideSizeCompatRestartButtonTolerance(tolerance);
mIsLetterboxRestartDialogAllowed = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_RESTART_DIALOG,
DEFAULT_VALUE_ENABLE_LETTERBOX_RESTART_DIALOG);
@@ -179,6 +190,10 @@
|| !hasSeenVerticalReachabilityEducation(taskInfo));
}
+ int getHideSizeCompatRestartButtonTolerance() {
+ return mHideSizeCompatRestartButtonTolerance;
+ }
+
boolean getHasSeenLetterboxEducation(int userId) {
return mLetterboxEduSharedPreferences
.getBoolean(dontShowLetterboxEduKey(userId), /* default= */ false);
@@ -218,6 +233,15 @@
}
}
+ // Returns the minimum tolerance of the percentage of activity bounds within its task to hide
+ // size compat restart button. Value lower than 0 or higher than 100 will be ignored.
+ // 100 is the default value where the activity has to fit exactly within the task to allow
+ // size compat restart button to be hidden. 0 means size compat restart button will always
+ // be hidden.
+ private int getHideSizeCompatRestartButtonTolerance(int tolerance) {
+ return tolerance < 0 || tolerance > MAX_PERCENTAGE_VAL ? MAX_PERCENTAGE_VAL : tolerance;
+ }
+
private boolean isReachabilityEducationEnabled() {
return mIsReachabilityEducationOverrideEnabled || (mIsReachabilityEducationEnabled
&& mIsLetterboxReachabilityEducationAllowed);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index 00e0cdb..2dd2743 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -22,7 +22,9 @@
import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.AppCompatTaskInfo;
import android.app.AppCompatTaskInfo.CameraCompatControlState;
import android.app.TaskInfo;
import android.content.Context;
@@ -33,6 +35,7 @@
import android.view.View;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayLayout;
@@ -68,6 +71,8 @@
@VisibleForTesting
CompatUILayout mLayout;
+ private final float mHideScmTolerance;
+
CompatUIWindowManager(Context context, TaskInfo taskInfo,
SyncTransactionQueue syncQueue, CompatUICallback callback,
ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
@@ -75,11 +80,13 @@
Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onRestartButtonClicked) {
super(context, taskInfo, syncQueue, taskListener, displayLayout);
mCallback = callback;
- mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
+ mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat
+ && shouldShowSizeCompatRestartButton(taskInfo);
mCameraCompatControlState = taskInfo.appCompatTaskInfo.cameraCompatControlState;
mCompatUIHintsState = compatUIHintsState;
mCompatUIConfiguration = compatUIConfiguration;
mOnRestartButtonClicked = onRestartButtonClicked;
+ mHideScmTolerance = mCompatUIConfiguration.getHideSizeCompatRestartButtonTolerance();
}
@Override
@@ -107,6 +114,11 @@
mLayout = inflateLayout();
mLayout.inject(this);
+ final TaskInfo taskInfo = getLastTaskInfo();
+ if (taskInfo != null) {
+ mHasSizeCompat = mHasSizeCompat && shouldShowSizeCompatRestartButton(taskInfo);
+ }
+
updateVisibilityOfViews();
if (mHasSizeCompat) {
@@ -127,7 +139,8 @@
boolean canShow) {
final boolean prevHasSizeCompat = mHasSizeCompat;
final int prevCameraCompatControlState = mCameraCompatControlState;
- mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
+ mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat
+ && shouldShowSizeCompatRestartButton(taskInfo);
mCameraCompatControlState = taskInfo.appCompatTaskInfo.cameraCompatControlState;
if (!super.updateCompatInfo(taskInfo, taskListener, canShow)) {
@@ -208,6 +221,30 @@
updateSurfacePosition(positionX, positionY);
}
+ @VisibleForTesting
+ boolean shouldShowSizeCompatRestartButton(@NonNull TaskInfo taskInfo) {
+ if (!Flags.allowHideScmButton()) {
+ return true;
+ }
+ final AppCompatTaskInfo appCompatTaskInfo = taskInfo.appCompatTaskInfo;
+ final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds();
+ final int letterboxArea = computeArea(appCompatTaskInfo.topActivityLetterboxWidth,
+ appCompatTaskInfo.topActivityLetterboxHeight);
+ final int taskArea = computeArea(taskBounds.width(), taskBounds.height());
+ if (letterboxArea == 0 || taskArea == 0) {
+ return false;
+ }
+ final float percentageAreaOfLetterboxInTask = (float) letterboxArea / taskArea * 100;
+ return percentageAreaOfLetterboxInTask < mHideScmTolerance;
+ }
+
+ private int computeArea(int width, int height) {
+ if (width == 0 || height == 0) {
+ return 0;
+ }
+ return width * height;
+ }
+
private void updateVisibilityOfViews() {
if (mLayout == null) {
return;
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 a587bed..da1ca8d 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
@@ -16,6 +16,8 @@
package com.android.wm.shell.desktopmode;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+
import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FINAL_FREEFORM_SCALE;
import android.animation.Animator;
@@ -39,7 +41,6 @@
import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
-import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -48,100 +49,71 @@
* Animated visual indicator for Desktop Mode windowing transitions.
*/
public class DesktopModeVisualIndicator {
- public static final int INVALID_INDICATOR = -1;
- /** Indicates impending transition into desktop mode */
- public static final int TO_DESKTOP_INDICATOR = 1;
- /** Indicates impending transition into fullscreen */
- public static final int TO_FULLSCREEN_INDICATOR = 2;
- /** Indicates impending transition into split select on the left side */
- public static final int TO_SPLIT_LEFT_INDICATOR = 3;
- /** Indicates impending transition into split select on the right side */
- public static final int TO_SPLIT_RIGHT_INDICATOR = 4;
+ public enum IndicatorType {
+ /** To be used when we don't want to indicate any transition */
+ NO_INDICATOR,
+ /** Indicates impending transition into desktop mode */
+ TO_DESKTOP_INDICATOR,
+ /** Indicates impending transition into fullscreen */
+ TO_FULLSCREEN_INDICATOR,
+ /** Indicates impending transition into split select on the left side */
+ TO_SPLIT_LEFT_INDICATOR,
+ /** Indicates impending transition into split select on the right side */
+ TO_SPLIT_RIGHT_INDICATOR
+ }
private final Context mContext;
private final DisplayController mDisplayController;
- private final ShellTaskOrganizer mTaskOrganizer;
private final RootTaskDisplayAreaOrganizer mRootTdaOrganizer;
private final ActivityManager.RunningTaskInfo mTaskInfo;
private final SurfaceControl mTaskSurface;
- private final Rect mIndicatorRange = new Rect();
private SurfaceControl mLeash;
private final SyncTransactionQueue mSyncQueue;
private SurfaceControlViewHost mViewHost;
private View mView;
- private boolean mIsFullscreen;
- private int mType;
+ private IndicatorType mCurrentType;
public DesktopModeVisualIndicator(SyncTransactionQueue syncQueue,
ActivityManager.RunningTaskInfo taskInfo, DisplayController displayController,
- Context context, SurfaceControl taskSurface, ShellTaskOrganizer taskOrganizer,
- RootTaskDisplayAreaOrganizer taskDisplayAreaOrganizer, int type) {
+ Context context, SurfaceControl taskSurface,
+ RootTaskDisplayAreaOrganizer taskDisplayAreaOrganizer) {
mSyncQueue = syncQueue;
mTaskInfo = taskInfo;
mDisplayController = displayController;
mContext = context;
mTaskSurface = taskSurface;
- mTaskOrganizer = taskOrganizer;
mRootTdaOrganizer = taskDisplayAreaOrganizer;
- mType = type;
- defineIndicatorRange();
+ mCurrentType = IndicatorType.NO_INDICATOR;
createView();
}
/**
- * If an indicator is warranted based on the input and task bounds, return the type of
- * indicator that should be created.
+ * Based on the coordinates of the current drag event, determine which indicator type we should
+ * display, including no visible indicator.
+ * TODO(b/280828642): Update drag zones per starting windowing mode.
*/
- public static int determineIndicatorType(PointF inputCoordinates, Rect taskBounds,
- DisplayLayout layout, Context context) {
- int transitionAreaHeight = context.getResources().getDimensionPixelSize(
- com.android.wm.shell.R.dimen.desktop_mode_transition_area_height);
- int transitionAreaWidth = context.getResources().getDimensionPixelSize(
- com.android.wm.shell.R.dimen.desktop_mode_transition_area_width);
- if (taskBounds.top <= transitionAreaHeight) return TO_FULLSCREEN_INDICATOR;
- if (inputCoordinates.x <= transitionAreaWidth) return TO_SPLIT_LEFT_INDICATOR;
- if (inputCoordinates.x >= layout.width() - transitionAreaWidth) {
- return TO_SPLIT_RIGHT_INDICATOR;
- }
- return INVALID_INDICATOR;
- }
-
- /**
- * Determine range of inputs that will keep this indicator displaying.
- */
- private void defineIndicatorRange() {
- DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
- int captionHeight = mContext.getResources().getDimensionPixelSize(
- com.android.wm.shell.R.dimen.freeform_decor_caption_height);
+ IndicatorType updateIndicatorType(PointF inputCoordinates) {
+ final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
+ // If we are in freeform, we don't want a visible indicator in the "freeform" drag zone.
+ IndicatorType result = mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
+ ? IndicatorType.NO_INDICATOR : IndicatorType.TO_DESKTOP_INDICATOR;
int transitionAreaHeight = mContext.getResources().getDimensionPixelSize(
com.android.wm.shell.R.dimen.desktop_mode_transition_area_height);
int transitionAreaWidth = mContext.getResources().getDimensionPixelSize(
com.android.wm.shell.R.dimen.desktop_mode_transition_area_width);
- switch (mType) {
- case TO_DESKTOP_INDICATOR:
- // TO_DESKTOP indicator is only dismissed on release; entire display is valid.
- mIndicatorRange.set(0, 0, layout.width(), layout.height());
- break;
- case TO_FULLSCREEN_INDICATOR:
- // If drag results in caption going above the top edge of the display, we still
- // want to transition to fullscreen.
- mIndicatorRange.set(0, -captionHeight, layout.width(), transitionAreaHeight);
- break;
- case TO_SPLIT_LEFT_INDICATOR:
- mIndicatorRange.set(0, transitionAreaHeight, transitionAreaWidth, layout.height());
- break;
- case TO_SPLIT_RIGHT_INDICATOR:
- mIndicatorRange.set(layout.width() - transitionAreaWidth, transitionAreaHeight,
- layout.width(), layout.height());
- break;
- default:
- break;
+ if (inputCoordinates.y <= transitionAreaHeight) {
+ result = IndicatorType.TO_FULLSCREEN_INDICATOR;
+ } else if (inputCoordinates.x <= transitionAreaWidth) {
+ result = IndicatorType.TO_SPLIT_LEFT_INDICATOR;
+ } else if (inputCoordinates.x >= layout.width() - transitionAreaWidth) {
+ result = IndicatorType.TO_SPLIT_RIGHT_INDICATOR;
}
+ transitionIndicator(result);
+ return result;
}
-
/**
* Create a fullscreen indicator with no animation
*/
@@ -156,7 +128,7 @@
final SurfaceControl.Builder builder = new SurfaceControl.Builder();
mRootTdaOrganizer.attachToDisplayArea(mTaskInfo.displayId, builder);
String description;
- switch (mType) {
+ switch (mCurrentType) {
case TO_DESKTOP_INDICATOR:
description = "Desktop indicator";
break;
@@ -201,46 +173,45 @@
}
/**
- * Create an indicator. Animator fades it in while expanding the bounds outwards.
+ * Fade indicator in as provided type. Animator fades it in while expanding the bounds outwards.
*/
- public void createIndicatorWithAnimatedBounds() {
- mIsFullscreen = mType == TO_FULLSCREEN_INDICATOR;
+ private void fadeInIndicator(IndicatorType type) {
mView.setBackgroundResource(R.drawable.desktop_windowing_transition_background);
final VisualIndicatorAnimator animator = VisualIndicatorAnimator
- .animateBounds(mView, mType,
+ .fadeBoundsIn(mView, type,
mDisplayController.getDisplayLayout(mTaskInfo.displayId));
animator.start();
+ mCurrentType = type;
}
/**
- * Takes existing fullscreen indicator and animates it to freeform bounds
+ * Fade out indicator without fully releasing it. Animator fades it out while shrinking bounds.
*/
- public void transitionFullscreenIndicatorToFreeform() {
- mIsFullscreen = false;
- mType = TO_DESKTOP_INDICATOR;
- final VisualIndicatorAnimator animator = VisualIndicatorAnimator.toFreeformAnimator(
- mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId));
+ private void fadeOutIndicator() {
+ final VisualIndicatorAnimator animator = VisualIndicatorAnimator
+ .fadeBoundsOut(mView, mCurrentType,
+ mDisplayController.getDisplayLayout(mTaskInfo.displayId));
animator.start();
+ mCurrentType = IndicatorType.NO_INDICATOR;
+
}
/**
- * Takes the existing freeform indicator and animates it to fullscreen
+ * Takes existing indicator and animates it to bounds reflecting a new indicator type.
*/
- public void transitionFreeformIndicatorToFullscreen() {
- mIsFullscreen = true;
- mType = TO_FULLSCREEN_INDICATOR;
- final VisualIndicatorAnimator animator =
- VisualIndicatorAnimator.toFullscreenAnimatorWithAnimatedBounds(
- mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId));
- animator.start();
- }
-
- /**
- * Determine if a MotionEvent is in the same range that enabled the indicator.
- * Used to dismiss the indicator when a transition will no longer result from releasing.
- */
- public boolean eventOutsideRange(float x, float y) {
- return !mIndicatorRange.contains((int) x, (int) y);
+ private void transitionIndicator(IndicatorType newType) {
+ if (mCurrentType == newType) return;
+ if (mCurrentType == IndicatorType.NO_INDICATOR) {
+ fadeInIndicator(newType);
+ } else if (newType == IndicatorType.NO_INDICATOR) {
+ fadeOutIndicator();
+ } else {
+ final VisualIndicatorAnimator animator = VisualIndicatorAnimator.animateIndicatorType(
+ mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId), mCurrentType,
+ newType);
+ mCurrentType = newType;
+ animator.start();
+ }
}
/**
@@ -260,13 +231,6 @@
}
/**
- * Returns true if visual indicator is fullscreen
- */
- public boolean isFullscreen() {
- return mIsFullscreen;
- }
-
- /**
* Animator for Desktop Mode transitions which supports bounds and alpha animation.
*/
private static class VisualIndicatorAnimator extends ValueAnimator {
@@ -274,6 +238,13 @@
private static final float FULLSCREEN_SCALE_ADJUSTMENT_PERCENT = 0.015f;
private static final float INDICATOR_FINAL_OPACITY = 0.7f;
+ /** Determines how this animator will interact with the view's alpha:
+ * Fade in, fade out, or no change to alpha
+ */
+ private enum AlphaAnimType{
+ ALPHA_FADE_IN_ANIM, ALPHA_FADE_OUT_ANIM, ALPHA_NO_CHANGE_ANIM
+ }
+
private final View mView;
private final Rect mStartBounds;
private final Rect mEndBounds;
@@ -288,87 +259,91 @@
mRectEvaluator = new RectEvaluator(new Rect());
}
- /**
- * Create animator for visual indicator of fullscreen transition
- *
- * @param view the view for this indicator
- * @param displayLayout information about the display the transitioning task is currently on
- */
- public static VisualIndicatorAnimator toFullscreenAnimatorWithAnimatedBounds(
- @NonNull View view, @NonNull DisplayLayout displayLayout) {
- final int padding = displayLayout.stableInsets().top;
- Rect startBounds = new Rect(padding, padding,
- displayLayout.width() - padding, displayLayout.height() - padding);
+ private static VisualIndicatorAnimator fadeBoundsIn(
+ @NonNull View view, IndicatorType type, @NonNull DisplayLayout displayLayout) {
+ final Rect startBounds = getIndicatorBounds(displayLayout, type);
view.getBackground().setBounds(startBounds);
final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
view, startBounds, getMaxBounds(startBounds));
animator.setInterpolator(new DecelerateInterpolator());
- setupIndicatorAnimation(animator);
+ setupIndicatorAnimation(animator, AlphaAnimType.ALPHA_FADE_IN_ANIM);
return animator;
}
- public static VisualIndicatorAnimator animateBounds(
- @NonNull View view, int type, @NonNull DisplayLayout displayLayout) {
- final int padding = displayLayout.stableInsets().top;
- Rect startBounds = new Rect();
- switch (type) {
- case TO_FULLSCREEN_INDICATOR:
- startBounds.set(padding, padding,
- displayLayout.width() - padding,
- displayLayout.height() - padding);
- break;
- case TO_SPLIT_LEFT_INDICATOR:
- startBounds.set(padding, padding,
- displayLayout.width() / 2 - padding,
- displayLayout.height() - padding);
- break;
- case TO_SPLIT_RIGHT_INDICATOR:
- startBounds.set(displayLayout.width() / 2 + padding, padding,
- displayLayout.width() - padding,
- displayLayout.height() - padding);
- break;
- }
+ private static VisualIndicatorAnimator fadeBoundsOut(
+ @NonNull View view, IndicatorType type, @NonNull DisplayLayout displayLayout) {
+ final Rect endBounds = getIndicatorBounds(displayLayout, type);
+ final Rect startBounds = getMaxBounds(endBounds);
view.getBackground().setBounds(startBounds);
final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
- view, startBounds, getMaxBounds(startBounds));
- animator.setInterpolator(new DecelerateInterpolator());
- setupIndicatorAnimation(animator);
- return animator;
- }
-
- /**
- * Create animator for visual indicator of freeform transition
- *
- * @param view the view for this indicator
- * @param displayLayout information about the display the transitioning task is currently on
- */
- public static VisualIndicatorAnimator toFreeformAnimator(@NonNull View view,
- @NonNull DisplayLayout displayLayout) {
- final float adjustmentPercentage = 1f - FINAL_FREEFORM_SCALE;
- final int width = displayLayout.width();
- final int height = displayLayout.height();
- Rect startBounds = new Rect(0, 0, width, height);
- Rect endBounds = new Rect((int) (adjustmentPercentage * width / 2),
- (int) (adjustmentPercentage * height / 2),
- (int) (displayLayout.width() - (adjustmentPercentage * width / 2)),
- (int) (displayLayout.height() - (adjustmentPercentage * height / 2)));
- final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
view, startBounds, endBounds);
animator.setInterpolator(new DecelerateInterpolator());
- setupIndicatorAnimation(animator);
+ setupIndicatorAnimation(animator, AlphaAnimType.ALPHA_FADE_OUT_ANIM);
return animator;
}
/**
+ * Create animator for visual indicator changing type (i.e., fullscreen to freeform,
+ * freeform to split, etc.)
+ *
+ * @param view the view for this indicator
+ * @param displayLayout information about the display the transitioning task is currently on
+ * @param origType the original indicator type
+ * @param newType the new indicator type
+ */
+ private static VisualIndicatorAnimator animateIndicatorType(@NonNull View view,
+ @NonNull DisplayLayout displayLayout, IndicatorType origType,
+ IndicatorType newType) {
+ final Rect startBounds = getIndicatorBounds(displayLayout, origType);
+ final Rect endBounds = getIndicatorBounds(displayLayout, newType);
+ final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
+ view, startBounds, endBounds);
+ animator.setInterpolator(new DecelerateInterpolator());
+ setupIndicatorAnimation(animator, AlphaAnimType.ALPHA_NO_CHANGE_ANIM);
+ return animator;
+ }
+
+ private static Rect getIndicatorBounds(DisplayLayout layout, IndicatorType type) {
+ final int padding = layout.stableInsets().top;
+ switch (type) {
+ case TO_FULLSCREEN_INDICATOR:
+ return new Rect(padding, padding,
+ layout.width() - padding,
+ layout.height() - padding);
+ case TO_DESKTOP_INDICATOR:
+ final float adjustmentPercentage = 1f - FINAL_FREEFORM_SCALE;
+ return new Rect((int) (adjustmentPercentage * layout.width() / 2),
+ (int) (adjustmentPercentage * layout.height() / 2),
+ (int) (layout.width() - (adjustmentPercentage * layout.width() / 2)),
+ (int) (layout.height() - (adjustmentPercentage * layout.height() / 2)));
+ case TO_SPLIT_LEFT_INDICATOR:
+ return new Rect(padding, padding,
+ layout.width() / 2 - padding,
+ layout.height() - padding);
+ case TO_SPLIT_RIGHT_INDICATOR:
+ return new Rect(layout.width() / 2 + padding, padding,
+ layout.width() - padding,
+ layout.height() - padding);
+ default:
+ throw new IllegalArgumentException("Invalid indicator type provided.");
+ }
+ }
+
+ /**
* Add necessary listener for animation of indicator
*/
- private static void setupIndicatorAnimation(@NonNull VisualIndicatorAnimator animator) {
+ private static void setupIndicatorAnimation(@NonNull VisualIndicatorAnimator animator,
+ AlphaAnimType animType) {
animator.addUpdateListener(a -> {
if (animator.mView != null) {
animator.updateBounds(a.getAnimatedFraction(), animator.mView);
- animator.updateIndicatorAlpha(a.getAnimatedFraction(), animator.mView);
+ if (animType == AlphaAnimType.ALPHA_FADE_IN_ANIM) {
+ animator.updateIndicatorAlpha(a.getAnimatedFraction(), animator.mView);
+ } else if (animType == AlphaAnimType.ALPHA_FADE_OUT_ANIM) {
+ animator.updateIndicatorAlpha(1 - a.getAnimatedFraction(), animator.mView);
+ }
} else {
animator.cancel();
}
@@ -394,7 +369,7 @@
if (mStartBounds.equals(mEndBounds)) {
return;
}
- Rect currentBounds = mRectEvaluator.evaluate(fraction, mStartBounds, mEndBounds);
+ final Rect currentBounds = mRectEvaluator.evaluate(fraction, mStartBounds, mEndBounds);
view.getBackground().setBounds(currentBounds);
}
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 b1c43c1..4f5c39a 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
@@ -57,7 +57,6 @@
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.DesktopModeVisualIndicator.TO_DESKTOP_INDICATOR
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.recents.RecentsTransitionHandler
@@ -871,31 +870,34 @@
*
* @param taskInfo the task being dragged.
* @param taskSurface SurfaceControl of dragged task.
- * @param inputCoordinate coordinates of input. Used for checks against left/right edge of screen.
+ * @param inputX x coordinate of input. Used for checks against left/right edge of screen.
* @param taskBounds bounds of dragged task. Used for checks against status bar height.
*/
fun onDragPositioningMove(
taskInfo: RunningTaskInfo,
taskSurface: SurfaceControl,
- inputCoordinate: PointF,
+ inputX: Float,
taskBounds: Rect
) {
- val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
if (taskInfo.windowingMode != WINDOWING_MODE_FREEFORM) return
- var type = DesktopModeVisualIndicator.determineIndicatorType(inputCoordinate,
- taskBounds, displayLayout, context)
- if (type != DesktopModeVisualIndicator.INVALID_INDICATOR && visualIndicator == null) {
+ updateVisualIndicator(taskInfo, taskSurface, inputX, taskBounds.top.toFloat())
+ }
+
+ fun updateVisualIndicator(
+ taskInfo: RunningTaskInfo,
+ taskSurface: SurfaceControl,
+ inputX: Float,
+ taskTop: Float
+ ) {
+ // If the visual indicator does not exist, create it.
+ if (visualIndicator == null) {
visualIndicator = DesktopModeVisualIndicator(
- syncQueue, taskInfo,
- displayController, context, taskSurface, shellTaskOrganizer,
- rootTaskDisplayAreaOrganizer, type)
- visualIndicator?.createIndicatorWithAnimatedBounds()
- return
+ syncQueue, taskInfo, displayController, context, taskSurface,
+ rootTaskDisplayAreaOrganizer)
}
- if (visualIndicator?.eventOutsideRange(inputCoordinate.x,
- taskBounds.top.toFloat()) == true) {
- releaseVisualIndicator()
- }
+ // Then, update the indicator type.
+ val indicator = visualIndicator ?: return
+ indicator.updateIndicatorType(PointF(inputX, taskTop))
}
/**
@@ -936,45 +938,6 @@
}
/**
- * Perform checks required on drag move. Create/release fullscreen indicator and transitions
- * indicator to freeform or fullscreen dimensions as needed.
- *
- * @param taskInfo the task being dragged.
- * @param taskSurface SurfaceControl of dragged task.
- * @param y coordinate of dragged task. Used for checks against status bar height.
- */
- fun onDragPositioningMoveThroughStatusBar(
- taskInfo: RunningTaskInfo,
- taskSurface: SurfaceControl,
- y: Float
- ) {
- // If the motion event is above the status bar and the visual indicator is not yet visible,
- // return since we do not need to show the visual indicator at this point.
- if (y < getStatusBarHeight(taskInfo) && visualIndicator == null) {
- return
- }
- if (visualIndicator == null) {
- visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo,
- displayController, context, taskSurface, shellTaskOrganizer,
- rootTaskDisplayAreaOrganizer, TO_DESKTOP_INDICATOR)
- // TODO(b/301106941): don't show the indicator until the drag-to-desktop animation has
- // started, or it'll be visible too early on top of the task surface, especially in
- // the cancel-early case. Also because it shouldn't even be shown in the cancel-early
- // case since its dismissal is tied to the cancel animation end, which doesn't even run
- // in cancel-early.
- visualIndicator?.createIndicatorWithAnimatedBounds()
- }
- val indicator = visualIndicator ?: return
- if (y >= getFreeformTransitionStatusBarDragThreshold(taskInfo)) {
- if (indicator.isFullscreen) {
- indicator.transitionFullscreenIndicatorToFreeform()
- }
- } else if (!indicator.isFullscreen) {
- indicator.transitionFreeformIndicatorToFullscreen()
- }
- }
-
- /**
* Perform checks required when drag ends under status bar area.
*
* @param taskInfo the task being dragged.
@@ -992,14 +955,6 @@
}
/**
- * Returns the threshold at which we transition a task into freeform when dragging a
- * fullscreen task down from the status bar
- */
- private fun getFreeformTransitionStatusBarDragThreshold(taskInfo: RunningTaskInfo): Int {
- return 2 * getStatusBarHeight(taskInfo)
- }
-
- /**
* Update the exclusion region for a specified task
*/
fun onExclusionRegionChanged(taskId: Int, exclusionRegion: Region) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 95d7ad5..c3a82ce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -99,7 +99,11 @@
windowDecoration: DesktopModeWindowDecoration
) {
if (inProgress) {
- error("A drag to desktop is already in progress")
+ KtProtoLog.v(
+ ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "DragToDesktop: Drag to desktop transition already in progress."
+ )
+ return
}
val options = ActivityOptions.makeBasic().apply {
@@ -144,6 +148,12 @@
* inside the desktop drop zone.
*/
fun finishDragToDesktopTransition(wct: WindowContainerTransaction) {
+ if (!inProgress) {
+ // Don't attempt to finish a drag to desktop transition since there is no transition in
+ // progress which means that the drag to desktop transition was never successfully
+ // started.
+ return
+ }
if (requireTransitionState().startAborted) {
// Don't attempt to complete the drag-to-desktop since the start transition didn't
// succeed as expected. Just reset the state as if nothing happened.
@@ -161,6 +171,12 @@
* means the user wants to remain in their current windowing mode.
*/
fun cancelDragToDesktopTransition() {
+ if (!inProgress) {
+ // Don't attempt to cancel a drag to desktop transition since there is no transition in
+ // progress which means that the drag to desktop transition was never successfully
+ // started.
+ return
+ }
val state = requireTransitionState()
if (state.startAborted) {
// Don't attempt to cancel the drag-to-desktop since the start transition didn't
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
index 1c94625..54e162b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
@@ -54,6 +54,7 @@
// Referenced in com.android.systemui.util.NotificationChannels.
public static final String NOTIFICATION_CHANNEL = "TVPIP";
private static final String NOTIFICATION_TAG = "TvPip";
+ private static final String EXTRA_COMPONENT_NAME = "TvPipComponentName";
private final Context mContext;
private final PackageManager mPackageManager;
@@ -176,6 +177,7 @@
Bundle extras = new Bundle();
extras.putParcelable(Notification.EXTRA_MEDIA_SESSION, mMediaSessionToken);
+ extras.putParcelable(EXTRA_COMPONENT_NAME, PipUtils.getTopPipActivity(mContext).first);
mNotificationBuilder.setExtras(extras);
PendingIntent closeIntent = mTvPipActionsProvider.getCloseAction().getPendingIntent();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index bc1a575..5de8a9b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -481,18 +481,20 @@
private void startFadeAnimation(@NonNull SurfaceControl leash, boolean show) {
final float end = show ? 1.f : 0.f;
final float start = 1.f - end;
- final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
final ValueAnimator va = ValueAnimator.ofFloat(start, end);
va.setDuration(FADE_DURATION);
va.setInterpolator(show ? ALPHA_IN : ALPHA_OUT);
va.addUpdateListener(animation -> {
float fraction = animation.getAnimatedFraction();
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction);
transaction.apply();
+ mTransactionPool.release(transaction);
});
va.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
transaction.setAlpha(leash, end);
transaction.apply();
mTransactionPool.release(transaction);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index e6418f3..1a0c011 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -135,7 +135,6 @@
} catch (RemoteException e) {
snapshotSurface.clearWindowSynced();
}
- window.setOuter(snapshotSurface);
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#relayout");
session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0, 0, 0,
@@ -161,7 +160,7 @@
ShellExecutor splashScreenExecutor) {
mSplashScreenExecutor = splashScreenExecutor;
mSession = WindowManagerGlobal.getWindowSession();
- mWindow = new Window();
+ mWindow = new Window(this);
mWindow.setSession(mSession);
int backgroundColor = taskDescription.getBackgroundColor();
mBackgroundPaint.setColor(backgroundColor != 0 ? backgroundColor : WHITE);
@@ -204,9 +203,9 @@
}
static class Window extends BaseIWindow {
- private WeakReference<TaskSnapshotWindow> mOuter;
+ private final WeakReference<TaskSnapshotWindow> mOuter;
- public void setOuter(TaskSnapshotWindow outer) {
+ Window(TaskSnapshotWindow outer) {
mOuter = new WeakReference<>(outer);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 8c861c6..8c2203e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -21,17 +21,9 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
import static android.view.WindowManager.TRANSIT_PIP;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
-import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
-import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
-import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
-import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
-import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
import android.annotation.NonNull;
@@ -56,7 +48,6 @@
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentsTransitionHandler;
-import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.splitscreen.StageCoordinator;
import com.android.wm.shell.sysui.ShellInit;
@@ -84,7 +75,7 @@
private UnfoldTransitionHandler mUnfoldHandler;
private ActivityEmbeddingController mActivityEmbeddingController;
- private static class MixedTransition {
+ abstract static class MixedTransition {
static final int TYPE_ENTER_PIP_FROM_SPLIT = 1;
/** Both the display and split-state (enter/exit) is changing */
@@ -124,15 +115,11 @@
int mAnimType = ANIM_TYPE_DEFAULT;
final IBinder mTransition;
- private final Transitions mPlayer;
- private final DefaultMixedHandler mMixedHandler;
- private final PipTransitionController mPipHandler;
- private final RecentsTransitionHandler mRecentsHandler;
- private final StageCoordinator mSplitHandler;
- private final KeyguardTransitionHandler mKeyguardHandler;
- private final DesktopTasksController mDesktopTasksController;
- private final UnfoldTransitionHandler mUnfoldHandler;
- private final ActivityEmbeddingController mActivityEmbeddingController;
+ protected final Transitions mPlayer;
+ protected final DefaultMixedHandler mMixedHandler;
+ protected final PipTransitionController mPipHandler;
+ protected final StageCoordinator mSplitHandler;
+ protected final KeyguardTransitionHandler mKeyguardHandler;
Transitions.TransitionHandler mLeftoversHandler = null;
TransitionInfo mInfo = null;
@@ -156,387 +143,33 @@
MixedTransition(int type, IBinder transition, Transitions player,
DefaultMixedHandler mixedHandler, PipTransitionController pipHandler,
- RecentsTransitionHandler recentsHandler, StageCoordinator splitHandler,
- KeyguardTransitionHandler keyguardHandler,
- DesktopTasksController desktopTasksController,
- UnfoldTransitionHandler unfoldHandler,
- ActivityEmbeddingController activityEmbeddingController) {
+ StageCoordinator splitHandler, KeyguardTransitionHandler keyguardHandler) {
mType = type;
mTransition = transition;
mPlayer = player;
mMixedHandler = mixedHandler;
mPipHandler = pipHandler;
- mRecentsHandler = recentsHandler;
mSplitHandler = splitHandler;
mKeyguardHandler = keyguardHandler;
- mDesktopTasksController = desktopTasksController;
- mUnfoldHandler = unfoldHandler;
- mActivityEmbeddingController = activityEmbeddingController;
-
- switch (type) {
- case TYPE_RECENTS_DURING_DESKTOP:
- case TYPE_RECENTS_DURING_KEYGUARD:
- case TYPE_RECENTS_DURING_SPLIT:
- mLeftoversHandler = mRecentsHandler;
- break;
- case TYPE_UNFOLD:
- mLeftoversHandler = mUnfoldHandler;
- break;
- case TYPE_DISPLAY_AND_SPLIT_CHANGE:
- case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING:
- case TYPE_ENTER_PIP_FROM_SPLIT:
- case TYPE_KEYGUARD:
- case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE:
- default:
- break;
- }
}
- boolean startAnimation(
+ abstract boolean startAnimation(
@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- if (mType == TYPE_ENTER_PIP_FROM_SPLIT) {
- return animateEnterPipFromSplit(this, info, startTransaction, finishTransaction,
- finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler);
- } else if (mType == TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
- return animateEnterPipFromActivityEmbedding(
- info, startTransaction, finishTransaction, finishCallback);
- } else if (mType == TYPE_DISPLAY_AND_SPLIT_CHANGE) {
- return false;
- } else if (mType == TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
- final boolean handledToPip = animateOpenIntentWithRemoteAndPip(
- info, startTransaction, finishTransaction, finishCallback);
- // Consume the transition on remote handler if the leftover handler already handle
- // this transition. And if it cannot, the transition will be handled by remote
- // handler, so don't consume here.
- // Need to check leftOverHandler as it may change in
- // #animateOpenIntentWithRemoteAndPip
- if (handledToPip && mHasRequestToRemote
- && mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) {
- mPlayer.getRemoteTransitionHandler().onTransitionConsumed(
- transition, false, null);
- }
- return handledToPip;
- } else if (mType == TYPE_RECENTS_DURING_SPLIT) {
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- // Pip auto-entering info might be appended to recent transition like pressing
- // home-key in 3-button navigation. This offers split handler the opportunity to
- // handle split to pip animation.
- if (mPipHandler.isEnteringPip(change, info.getType())
- && mSplitHandler.getSplitItemPosition(change.getLastParent())
- != SPLIT_POSITION_UNDEFINED) {
- return animateEnterPipFromSplit(
- this, info, startTransaction, finishTransaction, finishCallback,
- mPlayer, mMixedHandler, mPipHandler, mSplitHandler);
- }
- }
+ @NonNull Transitions.TransitionFinishCallback finishCallback);
- return animateRecentsDuringSplit(
- info, startTransaction, finishTransaction, finishCallback);
- } else if (mType == TYPE_KEYGUARD) {
- return animateKeyguard(this, info, startTransaction, finishTransaction,
- finishCallback, mKeyguardHandler, mPipHandler);
- } else if (mType == TYPE_RECENTS_DURING_KEYGUARD) {
- return animateRecentsDuringKeyguard(
- info, startTransaction, finishTransaction, finishCallback);
- } else if (mType == TYPE_RECENTS_DURING_DESKTOP) {
- return animateRecentsDuringDesktop(
- info, startTransaction, finishTransaction, finishCallback);
- } else if (mType == TYPE_UNFOLD) {
- return animateUnfold(
- info, startTransaction, finishTransaction, finishCallback);
- } else {
- throw new IllegalStateException(
- "Starting mixed animation without a known mixed type? " + mType);
- }
- }
-
- private boolean animateEnterPipFromActivityEmbedding(
- @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
- + "entering PIP from an Activity Embedding window");
- // Split into two transitions (wct)
- TransitionInfo.Change pipChange = null;
- final TransitionInfo everythingElse =
- subCopy(info, TRANSIT_TO_BACK, true /* changes */);
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- TransitionInfo.Change change = info.getChanges().get(i);
- if (mPipHandler.isEnteringPip(change, info.getType())) {
- if (pipChange != null) {
- throw new IllegalStateException("More than 1 pip-entering changes in one"
- + " transition? " + info);
- }
- pipChange = change;
- // going backwards, so remove-by-index is fine.
- everythingElse.getChanges().remove(i);
- }
- }
-
- final Transitions.TransitionFinishCallback finishCB = (wct) -> {
- --mInFlightSubAnimations;
- joinFinishArgs(wct);
- if (mInFlightSubAnimations > 0) return;
- finishCallback.onTransitionFinished(mFinishWCT);
- };
-
- if (!mActivityEmbeddingController.shouldAnimate(everythingElse)) {
- // Fallback to dispatching to other handlers.
- return false;
- }
-
- // PIP window should always be on the highest Z order.
- if (pipChange != null) {
- mInFlightSubAnimations = 2;
- mPipHandler.startEnterAnimation(
- pipChange,
- startTransaction.setLayer(pipChange.getLeash(), Integer.MAX_VALUE),
- finishTransaction,
- finishCB);
- } else {
- mInFlightSubAnimations = 1;
- }
-
- mActivityEmbeddingController.startAnimation(mTransition, everythingElse,
- startTransaction, finishTransaction, finishCB);
- return true;
- }
-
- private boolean animateOpenIntentWithRemoteAndPip(
- @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- TransitionInfo.Change pipChange = null;
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- TransitionInfo.Change change = info.getChanges().get(i);
- if (mPipHandler.isEnteringPip(change, info.getType())) {
- if (pipChange != null) {
- throw new IllegalStateException("More than 1 pip-entering changes in one"
- + " transition? " + info);
- }
- pipChange = change;
- info.getChanges().remove(i);
- }
- }
- Transitions.TransitionFinishCallback finishCB = (wct) -> {
- --mInFlightSubAnimations;
- joinFinishArgs(wct);
- if (mInFlightSubAnimations > 0) return;
- finishCallback.onTransitionFinished(mFinishWCT);
- };
- if (pipChange == null) {
- if (mLeftoversHandler != null) {
- mInFlightSubAnimations = 1;
- if (mLeftoversHandler.startAnimation(
- mTransition, info, startTransaction, finishTransaction, finishCB)) {
- return true;
- }
- }
- return false;
- }
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Splitting PIP into a separate"
- + " animation because remote-animation likely doesn't support it");
- // Split the transition into 2 parts: the pip part and the rest.
- mInFlightSubAnimations = 2;
- // make a new startTransaction because pip's startEnterAnimation "consumes" it so
- // we need a separate one to send over to launcher.
- SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction();
-
- mPipHandler.startEnterAnimation(pipChange, otherStartT, finishTransaction, finishCB);
-
- // Dispatch the rest of the transition normally.
- if (mLeftoversHandler != null
- && mLeftoversHandler.startAnimation(
- mTransition, info, startTransaction, finishTransaction, finishCB)) {
- return true;
- }
- mLeftoversHandler = mPlayer.dispatchTransition(mTransition, info,
- startTransaction, finishTransaction, finishCB, mMixedHandler);
- return true;
- }
-
- private boolean animateRecentsDuringSplit(
- @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- // Split-screen is only interested in the recents transition finishing (and merging), so
- // just wrap finish and start recents animation directly.
- Transitions.TransitionFinishCallback finishCB = (wct) -> {
- mInFlightSubAnimations = 0;
- // If pair-to-pair switching, the post-recents clean-up isn't needed.
- wct = wct != null ? wct : new WindowContainerTransaction();
- if (mAnimType != ANIM_TYPE_PAIR_TO_PAIR) {
- mSplitHandler.onRecentsInSplitAnimationFinish(wct, finishTransaction);
- } else {
- // notify pair-to-pair recents animation finish
- mSplitHandler.onRecentsPairToPairAnimationFinish(wct);
- }
- mSplitHandler.onTransitionAnimationComplete();
- finishCallback.onTransitionFinished(wct);
- };
- mInFlightSubAnimations = 1;
- mSplitHandler.onRecentsInSplitAnimationStart(info);
- final boolean handled = mLeftoversHandler.startAnimation(mTransition, info,
- startTransaction, finishTransaction, finishCB);
- if (!handled) {
- mSplitHandler.onRecentsInSplitAnimationCanceled();
- }
- return handled;
- }
-
- private boolean animateRecentsDuringKeyguard(
- @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- if (mInfo == null) {
- mInfo = info;
- mFinishT = finishTransaction;
- mFinishCB = finishCallback;
- }
- return startSubAnimation(mRecentsHandler, info, startTransaction, finishTransaction);
- }
-
- private boolean animateRecentsDuringDesktop(
- @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- Transitions.TransitionFinishCallback finishCB = wct -> {
- mInFlightSubAnimations--;
- if (mInFlightSubAnimations == 0) {
- finishCallback.onTransitionFinished(wct);
- }
- };
-
- mInFlightSubAnimations++;
- boolean consumed = mRecentsHandler.startAnimation(
- mTransition, info, startTransaction, finishTransaction, finishCB);
- if (!consumed) {
- mInFlightSubAnimations--;
- return false;
- }
- if (mDesktopTasksController != null) {
- mDesktopTasksController.syncSurfaceState(info, finishTransaction);
- return true;
- }
-
- return false;
- }
-
- private boolean animateUnfold(
- @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- final Transitions.TransitionFinishCallback finishCB = (wct) -> {
- mInFlightSubAnimations--;
- if (mInFlightSubAnimations > 0) return;
- finishCallback.onTransitionFinished(wct);
- };
- mInFlightSubAnimations = 1;
- // Sync pip state.
- if (mPipHandler != null) {
- mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
- }
- if (mSplitHandler != null && mSplitHandler.isSplitActive()) {
- mSplitHandler.updateSurfaces(startTransaction);
- }
- return mUnfoldHandler.startAnimation(
- mTransition, info, startTransaction, finishTransaction, finishCB);
- }
-
- void mergeAnimation(
+ abstract void mergeAnimation(
@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- if (mType == TYPE_DISPLAY_AND_SPLIT_CHANGE) {
- // queue since no actual animation.
- } else if (mType == TYPE_ENTER_PIP_FROM_SPLIT) {
- if (mAnimType == ANIM_TYPE_GOING_HOME) {
- boolean ended = mSplitHandler.end();
- // If split couldn't end (because it is remote), then don't end everything else
- // since we have to play out the animation anyways.
- if (!ended) return;
- mPipHandler.end();
- if (mLeftoversHandler != null) {
- mLeftoversHandler.mergeAnimation(
- transition, info, t, mergeTarget, finishCallback);
- }
- } else {
- mPipHandler.end();
- }
- } else if (mType == TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
- mPipHandler.end();
- mActivityEmbeddingController.mergeAnimation(transition, info, t, mergeTarget,
- finishCallback);
- } else if (mType == TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
- mPipHandler.end();
- if (mLeftoversHandler != null) {
- mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
- finishCallback);
- }
- } else if (mType == TYPE_RECENTS_DURING_SPLIT) {
- if (mSplitHandler.isPendingEnter(transition)) {
- // Recents -> enter-split means that we are switching from one pair to
- // another pair.
- mAnimType = ANIM_TYPE_PAIR_TO_PAIR;
- }
- mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
- } else if (mType == TYPE_KEYGUARD) {
- mKeyguardHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
- } else if (mType == TYPE_RECENTS_DURING_KEYGUARD) {
- if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) {
- DefaultMixedHandler.handoverTransitionLeashes(mInfo, info, t, mFinishT);
- if (animateKeyguard(
- this, info, t, mFinishT, mFinishCB, mKeyguardHandler, mPipHandler)) {
- finishCallback.onTransitionFinished(null);
- }
- }
- mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
- } else if (mType == TYPE_RECENTS_DURING_DESKTOP) {
- mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
- } else if (mType == TYPE_UNFOLD) {
- mUnfoldHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
- } else {
- throw new IllegalStateException(
- "Playing a mixed transition with unknown type? " + mType);
- }
- }
+ @NonNull Transitions.TransitionFinishCallback finishCallback);
- void onTransitionConsumed(
+ abstract void onTransitionConsumed(
@NonNull IBinder transition, boolean aborted,
- @Nullable SurfaceControl.Transaction finishT) {
- if (mType == TYPE_ENTER_PIP_FROM_SPLIT) {
- mPipHandler.onTransitionConsumed(transition, aborted, finishT);
- } else if (mType == TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
- mPipHandler.onTransitionConsumed(transition, aborted, finishT);
- mActivityEmbeddingController.onTransitionConsumed(transition, aborted, finishT);
- } else if (mType == TYPE_RECENTS_DURING_SPLIT) {
- mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
- } else if (mType == TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
- mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
- } else if (mType == TYPE_KEYGUARD) {
- mKeyguardHandler.onTransitionConsumed(transition, aborted, finishT);
- } else if (mType == TYPE_RECENTS_DURING_DESKTOP) {
- mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
- } else if (mType == TYPE_UNFOLD) {
- mUnfoldHandler.onTransitionConsumed(transition, aborted, finishT);
- }
- if (mHasRequestToRemote) {
- mPlayer.getRemoteTransitionHandler().onTransitionConsumed(
- transition, aborted, finishT);
- }
- }
+ @Nullable SurfaceControl.Transaction finishT);
- boolean startSubAnimation(Transitions.TransitionHandler handler, TransitionInfo info,
+ protected boolean startSubAnimation(
+ Transitions.TransitionHandler handler, TransitionInfo info,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) {
if (mInfo != null) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
@@ -551,7 +184,7 @@
return true;
}
- void onSubAnimationFinished(TransitionInfo info, WindowContainerTransaction wct) {
+ private void onSubAnimationFinished(TransitionInfo info, WindowContainerTransaction wct) {
mInFlightSubAnimations--;
if (mInfo != null) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
@@ -622,7 +255,7 @@
throw new IllegalStateException("Unexpected remote transition in"
+ "pip-enter-from-split request");
}
- mActiveTransitions.add(createMixedTransition(
+ mActiveTransitions.add(createDefaultMixedTransition(
MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT, transition));
WindowContainerTransaction out = new WindowContainerTransaction();
@@ -634,7 +267,7 @@
mActivityEmbeddingController != null)) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
" Got a PiP-enter request from an Activity Embedding split");
- mActiveTransitions.add(createMixedTransition(
+ mActiveTransitions.add(createDefaultMixedTransition(
MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING, transition));
// Postpone transition splitting to later.
WindowContainerTransaction out = new WindowContainerTransaction();
@@ -653,7 +286,7 @@
if (handler == null) {
return null;
}
- final MixedTransition mixed = createMixedTransition(
+ final MixedTransition mixed = createDefaultMixedTransition(
MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE, transition);
mixed.mLeftoversHandler = handler.first;
mActiveTransitions.add(mixed);
@@ -679,7 +312,7 @@
mPlayer.getRemoteTransitionHandler(),
new WindowContainerTransaction());
}
- final MixedTransition mixed = createMixedTransition(
+ final MixedTransition mixed = createRecentsMixedTransition(
MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition);
mixed.mLeftoversHandler = handler.first;
mActiveTransitions.add(mixed);
@@ -688,7 +321,7 @@
final WindowContainerTransaction wct =
mUnfoldHandler.handleRequest(transition, request);
if (wct != null) {
- mActiveTransitions.add(createMixedTransition(
+ mActiveTransitions.add(createDefaultMixedTransition(
MixedTransition.TYPE_UNFOLD, transition));
}
return wct;
@@ -696,6 +329,12 @@
return null;
}
+ private DefaultMixedTransition createDefaultMixedTransition(int type, IBinder transition) {
+ return new DefaultMixedTransition(
+ type, transition, mPlayer, this, mPipHandler, mSplitHandler, mKeyguardHandler,
+ mUnfoldHandler, mActivityEmbeddingController);
+ }
+
@Override
public Consumer<IBinder> handleRecentsRequest(WindowContainerTransaction outWCT) {
if (mRecentsHandler != null) {
@@ -715,31 +354,30 @@
private void setRecentsTransitionDuringSplit(IBinder transition) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
+ "Split-Screen is foreground, so treat it as Mixed.");
- mActiveTransitions.add(createMixedTransition(
+ mActiveTransitions.add(createRecentsMixedTransition(
MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition));
}
private void setRecentsTransitionDuringKeyguard(IBinder transition) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
+ "keyguard is visible, so treat it as Mixed.");
- mActiveTransitions.add(createMixedTransition(
+ mActiveTransitions.add(createRecentsMixedTransition(
MixedTransition.TYPE_RECENTS_DURING_KEYGUARD, transition));
}
private void setRecentsTransitionDuringDesktop(IBinder transition) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
+ "desktop mode is active, so treat it as Mixed.");
- mActiveTransitions.add(createMixedTransition(
+ mActiveTransitions.add(createRecentsMixedTransition(
MixedTransition.TYPE_RECENTS_DURING_DESKTOP, transition));
}
- private MixedTransition createMixedTransition(int type, IBinder transition) {
- return new MixedTransition(type, transition, mPlayer, this, mPipHandler, mRecentsHandler,
- mSplitHandler, mKeyguardHandler, mDesktopTasksController, mUnfoldHandler,
- mActivityEmbeddingController);
+ private MixedTransition createRecentsMixedTransition(int type, IBinder transition) {
+ return new RecentsMixedTransition(type, transition, mPlayer, this, mPipHandler,
+ mSplitHandler, mKeyguardHandler, mRecentsHandler, mDesktopTasksController);
}
- private static TransitionInfo subCopy(@NonNull TransitionInfo info,
+ static TransitionInfo subCopy(@NonNull TransitionInfo info,
@WindowManager.TransitionType int newType, boolean withChanges) {
final TransitionInfo out = new TransitionInfo(newType, withChanges ? info.getFlags() : 0);
out.setTrack(info.getTrack());
@@ -756,15 +394,6 @@
return out;
}
- private static boolean isHomeOpening(@NonNull TransitionInfo.Change change) {
- return change.getTaskInfo() != null
- && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME;
- }
-
- private static boolean isWallpaper(@NonNull TransitionInfo.Change change) {
- return (change.getFlags() & FLAG_IS_WALLPAPER) != 0;
- }
-
@Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@@ -783,7 +412,7 @@
if (KeyguardTransitionHandler.handles(info)) {
if (mixed != null && mixed.mType != MixedTransition.TYPE_KEYGUARD) {
final MixedTransition keyguardMixed =
- createMixedTransition(MixedTransition.TYPE_KEYGUARD, transition);
+ createDefaultMixedTransition(MixedTransition.TYPE_KEYGUARD, transition);
mActiveTransitions.add(keyguardMixed);
Transitions.TransitionFinishCallback callback = wct -> {
mActiveTransitions.remove(keyguardMixed);
@@ -823,117 +452,6 @@
return handled;
}
- private static boolean animateEnterPipFromSplit(@NonNull final MixedTransition mixed,
- @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback,
- @NonNull Transitions player, @NonNull DefaultMixedHandler mixedHandler,
- @NonNull PipTransitionController pipHandler, @NonNull StageCoordinator splitHandler) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
- + "entering PIP while Split-Screen is foreground.");
- TransitionInfo.Change pipChange = null;
- TransitionInfo.Change wallpaper = null;
- final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */);
- boolean homeIsOpening = false;
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- TransitionInfo.Change change = info.getChanges().get(i);
- if (pipHandler.isEnteringPip(change, info.getType())) {
- if (pipChange != null) {
- throw new IllegalStateException("More than 1 pip-entering changes in one"
- + " transition? " + info);
- }
- pipChange = change;
- // going backwards, so remove-by-index is fine.
- everythingElse.getChanges().remove(i);
- } else if (isHomeOpening(change)) {
- homeIsOpening = true;
- } else if (isWallpaper(change)) {
- wallpaper = change;
- }
- }
- if (pipChange == null) {
- // um, something probably went wrong.
- return false;
- }
- final boolean isGoingHome = homeIsOpening;
- Transitions.TransitionFinishCallback finishCB = (wct) -> {
- --mixed.mInFlightSubAnimations;
- mixed.joinFinishArgs(wct);
- if (mixed.mInFlightSubAnimations > 0) return;
- if (isGoingHome) {
- splitHandler.onTransitionAnimationComplete();
- }
- finishCallback.onTransitionFinished(mixed.mFinishWCT);
- };
- if (isGoingHome || splitHandler.getSplitItemPosition(pipChange.getLastParent())
- != SPLIT_POSITION_UNDEFINED) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is actually mixed "
- + "since entering-PiP caused us to leave split and return home.");
- // We need to split the transition into 2 parts: the pip part (animated by pip)
- // and the dismiss-part (animated by launcher).
- mixed.mInFlightSubAnimations = 2;
- // immediately make the wallpaper visible (so that we don't see it pop-in during
- // the time it takes to start recents animation (which is remote).
- if (wallpaper != null) {
- startTransaction.show(wallpaper.getLeash()).setAlpha(wallpaper.getLeash(), 1.f);
- }
- // make a new startTransaction because pip's startEnterAnimation "consumes" it so
- // we need a separate one to send over to launcher.
- SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction();
- @SplitScreen.StageType int topStageToKeep = STAGE_TYPE_UNDEFINED;
- if (splitHandler.isSplitScreenVisible()) {
- // The non-going home case, we could be pip-ing one of the split stages and keep
- // showing the other
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- TransitionInfo.Change change = info.getChanges().get(i);
- if (change == pipChange) {
- // Ignore the change/task that's going into Pip
- continue;
- }
- @SplitScreen.StageType int splitItemStage =
- splitHandler.getSplitItemStage(change.getLastParent());
- if (splitItemStage != STAGE_TYPE_UNDEFINED) {
- topStageToKeep = splitItemStage;
- break;
- }
- }
- }
- // Let split update internal state for dismiss.
- splitHandler.prepareDismissAnimation(topStageToKeep,
- EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT,
- finishTransaction);
-
- // We are trying to accommodate launcher's close animation which can't handle the
- // divider-bar, so if split-handler is closing the divider-bar, just hide it and remove
- // from transition info.
- for (int i = everythingElse.getChanges().size() - 1; i >= 0; --i) {
- if ((everythingElse.getChanges().get(i).getFlags() & FLAG_IS_DIVIDER_BAR) != 0) {
- everythingElse.getChanges().remove(i);
- break;
- }
- }
-
- pipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA);
- pipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction,
- finishCB);
- // Dispatch the rest of the transition normally. This will most-likely be taken by
- // recents or default handler.
- mixed.mLeftoversHandler = player.dispatchTransition(mixed.mTransition, everythingElse,
- otherStartT, finishTransaction, finishCB, mixedHandler);
- } else {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Not leaving split, so just "
- + "forward animation to Pip-Handler.");
- // This happens if the pip-ing activity is in a multi-activity task (and thus a
- // new pip task is spawned). In this case, we don't actually exit split so we can
- // just let pip transition handle the animation verbatim.
- mixed.mInFlightSubAnimations = 1;
- pipHandler.startAnimation(mixed.mTransition, info, startTransaction, finishTransaction,
- finishCB);
- }
- return true;
- }
-
private void unlinkMissingParents(TransitionInfo from) {
for (int i = 0; i < from.getChanges().size(); ++i) {
final TransitionInfo.Change chg = from.getChanges().get(i);
@@ -965,15 +483,14 @@
public boolean animatePendingEnterPipFromSplit(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
Transitions.TransitionFinishCallback finishCallback) {
- final MixedTransition mixed = createMixedTransition(
+ final MixedTransition mixed = createDefaultMixedTransition(
MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT, transition);
mActiveTransitions.add(mixed);
Transitions.TransitionFinishCallback callback = wct -> {
mActiveTransitions.remove(mixed);
finishCallback.onTransitionFinished(wct);
};
- return animateEnterPipFromSplit(mixed, info, startT, finishT, finishCallback, mPlayer, this,
- mPipHandler, mSplitHandler);
+ return mixed.startAnimation(transition, info, startT, finishT, callback);
}
/**
@@ -996,7 +513,7 @@
}
if (displayPart.getChanges().isEmpty()) return false;
unlinkMissingParents(everythingElse);
- final MixedTransition mixed = createMixedTransition(
+ final MixedTransition mixed = createDefaultMixedTransition(
MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE, transition);
mActiveTransitions.add(mixed);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is a mix of display change "
@@ -1113,7 +630,7 @@
* {@link TransitionInfo} so that it can take over some parts of the animation without
* reparenting to new transition roots.
*/
- private static void handoverTransitionLeashes(
+ static void handoverTransitionLeashes(
@NonNull TransitionInfo from,
@NonNull TransitionInfo to,
@NonNull SurfaceControl.Transaction startT,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
new file mode 100644
index 0000000..9ce46d6
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.transition;
+
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+
+import static com.android.wm.shell.transition.DefaultMixedHandler.subCopy;
+import static com.android.wm.shell.transition.MixedTransitionHelper.animateEnterPipFromSplit;
+import static com.android.wm.shell.transition.MixedTransitionHelper.animateKeyguard;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
+import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.splitscreen.StageCoordinator;
+import com.android.wm.shell.unfold.UnfoldTransitionHandler;
+
+class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition {
+ private final UnfoldTransitionHandler mUnfoldHandler;
+ private final ActivityEmbeddingController mActivityEmbeddingController;
+
+ DefaultMixedTransition(int type, IBinder transition, Transitions player,
+ DefaultMixedHandler mixedHandler, PipTransitionController pipHandler,
+ StageCoordinator splitHandler, KeyguardTransitionHandler keyguardHandler,
+ UnfoldTransitionHandler unfoldHandler,
+ ActivityEmbeddingController activityEmbeddingController) {
+ super(type, transition, player, mixedHandler, pipHandler, splitHandler, keyguardHandler);
+ mUnfoldHandler = unfoldHandler;
+ mActivityEmbeddingController = activityEmbeddingController;
+
+ switch (type) {
+ case TYPE_UNFOLD:
+ mLeftoversHandler = mUnfoldHandler;
+ break;
+ case TYPE_DISPLAY_AND_SPLIT_CHANGE:
+ case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING:
+ case TYPE_ENTER_PIP_FROM_SPLIT:
+ case TYPE_KEYGUARD:
+ case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE:
+ default:
+ break;
+ }
+ }
+
+ @Override
+ boolean startAnimation(
+ @NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ return switch (mType) {
+ case TYPE_DISPLAY_AND_SPLIT_CHANGE -> false;
+ case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING ->
+ animateEnterPipFromActivityEmbedding(
+ info, startTransaction, finishTransaction, finishCallback);
+ case TYPE_ENTER_PIP_FROM_SPLIT ->
+ animateEnterPipFromSplit(this, info, startTransaction, finishTransaction,
+ finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler);
+ case TYPE_KEYGUARD ->
+ animateKeyguard(this, info, startTransaction, finishTransaction, finishCallback,
+ mKeyguardHandler, mPipHandler);
+ case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE ->
+ animateOpenIntentWithRemoteAndPip(transition, info, startTransaction,
+ finishTransaction, finishCallback);
+ case TYPE_UNFOLD ->
+ animateUnfold(info, startTransaction, finishTransaction, finishCallback);
+ default -> throw new IllegalStateException(
+ "Starting default mixed animation with unknown or illegal type: " + mType);
+ };
+ }
+
+ private boolean animateEnterPipFromActivityEmbedding(
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
+ + "entering PIP from an Activity Embedding window");
+ // Split into two transitions (wct)
+ TransitionInfo.Change pipChange = null;
+ final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */);
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ TransitionInfo.Change change = info.getChanges().get(i);
+ if (mPipHandler.isEnteringPip(change, info.getType())) {
+ if (pipChange != null) {
+ throw new IllegalStateException("More than 1 pip-entering changes in one"
+ + " transition? " + info);
+ }
+ pipChange = change;
+ // going backwards, so remove-by-index is fine.
+ everythingElse.getChanges().remove(i);
+ }
+ }
+
+ final Transitions.TransitionFinishCallback finishCB = (wct) -> {
+ --mInFlightSubAnimations;
+ joinFinishArgs(wct);
+ if (mInFlightSubAnimations > 0) return;
+ finishCallback.onTransitionFinished(mFinishWCT);
+ };
+
+ if (!mActivityEmbeddingController.shouldAnimate(everythingElse)) {
+ // Fallback to dispatching to other handlers.
+ return false;
+ }
+
+ // PIP window should always be on the highest Z order.
+ if (pipChange != null) {
+ mInFlightSubAnimations = 2;
+ mPipHandler.startEnterAnimation(
+ pipChange, startTransaction.setLayer(pipChange.getLeash(), Integer.MAX_VALUE),
+ finishTransaction,
+ finishCB);
+ } else {
+ mInFlightSubAnimations = 1;
+ }
+
+ mActivityEmbeddingController.startAnimation(
+ mTransition, everythingElse, startTransaction, finishTransaction, finishCB);
+ return true;
+ }
+
+ private boolean animateOpenIntentWithRemoteAndPip(
+ @NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ boolean handledToPip = tryAnimateOpenIntentWithRemoteAndPip(
+ info, startTransaction, finishTransaction, finishCallback);
+ // Consume the transition on remote handler if the leftover handler already handle this
+ // transition. And if it cannot, the transition will be handled by remote handler, so don't
+ // consume here.
+ // Need to check leftOverHandler as it may change in #animateOpenIntentWithRemoteAndPip
+ if (handledToPip && mHasRequestToRemote
+ && mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) {
+ mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, false, null);
+ }
+ return handledToPip;
+ }
+
+ private boolean tryAnimateOpenIntentWithRemoteAndPip(
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ TransitionInfo.Change pipChange = null;
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ TransitionInfo.Change change = info.getChanges().get(i);
+ if (mPipHandler.isEnteringPip(change, info.getType())) {
+ if (pipChange != null) {
+ throw new IllegalStateException("More than 1 pip-entering changes in one"
+ + " transition? " + info);
+ }
+ pipChange = change;
+ info.getChanges().remove(i);
+ }
+ }
+ Transitions.TransitionFinishCallback finishCB = (wct) -> {
+ --mInFlightSubAnimations;
+ joinFinishArgs(wct);
+ if (mInFlightSubAnimations > 0) return;
+ finishCallback.onTransitionFinished(mFinishWCT);
+ };
+ if (pipChange == null) {
+ if (mLeftoversHandler != null) {
+ mInFlightSubAnimations = 1;
+ if (mLeftoversHandler.startAnimation(
+ mTransition, info, startTransaction, finishTransaction, finishCB)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Splitting PIP into a separate"
+ + " animation because remote-animation likely doesn't support it");
+ // Split the transition into 2 parts: the pip part and the rest.
+ mInFlightSubAnimations = 2;
+ // make a new startTransaction because pip's startEnterAnimation "consumes" it so
+ // we need a separate one to send over to launcher.
+ SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction();
+
+ mPipHandler.startEnterAnimation(pipChange, otherStartT, finishTransaction, finishCB);
+
+ // Dispatch the rest of the transition normally.
+ if (mLeftoversHandler != null
+ && mLeftoversHandler.startAnimation(mTransition, info,
+ startTransaction, finishTransaction, finishCB)) {
+ return true;
+ }
+ mLeftoversHandler = mPlayer.dispatchTransition(
+ mTransition, info, startTransaction, finishTransaction, finishCB, mMixedHandler);
+ return true;
+ }
+
+ private boolean animateUnfold(
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ final Transitions.TransitionFinishCallback finishCB = (wct) -> {
+ mInFlightSubAnimations--;
+ if (mInFlightSubAnimations > 0) return;
+ finishCallback.onTransitionFinished(wct);
+ };
+ mInFlightSubAnimations = 1;
+ // Sync pip state.
+ if (mPipHandler != null) {
+ mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
+ }
+ if (mSplitHandler != null && mSplitHandler.isSplitActive()) {
+ mSplitHandler.updateSurfaces(startTransaction);
+ }
+ return mUnfoldHandler.startAnimation(
+ mTransition, info, startTransaction, finishTransaction, finishCB);
+ }
+
+ @Override
+ void mergeAnimation(
+ @NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ switch (mType) {
+ case TYPE_DISPLAY_AND_SPLIT_CHANGE:
+ // queue since no actual animation.
+ return;
+ case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING:
+ mPipHandler.end();
+ mActivityEmbeddingController.mergeAnimation(
+ transition, info, t, mergeTarget, finishCallback);
+ return;
+ case TYPE_ENTER_PIP_FROM_SPLIT:
+ if (mAnimType == ANIM_TYPE_GOING_HOME) {
+ boolean ended = mSplitHandler.end();
+ // If split couldn't end (because it is remote), then don't end everything else
+ // since we have to play out the animation anyways.
+ if (!ended) return;
+ mPipHandler.end();
+ if (mLeftoversHandler != null) {
+ mLeftoversHandler.mergeAnimation(
+ transition, info, t, mergeTarget, finishCallback);
+ }
+ } else {
+ mPipHandler.end();
+ }
+ return;
+ case TYPE_KEYGUARD:
+ mKeyguardHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+ return;
+ case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE:
+ mPipHandler.end();
+ if (mLeftoversHandler != null) {
+ mLeftoversHandler.mergeAnimation(
+ transition, info, t, mergeTarget, finishCallback);
+ }
+ return;
+ case TYPE_UNFOLD:
+ mUnfoldHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+ return;
+ default:
+ throw new IllegalStateException("Playing a default mixed transition with unknown or"
+ + " illegal type: " + mType);
+ }
+ }
+
+ @Override
+ void onTransitionConsumed(
+ @NonNull IBinder transition, boolean aborted,
+ @Nullable SurfaceControl.Transaction finishT) {
+ switch (mType) {
+ case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING:
+ mPipHandler.onTransitionConsumed(transition, aborted, finishT);
+ mActivityEmbeddingController.onTransitionConsumed(transition, aborted, finishT);
+ break;
+ case TYPE_ENTER_PIP_FROM_SPLIT:
+ mPipHandler.onTransitionConsumed(transition, aborted, finishT);
+ break;
+ case TYPE_KEYGUARD:
+ mKeyguardHandler.onTransitionConsumed(transition, aborted, finishT);
+ break;
+ case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE:
+ mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
+ break;
+ case TYPE_UNFOLD:
+ mUnfoldHandler.onTransitionConsumed(transition, aborted, finishT);
+ break;
+ default:
+ break;
+ }
+
+ if (mHasRequestToRemote) {
+ mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, aborted, finishT);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
new file mode 100644
index 0000000..0974cd1
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.transition;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
+
+import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
+import static com.android.wm.shell.transition.DefaultMixedHandler.subCopy;
+
+import android.annotation.NonNull;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.splitscreen.SplitScreen;
+import com.android.wm.shell.splitscreen.StageCoordinator;
+
+public class MixedTransitionHelper {
+ static boolean animateEnterPipFromSplit(
+ @NonNull DefaultMixedHandler.MixedTransition mixed, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback,
+ @NonNull Transitions player, @NonNull DefaultMixedHandler mixedHandler,
+ @NonNull PipTransitionController pipHandler, @NonNull StageCoordinator splitHandler) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
+ + "entering PIP while Split-Screen is foreground.");
+ TransitionInfo.Change pipChange = null;
+ TransitionInfo.Change wallpaper = null;
+ final TransitionInfo everythingElse =
+ subCopy(info, TRANSIT_TO_BACK, true /* changes */);
+ boolean homeIsOpening = false;
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ TransitionInfo.Change change = info.getChanges().get(i);
+ if (pipHandler.isEnteringPip(change, info.getType())) {
+ if (pipChange != null) {
+ throw new IllegalStateException("More than 1 pip-entering changes in one"
+ + " transition? " + info);
+ }
+ pipChange = change;
+ // going backwards, so remove-by-index is fine.
+ everythingElse.getChanges().remove(i);
+ } else if (isHomeOpening(change)) {
+ homeIsOpening = true;
+ } else if (isWallpaper(change)) {
+ wallpaper = change;
+ }
+ }
+ if (pipChange == null) {
+ // um, something probably went wrong.
+ return false;
+ }
+ final boolean isGoingHome = homeIsOpening;
+ Transitions.TransitionFinishCallback finishCB = (wct) -> {
+ --mixed.mInFlightSubAnimations;
+ mixed.joinFinishArgs(wct);
+ if (mixed.mInFlightSubAnimations > 0) return;
+ if (isGoingHome) {
+ splitHandler.onTransitionAnimationComplete();
+ }
+ finishCallback.onTransitionFinished(mixed.mFinishWCT);
+ };
+ if (isGoingHome || splitHandler.getSplitItemPosition(pipChange.getLastParent())
+ != SPLIT_POSITION_UNDEFINED) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is actually mixed "
+ + "since entering-PiP caused us to leave split and return home.");
+ // We need to split the transition into 2 parts: the pip part (animated by pip)
+ // and the dismiss-part (animated by launcher).
+ mixed.mInFlightSubAnimations = 2;
+ // immediately make the wallpaper visible (so that we don't see it pop-in during
+ // the time it takes to start recents animation (which is remote).
+ if (wallpaper != null) {
+ startTransaction.show(wallpaper.getLeash()).setAlpha(wallpaper.getLeash(), 1.f);
+ }
+ // make a new startTransaction because pip's startEnterAnimation "consumes" it so
+ // we need a separate one to send over to launcher.
+ SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction();
+ @SplitScreen.StageType int topStageToKeep = STAGE_TYPE_UNDEFINED;
+ if (splitHandler.isSplitScreenVisible()) {
+ // The non-going home case, we could be pip-ing one of the split stages and keep
+ // showing the other
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ TransitionInfo.Change change = info.getChanges().get(i);
+ if (change == pipChange) {
+ // Ignore the change/task that's going into Pip
+ continue;
+ }
+ @SplitScreen.StageType int splitItemStage =
+ splitHandler.getSplitItemStage(change.getLastParent());
+ if (splitItemStage != STAGE_TYPE_UNDEFINED) {
+ topStageToKeep = splitItemStage;
+ break;
+ }
+ }
+ }
+ // Let split update internal state for dismiss.
+ splitHandler.prepareDismissAnimation(topStageToKeep,
+ EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT,
+ finishTransaction);
+
+ // We are trying to accommodate launcher's close animation which can't handle the
+ // divider-bar, so if split-handler is closing the divider-bar, just hide it and
+ // remove from transition info.
+ for (int i = everythingElse.getChanges().size() - 1; i >= 0; --i) {
+ if ((everythingElse.getChanges().get(i).getFlags() & FLAG_IS_DIVIDER_BAR)
+ != 0) {
+ everythingElse.getChanges().remove(i);
+ break;
+ }
+ }
+
+ pipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA);
+ pipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction,
+ finishCB);
+ // Dispatch the rest of the transition normally. This will most-likely be taken by
+ // recents or default handler.
+ mixed.mLeftoversHandler = player.dispatchTransition(mixed.mTransition, everythingElse,
+ otherStartT, finishTransaction, finishCB, mixedHandler);
+ } else {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Not leaving split, so just "
+ + "forward animation to Pip-Handler.");
+ // This happens if the pip-ing activity is in a multi-activity task (and thus a
+ // new pip task is spawned). In this case, we don't actually exit split so we can
+ // just let pip transition handle the animation verbatim.
+ mixed.mInFlightSubAnimations = 1;
+ pipHandler.startAnimation(
+ mixed.mTransition, info, startTransaction, finishTransaction, finishCB);
+ }
+ return true;
+ }
+
+ private static boolean isHomeOpening(@NonNull TransitionInfo.Change change) {
+ return change.getTaskInfo() != null
+ && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME;
+ }
+
+ private static boolean isWallpaper(@NonNull TransitionInfo.Change change) {
+ return (change.getFlags() & FLAG_IS_WALLPAPER) != 0;
+ }
+
+ static boolean animateKeyguard(
+ @NonNull DefaultMixedHandler.MixedTransition mixed, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback,
+ @NonNull KeyguardTransitionHandler keyguardHandler,
+ PipTransitionController pipHandler) {
+ if (mixed.mFinishT == null) {
+ mixed.mFinishT = finishTransaction;
+ mixed.mFinishCB = finishCallback;
+ }
+ // Sync pip state.
+ if (pipHandler != null) {
+ pipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
+ }
+ return mixed.startSubAnimation(keyguardHandler, info, startTransaction, finishTransaction);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
new file mode 100644
index 0000000..643e026
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.transition;
+
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
+
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.transition.DefaultMixedHandler.handoverTransitionLeashes;
+import static com.android.wm.shell.transition.MixedTransitionHelper.animateEnterPipFromSplit;
+import static com.android.wm.shell.transition.MixedTransitionHelper.animateKeyguard;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.desktopmode.DesktopTasksController;
+import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.recents.RecentsTransitionHandler;
+import com.android.wm.shell.splitscreen.StageCoordinator;
+
+class RecentsMixedTransition extends DefaultMixedHandler.MixedTransition {
+ private final RecentsTransitionHandler mRecentsHandler;
+ private final DesktopTasksController mDesktopTasksController;
+
+ RecentsMixedTransition(int type, IBinder transition, Transitions player,
+ DefaultMixedHandler mixedHandler, PipTransitionController pipHandler,
+ StageCoordinator splitHandler, KeyguardTransitionHandler keyguardHandler,
+ RecentsTransitionHandler recentsHandler,
+ DesktopTasksController desktopTasksController) {
+ super(type, transition, player, mixedHandler, pipHandler, splitHandler, keyguardHandler);
+ mRecentsHandler = recentsHandler;
+ mDesktopTasksController = desktopTasksController;
+ mLeftoversHandler = mRecentsHandler;
+ }
+
+ @Override
+ boolean startAnimation(
+ @NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ return switch (mType) {
+ case TYPE_RECENTS_DURING_DESKTOP ->
+ animateRecentsDuringDesktop(
+ info, startTransaction, finishTransaction, finishCallback);
+ case TYPE_RECENTS_DURING_KEYGUARD ->
+ animateRecentsDuringKeyguard(
+ info, startTransaction, finishTransaction, finishCallback);
+ case TYPE_RECENTS_DURING_SPLIT ->
+ animateRecentsDuringSplit(
+ info, startTransaction, finishTransaction, finishCallback);
+ default -> throw new IllegalStateException(
+ "Starting Recents mixed animation with unknown or illegal type: " + mType);
+ };
+ }
+
+ private boolean animateRecentsDuringDesktop(
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ if (mInfo == null) {
+ mInfo = info;
+ mFinishT = finishTransaction;
+ mFinishCB = finishCallback;
+ }
+ Transitions.TransitionFinishCallback finishCB = wct -> {
+ mInFlightSubAnimations--;
+ if (mInFlightSubAnimations == 0) {
+ finishCallback.onTransitionFinished(wct);
+ }
+ };
+
+ mInFlightSubAnimations++;
+ boolean consumed = mRecentsHandler.startAnimation(
+ mTransition, info, startTransaction, finishTransaction, finishCB);
+ if (!consumed) {
+ mInFlightSubAnimations--;
+ return false;
+ }
+ if (mDesktopTasksController != null) {
+ mDesktopTasksController.syncSurfaceState(info, finishTransaction);
+ return true;
+ }
+
+ return false;
+ }
+
+ private boolean animateRecentsDuringKeyguard(
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ if (mInfo == null) {
+ mInfo = info;
+ mFinishT = finishTransaction;
+ mFinishCB = finishCallback;
+ }
+ return startSubAnimation(mRecentsHandler, info, startTransaction, finishTransaction);
+ }
+
+ private boolean animateRecentsDuringSplit(
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ // Pip auto-entering info might be appended to recent transition like pressing
+ // home-key in 3-button navigation. This offers split handler the opportunity to
+ // handle split to pip animation.
+ if (mPipHandler.isEnteringPip(change, info.getType())
+ && mSplitHandler.getSplitItemPosition(change.getLastParent())
+ != SPLIT_POSITION_UNDEFINED) {
+ return animateEnterPipFromSplit(this, info, startTransaction, finishTransaction,
+ finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler);
+ }
+ }
+
+ // Split-screen is only interested in the recents transition finishing (and merging), so
+ // just wrap finish and start recents animation directly.
+ Transitions.TransitionFinishCallback finishCB = (wct) -> {
+ mInFlightSubAnimations = 0;
+ // If pair-to-pair switching, the post-recents clean-up isn't needed.
+ wct = wct != null ? wct : new WindowContainerTransaction();
+ if (mAnimType != ANIM_TYPE_PAIR_TO_PAIR) {
+ mSplitHandler.onRecentsInSplitAnimationFinish(wct, finishTransaction);
+ } else {
+ // notify pair-to-pair recents animation finish
+ mSplitHandler.onRecentsPairToPairAnimationFinish(wct);
+ }
+ mSplitHandler.onTransitionAnimationComplete();
+ finishCallback.onTransitionFinished(wct);
+ };
+ mInFlightSubAnimations = 1;
+ mSplitHandler.onRecentsInSplitAnimationStart(info);
+ final boolean handled = mLeftoversHandler.startAnimation(
+ mTransition, info, startTransaction, finishTransaction, finishCB);
+ if (!handled) {
+ mSplitHandler.onRecentsInSplitAnimationCanceled();
+ }
+ return handled;
+ }
+
+ @Override
+ void mergeAnimation(
+ @NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ switch (mType) {
+ case TYPE_RECENTS_DURING_DESKTOP:
+ mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+ return;
+ case TYPE_RECENTS_DURING_KEYGUARD:
+ if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) {
+ handoverTransitionLeashes(mInfo, info, t, mFinishT);
+ if (animateKeyguard(
+ this, info, t, mFinishT, mFinishCB, mKeyguardHandler, mPipHandler)) {
+ finishCallback.onTransitionFinished(null);
+ }
+ }
+ mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
+ finishCallback);
+ return;
+ case TYPE_RECENTS_DURING_SPLIT:
+ if (mSplitHandler.isPendingEnter(transition)) {
+ // Recents -> enter-split means that we are switching from one pair to
+ // another pair.
+ mAnimType = DefaultMixedHandler.MixedTransition.ANIM_TYPE_PAIR_TO_PAIR;
+ }
+ mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+ return;
+ default:
+ throw new IllegalStateException("Playing a Recents mixed transition with unknown or"
+ + " illegal type: " + mType);
+ }
+ }
+
+ @Override
+ void onTransitionConsumed(
+ @NonNull IBinder transition, boolean aborted,
+ @Nullable SurfaceControl.Transaction finishT) {
+ switch (mType) {
+ case TYPE_RECENTS_DURING_DESKTOP:
+ case TYPE_RECENTS_DURING_SPLIT:
+ mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
+ break;
+ default:
+ break;
+ }
+
+ if (mHasRequestToRemote) {
+ mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, aborted, finishT);
+ }
+ }
+}
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 b0d8b47..3fb0dbf 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
@@ -83,6 +83,9 @@
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.tracing.LegacyTransitionTracer;
+import com.android.wm.shell.transition.tracing.PerfettoTransitionTracer;
+import com.android.wm.shell.transition.tracing.TransitionTracer;
import com.android.wm.shell.util.TransitionUtil;
import java.io.PrintWriter;
@@ -184,7 +187,7 @@
private final ShellController mShellController;
private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
private final SleepHandler mSleepHandler = new SleepHandler();
- private final Tracer mTracer = new Tracer();
+ private final TransitionTracer mTransitionTracer;
private boolean mIsRegistered = false;
/** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
@@ -307,6 +310,12 @@
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote");
shellInit.addInitCallback(this::onInit, this);
mHomeTransitionObserver = observer;
+
+ if (android.tracing.Flags.perfettoTransitionTracing()) {
+ mTransitionTracer = new PerfettoTransitionTracer();
+ } else {
+ mTransitionTracer = new LegacyTransitionTracer();
+ }
}
private void onInit() {
@@ -868,7 +877,7 @@
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s ready while"
+ " %s is still animating. Notify the animating transition"
+ " in case they can be merged", ready, playing);
- mTracer.logMergeRequested(ready.mInfo.getDebugId(), playing.mInfo.getDebugId());
+ mTransitionTracer.logMergeRequested(ready.mInfo.getDebugId(), playing.mInfo.getDebugId());
playing.mHandler.mergeAnimation(ready.mToken, ready.mInfo, ready.mStartT,
playing.mToken, (wct) -> onMerged(playing, ready));
}
@@ -902,7 +911,7 @@
for (int i = 0; i < mObservers.size(); ++i) {
mObservers.get(i).onTransitionMerged(merged.mToken, playing.mToken);
}
- mTracer.logMerged(merged.mInfo.getDebugId(), playing.mInfo.getDebugId());
+ mTransitionTracer.logMerged(merged.mInfo.getDebugId(), playing.mInfo.getDebugId());
// See if we should merge another transition.
processReadyQueue(track);
}
@@ -923,7 +932,7 @@
active.mStartT, active.mFinishT, (wct) -> onFinish(active, wct));
if (consumed) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler");
- mTracer.logDispatched(active.mInfo.getDebugId(), active.mHandler);
+ mTransitionTracer.logDispatched(active.mInfo.getDebugId(), active.mHandler);
return;
}
}
@@ -948,7 +957,7 @@
if (consumed) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by %s",
mHandlers.get(i));
- mTracer.logDispatched(info.getDebugId(), mHandlers.get(i));
+ mTransitionTracer.logDispatched(info.getDebugId(), mHandlers.get(i));
return mHandlers.get(i);
}
}
@@ -978,7 +987,7 @@
final Track track = mTracks.get(transition.getTrack());
transition.mAborted = true;
- mTracer.logAborted(transition.mInfo.getDebugId());
+ mTransitionTracer.logAborted(transition.mInfo.getDebugId());
if (transition.mHandler != null) {
// Notifies to clean-up the aborted transition.
@@ -1506,12 +1515,18 @@
}
}
-
@Override
public boolean onShellCommand(String[] args, PrintWriter pw) {
switch (args[0]) {
case "tracing": {
- mTracer.onShellCommand(Arrays.copyOfRange(args, 1, args.length), pw);
+ if (!android.tracing.Flags.perfettoTransitionTracing()) {
+ ((LegacyTransitionTracer) mTransitionTracer)
+ .onShellCommand(Arrays.copyOfRange(args, 1, args.length), pw);
+ } else {
+ pw.println("Command not supported. Use the Perfetto command instead to start "
+ + "and stop this trace instead.");
+ return false;
+ }
return true;
}
default: {
@@ -1524,8 +1539,10 @@
@Override
public void printShellCommandHelp(PrintWriter pw, String prefix) {
- pw.println(prefix + "tracing");
- mTracer.printShellCommandHelp(pw, prefix + " ");
+ if (!android.tracing.Flags.perfettoTransitionTracing()) {
+ pw.println(prefix + "tracing");
+ ((LegacyTransitionTracer) mTransitionTracer).printShellCommandHelp(pw, prefix + " ");
+ }
}
private void dump(@NonNull PrintWriter pw, String prefix) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/LegacyTransitionTracer.java
similarity index 88%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/LegacyTransitionTracer.java
index 5919aad..9c84886 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/LegacyTransitionTracer.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.transition;
+package com.android.wm.shell.transition.tracing;
import static android.os.Build.IS_USER;
@@ -29,6 +29,7 @@
import com.android.internal.util.TraceBuffer;
import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.transition.Transitions;
import com.google.protobuf.nano.MessageNano;
@@ -45,7 +46,8 @@
/**
* Helper class to collect and dump transition traces.
*/
-public class Tracer implements ShellCommandHandler.ShellCommandActionHandler {
+public class LegacyTransitionTracer
+ implements ShellCommandHandler.ShellCommandActionHandler, TransitionTracer {
private static final int ALWAYS_ON_TRACING_CAPACITY = 15 * 1024; // 15 KB
private static final int ACTIVE_TRACING_BUFFER_CAPACITY = 5000 * 1024; // 5 MB
@@ -60,33 +62,33 @@
private final TraceBuffer.ProtoProvider mProtoProvider =
new TraceBuffer.ProtoProvider<MessageNano,
- com.android.wm.shell.nano.WmShellTransitionTraceProto,
- com.android.wm.shell.nano.Transition>() {
- @Override
- public int getItemSize(MessageNano proto) {
- return proto.getCachedSize();
- }
+ com.android.wm.shell.nano.WmShellTransitionTraceProto,
+ com.android.wm.shell.nano.Transition>() {
+ @Override
+ public int getItemSize(MessageNano proto) {
+ return proto.getCachedSize();
+ }
- @Override
- public byte[] getBytes(MessageNano proto) {
- return MessageNano.toByteArray(proto);
- }
+ @Override
+ public byte[] getBytes(MessageNano proto) {
+ return MessageNano.toByteArray(proto);
+ }
- @Override
- public void write(
- com.android.wm.shell.nano.WmShellTransitionTraceProto encapsulatingProto,
- Queue<com.android.wm.shell.nano.Transition> buffer, OutputStream os)
+ @Override
+ public void write(
+ com.android.wm.shell.nano.WmShellTransitionTraceProto encapsulatingProto,
+ Queue<com.android.wm.shell.nano.Transition> buffer, OutputStream os)
throws IOException {
- encapsulatingProto.transitions = buffer.toArray(
- new com.android.wm.shell.nano.Transition[0]);
- os.write(getBytes(encapsulatingProto));
- }
- };
+ encapsulatingProto.transitions = buffer.toArray(
+ new com.android.wm.shell.nano.Transition[0]);
+ os.write(getBytes(encapsulatingProto));
+ }
+ };
private final TraceBuffer<MessageNano,
com.android.wm.shell.nano.WmShellTransitionTraceProto,
- com.android.wm.shell.nano.Transition> mTraceBuffer
- = new TraceBuffer(ALWAYS_ON_TRACING_CAPACITY, mProtoProvider,
- (proto) -> handleOnEntryRemovedFromTrace(proto));
+ com.android.wm.shell.nano.Transition> mTraceBuffer =
+ new TraceBuffer(ALWAYS_ON_TRACING_CAPACITY, mProtoProvider,
+ this::handleOnEntryRemovedFromTrace);
private final Map<Object, Runnable> mRemovedFromTraceCallbacks = new HashMap<>();
private final Map<Transitions.TransitionHandler, Integer> mHandlerIds = new HashMap<>();
@@ -99,6 +101,7 @@
* @param transitionId The id of the transition being dispatched.
* @param handler The handler the transition is being dispatched to.
*/
+ @Override
public void logDispatched(int transitionId, Transitions.TransitionHandler handler) {
final int handlerId;
if (mHandlerIds.containsKey(handler)) {
@@ -130,6 +133,7 @@
*
* @param mergeRequestedTransitionId The id of the transition we are requesting to be merged.
*/
+ @Override
public void logMergeRequested(int mergeRequestedTransitionId, int playingTransitionId) {
com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
proto.id = mergeRequestedTransitionId;
@@ -145,6 +149,7 @@
* @param mergedTransitionId The id of the transition that was merged.
* @param playingTransitionId The id of the transition the transition was merged into.
*/
+ @Override
public void logMerged(int mergedTransitionId, int playingTransitionId) {
com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
proto.id = mergedTransitionId;
@@ -159,6 +164,7 @@
*
* @param transitionId The id of the transition that was aborted.
*/
+ @Override
public void logAborted(int transitionId) {
com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
proto.id = transitionId;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
new file mode 100644
index 0000000..99df6a3
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.transition.tracing;
+
+import android.internal.perfetto.protos.PerfettoTrace;
+import android.os.SystemClock;
+import android.tracing.perfetto.DataSourceInstance;
+import android.tracing.perfetto.DataSourceParams;
+import android.tracing.perfetto.InitArguments;
+import android.tracing.perfetto.Producer;
+import android.tracing.perfetto.TracingContext;
+import android.tracing.transition.TransitionDataSource;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.wm.shell.transition.Transitions;
+
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Helper class to collect and dump transition traces.
+ */
+public class PerfettoTransitionTracer implements TransitionTracer {
+ private final AtomicInteger mActiveTraces = new AtomicInteger(0);
+ private final TransitionDataSource mDataSource = new TransitionDataSource(
+ mActiveTraces::incrementAndGet,
+ this::onFlush,
+ mActiveTraces::decrementAndGet);
+
+ public PerfettoTransitionTracer() {
+ Producer.init(InitArguments.DEFAULTS);
+ mDataSource.register(DataSourceParams.DEFAULTS);
+ }
+
+ /**
+ * Adds an entry in the trace to log that a transition has been dispatched to a handler.
+ *
+ * @param transitionId The id of the transition being dispatched.
+ * @param handler The handler the transition is being dispatched to.
+ */
+ @Override
+ public void logDispatched(int transitionId, Transitions.TransitionHandler handler) {
+ if (!isTracing()) {
+ return;
+ }
+
+ mDataSource.trace(ctx -> {
+ final int handlerId = getHandlerId(handler, ctx);
+
+ final ProtoOutputStream os = ctx.newTracePacket();
+ final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
+ os.write(PerfettoTrace.ShellTransition.ID, transitionId);
+ os.write(PerfettoTrace.ShellTransition.DISPATCH_TIME_NS,
+ SystemClock.elapsedRealtimeNanos());
+ os.write(PerfettoTrace.ShellTransition.HANDLER, handlerId);
+ os.end(token);
+ });
+ }
+
+ private static int getHandlerId(Transitions.TransitionHandler handler,
+ TracingContext<DataSourceInstance, TransitionDataSource.TlsState, Void> ctx) {
+ final Map<String, Integer> handlerMapping =
+ ctx.getCustomTlsState().handlerMapping;
+ final int handlerId;
+ if (handlerMapping.containsKey(handler.getClass().getName())) {
+ handlerId = handlerMapping.get(handler.getClass().getName());
+ } else {
+ // + 1 to avoid 0 ids which can be confused with missing value when dumped to proto
+ handlerId = handlerMapping.size() + 1;
+ handlerMapping.put(handler.getClass().getName(), handlerId);
+ }
+ return handlerId;
+ }
+
+ /**
+ * Adds an entry in the trace to log that a request to merge a transition was made.
+ *
+ * @param mergeRequestedTransitionId The id of the transition we are requesting to be merged.
+ */
+ @Override
+ public void logMergeRequested(int mergeRequestedTransitionId, int playingTransitionId) {
+ if (!isTracing()) {
+ return;
+ }
+
+ mDataSource.trace(ctx -> {
+ final ProtoOutputStream os = ctx.newTracePacket();
+ final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
+ os.write(PerfettoTrace.ShellTransition.ID, mergeRequestedTransitionId);
+ os.write(PerfettoTrace.ShellTransition.MERGE_REQUEST_TIME_NS,
+ SystemClock.elapsedRealtimeNanos());
+ os.write(PerfettoTrace.ShellTransition.MERGE_TARGET, playingTransitionId);
+ os.end(token);
+ });
+ }
+
+ /**
+ * Adds an entry in the trace to log that a transition was merged by the handler.
+ *
+ * @param mergedTransitionId The id of the transition that was merged.
+ * @param playingTransitionId The id of the transition the transition was merged into.
+ */
+ @Override
+ public void logMerged(int mergedTransitionId, int playingTransitionId) {
+ if (!isTracing()) {
+ return;
+ }
+
+ mDataSource.trace(ctx -> {
+ final ProtoOutputStream os = ctx.newTracePacket();
+ final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
+ os.write(PerfettoTrace.ShellTransition.ID, mergedTransitionId);
+ os.write(PerfettoTrace.ShellTransition.MERGE_TIME_NS,
+ SystemClock.elapsedRealtimeNanos());
+ os.write(PerfettoTrace.ShellTransition.MERGE_TARGET, playingTransitionId);
+ os.end(token);
+ });
+ }
+
+ /**
+ * Adds an entry in the trace to log that a transition was aborted.
+ *
+ * @param transitionId The id of the transition that was aborted.
+ */
+ @Override
+ public void logAborted(int transitionId) {
+ if (!isTracing()) {
+ return;
+ }
+
+ mDataSource.trace(ctx -> {
+ final ProtoOutputStream os = ctx.newTracePacket();
+ final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
+ os.write(PerfettoTrace.ShellTransition.ID, transitionId);
+ os.write(PerfettoTrace.ShellTransition.SHELL_ABORT_TIME_NS,
+ SystemClock.elapsedRealtimeNanos());
+ os.end(token);
+ });
+ }
+
+ private boolean isTracing() {
+ return mActiveTraces.get() > 0;
+ }
+
+ private void onFlush() {
+ mDataSource.trace(ctx -> {
+ final ProtoOutputStream os = ctx.newTracePacket();
+
+ final Map<String, Integer> handlerMapping = ctx.getCustomTlsState().handlerMapping;
+ for (String handler : handlerMapping.keySet()) {
+ final long token = os.start(PerfettoTrace.TracePacket.SHELL_HANDLER_MAPPINGS);
+ os.write(PerfettoTrace.ShellHandlerMapping.ID, handlerMapping.get(handler));
+ os.write(PerfettoTrace.ShellHandlerMapping.NAME, handler);
+ os.end(token);
+ }
+
+ ctx.flush();
+ });
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/TransitionTracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/TransitionTracer.java
new file mode 100644
index 0000000..5857ad8
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/TransitionTracer.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.transition.tracing;
+
+import com.android.wm.shell.transition.Transitions;
+
+public interface TransitionTracer {
+ /**
+ * Adds an entry in the trace to log that a transition has been dispatched to a handler.
+ *
+ * @param transitionId The id of the transition being dispatched.
+ * @param handler The handler the transition is being dispatched to.
+ */
+ void logDispatched(int transitionId, Transitions.TransitionHandler handler);
+
+ /**
+ * Adds an entry in the trace to log that a request to merge a transition was made.
+ *
+ * @param mergeRequestedTransitionId The id of the transition we are requesting to be merged.
+ */
+ void logMergeRequested(int mergeRequestedTransitionId, int playingTransitionId);
+
+ /**
+ * Adds an entry in the trace to log that a transition was merged by the handler.
+ *
+ * @param mergedTransitionId The id of the transition that was merged.
+ * @param playingTransitionId The id of the transition the transition was merged into.
+ */
+ void logMerged(int mergedTransitionId, int playingTransitionId);
+
+ /**
+ * Adds an entry in the trace to log that a transition was aborted.
+ *
+ * @param transitionId The id of the transition that was aborted.
+ */
+ void logAborted(int transitionId);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
index d7cb490e..e6d35e8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
@@ -28,11 +28,11 @@
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator;
+import dagger.Lazy;
+
import java.util.List;
import java.util.Optional;
-import dagger.Lazy;
-
/**
* Manages fold/unfold animations of tasks on foldable devices.
* When folding or unfolding a foldable device we play animations that
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 1a793a1..b2eeea7 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
@@ -271,6 +271,9 @@
if (e.findPointerIndex(mDragPointerId) == -1) {
mDragPointerId = e.getPointerId(0);
}
+ final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+ // If a decor's resize drag zone is active, don't also try to reposition it.
+ if (decoration.isHandlingDragResize()) break;
final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
mDragPositioningCallback.onDragPositioningMove(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
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 1debb02..5a74255 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
@@ -286,6 +286,10 @@
closeBackground.setTintList(buttonTintColor);
}
+ boolean isHandlingDragResize() {
+ return mDragResizeListener.isHandlingDragResize();
+ }
+
private void closeDragResizeListener() {
if (mDragResizeListener == null) {
return;
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 d07c646..554b1fb 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
@@ -421,9 +421,9 @@
}
moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId));
- if (!mHasLongClicked) {
+ if (!mHasLongClicked && id != R.id.maximize_window) {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
- decoration.closeMaximizeMenu();
+ decoration.closeMaximizeMenuIfNeeded(e);
}
final long eventDuration = e.getEventTime() - e.getDownTime();
@@ -491,8 +491,11 @@
return true;
}
case MotionEvent.ACTION_MOVE: {
+ mShouldClick = false;
final DesktopModeWindowDecoration decoration =
mWindowDecorByTaskId.get(mTaskId);
+ // If a decor's resize drag zone is active, don't also try to reposition it.
+ if (decoration.isHandlingDragResize()) break;
decoration.closeMaximizeMenu();
if (e.findPointerIndex(mDragPointerId) == -1) {
mDragPointerId = e.getPointerId(0);
@@ -502,10 +505,9 @@
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
mDesktopTasksController.ifPresent(c -> c.onDragPositioningMove(taskInfo,
decoration.mTaskSurface,
- new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)),
+ e.getRawX(dragPointerIdx),
newTaskBounds));
mIsDragging = true;
- mShouldClick = false;
return true;
}
case MotionEvent.ACTION_UP:
@@ -643,7 +645,7 @@
handleCaptionThroughStatusBar(ev, relevantDecor);
}
}
- handleEventOutsideFocusedCaption(ev, relevantDecor);
+ handleEventOutsideCaption(ev, relevantDecor);
// Prevent status bar from reacting to a caption drag.
if (DesktopModeStatus.isEnabled()) {
if (mTransitionDragActive) {
@@ -652,11 +654,17 @@
}
}
- // If an UP/CANCEL action is received outside of caption bounds, turn off handle menu
- private void handleEventOutsideFocusedCaption(MotionEvent ev,
+ /**
+ * If an UP/CANCEL action is received outside of the caption bounds, close the handle and
+ * maximize the menu.
+ *
+ * @param relevantDecor the window decoration of the focused task's caption. This method only
+ * handles motion events outside this caption's bounds.
+ */
+ private void handleEventOutsideCaption(MotionEvent ev,
DesktopModeWindowDecoration relevantDecor) {
// Returns if event occurs within caption
- if (relevantDecor == null || relevantDecor.checkTouchEventInCaptionHandle(ev)) {
+ if (relevantDecor == null || relevantDecor.checkTouchEventInCaption(ev)) {
return;
}
@@ -692,7 +700,7 @@
}
if (dragFromStatusBarAllowed
- && relevantDecor.checkTouchEventInCaptionHandle(ev)) {
+ && relevantDecor.checkTouchEventInFocusedCaptionHandle(ev)) {
mTransitionDragActive = true;
}
}
@@ -731,9 +739,9 @@
}
if (mTransitionDragActive) {
mDesktopTasksController.ifPresent(
- c -> c.onDragPositioningMoveThroughStatusBar(
+ c -> c.updateVisualIndicator(
relevantDecor.mTaskInfo,
- relevantDecor.mTaskSurface, ev.getY()));
+ relevantDecor.mTaskSurface, ev.getX(), ev.getY()));
final int statusBarHeight = getStatusBarHeight(
relevantDecor.mTaskInfo.displayId);
if (ev.getY() > statusBarHeight) {
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 5f77192..2023333 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
@@ -387,6 +387,10 @@
return mHandleMenu != null;
}
+ boolean isHandlingDragResize() {
+ return mDragResizeListener.isHandlingDragResize();
+ }
+
private void loadAppInfo() {
String packageName = mTaskInfo.realActivity.getPackageName();
PackageManager pm = mContext.getApplicationContext().getPackageManager();
@@ -612,8 +616,7 @@
void closeMaximizeMenuIfNeeded(MotionEvent ev) {
if (!isMaximizeMenuActive()) return;
- final PointF inputPoint = offsetCaptionLocation(ev);
- if (!mMaximizeMenu.isValidMenuInput(inputPoint)) {
+ if (!mMaximizeMenu.isValidMenuInput(ev)) {
closeMaximizeMenu();
}
}
@@ -639,20 +642,34 @@
}
/**
- * Checks if motion event occurs in the caption handle area. This should be used in cases where
+ * Checks if motion event occurs in the caption handle area of a focused caption (the caption on
+ * a task in fullscreen or in multi-windowing mode). This should be used in cases where
* onTouchListener will not work (i.e. when caption is in status bar area).
*
* @param ev the {@link MotionEvent} to check
- * @return {@code true} if event is inside the specified view, {@code false} if not
+ * @return {@code true} if event is inside caption handle view, {@code false} if not
*/
- boolean checkTouchEventInCaptionHandle(MotionEvent ev) {
+ boolean checkTouchEventInFocusedCaptionHandle(MotionEvent ev) {
if (isHandleMenuActive() || !(mWindowDecorViewHolder
instanceof DesktopModeFocusedWindowDecorationViewHolder)) {
return false;
}
+
+ return checkTouchEventInCaption(ev);
+ }
+
+ /**
+ * Checks if touch event occurs in caption.
+ *
+ * @param ev the {@link MotionEvent} to check
+ * @return {@code true} if event is inside caption view, {@code false} if not
+ */
+ boolean checkTouchEventInCaption(MotionEvent ev) {
final PointF inputPoint = offsetCaptionLocation(ev);
- return ((DesktopModeFocusedWindowDecorationViewHolder) mWindowDecorViewHolder)
- .pointInCaption(inputPoint, mResult.mCaptionX);
+ return inputPoint.x >= mResult.mCaptionX
+ && inputPoint.x <= mResult.mCaptionX + mResult.mCaptionWidth
+ && inputPoint.y >= 0
+ && inputPoint.y <= mResult.mCaptionHeight;
}
/**
@@ -668,7 +685,7 @@
// Click if point in caption handle view
final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
final View handle = caption.findViewById(R.id.caption_handle);
- if (checkTouchEventInCaptionHandle(ev)) {
+ if (checkTouchEventInFocusedCaptionHandle(ev)) {
mOnCaptionButtonClickListener.onClick(handle);
}
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 8511a21..8b38f99 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -320,6 +320,10 @@
}
}
+ boolean isHandlingDragResize() {
+ return mInputEventReceiver.isHandlingEvents();
+ }
+
@Override
public void close() {
mInputEventReceiver.dispose();
@@ -386,6 +390,10 @@
finishInputEvent(inputEvent, handleInputEvent(inputEvent));
}
+ boolean isHandlingEvents() {
+ return mShouldHandleEvents;
+ }
+
private boolean handleInputEvent(InputEvent inputEvent) {
if (!(inputEvent instanceof MotionEvent)) {
return false;
@@ -409,7 +417,6 @@
mShouldHandleEvents = isInResizeHandleBounds(x, y);
}
if (mShouldHandleEvents) {
- mInputManager.pilferPointers(mInputChannel.getToken());
mDragPointerId = e.getPointerId(0);
float rawX = e.getRawX(0);
float rawY = e.getRawY(0);
@@ -427,6 +434,7 @@
if (!mShouldHandleEvents) {
break;
}
+ mInputManager.pilferPointers(mInputChannel.getToken());
int dragPointerIndex = e.findPointerIndex(mDragPointerId);
float rawX = e.getRawX(dragPointerIndex);
float rawY = e.getRawY(dragPointerIndex);
@@ -437,6 +445,7 @@
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
+ mInputManager.pilferPointers(mInputChannel.getToken());
if (mShouldHandleEvents) {
int dragPointerIndex = e.findPointerIndex(mDragPointerId);
final Rect taskBounds = mCallback.onDragPositioningEnd(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
index 921708f..794b357 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
@@ -22,10 +22,10 @@
import android.graphics.PixelFormat
import android.graphics.PointF
import android.view.LayoutInflater
+import android.view.MotionEvent
import android.view.SurfaceControl
import android.view.SurfaceControl.Transaction
import android.view.SurfaceControlViewHost
-import android.view.View
import android.view.View.OnClickListener
import android.view.WindowManager
import android.view.WindowlessWindowManager
@@ -62,6 +62,8 @@
private val cornerRadius = loadDimensionPixelSize(
R.dimen.desktop_mode_maximize_menu_corner_radius
).toFloat()
+ private val menuWidth = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_width)
+ private val menuHeight = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_height)
/** Position the menu relative to the caption's position. */
fun positionMenu(position: PointF, t: Transaction) {
@@ -95,8 +97,6 @@
.setName("Maximize Menu")
.setContainerLayer()
.build()
- val menuWidth = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_width)
- val menuHeight = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_height)
val lp = WindowManager.LayoutParams(
menuWidth,
menuHeight,
@@ -160,14 +160,11 @@
*
* @param inputPoint the input to compare against.
*/
- fun isValidMenuInput(inputPoint: PointF): Boolean {
- val menuView = maximizeMenu?.mWindowViewHost?.view ?: return true
- return !viewsLaidOut() || pointInView(menuView, inputPoint.x - menuPosition.x,
- inputPoint.y - menuPosition.y)
- }
-
- private fun pointInView(v: View, x: Float, y: Float): Boolean {
- return v.left <= x && v.right >= x && v.top <= y && v.bottom >= y
+ fun isValidMenuInput(ev: MotionEvent): Boolean {
+ val x = ev.rawX
+ val y = ev.rawY
+ return !viewsLaidOut() || (menuPosition.x <= x && menuPosition.x + menuWidth >= x &&
+ menuPosition.y <= y && menuPosition.y + menuHeight >= y)
}
/**
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 c1b18f9..7c6e69e 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
@@ -61,6 +61,7 @@
private int mCtrlType;
private boolean mIsResizingOrAnimatingResize;
@Surface.Rotation private int mRotation;
+ private boolean mVeilIsVisible;
public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
DesktopModeWindowDecoration windowDecoration,
@@ -94,7 +95,6 @@
mDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds());
mRepositionStartPoint.set(x, y);
if (isResizing()) {
- mDesktopWindowDecoration.showResizeVeil(mTaskBoundsAtDragStart);
if (!mDesktopWindowDecoration.mTaskInfo.isFocused) {
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reorder(mDesktopWindowDecoration.mTaskInfo.token, true);
@@ -119,8 +119,13 @@
if (isResizing() && DragPositioningCallbackUtility.changeBounds(mCtrlType,
mRepositionTaskBounds, mTaskBoundsAtDragStart, mStableBounds, delta,
mDisplayController, mDesktopWindowDecoration)) {
- mDesktopWindowDecoration.updateResizeVeil(mRepositionTaskBounds);
mIsResizingOrAnimatingResize = true;
+ if (!mVeilIsVisible) {
+ mDesktopWindowDecoration.showResizeVeil(mRepositionTaskBounds);
+ mVeilIsVisible = true;
+ } else {
+ mDesktopWindowDecoration.updateResizeVeil(mRepositionTaskBounds);
+ }
} else if (mCtrlType == CTRL_TYPE_UNDEFINED) {
final SurfaceControl.Transaction t = mTransactionSupplier.get();
DragPositioningCallbackUtility.setPositionOnDrag(mDesktopWindowDecoration,
@@ -143,7 +148,7 @@
final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setBounds(mDesktopWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
- } else {
+ } else if (mVeilIsVisible) {
// If bounds haven't changed, perform necessary veil reset here as startAnimation
// won't be called.
mDesktopWindowDecoration.hideResizeVeil();
@@ -163,6 +168,7 @@
mCtrlType = CTRL_TYPE_UNDEFINED;
mTaskBoundsAtDragStart.setEmpty();
mRepositionStartPoint.set(0, 0);
+ mVeilIsVisible = false;
return new Rect(mRepositionTaskBounds);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 6a9258c..afe837e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -279,11 +279,12 @@
}
outResult.mCaptionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
- final int captionWidth = params.mCaptionWidthId != Resources.ID_NULL
+ outResult.mCaptionWidth = params.mCaptionWidthId != Resources.ID_NULL
? loadDimensionPixelSize(resources, params.mCaptionWidthId) : taskBounds.width();
- outResult.mCaptionX = (outResult.mWidth - captionWidth) / 2;
+ outResult.mCaptionX = (outResult.mWidth - outResult.mCaptionWidth) / 2;
- startT.setWindowCrop(mCaptionContainerSurface, captionWidth, outResult.mCaptionHeight)
+ startT.setWindowCrop(mCaptionContainerSurface, outResult.mCaptionWidth,
+ outResult.mCaptionHeight)
.setPosition(mCaptionContainerSurface, outResult.mCaptionX, 0 /* y */)
.setLayer(mCaptionContainerSurface, CAPTION_LAYER_Z_ORDER)
.show(mCaptionContainerSurface);
@@ -356,7 +357,7 @@
// Caption view
mCaptionWindowManager.setConfiguration(taskConfig);
final WindowManager.LayoutParams lp =
- new WindowManager.LayoutParams(captionWidth, outResult.mCaptionHeight,
+ new WindowManager.LayoutParams(outResult.mCaptionWidth, outResult.mCaptionHeight,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
@@ -578,6 +579,7 @@
static class RelayoutResult<T extends View & TaskFocusStateConsumer> {
int mCaptionHeight;
+ int mCaptionWidth;
int mCaptionX;
int mWidth;
int mHeight;
@@ -587,6 +589,7 @@
mWidth = 0;
mHeight = 0;
mCaptionHeight = 0;
+ mCaptionWidth = 0;
mCaptionX = 0;
mRootView = null;
}
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 5f77022..6dcae27 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
@@ -5,7 +5,6 @@
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.content.res.ColorStateList
import android.graphics.Color
-import android.graphics.PointF
import android.view.View
import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
import android.widget.ImageButton
@@ -47,17 +46,6 @@
animateCaptionHandleAlpha(startValue = 0f, endValue = 1f)
}
- /**
- * Returns true if input point is in the caption's view.
- * @param inputPoint the input point relative to the task in full "focus" (i.e. fullscreen).
- */
- fun pointInCaption(inputPoint: PointF, captionX: Int): Boolean {
- return inputPoint.x >= captionX &&
- inputPoint.x <= captionX + captionView.width &&
- inputPoint.y >= 0 &&
- inputPoint.y <= captionView.height
- }
-
private fun getCaptionHandleBarColor(taskInfo: RunningTaskInfo): Int {
return if (shouldUseLightCaptionColors(taskInfo)) {
context.getColor(R.color.desktop_mode_caption_handle_bar_light)
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
index 182a908..be77171 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
@@ -101,7 +101,8 @@
override fun pipLayerReduces() {
flicker.assertLayers {
val pipLayerList =
- this.layers { standardAppHelper.layerMatchesAnyOf(it) && it.isVisible }
+ this.layers { standardAppHelper.packageNameMatcher.layerMatchesAnyOf(it)
+ && it.isVisible }
pipLayerList.zipWithNext { previous, current ->
current.visibleRegion.notBiggerThan(previous.visibleRegion.region)
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
deleted file mode 100644
index 6ebee73..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
+++ /dev/null
@@ -1,602 +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.wm.shell.bubbles;
-
-import static com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
-
-import static org.mockito.Mockito.mock;
-
-import android.content.Intent;
-import android.content.pm.ShortcutInfo;
-import android.graphics.Insets;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.os.UserHandle;
-import android.testing.AndroidTestingRunner;
-import android.view.WindowManager;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.wm.shell.R;
-import com.android.wm.shell.ShellTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests operations and the resulting state managed by {@link BubblePositioner}.
- */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class BubblePositionerTest extends ShellTestCase {
-
- private BubblePositioner mPositioner;
-
- @Before
- public void setUp() {
- WindowManager windowManager = mContext.getSystemService(WindowManager.class);
- mPositioner = new BubblePositioner(mContext, windowManager);
- }
-
- @Test
- public void testUpdate() {
- Insets insets = Insets.of(10, 20, 5, 15);
- Rect screenBounds = new Rect(0, 0, 1000, 1200);
- Rect availableRect = new Rect(screenBounds);
- availableRect.inset(insets);
-
- DeviceConfig deviceConfig = new ConfigBuilder()
- .setInsets(insets)
- .setScreenBounds(screenBounds)
- .build();
- mPositioner.update(deviceConfig);
-
- assertThat(mPositioner.getAvailableRect()).isEqualTo(availableRect);
- assertThat(mPositioner.isLandscape()).isFalse();
- assertThat(mPositioner.isLargeScreen()).isFalse();
- assertThat(mPositioner.getInsets()).isEqualTo(insets);
- }
-
- @Test
- public void testShowBubblesVertically_phonePortrait() {
- DeviceConfig deviceConfig = new ConfigBuilder().build();
- mPositioner.update(deviceConfig);
-
- assertThat(mPositioner.showBubblesVertically()).isFalse();
- }
-
- @Test
- public void testShowBubblesVertically_phoneLandscape() {
- DeviceConfig deviceConfig = new ConfigBuilder().setLandscape().build();
- mPositioner.update(deviceConfig);
-
- assertThat(mPositioner.isLandscape()).isTrue();
- assertThat(mPositioner.showBubblesVertically()).isTrue();
- }
-
- @Test
- public void testShowBubblesVertically_tablet() {
- DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().build();
- mPositioner.update(deviceConfig);
-
- assertThat(mPositioner.showBubblesVertically()).isTrue();
- }
-
- /** If a resting position hasn't been set, calling it will return the default position. */
- @Test
- public void testGetRestingPosition_returnsDefaultPosition() {
- DeviceConfig deviceConfig = new ConfigBuilder().build();
- mPositioner.update(deviceConfig);
-
- PointF restingPosition = mPositioner.getRestingPosition();
- PointF defaultPosition = mPositioner.getDefaultStartPosition();
-
- assertThat(restingPosition).isEqualTo(defaultPosition);
- }
-
- /** If a resting position has been set, it'll return that instead of the default position. */
- @Test
- public void testGetRestingPosition_returnsRestingPosition() {
- DeviceConfig deviceConfig = new ConfigBuilder().build();
- mPositioner.update(deviceConfig);
-
- PointF restingPosition = new PointF(100, 100);
- mPositioner.setRestingPosition(restingPosition);
-
- assertThat(mPositioner.getRestingPosition()).isEqualTo(restingPosition);
- }
-
- /** Test that the default resting position on phone is in upper left. */
- @Test
- public void testGetRestingPosition_bubble_onPhone() {
- DeviceConfig deviceConfig = new ConfigBuilder().build();
- mPositioner.update(deviceConfig);
-
- RectF allowableStackRegion =
- mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
- PointF restingPosition = mPositioner.getRestingPosition();
-
- assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left);
- assertThat(restingPosition.y).isEqualTo(getDefaultYPosition());
- }
-
- @Test
- public void testGetRestingPosition_bubble_onPhone_RTL() {
- DeviceConfig deviceConfig = new ConfigBuilder().setRtl().build();
- mPositioner.update(deviceConfig);
-
- RectF allowableStackRegion =
- mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
- PointF restingPosition = mPositioner.getRestingPosition();
-
- assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right);
- assertThat(restingPosition.y).isEqualTo(getDefaultYPosition());
- }
-
- /** Test that the default resting position on tablet is middle left. */
- @Test
- public void testGetRestingPosition_chatBubble_onTablet() {
- DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().build();
- mPositioner.update(deviceConfig);
-
- RectF allowableStackRegion =
- mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
- PointF restingPosition = mPositioner.getRestingPosition();
-
- assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left);
- assertThat(restingPosition.y).isEqualTo(getDefaultYPosition());
- }
-
- @Test
- public void testGetRestingPosition_chatBubble_onTablet_RTL() {
- DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().setRtl().build();
- mPositioner.update(deviceConfig);
-
- RectF allowableStackRegion =
- mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
- PointF restingPosition = mPositioner.getRestingPosition();
-
- assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right);
- assertThat(restingPosition.y).isEqualTo(getDefaultYPosition());
- }
-
- /** Test that the default resting position on tablet is middle right. */
- @Test
- public void testGetDefaultPosition_appBubble_onTablet() {
- DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().build();
- mPositioner.update(deviceConfig);
-
- RectF allowableStackRegion =
- mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
- PointF startPosition = mPositioner.getDefaultStartPosition(true /* isAppBubble */);
-
- assertThat(startPosition.x).isEqualTo(allowableStackRegion.right);
- assertThat(startPosition.y).isEqualTo(getDefaultYPosition());
- }
-
- @Test
- public void testGetRestingPosition_appBubble_onTablet_RTL() {
- DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().setRtl().build();
- mPositioner.update(deviceConfig);
-
- RectF allowableStackRegion =
- mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
- PointF startPosition = mPositioner.getDefaultStartPosition(true /* isAppBubble */);
-
- assertThat(startPosition.x).isEqualTo(allowableStackRegion.left);
- assertThat(startPosition.y).isEqualTo(getDefaultYPosition());
- }
-
- @Test
- public void testHasUserModifiedDefaultPosition_false() {
- DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().setRtl().build();
- mPositioner.update(deviceConfig);
-
- assertThat(mPositioner.hasUserModifiedDefaultPosition()).isFalse();
-
- mPositioner.setRestingPosition(mPositioner.getDefaultStartPosition());
-
- assertThat(mPositioner.hasUserModifiedDefaultPosition()).isFalse();
- }
-
- @Test
- public void testHasUserModifiedDefaultPosition_true() {
- DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().setRtl().build();
- mPositioner.update(deviceConfig);
-
- assertThat(mPositioner.hasUserModifiedDefaultPosition()).isFalse();
-
- mPositioner.setRestingPosition(new PointF(0, 100));
-
- assertThat(mPositioner.hasUserModifiedDefaultPosition()).isTrue();
- }
-
- @Test
- public void testGetExpandedViewHeight_max() {
- Insets insets = Insets.of(10, 20, 5, 15);
- Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
- DeviceConfig deviceConfig = new ConfigBuilder()
- .setLargeScreen()
- .setInsets(insets)
- .setScreenBounds(screenBounds)
- .build();
- mPositioner.update(deviceConfig);
-
- Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
- Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
-
- assertThat(mPositioner.getExpandedViewHeight(bubble)).isEqualTo(MAX_HEIGHT);
- }
-
- @Test
- public void testGetExpandedViewHeight_customHeight_valid() {
- Insets insets = Insets.of(10, 20, 5, 15);
- Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
- DeviceConfig deviceConfig = new ConfigBuilder()
- .setLargeScreen()
- .setInsets(insets)
- .setScreenBounds(screenBounds)
- .build();
- mPositioner.update(deviceConfig);
-
- final int minHeight = mContext.getResources().getDimensionPixelSize(
- R.dimen.bubble_expanded_default_height);
- Bubble bubble = new Bubble("key",
- mock(ShortcutInfo.class),
- minHeight + 100 /* desiredHeight */,
- 0 /* desiredHeightResId */,
- "title",
- 0 /* taskId */,
- null /* locus */,
- true /* isDismissable */,
- directExecutor(),
- mock(Bubbles.BubbleMetadataFlagListener.class));
-
- // Ensure the height is the same as the desired value
- assertThat(mPositioner.getExpandedViewHeight(bubble)).isEqualTo(
- bubble.getDesiredHeight(mContext));
- }
-
-
- @Test
- public void testGetExpandedViewHeight_customHeight_tooSmall() {
- Insets insets = Insets.of(10, 20, 5, 15);
- Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
- DeviceConfig deviceConfig = new ConfigBuilder()
- .setLargeScreen()
- .setInsets(insets)
- .setScreenBounds(screenBounds)
- .build();
- mPositioner.update(deviceConfig);
-
- Bubble bubble = new Bubble("key",
- mock(ShortcutInfo.class),
- 10 /* desiredHeight */,
- 0 /* desiredHeightResId */,
- "title",
- 0 /* taskId */,
- null /* locus */,
- true /* isDismissable */,
- directExecutor(),
- mock(Bubbles.BubbleMetadataFlagListener.class));
-
- // Ensure the height is the same as the minimum value
- final int minHeight = mContext.getResources().getDimensionPixelSize(
- R.dimen.bubble_expanded_default_height);
- assertThat(mPositioner.getExpandedViewHeight(bubble)).isEqualTo(minHeight);
- }
-
- @Test
- public void testGetMaxExpandedViewHeight_onLargeTablet() {
- Insets insets = Insets.of(10, 20, 5, 15);
- Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
- DeviceConfig deviceConfig = new ConfigBuilder()
- .setLargeScreen()
- .setInsets(insets)
- .setScreenBounds(screenBounds)
- .build();
- mPositioner.update(deviceConfig);
-
- int manageButtonHeight =
- mContext.getResources().getDimensionPixelSize(R.dimen.bubble_manage_button_height);
- int pointerWidth = mContext.getResources().getDimensionPixelSize(
- R.dimen.bubble_pointer_width);
- int expandedViewPadding = mContext.getResources().getDimensionPixelSize(R
- .dimen.bubble_expanded_view_padding);
- float expectedHeight = 1800 - 2 * 20 - manageButtonHeight - pointerWidth
- - expandedViewPadding * 2;
- assertThat(((float) mPositioner.getMaxExpandedViewHeight(false /* isOverflow */)))
- .isWithin(0.1f).of(expectedHeight);
- }
-
- @Test
- public void testAreBubblesBottomAligned_largeScreen_true() {
- Insets insets = Insets.of(10, 20, 5, 15);
- Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
- DeviceConfig deviceConfig = new ConfigBuilder()
- .setLargeScreen()
- .setInsets(insets)
- .setScreenBounds(screenBounds)
- .build();
- mPositioner.update(deviceConfig);
-
- assertThat(mPositioner.areBubblesBottomAligned()).isTrue();
- }
-
- @Test
- public void testAreBubblesBottomAligned_largeScreen_false() {
- Insets insets = Insets.of(10, 20, 5, 15);
- Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
- DeviceConfig deviceConfig = new ConfigBuilder()
- .setLargeScreen()
- .setLandscape()
- .setInsets(insets)
- .setScreenBounds(screenBounds)
- .build();
- mPositioner.update(deviceConfig);
-
- assertThat(mPositioner.areBubblesBottomAligned()).isFalse();
- }
-
- @Test
- public void testAreBubblesBottomAligned_smallTablet_false() {
- Insets insets = Insets.of(10, 20, 5, 15);
- Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
- DeviceConfig deviceConfig = new ConfigBuilder()
- .setLargeScreen()
- .setSmallTablet()
- .setInsets(insets)
- .setScreenBounds(screenBounds)
- .build();
- mPositioner.update(deviceConfig);
-
- assertThat(mPositioner.areBubblesBottomAligned()).isFalse();
- }
-
- @Test
- public void testAreBubblesBottomAligned_phone_false() {
- Insets insets = Insets.of(10, 20, 5, 15);
- Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
- DeviceConfig deviceConfig = new ConfigBuilder()
- .setInsets(insets)
- .setScreenBounds(screenBounds)
- .build();
- mPositioner.update(deviceConfig);
-
- assertThat(mPositioner.areBubblesBottomAligned()).isFalse();
- }
-
- @Test
- public void testExpandedViewY_phoneLandscape() {
- Insets insets = Insets.of(10, 20, 5, 15);
- Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
- DeviceConfig deviceConfig = new ConfigBuilder()
- .setLandscape()
- .setInsets(insets)
- .setScreenBounds(screenBounds)
- .build();
- mPositioner.update(deviceConfig);
-
- Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
- Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
-
- // This bubble will have max height so it'll always be top aligned
- assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
- .isEqualTo(mPositioner.getExpandedViewYTopAligned());
- }
-
- @Test
- public void testExpandedViewY_phonePortrait() {
- Insets insets = Insets.of(10, 20, 5, 15);
- Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
- DeviceConfig deviceConfig = new ConfigBuilder()
- .setInsets(insets)
- .setScreenBounds(screenBounds)
- .build();
- mPositioner.update(deviceConfig);
-
- Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
- Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
-
- // Always top aligned in phone portrait
- assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
- .isEqualTo(mPositioner.getExpandedViewYTopAligned());
- }
-
- @Test
- public void testExpandedViewY_smallTabletLandscape() {
- Insets insets = Insets.of(10, 20, 5, 15);
- Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
- DeviceConfig deviceConfig = new ConfigBuilder()
- .setSmallTablet()
- .setLandscape()
- .setInsets(insets)
- .setScreenBounds(screenBounds)
- .build();
- mPositioner.update(deviceConfig);
-
- Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
- Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
-
- // This bubble will have max height which is always top aligned on small tablets
- assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
- .isEqualTo(mPositioner.getExpandedViewYTopAligned());
- }
-
- @Test
- public void testExpandedViewY_smallTabletPortrait() {
- Insets insets = Insets.of(10, 20, 5, 15);
- Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
- DeviceConfig deviceConfig = new ConfigBuilder()
- .setSmallTablet()
- .setInsets(insets)
- .setScreenBounds(screenBounds)
- .build();
- mPositioner.update(deviceConfig);
-
- Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
- Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
-
- // This bubble will have max height which is always top aligned on small tablets
- assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
- .isEqualTo(mPositioner.getExpandedViewYTopAligned());
- }
-
- @Test
- public void testExpandedViewY_largeScreenLandscape() {
- Insets insets = Insets.of(10, 20, 5, 15);
- Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
- DeviceConfig deviceConfig = new ConfigBuilder()
- .setLargeScreen()
- .setLandscape()
- .setInsets(insets)
- .setScreenBounds(screenBounds)
- .build();
- mPositioner.update(deviceConfig);
-
- Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
- Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
-
- // This bubble will have max height which is always top aligned on landscape, large tablet
- assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
- .isEqualTo(mPositioner.getExpandedViewYTopAligned());
- }
-
- @Test
- public void testExpandedViewY_largeScreenPortrait() {
- Insets insets = Insets.of(10, 20, 5, 15);
- Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
- DeviceConfig deviceConfig = new ConfigBuilder()
- .setLargeScreen()
- .setInsets(insets)
- .setScreenBounds(screenBounds)
- .build();
- mPositioner.update(deviceConfig);
-
- Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
- Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
-
- int manageButtonHeight =
- mContext.getResources().getDimensionPixelSize(R.dimen.bubble_manage_button_height);
- int manageButtonPlusMargin = manageButtonHeight + 2
- * mContext.getResources().getDimensionPixelSize(
- R.dimen.bubble_manage_button_margin);
- int pointerWidth = mContext.getResources().getDimensionPixelSize(
- R.dimen.bubble_pointer_width);
-
- final float expectedExpandedViewY = mPositioner.getAvailableRect().bottom
- - manageButtonPlusMargin
- - mPositioner.getExpandedViewHeightForLargeScreen()
- - pointerWidth;
-
- // Bubbles are bottom aligned on portrait, large tablet
- assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
- .isEqualTo(expectedExpandedViewY);
- }
-
- /**
- * Calculates the Y position bubbles should be placed based on the config. Based on
- * the calculations in {@link BubblePositioner#getDefaultStartPosition()} and
- * {@link BubbleStackView.RelativeStackPosition}.
- */
- private float getDefaultYPosition() {
- final boolean isTablet = mPositioner.isLargeScreen();
-
- // On tablet the position is centered, on phone it is an offset from the top.
- final float desiredY = isTablet
- ? mPositioner.getScreenRect().height() / 2f - (mPositioner.getBubbleSize() / 2f)
- : mContext.getResources().getDimensionPixelOffset(
- R.dimen.bubble_stack_starting_offset_y);
- // Since we're visually centering the bubbles on tablet, use total screen height rather
- // than the available height.
- final float height = isTablet
- ? mPositioner.getScreenRect().height()
- : mPositioner.getAvailableRect().height();
- float offsetPercent = desiredY / height;
- offsetPercent = Math.max(0f, Math.min(1f, offsetPercent));
- final RectF allowableStackRegion =
- mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
- return allowableStackRegion.top + allowableStackRegion.height() * offsetPercent;
- }
-
- /**
- * Sets up window manager to return config values based on what you need for the test.
- * By default it sets up a portrait phone without any insets.
- */
- private static class ConfigBuilder {
- private Rect mScreenBounds = new Rect(0, 0, 1000, 2000);
- private boolean mIsLargeScreen = false;
- private boolean mIsSmallTablet = false;
- private boolean mIsLandscape = false;
- private boolean mIsRtl = false;
- private Insets mInsets = Insets.of(0, 0, 0, 0);
-
- public ConfigBuilder setScreenBounds(Rect screenBounds) {
- mScreenBounds = screenBounds;
- return this;
- }
-
- public ConfigBuilder setLargeScreen() {
- mIsLargeScreen = true;
- return this;
- }
-
- public ConfigBuilder setSmallTablet() {
- mIsSmallTablet = true;
- return this;
- }
-
- public ConfigBuilder setLandscape() {
- mIsLandscape = true;
- return this;
- }
-
- public ConfigBuilder setRtl() {
- mIsRtl = true;
- return this;
- }
-
- public ConfigBuilder setInsets(Insets insets) {
- mInsets = insets;
- return this;
- }
-
- private DeviceConfig build() {
- return new DeviceConfig(mIsLargeScreen, mIsSmallTablet, mIsLandscape, mIsRtl,
- mScreenBounds, mInsets);
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
index 23a4e39..4ddc539 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
@@ -127,6 +127,7 @@
@Test
public void testOnClickForSizeCompatHint() {
mWindowManager.mHasSizeCompat = true;
+ doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(mTaskInfo);
mWindowManager.createLayout(/* canShow= */ true);
final LinearLayout sizeCompatHint = mLayout.findViewById(R.id.size_compat_hint);
sizeCompatHint.performClick();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index d4b97ed..2acfd83 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -20,6 +20,8 @@
import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.view.WindowInsets.Type.navigationBars;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -27,6 +29,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
@@ -39,6 +42,7 @@
import android.app.TaskInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
import android.view.DisplayInfo;
@@ -50,6 +54,7 @@
import androidx.test.filters.SmallTest;
+import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
@@ -59,6 +64,7 @@
import junit.framework.Assert;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -76,6 +82,8 @@
@RunWith(AndroidTestingRunner.class)
@SmallTest
public class CompatUIWindowManagerTest extends ShellTestCase {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
private static final int TASK_ID = 1;
@@ -107,6 +115,7 @@
public void testCreateSizeCompatButton() {
// Doesn't create layout if show is false.
mWindowManager.mHasSizeCompat = true;
+ doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(mTaskInfo);
assertTrue(mWindowManager.createLayout(/* canShow= */ false));
verify(mWindowManager, never()).inflateLayout();
@@ -199,6 +208,7 @@
// No diff
clearInvocations(mWindowManager);
TaskInfo taskInfo = createTaskInfo(/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN);
+ doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(any());
assertTrue(mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true));
verify(mWindowManager, never()).updateSurfacePosition();
@@ -284,6 +294,7 @@
@Test
public void testUpdateCompatInfoLayoutNotInflatedYet() {
mWindowManager.mHasSizeCompat = true;
+ doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(any());
mWindowManager.createLayout(/* canShow= */ false);
verify(mWindowManager, never()).inflateLayout();
@@ -353,6 +364,7 @@
// Create button if it is not created.
mWindowManager.mLayout = null;
mWindowManager.mHasSizeCompat = true;
+ doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(mTaskInfo);
mWindowManager.updateVisibility(/* canShow= */ true);
verify(mWindowManager).createLayout(/* canShow= */ true);
@@ -464,6 +476,37 @@
Assert.assertTrue(mWindowManager.needsToBeRecreated(newTaskInfo, mTaskListener));
}
+ @Test
+ public void testShouldShowSizeCompatRestartButton() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_HIDE_SCM_BUTTON);
+
+ doReturn(86).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance();
+ mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue,
+ mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
+ mCompatUIConfiguration, mOnRestartButtonClicked);
+
+ // Simulate rotation of activity in square display
+ TaskInfo taskInfo = createTaskInfo(true, CAMERA_COMPAT_CONTROL_HIDDEN);
+ taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 2000, 2000));
+ taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 2000;
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1850;
+
+ assertFalse(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
+
+ // Simulate exiting split screen/folding
+ taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000;
+ assertTrue(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
+
+ // Simulate folding
+ taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 1000, 2000));
+ assertFalse(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
+
+ taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000;
+ taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 500;
+ assertTrue(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
+ }
+
private static TaskInfo createTaskInfo(boolean hasSizeCompat,
@AppCompatTaskInfo.CameraCompatControlState int cameraCompatControlState) {
ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index 3bc90ad..be639e8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -34,6 +34,7 @@
import org.mockito.Mock
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
+import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyZeroInteractions
import org.mockito.kotlin.whenever
@@ -150,6 +151,23 @@
}
@Test
+ fun startDragToDesktop_anotherTransitionInProgress_startDropped() {
+ val task = createTask()
+ val dragAnimator = mock<MoveToDesktopAnimator>()
+
+ // Simulate attempt to start two drag to desktop transitions.
+ startDragToDesktopTransition(task, dragAnimator)
+ startDragToDesktopTransition(task, dragAnimator)
+
+ // Verify transition only started once.
+ verify(transitions, times(1)).startTransition(
+ eq(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP),
+ any(),
+ eq(handler)
+ )
+ }
+
+ @Test
fun cancelDragToDesktop_startWasReady_cancel() {
val task = createTask()
val dragAnimator = mock<MoveToDesktopAnimator>()
@@ -189,6 +207,32 @@
verifyZeroInteractions(dragAnimator)
}
+ @Test
+ fun cancelDragToDesktop_transitionNotInProgress_dropCancel() {
+ // Then cancel is called before the transition was started.
+ handler.cancelDragToDesktopTransition()
+
+ // Verify cancel is dropped.
+ verify(transitions, never()).startTransition(
+ eq(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP),
+ any(),
+ eq(handler)
+ )
+ }
+
+ @Test
+ fun finishDragToDesktop_transitionNotInProgress_dropFinish() {
+ // Then finish is called before the transition was started.
+ handler.finishDragToDesktopTransition(WindowContainerTransaction())
+
+ // Verify finish is dropped.
+ verify(transitions, never()).startTransition(
+ eq(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP),
+ any(),
+ eq(handler)
+ )
+ }
+
private fun startDragToDesktopTransition(
task: RunningTaskInfo,
dragAnimator: MoveToDesktopAnimator
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 0841210..86253f3 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
@@ -144,13 +144,13 @@
}
@Test
- fun testDragResize_noMove_showsResizeVeil() {
+ fun testDragResize_noMove_doesNotShowResizeVeil() {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
- verify(mockDesktopWindowDecoration).showResizeVeil(STARTING_BOUNDS)
+ verify(mockDesktopWindowDecoration, never()).showResizeVeil(STARTING_BOUNDS)
taskPositioner.onDragPositioningEnd(
STARTING_BOUNDS.left.toFloat(),
@@ -162,7 +162,7 @@
(change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
change.configuration.windowConfiguration.bounds == STARTING_BOUNDS}},
eq(taskPositioner))
- verify(mockDesktopWindowDecoration).hideResizeVeil()
+ verify(mockDesktopWindowDecoration, never()).hideResizeVeil()
}
@Test
@@ -212,7 +212,6 @@
STARTING_BOUNDS.right.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
- verify(mockDesktopWindowDecoration).showResizeVeil(STARTING_BOUNDS)
taskPositioner.onDragPositioningMove(
STARTING_BOUNDS.right.toFloat() + 10,
@@ -222,6 +221,7 @@
val rectAfterMove = Rect(STARTING_BOUNDS)
rectAfterMove.right += 10
rectAfterMove.top += 10
+ verify(mockDesktopWindowDecoration).showResizeVeil(rectAfterMove)
verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
token == taskBinder &&
@@ -237,7 +237,7 @@
val rectAfterEnd = Rect(rectAfterMove)
rectAfterEnd.right += 10
rectAfterEnd.top += 10
- verify(mockDesktopWindowDecoration, times(2)).updateResizeVeil(any())
+ verify(mockDesktopWindowDecoration).updateResizeVeil(any())
verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
token == taskBinder &&
@@ -253,7 +253,6 @@
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
- verify(mockDesktopWindowDecoration).showResizeVeil(STARTING_BOUNDS)
taskPositioner.onDragPositioningMove(
STARTING_BOUNDS.left.toFloat(),
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index 2f28363..77800a3 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -31,6 +31,12 @@
],
}
+cc_aconfig_library {
+ name: "backup_flags_cc_lib",
+ host_supported: true,
+ aconfig_declarations: "backup_flags",
+}
+
cc_defaults {
name: "libandroidfw_defaults",
cpp_std: "gnu++2b",
@@ -115,7 +121,10 @@
"libutils",
"libz",
],
- static_libs: ["libziparchive_for_incfs"],
+ static_libs: [
+ "libziparchive_for_incfs",
+ "backup_flags_cc_lib",
+ ],
static: {
enabled: false,
},
diff --git a/libs/androidfw/BackupHelpers.cpp b/libs/androidfw/BackupHelpers.cpp
index 1a6a952..a1e7c2f 100644
--- a/libs/androidfw/BackupHelpers.cpp
+++ b/libs/androidfw/BackupHelpers.cpp
@@ -36,6 +36,9 @@
#include <utils/KeyedVector.h>
#include <utils/String8.h>
+#include <com_android_server_backup.h>
+namespace backup_flags = com::android::server::backup;
+
namespace android {
#define MAGIC0 0x70616e53 // Snap
@@ -214,7 +217,7 @@
{
LOGP("write_update_file %s (%s) : mode 0%o\n", realFilename, key.c_str(), mode);
- const int bufsize = 4*1024;
+ const int bufsize = backup_flags::enable_max_size_writes_to_pipes() ? (64*1024) : (4*1024);
int err;
int amt;
int fileSize;
@@ -550,7 +553,8 @@
}
// read/write up to this much at a time.
- const size_t BUFSIZE = 32 * 1024;
+ const size_t BUFSIZE = backup_flags::enable_max_size_writes_to_pipes() ? (64*1024) : (32*1024);
+
char* buf = (char *)calloc(1,BUFSIZE);
const size_t PAXHEADER_OFFSET = 512;
const size_t PAXHEADER_SIZE = 512;
@@ -726,7 +730,7 @@
-#define RESTORE_BUF_SIZE (8*1024)
+const size_t RESTORE_BUF_SIZE = backup_flags::enable_max_size_writes_to_pipes() ? 64*1024 : 8*1024;
RestoreHelperBase::RestoreHelperBase()
{
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index c9d5e07..d9166a1 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -21,6 +21,7 @@
#include <algorithm>
#include <cstddef>
#include <limits>
+#include <optional>
#include "android-base/logging.h"
#include "android-base/stringprintf.h"
@@ -50,7 +51,9 @@
// contiguous block of memory to store both the TypeSpec struct and
// the Type structs.
struct TypeSpecBuilder {
- explicit TypeSpecBuilder(incfs::verified_map_ptr<ResTable_typeSpec> header) : header_(header) {}
+ explicit TypeSpecBuilder(incfs::verified_map_ptr<ResTable_typeSpec> header) : header_(header) {
+ type_entries.reserve(dtohs(header_->typesCount));
+ }
void AddType(incfs::verified_map_ptr<ResTable_type> type) {
TypeSpec::TypeEntry& entry = type_entries.emplace_back();
@@ -59,6 +62,7 @@
}
TypeSpec Build() {
+ type_entries.shrink_to_fit();
return {header_, std::move(type_entries)};
}
@@ -450,7 +454,8 @@
std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
package_property_t property_flags) {
ATRACE_NAME("LoadedPackage::Load");
- std::unique_ptr<LoadedPackage> loaded_package(new LoadedPackage());
+ const bool optimize_name_lookups = (property_flags & PROPERTY_OPTIMIZE_NAME_LOOKUPS) != 0;
+ std::unique_ptr<LoadedPackage> loaded_package(new LoadedPackage(optimize_name_lookups));
// typeIdOffset was added at some point, but we still must recognize apps built before this
// was added.
@@ -499,7 +504,7 @@
// A map of TypeSpec builders, each associated with an type index.
// We use these to accumulate the set of Types available for a TypeSpec, and later build a single,
// contiguous block of memory that holds all the Types together with the TypeSpec.
- std::unordered_map<int, std::unique_ptr<TypeSpecBuilder>> type_builder_map;
+ std::unordered_map<int, std::optional<TypeSpecBuilder>> type_builder_map;
ChunkIterator iter(chunk.data_ptr(), chunk.data_size());
while (iter.HasNext()) {
@@ -567,14 +572,14 @@
return {};
}
- if (entry_count * sizeof(uint32_t) > chunk.data_size()) {
+ if (entry_count * sizeof(uint32_t) > child_chunk.data_size()) {
LOG(ERROR) << "RES_TABLE_TYPE_SPEC_TYPE too small to hold entries.";
return {};
}
- std::unique_ptr<TypeSpecBuilder>& builder_ptr = type_builder_map[type_spec->id];
- if (builder_ptr == nullptr) {
- builder_ptr = util::make_unique<TypeSpecBuilder>(type_spec.verified());
+ auto& maybe_type_builder = type_builder_map[type_spec->id];
+ if (!maybe_type_builder) {
+ maybe_type_builder.emplace(type_spec.verified());
loaded_package->resource_ids_.set(type_spec->id, entry_count);
} else {
LOG(WARNING) << StringPrintf("RES_TABLE_TYPE_SPEC_TYPE already defined for ID %02x",
@@ -594,9 +599,9 @@
}
// Type chunks must be preceded by their TypeSpec chunks.
- std::unique_ptr<TypeSpecBuilder>& builder_ptr = type_builder_map[type->id];
- if (builder_ptr != nullptr) {
- builder_ptr->AddType(type.verified());
+ auto& maybe_type_builder = type_builder_map[type->id];
+ if (maybe_type_builder) {
+ maybe_type_builder->AddType(type.verified());
} else {
LOG(ERROR) << StringPrintf(
"RES_TABLE_TYPE_TYPE with ID %02x found without preceding RES_TABLE_TYPE_SPEC_TYPE.",
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 4c992be..2c99f1a 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -447,15 +447,19 @@
// --------------------------------------------------------------------
// --------------------------------------------------------------------
-ResStringPool::ResStringPool()
- : mError(NO_INIT), mOwnedData(NULL), mHeader(NULL), mCache(NULL)
-{
+ResStringPool::ResStringPool() : mError(NO_INIT), mOwnedData(NULL), mHeader(NULL), mCache(NULL) {
}
-ResStringPool::ResStringPool(const void* data, size_t size, bool copyData)
- : mError(NO_INIT), mOwnedData(NULL), mHeader(NULL), mCache(NULL)
-{
- setTo(data, size, copyData);
+ResStringPool::ResStringPool(bool optimize_name_lookups) : ResStringPool() {
+ if (optimize_name_lookups) {
+ mIndexLookupCache.emplace();
+ }
+}
+
+ResStringPool::ResStringPool(const void* data, size_t size, bool copyData,
+ bool optimize_name_lookups)
+ : ResStringPool(optimize_name_lookups) {
+ setTo(data, size, copyData);
}
ResStringPool::~ResStringPool()
@@ -683,6 +687,14 @@
mStylePoolSize = 0;
}
+ if (mIndexLookupCache) {
+ if ((mHeader->flags & ResStringPool_header::UTF8_FLAG) != 0) {
+ mIndexLookupCache->first.reserve(mHeader->stringCount);
+ } else {
+ mIndexLookupCache->second.reserve(mHeader->stringCount);
+ }
+ }
+
return (mError=NO_ERROR);
}
@@ -708,6 +720,10 @@
free(mOwnedData);
mOwnedData = NULL;
}
+ if (mIndexLookupCache) {
+ mIndexLookupCache->first.clear();
+ mIndexLookupCache->second.clear();
+ }
}
/**
@@ -824,11 +840,11 @@
// encLen must be less than 0x7FFF due to encoding.
if ((uint32_t)(u8str+*u8len-strings) < mStringPoolSize) {
- AutoMutex lock(mDecodeLock);
+ AutoMutex lock(mCachesLock);
- if (mCache != NULL && mCache[idx] != NULL) {
- return StringPiece16(mCache[idx], *u16len);
- }
+ if (mCache != NULL && mCache[idx] != NULL) {
+ return StringPiece16(mCache[idx], *u16len);
+ }
// Retrieve the actual length of the utf8 string if the
// encoded length was truncated
@@ -1093,12 +1109,24 @@
// block, start searching at the back.
String8 str8(str, strLen);
const size_t str8Len = str8.size();
+ std::optional<AutoMutex> cacheLock;
+ if (mIndexLookupCache) {
+ cacheLock.emplace(mCachesLock);
+ if (auto it = mIndexLookupCache->first.find(std::string_view(str8));
+ it != mIndexLookupCache->first.end()) {
+ return it->second;
+ }
+ }
+
for (int i=mHeader->stringCount-1; i>=0; i--) {
const base::expected<StringPiece, NullOrIOError> s = string8At(i);
if (UNLIKELY(IsIOError(s))) {
return base::unexpected(s.error());
}
if (s.has_value()) {
+ if (mIndexLookupCache) {
+ mIndexLookupCache->first.insert({*s, i});
+ }
if (kDebugStringPoolNoisy) {
ALOGI("Looking at %s, i=%d\n", s->data(), i);
}
@@ -1151,20 +1179,32 @@
// most often this happens because we want to get IDs for style
// span tags; since those always appear at the end of the string
// block, start searching at the back.
+ std::optional<AutoMutex> cacheLock;
+ if (mIndexLookupCache) {
+ cacheLock.emplace(mCachesLock);
+ if (auto it = mIndexLookupCache->second.find({str, strLen});
+ it != mIndexLookupCache->second.end()) {
+ return it->second;
+ }
+ }
for (int i=mHeader->stringCount-1; i>=0; i--) {
const base::expected<StringPiece16, NullOrIOError> s = stringAt(i);
if (UNLIKELY(IsIOError(s))) {
return base::unexpected(s.error());
}
if (kDebugStringPoolNoisy) {
- ALOGI("Looking at %s, i=%d\n", String8(s->data(), s->size()).c_str(), i);
+ ALOGI("Looking16 at %s, i=%d\n", String8(s->data(), s->size()).c_str(), i);
}
- if (s.has_value() && strLen == s->size() &&
- strzcmp16(s->data(), s->size(), str, strLen) == 0) {
+ if (s.has_value()) {
+ if (mIndexLookupCache) {
+ mIndexLookupCache->second.insert({*s, i});
+ }
+ if (strLen == s->size() && strzcmp16(s->data(), s->size(), str, strLen) == 0) {
if (kDebugStringPoolNoisy) {
- ALOGI("MATCH!");
+ ALOGI("MATCH16!");
}
return i;
+ }
}
}
}
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
index 6068912..d9f7c2a 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -71,7 +71,7 @@
// Rewrites a compile-time overlay resource id to the runtime resource id of corresponding target
// resource.
- virtual status_t lookupResourceIdNoRewrite(uint32_t* resId) const;
+ status_t lookupResourceIdNoRewrite(uint32_t* resId) const;
const Idmap_data_header* data_header_;
const Idmap_overlay_entry* entries_;
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index 3a72871..413b278 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -99,6 +99,9 @@
// The apk assets only contain the overlayable declarations information.
PROPERTY_ONLY_OVERLAYABLES = 1U << 5U,
+
+ // Optimize the resource lookups by name via an in-memory lookup table.
+ PROPERTY_OPTIMIZE_NAME_LOOKUPS = 1U << 6U,
};
struct OverlayableInfo {
@@ -285,7 +288,9 @@
private:
DISALLOW_COPY_AND_ASSIGN(LoadedPackage);
- LoadedPackage() = default;
+ explicit LoadedPackage(bool optimize_name_lookups = false)
+ : type_string_pool_(optimize_name_lookups), key_string_pool_(optimize_name_lookups) {
+ }
ResStringPool type_string_pool_;
ResStringPool key_string_pool_;
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index c0514fd..3d1403d 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -22,27 +22,28 @@
#include <android-base/expected.h>
#include <android-base/unique_fd.h>
-
+#include <android/configuration.h>
#include <androidfw/Asset.h>
#include <androidfw/Errors.h>
#include <androidfw/LocaleData.h>
#include <androidfw/StringPiece.h>
#include <utils/ByteOrder.h>
#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
#include <utils/String16.h>
#include <utils/Vector.h>
-#include <utils/KeyedVector.h>
-
#include <utils/threads.h>
#include <stdint.h>
#include <sys/types.h>
-#include <android/configuration.h>
-
#include <array>
#include <map>
#include <memory>
+#include <optional>
+#include <string_view>
+#include <unordered_map>
+#include <utility>
namespace android {
@@ -512,23 +513,24 @@
class ResStringPool
{
public:
- ResStringPool();
- ResStringPool(const void* data, size_t size, bool copyData=false);
- virtual ~ResStringPool();
+ ResStringPool();
+ explicit ResStringPool(bool optimize_name_lookups);
+ ResStringPool(const void* data, size_t size, bool copyData = false,
+ bool optimize_name_lookups = false);
+ virtual ~ResStringPool();
- void setToEmpty();
- status_t setTo(incfs::map_ptr<void> data, size_t size, bool copyData=false);
+ void setToEmpty();
+ status_t setTo(incfs::map_ptr<void> data, size_t size, bool copyData = false);
- status_t getError() const;
+ status_t getError() const;
- void uninit();
+ void uninit();
- // Return string entry as UTF16; if the pool is UTF8, the string will
- // be converted before returning.
- inline base::expected<StringPiece16, NullOrIOError> stringAt(
- const ResStringPool_ref& ref) const {
- return stringAt(ref.index);
- }
+ // Return string entry as UTF16; if the pool is UTF8, the string will
+ // be converted before returning.
+ inline base::expected<StringPiece16, NullOrIOError> stringAt(const ResStringPool_ref& ref) const {
+ return stringAt(ref.index);
+ }
virtual base::expected<StringPiece16, NullOrIOError> stringAt(size_t idx) const;
// Note: returns null if the string pool is not UTF8.
@@ -557,7 +559,7 @@
void* mOwnedData;
incfs::verified_map_ptr<ResStringPool_header> mHeader;
size_t mSize;
- mutable Mutex mDecodeLock;
+ mutable Mutex mCachesLock;
incfs::map_ptr<uint32_t> mEntries;
incfs::map_ptr<uint32_t> mEntryStyles;
incfs::map_ptr<void> mStrings;
@@ -566,6 +568,10 @@
incfs::map_ptr<uint32_t> mStyles;
uint32_t mStylePoolSize; // number of uint32_t
+ mutable std::optional<std::pair<std::unordered_map<std::string_view, int>,
+ std::unordered_map<std::u16string_view, int>>>
+ mIndexLookupCache;
+
base::expected<StringPiece, NullOrIOError> stringDecodeAt(
size_t idx, incfs::map_ptr<uint8_t> str, size_t encLen) const;
};
@@ -1401,8 +1407,8 @@
// Must be 0.
uint8_t res0;
- // Must be 0.
- uint16_t res1;
+ // Used to be reserved, if >0 specifies the number of ResTable_type entries for this spec.
+ uint16_t typesCount;
// Number of uint32_t entry configuration masks that follow.
uint32_t entryCount;
diff --git a/libs/hostgraphics/gui/Surface.h b/libs/hostgraphics/gui/Surface.h
index de1ba00..2573931 100644
--- a/libs/hostgraphics/gui/Surface.h
+++ b/libs/hostgraphics/gui/Surface.h
@@ -50,6 +50,8 @@
virtual int unlockAndPost() { return 0; }
virtual int query(int what, int* value) const { return 0; }
+ virtual void destroy() {}
+
protected:
virtual ~Surface() {}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index eebf8aa..b40b73c 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -716,7 +716,6 @@
],
shared_libs: [
"libmemunreachable",
- "server_configurable_flags",
],
srcs: [
"tests/unit/main.cpp",
diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h
index d4af087..a5a841e 100644
--- a/libs/hwui/DeviceInfo.h
+++ b/libs/hwui/DeviceInfo.h
@@ -23,6 +23,7 @@
#include <mutex>
+#include "Properties.h"
#include "utils/Macros.h"
namespace android {
@@ -60,7 +61,13 @@
static void setWideColorDataspace(ADataSpace dataspace);
static void setSupportFp16ForHdr(bool supportFp16ForHdr);
- static bool isSupportFp16ForHdr() { return get()->mSupportFp16ForHdr; };
+ static bool isSupportFp16ForHdr() {
+ if (!Properties::hdr10bitPlus) {
+ return false;
+ }
+
+ return get()->mSupportFp16ForHdr;
+ };
static void setSupportMixedColorSpaces(bool supportMixedColorSpaces);
static bool isSupportMixedColorSpaces() { return get()->mSupportMixedColorSpaces; };
diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h
index 00d049c..6ebfc63 100644
--- a/libs/hwui/FeatureFlags.h
+++ b/libs/hwui/FeatureFlags.h
@@ -41,6 +41,14 @@
#endif // __ANDROID__
}
+inline bool inter_character_justification() {
+#ifdef __ANDROID__
+ return com_android_text_flags_inter_character_justification();
+#else
+ return true;
+#endif // __ANDROID__
+}
+
} // namespace text_feature
} // namespace android
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp
index 16de21d..71f7926 100644
--- a/libs/hwui/HardwareBitmapUploader.cpp
+++ b/libs/hwui/HardwareBitmapUploader.cpp
@@ -379,7 +379,7 @@
case kAlpha_8_SkColorType:
formatInfo.isSupported = HardwareBitmapUploader::hasAlpha8Support();
formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R8_UNORM;
- formatInfo.format = GL_R8;
+ formatInfo.format = GL_RED;
formatInfo.type = GL_UNSIGNED_BYTE;
formatInfo.vkFormat = VK_FORMAT_R8_UNORM;
break;
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 6c3172a..755332ff 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -38,6 +38,9 @@
constexpr bool clip_surfaceviews() {
return false;
}
+constexpr bool hdr_10bit_plus() {
+ return false;
+}
} // namespace hwui_flags
#endif
@@ -56,6 +59,7 @@
bool Properties::debugLayersUpdates = false;
bool Properties::debugOverdraw = false;
+bool Properties::debugTraceGpuResourceCategories = false;
bool Properties::showDirtyRegions = false;
bool Properties::skipEmptyFrames = true;
bool Properties::useBufferAge = true;
@@ -104,6 +108,7 @@
float Properties::maxHdrHeadroomOn8bit = 5.f; // TODO: Refine this number
bool Properties::clipSurfaceViews = false;
+bool Properties::hdr10bitPlus = false;
StretchEffectBehavior Properties::stretchEffectBehavior = StretchEffectBehavior::ShaderHWUI;
@@ -151,10 +156,12 @@
skpCaptureEnabled = debuggingEnabled && base::GetBoolProperty(PROPERTY_CAPTURE_SKP_ENABLED, false);
- SkAndroidFrameworkTraceUtil::setEnableTracing(
- base::GetBoolProperty(PROPERTY_SKIA_TRACING_ENABLED, false));
+ bool skiaBroadTracing = base::GetBoolProperty(PROPERTY_SKIA_TRACING_ENABLED, false);
+ SkAndroidFrameworkTraceUtil::setEnableTracing(skiaBroadTracing);
SkAndroidFrameworkTraceUtil::setUsePerfettoTrackEvents(
base::GetBoolProperty(PROPERTY_SKIA_USE_PERFETTO_TRACK_EVENTS, false));
+ debugTraceGpuResourceCategories =
+ base::GetBoolProperty(PROPERTY_TRACE_GPU_RESOURCES, skiaBroadTracing);
runningInEmulator = base::GetBoolProperty(PROPERTY_IS_EMULATOR, false);
@@ -174,6 +181,7 @@
clipSurfaceViews =
base::GetBoolProperty("debug.hwui.clip_surfaceviews", hwui_flags::clip_surfaceviews());
+ hdr10bitPlus = hwui_flags::hdr_10bit_plus();
return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw);
}
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index bca57e9..ec53070 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -143,6 +143,15 @@
#define PROPERTY_CAPTURE_SKP_ENABLED "debug.hwui.capture_skp_enabled"
/**
+ * Might split Skia's GPU resource utilization into separate tracing tracks (slow).
+ *
+ * Aggregate total and purgeable numbers will still be reported under a "misc" track when this is
+ * disabled, they just won't be split into distinct categories. Results may vary depending on GPU
+ * backend/API, and the category mappings defined in ATraceMemoryDump's hardcoded sResourceMap.
+ */
+#define PROPERTY_TRACE_GPU_RESOURCES "debug.hwui.trace_gpu_resources"
+
+/**
* Allows broad recording of Skia drawing commands.
*
* If disabled, a very minimal set of trace events *may* be recorded.
@@ -254,6 +263,7 @@
static bool debugLayersUpdates;
static bool debugOverdraw;
+ static bool debugTraceGpuResourceCategories;
static bool showDirtyRegions;
// TODO: Remove after stabilization period
static bool skipEmptyFrames;
@@ -326,6 +336,7 @@
static float maxHdrHeadroomOn8bit;
static bool clipSurfaceViews;
+ static bool hdr10bitPlus;
static StretchEffectBehavior getStretchEffectBehavior() {
return stretchEffectBehavior;
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index c156c46..72ddecc 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -22,6 +22,13 @@
}
flag {
+ name: "high_contrast_text_small_text_rect"
+ namespace: "accessibility"
+ description: "Draw a solid rectangle background behind text instead of a stroke outline"
+ bug: "186567103"
+}
+
+flag {
name: "hdr_10bit_plus"
namespace: "core_graphics"
description: "Use 10101010 and FP16 formats for HDR-UI when available"
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index 80b6c03..e9f4b81c 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -18,6 +18,7 @@
#include <SkFontMetrics.h>
#include <SkRRect.h>
+#include <minikin/MinikinRect.h>
#include "FeatureFlags.h"
#include "MinikinUtils.h"
@@ -107,7 +108,13 @@
// care of all alignment.
paint.setTextAlign(Paint::kLeft_Align);
- DrawTextFunctor f(layout, this, paint, x, y, layout.getAdvance());
+ minikin::MinikinRect bounds;
+ // We only need the bounds to draw a rectangular background in high contrast mode. Let's save
+ // the cycles otherwise.
+ if (flags::high_contrast_text_small_text_rect() && isHighContrastText()) {
+ MinikinUtils::getBounds(&paint, bidiFlags, typeface, text, textSize, &bounds);
+ }
+ DrawTextFunctor f(layout, this, paint, x, y, layout.getAdvance(), bounds);
MinikinUtils::forFontRun(layout, &paint, f);
if (text_feature::fix_double_underline()) {
diff --git a/libs/hwui/hwui/DrawTextFunctor.h b/libs/hwui/hwui/DrawTextFunctor.h
index 8f99990..ba65439 100644
--- a/libs/hwui/hwui/DrawTextFunctor.h
+++ b/libs/hwui/hwui/DrawTextFunctor.h
@@ -33,6 +33,8 @@
namespace android {
+inline constexpr int kHighContrastTextBorderWidth = 4;
+
static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkScalar thickness,
const Paint& paint, Canvas* canvas) {
const SkScalar strokeWidth = fmax(thickness, 1.0f);
@@ -45,15 +47,26 @@
paint->setShader(nullptr);
paint->setColorFilter(nullptr);
paint->setLooper(nullptr);
- paint->setStrokeWidth(4 + 0.04 * paint->getSkFont().getSize());
+ paint->setStrokeWidth(kHighContrastTextBorderWidth + 0.04 * paint->getSkFont().getSize());
paint->setStrokeJoin(SkPaint::kRound_Join);
paint->setLooper(nullptr);
}
class DrawTextFunctor {
public:
+ /**
+ * Creates a Functor to draw the given text layout.
+ *
+ * @param layout
+ * @param canvas
+ * @param paint
+ * @param x
+ * @param y
+ * @param totalAdvance
+ * @param bounds bounds of the text. Only required if high contrast text mode is enabled.
+ */
DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const Paint& paint, float x,
- float y, float totalAdvance)
+ float y, float totalAdvance, const minikin::MinikinRect& bounds)
: layout(layout)
, canvas(canvas)
, paint(paint)
@@ -61,7 +74,8 @@
, y(y)
, totalAdvance(totalAdvance)
, underlinePosition(0)
- , underlineThickness(0) {}
+ , underlineThickness(0)
+ , bounds(bounds) {}
void operator()(size_t start, size_t end) {
auto glyphFunc = [&](uint16_t* text, float* positions) {
@@ -91,7 +105,16 @@
Paint outlinePaint(paint);
simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint);
outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style);
- canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance);
+ if (flags::high_contrast_text_small_text_rect()) {
+ auto bgBounds(bounds);
+ auto padding = kHighContrastTextBorderWidth + 0.1f * paint.getSkFont().getSize();
+ bgBounds.offset(x, y);
+ canvas->drawRect(bgBounds.mLeft - padding, bgBounds.mTop - padding,
+ bgBounds.mRight + padding, bgBounds.mBottom + padding,
+ outlinePaint);
+ } else {
+ canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance);
+ }
// inner
gDrawTextBlobMode = DrawTextBlobMode::HctInner;
@@ -146,6 +169,7 @@
float totalAdvance;
float underlinePosition;
float underlineThickness;
+ const minikin::MinikinRect& bounds;
};
} // namespace android
diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp
index f4ee36ec..bbb1420 100644
--- a/libs/hwui/hwui/MinikinSkia.cpp
+++ b/libs/hwui/hwui/MinikinSkia.cpp
@@ -131,11 +131,6 @@
const std::vector<minikin::FontVariation>& variations) const {
SkFontArguments args;
- int ttcIndex;
- std::unique_ptr<SkStreamAsset> stream(mTypeface->openStream(&ttcIndex));
- LOG_ALWAYS_FATAL_IF(stream == nullptr, "openStream failed");
-
- args.setCollectionIndex(ttcIndex);
std::vector<SkFontArguments::VariationPosition::Coordinate> skVariation;
skVariation.resize(variations.size());
for (size_t i = 0; i < variations.size(); i++) {
@@ -143,11 +138,10 @@
skVariation[i].value = SkFloatToScalar(variations[i].value);
}
args.setVariationDesignPosition({skVariation.data(), static_cast<int>(skVariation.size())});
- sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr();
- sk_sp<SkTypeface> face(fm->makeFromStream(std::move(stream), args));
+ sk_sp<SkTypeface> face = mTypeface->makeClone(args);
return std::make_shared<MinikinFontSkia>(std::move(face), mSourceId, mFontData, mFontSize,
- mFilePath, ttcIndex, variations);
+ mFilePath, mTtcIndex, variations);
}
// hinting<<16 | edging<<8 | bools:5bits
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index 7552b56d..5613369 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -72,10 +72,13 @@
const minikin::Range contextRange(contextStart, contextStart + contextCount);
const minikin::StartHyphenEdit startHyphen = paint->getStartHyphenEdit();
const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit();
+ const minikin::RunFlag minikinRunFlag = text_feature::inter_character_justification()
+ ? paint->getRunFlag()
+ : minikin::RunFlag::NONE;
if (mt == nullptr) {
return minikin::Layout(textBuf.substr(contextRange), range - contextStart, bidiFlags,
- minikinPaint, startHyphen, endHyphen);
+ minikinPaint, startHyphen, endHyphen, minikinRunFlag);
} else {
return mt->buildLayout(textBuf, range, contextRange, minikinPaint, startHyphen, endHyphen);
}
@@ -96,15 +99,18 @@
float MinikinUtils::measureText(const Paint* paint, minikin::Bidi bidiFlags,
const Typeface* typeface, const uint16_t* buf, size_t start,
size_t count, size_t bufSize, float* advances,
- minikin::MinikinRect* bounds) {
+ minikin::MinikinRect* bounds, uint32_t* clusterCount) {
minikin::MinikinPaint minikinPaint = prepareMinikinPaint(paint, typeface);
const minikin::U16StringPiece textBuf(buf, bufSize);
const minikin::Range range(start, start + count);
const minikin::StartHyphenEdit startHyphen = paint->getStartHyphenEdit();
const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit();
+ const minikin::RunFlag minikinRunFlag = text_feature::inter_character_justification()
+ ? paint->getRunFlag()
+ : minikin::RunFlag::NONE;
return minikin::Layout::measureText(textBuf, range, bidiFlags, minikinPaint, startHyphen,
- endHyphen, advances, bounds);
+ endHyphen, advances, bounds, clusterCount, minikinRunFlag);
}
minikin::MinikinExtent MinikinUtils::getFontExtent(const Paint* paint, minikin::Bidi bidiFlags,
diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h
index 61bc881..f8574ee 100644
--- a/libs/hwui/hwui/MinikinUtils.h
+++ b/libs/hwui/hwui/MinikinUtils.h
@@ -53,7 +53,7 @@
static float measureText(const Paint* paint, minikin::Bidi bidiFlags, const Typeface* typeface,
const uint16_t* buf, size_t start, size_t count, size_t bufSize,
- float* advances, minikin::MinikinRect* bounds);
+ float* advances, minikin::MinikinRect* bounds, uint32_t* clusterCount);
static minikin::MinikinExtent getFontExtent(const Paint* paint, minikin::Bidi bidiFlags,
const Typeface* typeface, const uint16_t* buf,
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index ef4dce5..708f96e5 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -25,6 +25,7 @@
#include <minikin/FontFamily.h>
#include <minikin/FontFeature.h>
#include <minikin/Hyphenator.h>
+#include <minikin/Layout.h>
#include <string>
@@ -144,6 +145,9 @@
bool isDevKern() const { return mDevKern; }
void setDevKern(bool d) { mDevKern = d; }
+ minikin::RunFlag getRunFlag() const { return mRunFlag; }
+ void setRunFlag(minikin::RunFlag runFlag) { mRunFlag = runFlag; }
+
// Deprecated -- bitmapshaders will be taking this flag explicitly
bool isFilterBitmap() const { return mFilterBitmap; }
void setFilterBitmap(bool filter) { mFilterBitmap = filter; }
@@ -188,6 +192,7 @@
bool mStrikeThru = false;
bool mUnderline = false;
bool mDevKern = false;
+ minikin::RunFlag mRunFlag = minikin::RunFlag::NONE;
};
} // namespace android
diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp
index aac928f..c32ea01 100644
--- a/libs/hwui/hwui/PaintImpl.cpp
+++ b/libs/hwui/hwui/PaintImpl.cpp
@@ -47,8 +47,8 @@
, mFilterBitmap(paint.mFilterBitmap)
, mStrikeThru(paint.mStrikeThru)
, mUnderline(paint.mUnderline)
- , mDevKern(paint.mDevKern) {}
-
+ , mDevKern(paint.mDevKern)
+ , mRunFlag(paint.mRunFlag) {}
Paint::~Paint() {}
@@ -68,21 +68,19 @@
mStrikeThru = other.mStrikeThru;
mUnderline = other.mUnderline;
mDevKern = other.mDevKern;
+ mRunFlag = other.mRunFlag;
return *this;
}
bool operator==(const Paint& a, const Paint& b) {
- return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) &&
- a.mFont == b.mFont &&
- a.mLooper == b.mLooper &&
- a.mLetterSpacing == b.mLetterSpacing && a.mWordSpacing == b.mWordSpacing &&
- a.mFontFeatureSettings == b.mFontFeatureSettings &&
+ return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) && a.mFont == b.mFont &&
+ a.mLooper == b.mLooper && a.mLetterSpacing == b.mLetterSpacing &&
+ a.mWordSpacing == b.mWordSpacing && a.mFontFeatureSettings == b.mFontFeatureSettings &&
a.mMinikinLocaleListId == b.mMinikinLocaleListId &&
a.mFamilyVariant == b.mFamilyVariant && a.mHyphenEdit == b.mHyphenEdit &&
a.mTypeface == b.mTypeface && a.mAlign == b.mAlign &&
- a.mFilterBitmap == b.mFilterBitmap &&
- a.mStrikeThru == b.mStrikeThru && a.mUnderline == b.mUnderline &&
- a.mDevKern == b.mDevKern;
+ a.mFilterBitmap == b.mFilterBitmap && a.mStrikeThru == b.mStrikeThru &&
+ a.mUnderline == b.mUnderline && a.mDevKern == b.mDevKern && a.mRunFlag == b.mRunFlag;
}
void Paint::reset() {
@@ -96,6 +94,7 @@
mStrikeThru = false;
mUnderline = false;
mDevKern = false;
+ mRunFlag = minikin::RunFlag::NONE;
}
void Paint::setLooper(sk_sp<BlurDrawLooper> looper) {
@@ -133,6 +132,8 @@
// flags related to minikin::Paint
static const uint32_t sUnderlineFlag = 0x08;
static const uint32_t sStrikeThruFlag = 0x10;
+static const uint32_t sTextRunLeftEdge = 0x2000;
+static const uint32_t sTextRunRightEdge = 0x4000;
// flags no longer supported on native side (but mirrored for compatibility)
static const uint32_t sDevKernFlag = 0x100;
@@ -186,6 +187,12 @@
flags |= -(int)mUnderline & sUnderlineFlag;
flags |= -(int)mDevKern & sDevKernFlag;
flags |= -(int)mFilterBitmap & sFilterBitmapFlag;
+ if (mRunFlag & minikin::RunFlag::LEFT_EDGE) {
+ flags |= sTextRunLeftEdge;
+ }
+ if (mRunFlag & minikin::RunFlag::RIGHT_EDGE) {
+ flags |= sTextRunRightEdge;
+ }
return flags;
}
@@ -196,6 +203,15 @@
mUnderline = (flags & sUnderlineFlag) != 0;
mDevKern = (flags & sDevKernFlag) != 0;
mFilterBitmap = (flags & sFilterBitmapFlag) != 0;
+
+ std::underlying_type<minikin::RunFlag>::type rawFlag = minikin::RunFlag::NONE;
+ if (flags & sTextRunLeftEdge) {
+ rawFlag |= minikin::RunFlag::LEFT_EDGE;
+ }
+ if (flags & sTextRunRightEdge) {
+ rawFlag |= minikin::RunFlag::RIGHT_EDGE;
+ }
+ mRunFlag = static_cast<minikin::RunFlag>(rawFlag);
}
} // namespace android
diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp
index 7cc4866..8315c4c 100644
--- a/libs/hwui/jni/Graphics.cpp
+++ b/libs/hwui/jni/Graphics.cpp
@@ -247,6 +247,9 @@
static jfieldID gFontMetricsInt_bottom;
static jfieldID gFontMetricsInt_leading;
+static jclass gRunInfo_class;
+static jfieldID gRunInfo_clusterCount;
+
///////////////////////////////////////////////////////////////////////////////
void GraphicsJNI::get_jrect(JNIEnv* env, jobject obj, int* L, int* T, int* R, int* B)
@@ -511,6 +514,10 @@
return descent - ascent + leading;
}
+void GraphicsJNI::set_cluster_count_to_run_info(JNIEnv* env, jobject runInfo, jint clusterCount) {
+ env->SetIntField(runInfo, gRunInfo_clusterCount, clusterCount);
+}
+
///////////////////////////////////////////////////////////////////////////////////////////
jobject GraphicsJNI::createBitmapRegionDecoder(JNIEnv* env, BitmapRegionDecoderWrapper* bitmap) {
@@ -834,5 +841,10 @@
gFontMetricsInt_bottom = GetFieldIDOrDie(env, gFontMetricsInt_class, "bottom", "I");
gFontMetricsInt_leading = GetFieldIDOrDie(env, gFontMetricsInt_class, "leading", "I");
+ gRunInfo_class = FindClassOrDie(env, "android/graphics/Paint$RunInfo");
+ gRunInfo_class = MakeGlobalRefOrDie(env, gRunInfo_class);
+
+ gRunInfo_clusterCount = GetFieldIDOrDie(env, gRunInfo_class, "mClusterCount", "I");
+
return 0;
}
diff --git a/libs/hwui/jni/GraphicsJNI.h b/libs/hwui/jni/GraphicsJNI.h
index b9fff36..b0a1074 100644
--- a/libs/hwui/jni/GraphicsJNI.h
+++ b/libs/hwui/jni/GraphicsJNI.h
@@ -77,6 +77,8 @@
static SkRect* jrect_to_rect(JNIEnv*, jobject jrect, SkRect*);
static void rect_to_jrectf(const SkRect&, JNIEnv*, jobject jrectf);
+ static void set_cluster_count_to_run_info(JNIEnv* env, jobject runInfo, jint clusterCount);
+
static void set_jpoint(JNIEnv*, jobject jrect, int x, int y);
static SkIPoint* jpoint_to_ipoint(JNIEnv*, jobject jpoint, SkIPoint* point);
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index d84b73d..286f06a 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -114,7 +114,7 @@
std::unique_ptr<float[]> advancesArray(new float[count]);
MinikinUtils::measureText(&paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text, 0,
- count, count, advancesArray.get(), nullptr);
+ count, count, advancesArray.get(), nullptr, nullptr);
for (int i = 0; i < count; i++) {
// traverse in the given direction
@@ -206,7 +206,7 @@
}
const float advance = MinikinUtils::measureText(
paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text, start, count,
- contextCount, advancesArray.get(), nullptr);
+ contextCount, advancesArray.get(), nullptr, nullptr);
if (advances) {
env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray.get());
}
@@ -244,7 +244,7 @@
minikin::Bidi bidiFlags = dir == 1 ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
std::unique_ptr<float[]> advancesArray(new float[count]);
MinikinUtils::measureText(paint, bidiFlags, typeface, text, start, count, start + count,
- advancesArray.get(), nullptr);
+ advancesArray.get(), nullptr, nullptr);
size_t result = minikin::GraphemeBreak::getTextRunCursor(advancesArray.get(), text,
start, count, offset, moveOpt);
return static_cast<jint>(result);
@@ -508,7 +508,7 @@
static jfloat doRunAdvance(JNIEnv* env, const Paint* paint, const Typeface* typeface,
const jchar buf[], jint start, jint count, jint bufSize,
jboolean isRtl, jint offset, jfloatArray advances,
- jint advancesIndex, SkRect* drawBounds) {
+ jint advancesIndex, SkRect* drawBounds, uint32_t* clusterCount) {
if (advances) {
size_t advancesLength = env->GetArrayLength(advances);
if ((size_t)(count + advancesIndex) > advancesLength) {
@@ -519,9 +519,9 @@
minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
minikin::MinikinRect bounds;
if (offset == start + count && advances == nullptr) {
- float result =
- MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count,
- bufSize, nullptr, drawBounds ? &bounds : nullptr);
+ float result = MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count,
+ bufSize, nullptr,
+ drawBounds ? &bounds : nullptr, clusterCount);
if (drawBounds) {
copyMinikinRectToSkRect(bounds, drawBounds);
}
@@ -529,7 +529,8 @@
}
std::unique_ptr<float[]> advancesArray(new float[count]);
MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, bufSize,
- advancesArray.get(), drawBounds ? &bounds : nullptr);
+ advancesArray.get(), drawBounds ? &bounds : nullptr,
+ clusterCount);
if (drawBounds) {
copyMinikinRectToSkRect(bounds, drawBounds);
@@ -549,7 +550,7 @@
ScopedCharArrayRO textArray(env, text);
jfloat result = doRunAdvance(env, paint, typeface, textArray.get() + contextStart,
start - contextStart, end - start, contextEnd - contextStart,
- isRtl, offset - contextStart, nullptr, 0, nullptr);
+ isRtl, offset - contextStart, nullptr, 0, nullptr, nullptr);
return result;
}
@@ -558,18 +559,22 @@
jint contextStart, jint contextEnd,
jboolean isRtl, jint offset,
jfloatArray advances, jint advancesIndex,
- jobject drawBounds) {
+ jobject drawBounds, jobject runInfo) {
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
const Typeface* typeface = paint->getAndroidTypeface();
ScopedCharArrayRO textArray(env, text);
SkRect skDrawBounds;
+ uint32_t clusterCount = 0;
jfloat result = doRunAdvance(env, paint, typeface, textArray.get() + contextStart,
start - contextStart, end - start, contextEnd - contextStart,
isRtl, offset - contextStart, advances, advancesIndex,
- drawBounds ? &skDrawBounds : nullptr);
+ drawBounds ? &skDrawBounds : nullptr, &clusterCount);
if (drawBounds != nullptr) {
GraphicsJNI::rect_to_jrectf(skDrawBounds, env, drawBounds);
}
+ if (runInfo) {
+ GraphicsJNI::set_cluster_count_to_run_info(env, runInfo, clusterCount);
+ }
return result;
}
@@ -578,7 +583,7 @@
minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
std::unique_ptr<float[]> advancesArray(new float[count]);
MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, bufSize,
- advancesArray.get(), nullptr);
+ advancesArray.get(), nullptr, nullptr);
return minikin::getOffsetForAdvance(advancesArray.get(), buf, start, count, advance);
}
@@ -1145,7 +1150,8 @@
(void*)PaintGlue::getCharArrayBounds},
{"nHasGlyph", "(JILjava/lang/String;)Z", (void*)PaintGlue::hasGlyph},
{"nGetRunAdvance", "(J[CIIIIZI)F", (void*)PaintGlue::getRunAdvance___CIIIIZI_F},
- {"nGetRunCharacterAdvance", "(J[CIIIIZI[FILandroid/graphics/RectF;)F",
+ {"nGetRunCharacterAdvance",
+ "(J[CIIIIZI[FILandroid/graphics/RectF;Landroid/graphics/Paint$RunInfo;)F",
(void*)PaintGlue::getRunCharacterAdvance___CIIIIZI_FI_F},
{"nGetOffsetForAdvance", "(J[CIIIIZF)I", (void*)PaintGlue::getOffsetForAdvance___CIIIIZF_I},
{"nGetFontMetricsIntForText", "(J[CIIIIZLandroid/graphics/Paint$FontMetricsInt;)V",
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index 8ba7503..d572593 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -613,6 +613,12 @@
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
const Typeface* typeface = paint->getAndroidTypeface();
ScopedCharArrayRO text(env, charArray);
+
+ // The drawText API is designed to draw entire line, so ignore the text run flag and draw the
+ // text as entire line mode.
+ const minikin::RunFlag originalRunFlag = paint->getRunFlag();
+ paint->setRunFlag(minikin::RunFlag::WHOLE_LINE);
+
// drawTextString and drawTextChars doesn't use context info
get_canvas(canvasHandle)->drawText(
text.get() + index, count, // text buffer
@@ -620,6 +626,7 @@
0, count, // context range
x, y, // draw position
static_cast<minikin::Bidi>(bidiFlags), *paint, typeface, nullptr /* measured text */);
+ paint->setRunFlag(originalRunFlag);
}
static void drawTextString(JNIEnv* env, jobject, jlong canvasHandle, jstring strObj,
@@ -629,6 +636,12 @@
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
const Typeface* typeface = paint->getAndroidTypeface();
const int count = end - start;
+
+ // The drawText API is designed to draw entire line, so ignore the text run flag and draw the
+ // text as entire line mode.
+ const minikin::RunFlag originalRunFlag = paint->getRunFlag();
+ paint->setRunFlag(minikin::RunFlag::WHOLE_LINE);
+
// drawTextString and drawTextChars doesn't use context info
get_canvas(canvasHandle)->drawText(
text.get() + start, count, // text buffer
@@ -636,6 +649,7 @@
0, count, // context range
x, y, // draw position
static_cast<minikin::Bidi>(bidiFlags), *paint, typeface, nullptr /* measured text */);
+ paint->setRunFlag(originalRunFlag);
}
static void drawTextRunChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray charArray,
@@ -681,9 +695,15 @@
jchar* jchars = env->GetCharArrayElements(text, NULL);
+ // The drawText API is designed to draw entire line, so ignore the text run flag and draw the
+ // text as entire line mode.
+ const minikin::RunFlag originalRunFlag = paint->getRunFlag();
+ paint->setRunFlag(minikin::RunFlag::WHOLE_LINE);
+
get_canvas(canvasHandle)->drawTextOnPath(jchars + index, count,
static_cast<minikin::Bidi>(bidiFlags), *path, hOffset, vOffset, *paint, typeface);
+ paint->setRunFlag(originalRunFlag);
env->ReleaseCharArrayElements(text, jchars, 0);
}
@@ -697,9 +717,15 @@
const jchar* jchars = env->GetStringChars(text, NULL);
int count = env->GetStringLength(text);
+ // The drawText API is designed to draw entire line, so ignore the text run flag and draw the
+ // text as entire line mode.
+ const minikin::RunFlag originalRunFlag = paint->getRunFlag();
+ paint->setRunFlag(minikin::RunFlag::WHOLE_LINE);
+
get_canvas(canvasHandle)->drawTextOnPath(jchars, count, static_cast<minikin::Bidi>(bidiFlags),
*path, hOffset, vOffset, *paint, typeface);
+ paint->setRunFlag(originalRunFlag);
env->ReleaseStringChars(text, jchars);
}
diff --git a/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp b/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp
index 234f42d..756b937 100644
--- a/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp
+++ b/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp
@@ -20,6 +20,8 @@
#include <cstring>
+#include "GrDirectContext.h"
+
namespace android {
namespace uirenderer {
namespace skiapipeline {
@@ -114,8 +116,16 @@
/**
* logTraces reads from mCurrentValues and logs the counters with ATRACE.
+ *
+ * gpuMemoryIsAlreadyInDump must be true if GrDirectContext::dumpMemoryStatistics(...) was called
+ * with this tracer, false otherwise. Leaving this false allows this function to quickly query total
+ * and purgable GPU memory without the caller having to spend time in
+ * GrDirectContext::dumpMemoryStatistics(...) first, which iterates over every resource in the GPU
+ * cache. This can save significant time, but buckets all GPU memory into a single "misc" track,
+ * which may be a loss of granularity depending on the GPU backend and the categories defined in
+ * sResourceMap.
*/
-void ATraceMemoryDump::logTraces() {
+void ATraceMemoryDump::logTraces(bool gpuMemoryIsAlreadyInDump, GrDirectContext* grContext) {
// Accumulate data from last dumpName
recordAndResetCountersIfNeeded("");
uint64_t hwui_all_frame_memory = 0;
@@ -126,6 +136,20 @@
ATRACE_INT64((std::string("Purgeable ") + it.first).c_str(), it.second.purgeableMemory);
}
}
+
+ if (!gpuMemoryIsAlreadyInDump && grContext) {
+ // Total GPU memory
+ int gpuResourceCount;
+ size_t gpuResourceBytes;
+ grContext->getResourceCacheUsage(&gpuResourceCount, &gpuResourceBytes);
+ hwui_all_frame_memory += (uint64_t)gpuResourceBytes;
+ ATRACE_INT64("HWUI Misc Memory", gpuResourceBytes);
+
+ // Purgable subset of GPU memory
+ size_t purgeableGpuResourceBytes = grContext->getResourceCachePurgeableBytes();
+ ATRACE_INT64("Purgeable HWUI Misc Memory", purgeableGpuResourceBytes);
+ }
+
ATRACE_INT64("HWUI All Memory", hwui_all_frame_memory);
}
diff --git a/libs/hwui/pipeline/skia/ATraceMemoryDump.h b/libs/hwui/pipeline/skia/ATraceMemoryDump.h
index 4592711..777d1a2 100644
--- a/libs/hwui/pipeline/skia/ATraceMemoryDump.h
+++ b/libs/hwui/pipeline/skia/ATraceMemoryDump.h
@@ -16,6 +16,7 @@
#pragma once
+#include <GrDirectContext.h>
#include <SkString.h>
#include <SkTraceMemoryDump.h>
@@ -50,7 +51,7 @@
void startFrame();
- void logTraces();
+ void logTraces(bool gpuMemoryIsAlreadyInDump, GrDirectContext* grContext);
private:
std::string mLastDumpName;
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index c5ffbb7..c8d5987 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -118,7 +118,7 @@
const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler,
- const HardwareBufferRenderParams& bufferParams) {
+ const HardwareBufferRenderParams& bufferParams, std::mutex& profilerLock) {
if (!isCapturingSkp() && !mHardwareBuffer) {
mEglManager.damageFrame(frame, dirty);
}
@@ -171,6 +171,7 @@
// Draw visual debugging features
if (CC_UNLIKELY(Properties::showDirtyRegions ||
ProfileType::None != Properties::getProfileType())) {
+ std::scoped_lock lock(profilerLock);
SkCanvas* profileCanvas = surface->getCanvas();
SkiaProfileRenderer profileRenderer(profileCanvas, frame.width(), frame.height());
profiler->draw(profileRenderer);
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
index 098a7465..ebe8b6e 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
@@ -42,7 +42,8 @@
const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
const std::vector<sp<RenderNode> >& renderNodes, FrameInfoVisualizer* profiler,
- const renderthread::HardwareBufferRenderParams& bufferParams) override;
+ const renderthread::HardwareBufferRenderParams& bufferParams,
+ std::mutex& profilerLock) override;
GrSurfaceOrigin getSurfaceOrigin() override { return kBottomLeft_GrSurfaceOrigin; }
bool swapBuffers(const renderthread::Frame& frame, IRenderPipeline::DrawResult& drawResult,
const SkRect& screenDirty, FrameInfo* currentFrameInfo,
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index e0f1f6e..326b6ed 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -650,9 +650,14 @@
mSurfaceColorSpace = DeviceInfo::get()->getWideColorSpace();
break;
case ColorMode::Hdr:
- mSurfaceColorType = SkColorType::kN32_SkColorType;
- mSurfaceColorSpace = SkColorSpace::MakeRGB(
- GetExtendedTransferFunction(mTargetSdrHdrRatio), SkNamedGamut::kDisplayP3);
+ if (DeviceInfo::get()->isSupportFp16ForHdr()) {
+ mSurfaceColorType = SkColorType::kRGBA_F16_SkColorType;
+ mSurfaceColorSpace = SkColorSpace::MakeSRGB();
+ } else {
+ mSurfaceColorType = SkColorType::kN32_SkColorType;
+ mSurfaceColorSpace = SkColorSpace::MakeRGB(
+ GetExtendedTransferFunction(mTargetSdrHdrRatio), SkNamedGamut::kDisplayP3);
+ }
break;
case ColorMode::Hdr10:
mSurfaceColorType = SkColorType::kRGBA_1010102_SkColorType;
@@ -669,8 +674,13 @@
void SkiaPipeline::setTargetSdrHdrRatio(float ratio) {
if (mColorMode == ColorMode::Hdr || mColorMode == ColorMode::Hdr10) {
mTargetSdrHdrRatio = ratio;
- mSurfaceColorSpace = SkColorSpace::MakeRGB(GetExtendedTransferFunction(mTargetSdrHdrRatio),
- SkNamedGamut::kDisplayP3);
+
+ if (mColorMode == ColorMode::Hdr && DeviceInfo::get()->isSupportFp16ForHdr()) {
+ mSurfaceColorSpace = SkColorSpace::MakeSRGB();
+ } else {
+ mSurfaceColorSpace = SkColorSpace::MakeRGB(
+ GetExtendedTransferFunction(mTargetSdrHdrRatio), SkNamedGamut::kDisplayP3);
+ }
} else {
mTargetSdrHdrRatio = 1.f;
}
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index d747489..fd0a8e0 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -75,7 +75,7 @@
const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler,
- const HardwareBufferRenderParams& bufferParams) {
+ const HardwareBufferRenderParams& bufferParams, std::mutex& profilerLock) {
sk_sp<SkSurface> backBuffer;
SkMatrix preTransform;
if (mHardwareBuffer) {
@@ -103,6 +103,7 @@
// Draw visual debugging features
if (CC_UNLIKELY(Properties::showDirtyRegions ||
ProfileType::None != Properties::getProfileType())) {
+ std::scoped_lock lock(profilerLock);
SkCanvas* profileCanvas = backBuffer->getCanvas();
SkAutoCanvasRestore saver(profileCanvas, true);
profileCanvas->concat(preTransform);
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
index e2ea57d..624eaa5 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
@@ -42,7 +42,8 @@
const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
const std::vector<sp<RenderNode> >& renderNodes, FrameInfoVisualizer* profiler,
- const renderthread::HardwareBufferRenderParams& bufferParams) override;
+ const renderthread::HardwareBufferRenderParams& bufferParams,
+ std::mutex& profilerLock) override;
GrSurfaceOrigin getSurfaceOrigin() override { return kTopLeft_GrSurfaceOrigin; }
bool swapBuffers(const renderthread::Frame& frame, IRenderPipeline::DrawResult& drawResult,
const SkRect& screenDirty, FrameInfo* currentFrameInfo,
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index 30d4612..ac2a936 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -124,24 +124,19 @@
// flush and submit all work to the gpu and wait for it to finish
mGrContext->flushAndSubmit(GrSyncCpu::kYes);
- switch (mode) {
- case TrimLevel::BACKGROUND:
- mGrContext->freeGpuResources();
- SkGraphics::PurgeAllCaches();
- mRenderThread.destroyRenderingContext();
- break;
- case TrimLevel::UI_HIDDEN:
- // Here we purge all the unlocked scratch resources and then toggle the resources cache
- // limits between the background and max amounts. This causes the unlocked resources
- // that have persistent data to be purged in LRU order.
- mGrContext->setResourceCacheLimit(mBackgroundResourceBytes);
- SkGraphics::SetFontCacheLimit(mBackgroundCpuFontCacheBytes);
- mGrContext->purgeUnlockedResources(toSkiaEnum(mMemoryPolicy.purgeScratchOnly));
- mGrContext->setResourceCacheLimit(mMaxResourceBytes);
- SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes);
- break;
- default:
- break;
+ if (mode >= TrimLevel::BACKGROUND) {
+ mGrContext->freeGpuResources();
+ SkGraphics::PurgeAllCaches();
+ mRenderThread.destroyRenderingContext();
+ } else if (mode == TrimLevel::UI_HIDDEN) {
+ // Here we purge all the unlocked scratch resources and then toggle the resources cache
+ // limits between the background and max amounts. This causes the unlocked resources
+ // that have persistent data to be purged in LRU order.
+ mGrContext->setResourceCacheLimit(mBackgroundResourceBytes);
+ SkGraphics::SetFontCacheLimit(mBackgroundCpuFontCacheBytes);
+ mGrContext->purgeUnlockedResources(toSkiaEnum(mMemoryPolicy.purgeScratchOnly));
+ mGrContext->setResourceCacheLimit(mMaxResourceBytes);
+ SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes);
}
}
@@ -269,13 +264,14 @@
cancelDestroyContext();
mFrameCompletions.next() = systemTime(CLOCK_MONOTONIC);
if (ATRACE_ENABLED()) {
+ ATRACE_NAME("dumpingMemoryStatistics");
static skiapipeline::ATraceMemoryDump tracer;
tracer.startFrame();
SkGraphics::DumpMemoryStatistics(&tracer);
- if (mGrContext) {
+ if (mGrContext && Properties::debugTraceGpuResourceCategories) {
mGrContext->dumpMemoryStatistics(&tracer);
}
- tracer.logTraces();
+ tracer.logTraces(Properties::debugTraceGpuResourceCategories, mGrContext.get());
}
}
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 56b52dc..9c7f7cc 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -625,14 +625,9 @@
{
// FrameInfoVisualizer accesses the frame events, which cannot be mutated mid-draw
// or it can lead to memory corruption.
- // This lock is overly broad, but it's the quickest fix since this mutex is otherwise
- // not visible to IRenderPipeline much less FrameInfoVisualizer. And since this is
- // the thread we're primarily concerned about being responsive, this being too broad
- // shouldn't pose a performance issue.
- std::scoped_lock lock(mFrameMetricsReporterMutex);
- drawResult = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry,
- &mLayerUpdateQueue, mContentDrawBounds, mOpaque,
- mLightInfo, mRenderNodes, &(profiler()), mBufferParams);
+ drawResult = mRenderPipeline->draw(
+ frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue, mContentDrawBounds,
+ mOpaque, mLightInfo, mRenderNodes, &(profiler()), mBufferParams, profilerLock());
}
uint64_t frameCompleteNr = getFrameNumber();
@@ -762,7 +757,7 @@
mCurrentFrameInfo->markFrameCompleted();
mCurrentFrameInfo->set(FrameInfoIndex::GpuCompleted)
= mCurrentFrameInfo->get(FrameInfoIndex::FrameCompleted);
- std::scoped_lock lock(mFrameMetricsReporterMutex);
+ std::scoped_lock lock(mFrameInfoMutex);
mJankTracker.finishFrame(*mCurrentFrameInfo, mFrameMetricsReporter, frameCompleteNr,
mSurfaceControlGenerationId);
}
@@ -791,7 +786,7 @@
void CanvasContext::reportMetricsWithPresentTime() {
{ // acquire lock
- std::scoped_lock lock(mFrameMetricsReporterMutex);
+ std::scoped_lock lock(mFrameInfoMutex);
if (mFrameMetricsReporter == nullptr) {
return;
}
@@ -826,7 +821,7 @@
forthBehind->set(FrameInfoIndex::DisplayPresentTime) = presentTime;
{ // acquire lock
- std::scoped_lock lock(mFrameMetricsReporterMutex);
+ std::scoped_lock lock(mFrameInfoMutex);
if (mFrameMetricsReporter != nullptr) {
mFrameMetricsReporter->reportFrameMetrics(forthBehind->data(), true /*hasPresentTime*/,
frameNumber, surfaceControlId);
@@ -835,7 +830,7 @@
}
void CanvasContext::addFrameMetricsObserver(FrameMetricsObserver* observer) {
- std::scoped_lock lock(mFrameMetricsReporterMutex);
+ std::scoped_lock lock(mFrameInfoMutex);
if (mFrameMetricsReporter.get() == nullptr) {
mFrameMetricsReporter.reset(new FrameMetricsReporter());
}
@@ -849,7 +844,7 @@
}
void CanvasContext::removeFrameMetricsObserver(FrameMetricsObserver* observer) {
- std::scoped_lock lock(mFrameMetricsReporterMutex);
+ std::scoped_lock lock(mFrameInfoMutex);
if (mFrameMetricsReporter.get() != nullptr) {
mFrameMetricsReporter->removeObserver(observer);
if (!mFrameMetricsReporter->hasObservers()) {
@@ -886,7 +881,7 @@
FrameInfo* frameInfo = instance->getFrameInfoFromLast4(frameNumber, surfaceControlId);
if (frameInfo != nullptr) {
- std::scoped_lock lock(instance->mFrameMetricsReporterMutex);
+ std::scoped_lock lock(instance->mFrameInfoMutex);
frameInfo->set(FrameInfoIndex::FrameCompleted) = std::max(gpuCompleteTime,
frameInfo->get(FrameInfoIndex::SwapBuffersCompleted));
frameInfo->set(FrameInfoIndex::GpuCompleted) = std::max(
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index be9b649..e2e3fa3 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -164,6 +164,7 @@
void notifyFramePending();
FrameInfoVisualizer& profiler() { return mProfiler; }
+ std::mutex& profilerLock() { return mFrameInfoMutex; }
void dumpFrames(int fd);
void resetFrameStats();
@@ -342,9 +343,8 @@
std::string mName;
JankTracker mJankTracker;
FrameInfoVisualizer mProfiler;
- std::unique_ptr<FrameMetricsReporter> mFrameMetricsReporter
- GUARDED_BY(mFrameMetricsReporterMutex);
- std::mutex mFrameMetricsReporterMutex;
+ std::unique_ptr<FrameMetricsReporter> mFrameMetricsReporter GUARDED_BY(mFrameInfoMutex);
+ std::mutex mFrameInfoMutex;
std::set<RenderNode*> mPrefetchedLayers;
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index facf30b..2904dfe 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -441,22 +441,32 @@
colorMode = ColorMode::Default;
}
- if (DeviceInfo::get()->getWideColorType() == kRGBA_F16_SkColorType) {
+ // TODO: maybe we want to get rid of the WCG check if overlay properties just works?
+ const bool canUseFp16 = DeviceInfo::get()->isSupportFp16ForHdr() ||
+ DeviceInfo::get()->getWideColorType() == kRGBA_F16_SkColorType;
+
+ if (canUseFp16) {
if (mEglConfigF16 == EGL_NO_CONFIG_KHR) {
colorMode = ColorMode::Default;
} else {
config = mEglConfigF16;
}
}
+
if (EglExtensions.glColorSpace) {
attribs[0] = EGL_GL_COLORSPACE_KHR;
switch (colorMode) {
case ColorMode::Default:
attribs[1] = EGL_GL_COLORSPACE_LINEAR_KHR;
break;
+ case ColorMode::Hdr:
+ if (canUseFp16) {
+ attribs[1] = EGL_GL_COLORSPACE_SCRGB_EXT;
+ break;
+ // No fp16 support so fallthrough to HDR10
+ }
// We don't have an EGL colorspace for extended range P3 that's used for HDR
// So override it after configuring the EGL context
- case ColorMode::Hdr:
case ColorMode::Hdr10:
overrideWindowDataSpaceForHdr = true;
attribs[1] = EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT;
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index 9c879d5..b8c3a4d 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -68,7 +68,8 @@
const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
const std::vector<sp<RenderNode>>& renderNodes,
FrameInfoVisualizer* profiler,
- const HardwareBufferRenderParams& bufferParams) = 0;
+ const HardwareBufferRenderParams& bufferParams,
+ std::mutex& profilerLock) = 0;
virtual bool swapBuffers(const Frame& frame, IRenderPipeline::DrawResult&,
const SkRect& screenDirty, FrameInfo* currentFrameInfo,
bool* requireSwap) = 0;
diff --git a/libs/hwui/tests/unit/UnderlineTest.cpp b/libs/hwui/tests/unit/UnderlineTest.cpp
index c70a304..9911bfa 100644
--- a/libs/hwui/tests/unit/UnderlineTest.cpp
+++ b/libs/hwui/tests/unit/UnderlineTest.cpp
@@ -103,8 +103,9 @@
// Create minikin::Layout
std::unique_ptr<Typeface> typeface(makeTypeface());
minikin::Layout layout = doLayout(text, *paint, typeface.get());
+ minikin::MinikinRect bounds;
- DrawTextFunctor f(layout, &canvas, *paint, 0, 0, layout.getAdvance());
+ DrawTextFunctor f(layout, &canvas, *paint, 0, 0, layout.getAdvance(), bounds);
MinikinUtils::forFontRun(layout, paint, f);
return f;
}
diff --git a/lint-baseline.xml b/lint-baseline.xml
index 79b2155..660884a 100644
--- a/lint-baseline.xml
+++ b/lint-baseline.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 7.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="7.1.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
<issue
id="NonUserGetterCalled"
@@ -433,7 +433,7 @@
<issue
id="NonUserGetterCalled"
message="`android.provider.Settings.System#getInt()` called from system process. Please call `android.provider.Settings.System#getIntForUser()` instead. "
- errorLine1=" boolean cap = System.getInt(resolver, System.TEXT_AUTO_CAPS, 1) > 0;"
+ errorLine1=" boolean cap = System.getInt(resolver, System.TEXT_AUTO_CAPS, 1) > 0;"
errorLine2=" ~~~~~~">
<location
file="frameworks/base/core/java/android/text/method/TextKeyListener.java"
@@ -444,7 +444,7 @@
<issue
id="NonUserGetterCalled"
message="`android.provider.Settings.System#getInt()` called from system process. Please call `android.provider.Settings.System#getIntForUser()` instead. "
- errorLine1=" boolean text = System.getInt(resolver, System.TEXT_AUTO_REPLACE, 1) > 0;"
+ errorLine1=" boolean text = System.getInt(resolver, System.TEXT_AUTO_REPLACE, 1) > 0;"
errorLine2=" ~~~~~~">
<location
file="frameworks/base/core/java/android/text/method/TextKeyListener.java"
@@ -455,7 +455,7 @@
<issue
id="NonUserGetterCalled"
message="`android.provider.Settings.System#getInt()` called from system process. Please call `android.provider.Settings.System#getIntForUser()` instead. "
- errorLine1=" boolean period = System.getInt(resolver, System.TEXT_AUTO_PUNCTUATE, 1) > 0;"
+ errorLine1=" boolean period = System.getInt(resolver, System.TEXT_AUTO_PUNCTUATE, 1) > 0;"
errorLine2=" ~~~~~~">
<location
file="frameworks/base/core/java/android/text/method/TextKeyListener.java"
@@ -466,7 +466,7 @@
<issue
id="NonUserGetterCalled"
message="`android.provider.Settings.System#getInt()` called from system process. Please call `android.provider.Settings.System#getIntForUser()` instead. "
- errorLine1=" boolean pw = System.getInt(resolver, System.TEXT_SHOW_PASSWORD, 1) > 0;"
+ errorLine1=" boolean pw = System.getInt(resolver, System.TEXT_SHOW_PASSWORD, 1) > 0;"
errorLine2=" ~~~~~~">
<location
file="frameworks/base/core/java/android/text/method/TextKeyListener.java"
@@ -562,4 +562,4 @@
column="74"/>
</issue>
-</issues>
+</issues>
\ No newline at end of file
diff --git a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
index ee2510f..0d5af50 100644
--- a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
+++ b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
@@ -19,6 +19,7 @@
import android.Manifest;
import android.annotation.RequiresPermission;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.location.LocationManager;
import android.os.SystemClock;
import android.telephony.TelephonyCallback;
@@ -26,6 +27,8 @@
import android.telephony.emergency.EmergencyNumber;
import android.util.Log;
+import com.android.internal.telephony.flags.Flags;
+
import java.util.concurrent.TimeUnit;
/**
@@ -139,8 +142,20 @@
(mCallEndElapsedRealtimeMillis > 0)
&& ((SystemClock.elapsedRealtime() - mCallEndElapsedRealtimeMillis)
< emergencyExtensionMillis);
- boolean isInEmergencyCallback = mTelephonyManager.getEmergencyCallbackMode();
- boolean isInEmergencySmsMode = mTelephonyManager.isInEmergencySmsMode();
+ boolean isInEmergencyCallback = false;
+ boolean isInEmergencySmsMode = false;
+ if (!Flags.enforceTelephonyFeatureMappingForPublicApis()) {
+ isInEmergencyCallback = mTelephonyManager.getEmergencyCallbackMode();
+ isInEmergencySmsMode = mTelephonyManager.isInEmergencySmsMode();
+ } else {
+ PackageManager pm = mContext.getPackageManager();
+ if (pm != null && pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CALLING)) {
+ isInEmergencyCallback = mTelephonyManager.getEmergencyCallbackMode();
+ }
+ if (pm != null && pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)) {
+ isInEmergencySmsMode = mTelephonyManager.isInEmergencySmsMode();
+ }
+ }
return mIsInEmergencyCall || isInEmergencyCallback || isInEmergencyExtension
|| isInEmergencySmsMode;
}
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index b002bbf..ea136ed 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -1079,7 +1079,7 @@
* @return one of the values that can be set in {@link Builder#setEncoding(int)} or
* {@link AudioFormat#ENCODING_INVALID} if not set.
*/
- public int getEncoding() {
+ public @Encoding int getEncoding() {
return mEncoding;
}
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index 46db777..587e35b 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -16,6 +16,9 @@
package android.media;
+import static com.android.media.codec.flags.Flags.FLAG_CODEC_IMPORTANCE;
+
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1635,6 +1638,34 @@
*/
public static final String KEY_ALLOW_FRAME_DROP = "allow-frame-drop";
+ /**
+ * A key describing the desired codec importance for the application.
+ * <p>
+ * The associated value is a positive integer including zero.
+ * Higher value means lesser importance.
+ * <p>
+ * The resource manager may use the codec importance, along with other factors
+ * when reclaiming codecs from an application.
+ * The specifics of reclaim policy is device dependent, but specifying the codec importance,
+ * will allow the resource manager to prioritize reclaiming less important codecs
+ * (assigned higher values) from the (reclaim) requesting application first.
+ * So, the codec importance is only relevant within the context of that application.
+ * <p>
+ * The codec importance can be set:
+ * <ul>
+ * <li>through {@link MediaCodec#configure}. </li>
+ * <li>through {@link MediaCodec#setParameters} if the codec has been configured already,
+ * which allows the users to change the codec importance multiple times.
+ * </ul>
+ * Any change/update in codec importance is guaranteed upon the completion of the function call
+ * that sets the codec importance. So, in case of concurrent codec operations,
+ * make sure to wait for the change in codec importance, before using another codec.
+ * Note that unless specified, by default the codecs will have highest importance (of value 0).
+ *
+ */
+ @FlaggedApi(FLAG_CODEC_IMPORTANCE)
+ public static final String KEY_IMPORTANCE = "importance";
+
/* package private */ MediaFormat(@NonNull Map<String, Object> map) {
mMap = map;
}
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 89792c7..17f2525 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -48,6 +48,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -141,7 +142,9 @@
* dispatch. This is only used to determine what callback a route should be assigned to (added,
* removed, changed) in {@link #dispatchFilteredRoutesUpdatedOnHandler(List)}.
*/
- private volatile ArrayMap<String, MediaRoute2Info> mPreviousRoutes = new ArrayMap<>();
+ private volatile ArrayMap<String, MediaRoute2Info> mPreviousFilteredRoutes = new ArrayMap<>();
+
+ private final Map<String, MediaRoute2Info> mPreviousUnfilteredRoutes = new ArrayMap<>();
/**
* Stores the latest copy of exposed routes after filtering, sorting, and deduplication. Can be
@@ -282,6 +285,8 @@
MediaRouter2 instance = sAppToProxyRouterMap.get(key);
if (instance == null) {
instance = new MediaRouter2(context, looper, clientPackageName, user);
+ // Register proxy router after instantiation to avoid race condition.
+ ((ProxyMediaRouter2Impl) instance.mImpl).registerProxyRouter();
sAppToProxyRouterMap.put(key, instance);
}
return instance;
@@ -368,6 +373,7 @@
new SystemRoutingController(
ProxyMediaRouter2Impl.getSystemSessionInfoImpl(
mMediaRouterService, clientPackageName));
+
mImpl = new ProxyMediaRouter2Impl(context, clientPackageName, user);
}
@@ -713,7 +719,7 @@
mImpl.transfer(
controller.getRoutingSessionInfo(),
route,
- android.os.Process.myUserHandle(),
+ Process.myUserHandle(),
mContext.getPackageName());
}
@@ -728,7 +734,7 @@
* request.
* @param transferInitiatorPackageName the package name of the app that initiated the transfer.
* This value is used with the user handle to populate {@link
- * RoutingController#wasTransferRequestedBySelf()}.
+ * RoutingController#wasTransferInitiatedBySelf()}.
* @hide
*/
@FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
@@ -883,7 +889,7 @@
newRoutes.stream().map(MediaRoute2Info::getId).collect(Collectors.toSet());
for (MediaRoute2Info route : newRoutes) {
- MediaRoute2Info prevRoute = mPreviousRoutes.get(route.getId());
+ MediaRoute2Info prevRoute = mPreviousFilteredRoutes.get(route.getId());
if (prevRoute == null) {
addedRoutes.add(route);
} else if (!prevRoute.equals(route)) {
@@ -891,21 +897,21 @@
}
}
- for (int i = 0; i < mPreviousRoutes.size(); i++) {
- if (!newRouteIds.contains(mPreviousRoutes.keyAt(i))) {
- removedRoutes.add(mPreviousRoutes.valueAt(i));
+ for (int i = 0; i < mPreviousFilteredRoutes.size(); i++) {
+ if (!newRouteIds.contains(mPreviousFilteredRoutes.keyAt(i))) {
+ removedRoutes.add(mPreviousFilteredRoutes.valueAt(i));
}
}
// update previous routes
for (MediaRoute2Info route : removedRoutes) {
- mPreviousRoutes.remove(route.getId());
+ mPreviousFilteredRoutes.remove(route.getId());
}
for (MediaRoute2Info route : addedRoutes) {
- mPreviousRoutes.put(route.getId(), route);
+ mPreviousFilteredRoutes.put(route.getId(), route);
}
for (MediaRoute2Info route : changedRoutes) {
- mPreviousRoutes.put(route.getId(), route);
+ mPreviousFilteredRoutes.put(route.getId(), route);
}
if (!addedRoutes.isEmpty()) {
@@ -924,6 +930,27 @@
}
}
+ void dispatchControllerUpdatedIfNeededOnHandler(Map<String, MediaRoute2Info> routesMap) {
+ List<RoutingController> controllers = getControllers();
+ for (RoutingController controller : controllers) {
+
+ for (String selectedRoute : controller.getRoutingSessionInfo().getSelectedRoutes()) {
+ if (routesMap.containsKey(selectedRoute)
+ && mPreviousUnfilteredRoutes.containsKey(selectedRoute)) {
+ MediaRoute2Info currentRoute = routesMap.get(selectedRoute);
+ MediaRoute2Info oldRoute = mPreviousUnfilteredRoutes.get(selectedRoute);
+ if (!currentRoute.equals(oldRoute)) {
+ notifyControllerUpdated(controller);
+ break;
+ }
+ }
+ }
+ }
+
+ mPreviousUnfilteredRoutes.clear();
+ mPreviousUnfilteredRoutes.putAll(routesMap);
+ }
+
void updateRoutesOnHandler(List<MediaRoute2Info> newRoutes) {
synchronized (mLock) {
mRoutes.clear();
@@ -945,6 +972,11 @@
MediaRouter2::dispatchFilteredRoutesUpdatedOnHandler,
this,
mFilteredRoutes));
+ mHandler.sendMessage(
+ obtainMessage(
+ MediaRouter2::dispatchControllerUpdatedIfNeededOnHandler,
+ this,
+ new HashMap<>(mRoutes)));
}
/**
@@ -1518,17 +1550,17 @@
}
/**
- * Returns whether the transfer was requested by the calling app (as determined by comparing
+ * Returns whether the transfer was initiated by the calling app (as determined by comparing
* {@link UserHandle} and package name).
*/
@FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
- public boolean wasTransferRequestedBySelf() {
+ public boolean wasTransferInitiatedBySelf() {
RoutingSessionInfo sessionInfo = getRoutingSessionInfo();
UserHandle transferInitiatorUserHandle = sessionInfo.getTransferInitiatorUserHandle();
String transferInitiatorPackageName = sessionInfo.getTransferInitiatorPackageName();
- return Objects.equals(android.os.Process.myUserHandle(), transferInitiatorUserHandle)
+ return Objects.equals(Process.myUserHandle(), transferInitiatorUserHandle)
&& Objects.equals(mContext.getPackageName(), transferInitiatorPackageName);
}
@@ -2153,18 +2185,19 @@
mClientUser = user;
mClientPackageName = clientPackageName;
mClient = new Client();
+ mDiscoveryPreference = RouteDiscoveryPreference.EMPTY;
+ }
+ public void registerProxyRouter() {
try {
mMediaRouterService.registerProxyRouter(
mClient,
- context.getApplicationContext().getPackageName(),
- clientPackageName,
- user);
+ mContext.getApplicationContext().getPackageName(),
+ mClientPackageName,
+ mClientUser);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
-
- mDiscoveryPreference = RouteDiscoveryPreference.EMPTY;
}
@Override
@@ -2294,11 +2327,7 @@
List<RoutingSessionInfo> sessionInfos = getRoutingSessions();
RoutingSessionInfo targetSession = sessionInfos.get(sessionInfos.size() - 1);
- transfer(
- targetSession,
- route,
- android.os.Process.myUserHandle(),
- mContext.getPackageName());
+ transfer(targetSession, route, Process.myUserHandle(), mContext.getPackageName());
}
@Override
@@ -3055,11 +3084,7 @@
public void registerRouteCallback() {
synchronized (mLock) {
try {
- if (mStub == null) {
- MediaRouter2Stub stub = new MediaRouter2Stub();
- mMediaRouterService.registerRouter2(stub, mPackageName);
- mStub = stub;
- }
+ registerRouterStubIfNeededLocked();
if (updateDiscoveryPreferenceIfNeededLocked()) {
mMediaRouterService.setDiscoveryRequestWithRouter2(
@@ -3085,8 +3110,7 @@
}
if (mRouteCallbackRecords.isEmpty() && mNonSystemRoutingControllers.isEmpty()) {
- mMediaRouterService.unregisterRouter2(mStub);
- mStub = null;
+ unregisterRouterStubLocked();
}
} catch (RemoteException ex) {
Log.e(TAG, "unregisterRouteCallback: Unable to set discovery request.", ex);
@@ -3103,11 +3127,7 @@
}
mRouteListingPreference = preference;
try {
- if (mStub == null) {
- MediaRouter2Stub stub = new MediaRouter2Stub();
- mMediaRouterService.registerRouter2(stub, mImpl.getPackageName());
- mStub = stub;
- }
+ registerRouterStubIfNeededLocked();
mMediaRouterService.setRouteListingPreference(mStub, mRouteListingPreference);
} catch (RemoteException ex) {
ex.rethrowFromSystemServer();
@@ -3165,8 +3185,12 @@
return;
}
- requestCreateController(controller, route, MANAGER_REQUEST_ID_NONE,
- android.os.Process.myUserHandle(), mContext.getPackageName());
+ requestCreateController(
+ controller,
+ route,
+ MANAGER_REQUEST_ID_NONE,
+ Process.myUserHandle(),
+ mContext.getPackageName());
}
@Override
@@ -3295,18 +3319,31 @@
obtainMessage(MediaRouter2::notifyStop, MediaRouter2.this, controller));
}
- if (mRouteCallbackRecords.isEmpty()
- && mNonSystemRoutingControllers.isEmpty()
- && mStub != null) {
+ if (mRouteCallbackRecords.isEmpty() && mNonSystemRoutingControllers.isEmpty()) {
try {
- mMediaRouterService.unregisterRouter2(mStub);
+ unregisterRouterStubLocked();
} catch (RemoteException ex) {
ex.rethrowFromSystemServer();
}
- mStub = null;
}
}
}
+ @GuardedBy("mLock")
+ private void registerRouterStubIfNeededLocked() throws RemoteException {
+ if (mStub == null) {
+ MediaRouter2Stub stub = new MediaRouter2Stub();
+ mMediaRouterService.registerRouter2(stub, mPackageName);
+ mStub = stub;
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void unregisterRouterStubLocked() throws RemoteException {
+ if (mStub != null) {
+ mMediaRouterService.unregisterRouter2(mStub);
+ mStub = null;
+ }
+ }
}
}
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 7f95886..df9ecdc 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -83,3 +83,10 @@
description: "Notify ActivityManager with the changes in playback state of the media session."
bug: "295518668"
}
+
+flag {
+ name: "enable_prevention_of_keep_alive_route_providers"
+ namespace: "media_solutions"
+ description: "Enables mechanisms to prevent route providers from keeping malicious apps alive."
+ bug: "263520343"
+}
diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl
index a7ec6c6..3d927d3 100644
--- a/media/java/android/media/projection/IMediaProjectionManager.aidl
+++ b/media/java/android/media/projection/IMediaProjectionManager.aidl
@@ -37,31 +37,41 @@
const String EXTRA_PACKAGE_REUSING_GRANTED_CONSENT =
"extra_media_projection_package_reusing_consent";
+ /**
+ * Returns whether a combination of process UID and package has the projection permission.
+ *
+ * @param processUid the process UID as returned by {@link android.os.Process.myUid()}.
+ */
@UnsupportedAppUsage
- boolean hasProjectionPermission(int uid, String packageName);
+ boolean hasProjectionPermission(int processUid, String packageName);
/**
* Returns a new {@link IMediaProjection} instance associated with the given package.
+ *
+ * @param processUid the process UID as returned by {@link android.os.Process.myUid()}.
*/
@JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ ".permission.MANAGE_MEDIA_PROJECTION)")
- IMediaProjection createProjection(int uid, String packageName, int type,
+ IMediaProjection createProjection(int processUid, String packageName, int type,
boolean permanentGrant);
/**
* Returns the current {@link IMediaProjection} instance associated with the given
- * package, or {@code null} if it is not possible to re-use the current projection.
+ * package and process UID, or {@code null} if it is not possible to re-use the current
+ * projection.
*
* <p>Should only be invoked when the user has reviewed consent for a re-used projection token.
* Requires that there is a prior session waiting for the user to review consent, and the given
* package details match those on the current projection.
*
* @see {@link #isCurrentProjection}
+ *
+ * @param processUid the process UID as returned by {@link android.os.Process.myUid()}.
*/
@EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION")
@JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ ".permission.MANAGE_MEDIA_PROJECTION)")
- IMediaProjection getProjection(int uid, String packageName);
+ IMediaProjection getProjection(int processUid, String packageName);
/**
* Returns {@code true} if the given {@link IMediaProjection} corresponds to the current
@@ -111,7 +121,7 @@
@EnforcePermission("MANAGE_MEDIA_PROJECTION")
@JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ ".permission.MANAGE_MEDIA_PROJECTION)")
- void addCallback(IMediaProjectionWatcherCallback callback);
+ MediaProjectionInfo addCallback(IMediaProjectionWatcherCallback callback);
@JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ ".permission.MANAGE_MEDIA_PROJECTION)")
@@ -162,8 +172,8 @@
*
* <p>Only used for emitting atoms.
*
- * @param hostUid The uid of the process requesting consent to capture, may be an app or
- * SystemUI.
+ * @param hostProcessUid The uid of the process requesting consent to capture, may be an
+ * app or SystemUI.
* @param sessionCreationSource Only set if the state is MEDIA_PROJECTION_STATE_INITIATED.
* Indicates the entry point for requesting the permission. Must be
* a valid state defined
@@ -172,49 +182,49 @@
@EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION")
@JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ ".permission.MANAGE_MEDIA_PROJECTION)")
- oneway void notifyPermissionRequestInitiated(int hostUid, int sessionCreationSource);
+ oneway void notifyPermissionRequestInitiated(int hostProcessUid, int sessionCreationSource);
/**
* Notifies system server that the permission request was displayed.
*
* <p>Only used for emitting atoms.
*
- * @param hostUid The uid of the process requesting consent to capture, may be an app or
- * SystemUI.
+ * @param hostProcessUid The uid of the process requesting consent to capture, may be an app or
+ * SystemUI.
*/
@EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION")
@JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ ".permission.MANAGE_MEDIA_PROJECTION)")
- oneway void notifyPermissionRequestDisplayed(int hostUid);
+ oneway void notifyPermissionRequestDisplayed(int hostProcessUid);
/**
* Notifies system server that the permission request was cancelled.
*
* <p>Only used for emitting atoms.
*
- * @param hostUid The uid of the process requesting consent to capture, may be an app or
- * SystemUI.
+ * @param hostProcessUid The uid of the process requesting consent to capture, may be an app or
+ * SystemUI.
*/
@EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION")
@JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ ".permission.MANAGE_MEDIA_PROJECTION)")
- oneway void notifyPermissionRequestCancelled(int hostUid);
+ oneway void notifyPermissionRequestCancelled(int hostProcessUid);
/**
* Notifies system server that the app selector was displayed.
*
* <p>Only used for emitting atoms.
*
- * @param hostUid The uid of the process requesting consent to capture, may be an app or
- * SystemUI.
+ * @param hostProcessUid The uid of the process requesting consent to capture, may be an app or
+ * SystemUI.
*/
@EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION")
@JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ ".permission.MANAGE_MEDIA_PROJECTION)")
- oneway void notifyAppSelectorDisplayed(int hostUid);
+ oneway void notifyAppSelectorDisplayed(int hostProcessUid);
@EnforcePermission("MANAGE_MEDIA_PROJECTION")
@JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ ".permission.MANAGE_MEDIA_PROJECTION)")
- void notifyWindowingModeChanged(int contentToRecord, int targetUid, int windowingMode);
+ void notifyWindowingModeChanged(int contentToRecord, int targetProcessUid, int windowingMode);
}
diff --git a/media/java/android/media/projection/MediaProjectionInfo.java b/media/java/android/media/projection/MediaProjectionInfo.java
index ff60856..c820392 100644
--- a/media/java/android/media/projection/MediaProjectionInfo.java
+++ b/media/java/android/media/projection/MediaProjectionInfo.java
@@ -16,6 +16,7 @@
package android.media.projection;
+import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
@@ -26,15 +27,18 @@
public final class MediaProjectionInfo implements Parcelable {
private final String mPackageName;
private final UserHandle mUserHandle;
+ private final IBinder mLaunchCookie;
- public MediaProjectionInfo(String packageName, UserHandle handle) {
+ public MediaProjectionInfo(String packageName, UserHandle handle, IBinder launchCookie) {
mPackageName = packageName;
mUserHandle = handle;
+ mLaunchCookie = launchCookie;
}
public MediaProjectionInfo(Parcel in) {
mPackageName = in.readString();
mUserHandle = UserHandle.readFromParcel(in);
+ mLaunchCookie = in.readStrongBinder();
}
public String getPackageName() {
@@ -45,12 +49,16 @@
return mUserHandle;
}
+ public IBinder getLaunchCookie() {
+ return mLaunchCookie;
+ }
+
@Override
public boolean equals(Object o) {
- if (o instanceof MediaProjectionInfo) {
- final MediaProjectionInfo other = (MediaProjectionInfo) o;
+ if (o instanceof MediaProjectionInfo other) {
return Objects.equals(other.mPackageName, mPackageName)
- && Objects.equals(other.mUserHandle, mUserHandle);
+ && Objects.equals(other.mUserHandle, mUserHandle)
+ && Objects.equals(other.mLaunchCookie, mLaunchCookie);
}
return false;
}
@@ -64,7 +72,8 @@
public String toString() {
return "MediaProjectionInfo{mPackageName="
+ mPackageName + ", mUserHandle="
- + mUserHandle + "}";
+ + mUserHandle + ", mLaunchCookie"
+ + mLaunchCookie + "}";
}
@Override
@@ -76,6 +85,7 @@
public void writeToParcel(Parcel out, int flags) {
out.writeString(mPackageName);
UserHandle.writeToParcel(mUserHandle, out);
+ out.writeStrongBinder(mLaunchCookie);
}
public static final @android.annotation.NonNull Parcelable.Creator<MediaProjectionInfo> CREATOR =
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index a8ffd2b..2f6575e 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -149,4 +149,7 @@
// For CTS purpose only. Add/remove a TvInputHardware device
void addHardwareDevice(in int deviceId);
void removeHardwareDevice(in int deviceId);
+
+ // For freezing video playback
+ void setVideoFrozen(in IBinder sessionToken, boolean isFrozen, int userId);
}
diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl
index e37ee6e..a93f18d 100644
--- a/media/java/android/media/tv/ITvInputSession.aidl
+++ b/media/java/android/media/tv/ITvInputSession.aidl
@@ -83,4 +83,7 @@
// For TV messages
void notifyTvMessage(int type, in Bundle data);
void setTvMessageEnabled(int type, boolean enabled);
+
+ // For freezing video
+ void setVideoFrozen(boolean isFrozen);
}
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index ae3ee65..921104d 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -81,6 +81,7 @@
private static final int DO_NOTIFY_TV_MESSAGE = 32;
private static final int DO_STOP_PLAYBACK = 33;
private static final int DO_START_PLAYBACK = 34;
+ private static final int DO_SET_VIDEO_FROZEN = 35;
private final boolean mIsRecordingSession;
private final HandlerCaller mCaller;
@@ -296,6 +297,10 @@
mTvInputSessionImpl.startPlayback();
break;
}
+ case DO_SET_VIDEO_FROZEN: {
+ mTvInputSessionImpl.setVideoFrozen((Boolean) msg.obj);
+ break;
+ }
default: {
Log.w(TAG, "Unhandled message code: " + msg.what);
break;
@@ -483,6 +488,11 @@
}
@Override
+ public void setVideoFrozen(boolean isFrozen) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_VIDEO_FROZEN, isFrozen));
+ }
+
+ @Override
public void notifyTvMessage(int type, Bundle data) {
mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_NOTIFY_TV_MESSAGE, type, data));
}
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index db01950..7f8f1a3 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -16,6 +16,7 @@
package android.media.tv;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -29,6 +30,7 @@
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
+import android.media.tv.flags.Flags;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
@@ -2540,9 +2542,9 @@
* <p>This is used to indicate the broadcast visibility type defined in the underlying
* broadcast standard or country/operator profile, if applicable. For example,
* {@code visible_service_flag} and {@code numeric_selection_flag} of
- * {@code service_attribute_descriptor} in D-Book, {@code visible_service_flag} and
- * {@code selectable_service_flag} of {@code ciplus_service_descriptor} in CI Plus 1.3
- * specification.
+ * {@code service_attribute_descriptor} in D-Book, the specification for UK-based TV
+ * products, {@code visible_service_flag} and {@code selectable_service_flag} of
+ * {@code ciplus_service_descriptor} in the CI Plus 1.3 specification.
*
* <p>The value should match one of the following:
* {@link #BROADCAST_VISIBILITY_TYPE_VISIBLE},
@@ -2553,8 +2555,8 @@
* by default.
*
* <p>Type: INTEGER
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_BROADCAST_VISIBILITY_TYPES)
public static final String COLUMN_BROADCAST_VISIBILITY_TYPE = "broadcast_visibility_type";
/** @hide */
@@ -2571,8 +2573,8 @@
* visible from users and selectable by users via normal service navigation mechanisms.
*
* @see #COLUMN_BROADCAST_VISIBILITY_TYPE
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_BROADCAST_VISIBILITY_TYPES)
public static final int BROADCAST_VISIBILITY_TYPE_VISIBLE = 0;
/**
@@ -2581,18 +2583,18 @@
* the logical channel number.
*
* @see #COLUMN_BROADCAST_VISIBILITY_TYPE
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_BROADCAST_VISIBILITY_TYPES)
public static final int BROADCAST_VISIBILITY_TYPE_NUMERIC_SELECTABLE_ONLY = 1;
/**
* The broadcast visibility type for invisible services. Use this type when the service
- * is invisible from users and unselectable by users via any of normal service navigation
- * mechanisms.
+ * is invisible from users and not able to be selected by users via any of the normal
+ * service navigation mechanisms.
*
* @see #COLUMN_BROADCAST_VISIBILITY_TYPE
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_BROADCAST_VISIBILITY_TYPES)
public static final int BROADCAST_VISIBILITY_TYPE_INVISIBLE = 2;
private Channels() {}
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index c685a5a..51b2542 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -3334,6 +3334,18 @@
}
}
+ void setVideoFrozen(boolean isFrozen) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.setVideoFrozen(mToken, isFrozen, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/**
* Sends TV messages to the service for testing purposes
*/
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 55fa517..76d8e50 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -1558,6 +1558,17 @@
}
/**
+ * Called when a request to freeze the video is received from the TV app. The audio should
+ * continue playback while the video is frozen.
+ *
+ * <p> This should freeze the video to the last frame when the state is set to {@code true}.
+ * @param isFrozen whether or not the video should be frozen.
+ * @hide
+ */
+ public void onSetVideoFrozen(boolean isFrozen) {
+ }
+
+ /**
* Called when the application requests to play a given recorded TV program.
*
* @param recordedProgramUri The URI of a recorded TV program.
@@ -2034,6 +2045,13 @@
}
/**
+ * Calls {@link #onSetVideoFrozen(boolean)}.
+ */
+ void setVideoFrozen(boolean isFrozen) {
+ onSetVideoFrozen(isFrozen);
+ }
+
+ /**
* Calls {@link #onTimeShiftPlay(Uri)}.
*/
void timeShiftPlay(Uri recordedProgramUri) {
diff --git a/media/java/android/media/tv/TvTrackInfo.java b/media/java/android/media/tv/TvTrackInfo.java
index 78d7d76..2ebb19a 100644
--- a/media/java/android/media/tv/TvTrackInfo.java
+++ b/media/java/android/media/tv/TvTrackInfo.java
@@ -55,6 +55,27 @@
*/
public static final int TYPE_SUBTITLE = 2;
+ /**
+ * The component tag identifies a component carried by a MPEG-2 TS.
+ *
+ * This corresponds to the component_tag in the component descriptor in the
+ * Elementary Stream loop of the stream in the Program Map Table
+ * (PMT) [EN 300 468], or undefined if the component is not carried in an
+ * MPEG-2 TS.
+ *
+ * @hide
+ */
+ public static final String EXTRA_BUNDLE_KEY_COMPONENT_TAG = "component_tag";
+
+ /**
+ * The MPEG Program ID (PID) of the component in the MPEG2-TS in
+ * which it is carried, or undefined if the component is not carried in an
+ * MPEG-2 TS.
+ *
+ * @hide
+ */
+ public static final String EXTRA_BUNDLE_KEY_PID = "pid";
+
private final int mType;
private final String mId;
private final String mLanguage;
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 233f966..cb45661 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -675,6 +675,23 @@
}
/**
+ * Sets whether or not the video is frozen. While the video is frozen, audio playback will
+ * continue.
+ *
+ * <p> This should be invoked after a {@link TvInteractiveAppService.Session#requestCommand} is
+ * received with the command to freeze the video.
+ *
+ * <p> This will freeze the video to the last frame when the state is set to {@code true}.
+ * @param isFrozen whether or not the video is frozen.
+ * @hide
+ */
+ public void setVideoFrozen(boolean isFrozen) {
+ if (mSession != null) {
+ mSession.setVideoFrozen(isFrozen);
+ }
+ }
+
+ /**
* Sends TV messages to the session for testing purposes
*
* @hide
diff --git a/media/java/android/media/tv/ad/ITvAdClient.aidl b/media/java/android/media/tv/ad/ITvAdClient.aidl
new file mode 100644
index 0000000..34d96b3
--- /dev/null
+++ b/media/java/android/media/tv/ad/ITvAdClient.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.ad;
+
+import android.view.InputChannel;
+
+/**
+ * Interface a client of the ITvAdManager implements, to identify itself and receive
+ * information about changes to the state of each TV AD service.
+ * @hide
+ */
+oneway interface ITvAdClient {
+ void onSessionCreated(in String serviceId, IBinder token, in InputChannel channel, int seq);
+ void onSessionReleased(int seq);
+ void onLayoutSurface(int left, int top, int right, int bottom, int seq);
+}
\ No newline at end of file
diff --git a/media/java/android/media/tv/ad/ITvAdManager.aidl b/media/java/android/media/tv/ad/ITvAdManager.aidl
index 92cc923..a747e49 100644
--- a/media/java/android/media/tv/ad/ITvAdManager.aidl
+++ b/media/java/android/media/tv/ad/ITvAdManager.aidl
@@ -16,10 +16,25 @@
package android.media.tv.ad;
+import android.media.tv.ad.ITvAdClient;
+import android.media.tv.ad.ITvAdManagerCallback;
+import android.media.tv.ad.TvAdServiceInfo;
+import android.view.Surface;
+
/**
* Interface to the TV AD service.
* @hide
*/
interface ITvAdManager {
+ List<TvAdServiceInfo> getTvAdServiceList(int userId);
+ void createSession(
+ in ITvAdClient client, in String serviceId, in String type, int seq, int userId);
+ void releaseSession(in IBinder sessionToken, int userId);
void startAdService(in IBinder sessionToken, int userId);
+ void setSurface(in IBinder sessionToken, in Surface surface, int userId);
+ void dispatchSurfaceChanged(in IBinder sessionToken, int format, int width, int height,
+ int userId);
+
+ void registerCallback(in ITvAdManagerCallback callback, int userId);
+ void unregisterCallback(in ITvAdManagerCallback callback, int userId);
}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl b/media/java/android/media/tv/ad/ITvAdManagerCallback.aidl
similarity index 67%
copy from core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
copy to media/java/android/media/tv/ad/ITvAdManagerCallback.aidl
index ce92b6d..f55f67e 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
+++ b/media/java/android/media/tv/ad/ITvAdManagerCallback.aidl
@@ -13,10 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.companion.virtual.camera;
+
+package android.media.tv.ad;
/**
- * The configuration of a single virtual camera stream.
+ * Interface to receive callbacks from ITvAdManager regardless of sessions.
* @hide
*/
-parcelable VirtualCameraStreamConfig;
\ No newline at end of file
+oneway interface ITvAdManagerCallback {
+ void onAdServiceAdded(in String serviceId);
+ void onAdServiceRemoved(in String serviceId);
+ void onAdServiceUpdated(in String serviceId);
+}
\ No newline at end of file
diff --git a/media/java/android/media/tv/ad/ITvAdService.aidl b/media/java/android/media/tv/ad/ITvAdService.aidl
new file mode 100644
index 0000000..3bb0409
--- /dev/null
+++ b/media/java/android/media/tv/ad/ITvAdService.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.ad;
+
+import android.media.tv.ad.ITvAdServiceCallback;
+import android.media.tv.ad.ITvAdSessionCallback;
+import android.os.Bundle;
+import android.view.InputChannel;
+
+/**
+ * Top-level interface to a TV AD component (implemented in a Service). It's used for
+ * TvAdManagerService to communicate with TvAdService.
+ * @hide
+ */
+oneway interface ITvAdService {
+ void registerCallback(in ITvAdServiceCallback callback);
+ void unregisterCallback(in ITvAdServiceCallback callback);
+ void createSession(in InputChannel channel, in ITvAdSessionCallback callback,
+ in String serviceId, in String type);
+ void sendAppLinkCommand(in Bundle command);
+}
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl b/media/java/android/media/tv/ad/ITvAdServiceCallback.aidl
similarity index 78%
copy from core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
copy to media/java/android/media/tv/ad/ITvAdServiceCallback.aidl
index ce92b6d..a087181 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
+++ b/media/java/android/media/tv/ad/ITvAdServiceCallback.aidl
@@ -13,10 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.companion.virtual.camera;
+
+package android.media.tv.ad;
/**
- * The configuration of a single virtual camera stream.
+ * Helper interface for ITvAdService to allow the TvAdService to notify the TvAdManagerService.
* @hide
*/
-parcelable VirtualCameraStreamConfig;
\ No newline at end of file
+oneway interface ITvAdServiceCallback {
+}
\ No newline at end of file
diff --git a/media/java/android/media/tv/ad/ITvAdSession.aidl b/media/java/android/media/tv/ad/ITvAdSession.aidl
index b834f1b9..751257c 100644
--- a/media/java/android/media/tv/ad/ITvAdSession.aidl
+++ b/media/java/android/media/tv/ad/ITvAdSession.aidl
@@ -16,10 +16,15 @@
package android.media.tv.ad;
+import android.view.Surface;
+
/**
- * Sub-interface of ITvAdService which is created per session and has its own context.
+ * Sub-interface of ITvAdService.aidl which is created per session and has its own context.
* @hide
*/
oneway interface ITvAdSession {
+ void release();
void startAdService();
+ void setSurface(in Surface surface);
+ void dispatchSurfaceChanged(int format, int width, int height);
}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl b/media/java/android/media/tv/ad/ITvAdSessionCallback.aidl
similarity index 63%
copy from core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
copy to media/java/android/media/tv/ad/ITvAdSessionCallback.aidl
index ce92b6d..f21ef19 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
+++ b/media/java/android/media/tv/ad/ITvAdSessionCallback.aidl
@@ -13,10 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.companion.virtual.camera;
+
+package android.media.tv.ad;
+
+import android.media.tv.ad.ITvAdSession;
/**
- * The configuration of a single virtual camera stream.
+ * Helper interface for ITvAdSession to allow TvAdService to notify the system service when there is
+ * a related event.
* @hide
*/
-parcelable VirtualCameraStreamConfig;
\ No newline at end of file
+oneway interface ITvAdSessionCallback {
+ void onSessionCreated(in ITvAdSession session);
+ void onLayoutSurface(int left, int top, int right, int bottom);
+}
\ No newline at end of file
diff --git a/media/java/android/media/tv/ad/ITvAdSessionWrapper.java b/media/java/android/media/tv/ad/ITvAdSessionWrapper.java
new file mode 100644
index 0000000..4df2783
--- /dev/null
+++ b/media/java/android/media/tv/ad/ITvAdSessionWrapper.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.ad;
+
+import android.content.Context;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.Surface;
+
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
+
+/**
+ * Implements the internal ITvAdSession interface.
+ * @hide
+ */
+public class ITvAdSessionWrapper
+ extends ITvAdSession.Stub implements HandlerCaller.Callback {
+
+ private static final String TAG = "ITvAdSessionWrapper";
+
+ private static final int EXECUTE_MESSAGE_TIMEOUT_SHORT_MILLIS = 1000;
+ private static final int EXECUTE_MESSAGE_TIMEOUT_LONG_MILLIS = 5 * 1000;
+ private static final int DO_RELEASE = 1;
+ private static final int DO_SET_SURFACE = 2;
+ private static final int DO_DISPATCH_SURFACE_CHANGED = 3;
+
+ private final HandlerCaller mCaller;
+ private TvAdService.Session mSessionImpl;
+ private InputChannel mChannel;
+ private TvAdEventReceiver mReceiver;
+
+ public ITvAdSessionWrapper(
+ Context context, TvAdService.Session mSessionImpl, InputChannel channel) {
+ this.mSessionImpl = mSessionImpl;
+ mCaller = new HandlerCaller(context, null, this, true /* asyncHandler */);
+ mChannel = channel;
+ if (channel != null) {
+ mReceiver = new TvAdEventReceiver(channel, context.getMainLooper());
+ }
+ }
+
+ @Override
+ public void release() {
+ mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_RELEASE));
+ }
+
+
+ @Override
+ public void executeMessage(Message msg) {
+ if (mSessionImpl == null) {
+ return;
+ }
+
+ long startTime = System.nanoTime();
+ switch (msg.what) {
+ case DO_RELEASE: {
+ mSessionImpl.release();
+ mSessionImpl = null;
+ if (mReceiver != null) {
+ mReceiver.dispose();
+ mReceiver = null;
+ }
+ if (mChannel != null) {
+ mChannel.dispose();
+ mChannel = null;
+ }
+ break;
+ }
+ case DO_SET_SURFACE: {
+ mSessionImpl.setSurface((Surface) msg.obj);
+ break;
+ }
+ case DO_DISPATCH_SURFACE_CHANGED: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ mSessionImpl.dispatchSurfaceChanged(
+ (Integer) args.argi1, (Integer) args.argi2, (Integer) args.argi3);
+ args.recycle();
+ break;
+ }
+ default: {
+ Log.w(TAG, "Unhandled message code: " + msg.what);
+ break;
+ }
+ }
+ long durationMs = (System.nanoTime() - startTime) / (1000 * 1000);
+ if (durationMs > EXECUTE_MESSAGE_TIMEOUT_SHORT_MILLIS) {
+ Log.w(TAG, "Handling message (" + msg.what + ") took too long time (duration="
+ + durationMs + "ms)");
+ if (durationMs > EXECUTE_MESSAGE_TIMEOUT_LONG_MILLIS) {
+ // TODO: handle timeout
+ }
+ }
+
+ }
+
+ @Override
+ public void startAdService() throws RemoteException {
+
+ }
+
+ @Override
+ public void setSurface(Surface surface) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_SURFACE, surface));
+ }
+
+ @Override
+ public void dispatchSurfaceChanged(int format, int width, int height) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageIIII(DO_DISPATCH_SURFACE_CHANGED, format, width, height, 0));
+ }
+
+ private final class TvAdEventReceiver extends InputEventReceiver {
+ TvAdEventReceiver(InputChannel inputChannel, Looper looper) {
+ super(inputChannel, looper);
+ }
+
+ @Override
+ public void onInputEvent(InputEvent event) {
+ if (mSessionImpl == null) {
+ // The session has been finished.
+ finishInputEvent(event, false);
+ return;
+ }
+
+ int handled = mSessionImpl.dispatchInputEvent(event, this);
+ if (handled != TvAdManager.Session.DISPATCH_IN_PROGRESS) {
+ finishInputEvent(
+ event, handled == TvAdManager.Session.DISPATCH_HANDLED);
+ }
+ }
+ }
+}
diff --git a/media/java/android/media/tv/ad/TvAdManager.java b/media/java/android/media/tv/ad/TvAdManager.java
index 2b52c4b..9c75051 100644
--- a/media/java/android/media/tv/ad/TvAdManager.java
+++ b/media/java/android/media/tv/ad/TvAdManager.java
@@ -17,12 +17,30 @@
package android.media.tv.ad;
import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemService;
import android.content.Context;
+import android.media.tv.TvInputManager;
import android.media.tv.flags.Flags;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
+import android.util.Pools;
+import android.util.SparseArray;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventSender;
+import android.view.Surface;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
/**
* Central system API to the overall client-side TV AD architecture, which arbitrates interaction
@@ -37,10 +55,163 @@
private final ITvAdManager mService;
private final int mUserId;
+ // A mapping from the sequence number of a session to its SessionCallbackRecord.
+ private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap =
+ new SparseArray<>();
+
+ // @GuardedBy("mLock")
+ private final List<TvAdServiceCallbackRecord> mCallbackRecords = new ArrayList<>();
+
+ // A sequence number for the next session to be created. Should be protected by a lock
+ // {@code mSessionCallbackRecordMap}.
+ private int mNextSeq;
+
+ private final Object mLock = new Object();
+ private final ITvAdClient mClient;
+
/** @hide */
public TvAdManager(ITvAdManager service, int userId) {
mService = service;
mUserId = userId;
+ mClient = new ITvAdClient.Stub() {
+ @Override
+ public void onSessionCreated(String serviceId, IBinder token, InputChannel channel,
+ int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for " + token);
+ return;
+ }
+ Session session = null;
+ if (token != null) {
+ session = new Session(token, channel, mService, mUserId, seq,
+ mSessionCallbackRecordMap);
+ } else {
+ mSessionCallbackRecordMap.delete(seq);
+ }
+ record.postSessionCreated(session);
+ }
+ }
+
+ @Override
+ public void onSessionReleased(int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ mSessionCallbackRecordMap.delete(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq:" + seq);
+ return;
+ }
+ record.mSession.releaseInternal();
+ record.postSessionReleased();
+ }
+ }
+
+ @Override
+ public void onLayoutSurface(int left, int top, int right, int bottom, int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postLayoutSurface(left, top, right, bottom);
+ }
+ }
+
+ };
+
+ ITvAdManagerCallback managerCallback =
+ new ITvAdManagerCallback.Stub() {
+ @Override
+ public void onAdServiceAdded(String serviceId) {
+ synchronized (mLock) {
+ for (TvAdServiceCallbackRecord record : mCallbackRecords) {
+ record.postAdServiceAdded(serviceId);
+ }
+ }
+ }
+
+ @Override
+ public void onAdServiceRemoved(String serviceId) {
+ synchronized (mLock) {
+ for (TvAdServiceCallbackRecord record : mCallbackRecords) {
+ record.postAdServiceRemoved(serviceId);
+ }
+ }
+ }
+
+ @Override
+ public void onAdServiceUpdated(String serviceId) {
+ synchronized (mLock) {
+ for (TvAdServiceCallbackRecord record : mCallbackRecords) {
+ record.postAdServiceUpdated(serviceId);
+ }
+ }
+ }
+ };
+ try {
+ if (mService != null) {
+ mService.registerCallback(managerCallback, mUserId);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the complete list of TV AD service on the system.
+ *
+ * @return List of {@link TvAdServiceInfo} for each TV AD service that describes its meta
+ * information.
+ * @hide
+ */
+ @NonNull
+ public List<TvAdServiceInfo> getTvAdServiceList() {
+ try {
+ return mService.getTvAdServiceList(mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Creates a {@link Session} for a given TV AD service.
+ *
+ * <p>The number of sessions that can be created at the same time is limited by the capability
+ * of the given AD service.
+ *
+ * @param serviceId The ID of the AD service.
+ * @param callback A callback used to receive the created session.
+ * @param handler A {@link Handler} that the session creation will be delivered to.
+ * @hide
+ */
+ public void createSession(
+ @NonNull String serviceId,
+ @NonNull String type,
+ @NonNull final TvAdManager.SessionCallback callback,
+ @NonNull Handler handler) {
+ createSessionInternal(serviceId, type, callback, handler);
+ }
+
+ private void createSessionInternal(String serviceId, String type,
+ TvAdManager.SessionCallback callback, Handler handler) {
+ Preconditions.checkNotNull(serviceId);
+ Preconditions.checkNotNull(type);
+ Preconditions.checkNotNull(callback);
+ Preconditions.checkNotNull(handler);
+ TvAdManager.SessionCallbackRecord
+ record = new TvAdManager.SessionCallbackRecord(callback, handler);
+ synchronized (mSessionCallbackRecordMap) {
+ int seq = mNextSeq++;
+ mSessionCallbackRecordMap.put(seq, record);
+ try {
+ mService.createSession(mClient, serviceId, type, seq, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
/**
@@ -48,14 +219,121 @@
* @hide
*/
public static final class Session {
- private final IBinder mToken;
+ static final int DISPATCH_IN_PROGRESS = -1;
+ static final int DISPATCH_NOT_HANDLED = 0;
+ static final int DISPATCH_HANDLED = 1;
+
+ private static final long INPUT_SESSION_NOT_RESPONDING_TIMEOUT = 2500;
private final ITvAdManager mService;
private final int mUserId;
+ private final int mSeq;
+ private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap;
- private Session(IBinder token, ITvAdManager service, int userId) {
+ // For scheduling input event handling on the main thread. This also serves as a lock to
+ // protect pending input events and the input channel.
+ private final InputEventHandler mHandler = new InputEventHandler(Looper.getMainLooper());
+
+ private TvInputManager.Session mInputSession;
+ private final Pools.Pool<PendingEvent> mPendingEventPool = new Pools.SimplePool<>(20);
+ private final SparseArray<PendingEvent> mPendingEvents = new SparseArray<>(20);
+ private TvInputEventSender mSender;
+ private InputChannel mInputChannel;
+ private IBinder mToken;
+
+ private Session(IBinder token, InputChannel channel, ITvAdManager service, int userId,
+ int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) {
mToken = token;
+ mInputChannel = channel;
mService = service;
mUserId = userId;
+ mSeq = seq;
+ mSessionCallbackRecordMap = sessionCallbackRecordMap;
+ }
+
+ /**
+ * Releases this session.
+ */
+ public void release() {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.releaseSession(mToken, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ releaseInternal();
+ }
+
+ /**
+ * Sets the {@link android.view.Surface} for this session.
+ *
+ * @param surface A {@link android.view.Surface} used to render AD.
+ */
+ public void setSurface(Surface surface) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ // surface can be null.
+ try {
+ mService.setSurface(mToken, surface, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Notifies of any structural changes (format or size) of the surface passed in
+ * {@link #setSurface}.
+ *
+ * @param format The new PixelFormat of the surface.
+ * @param width The new width of the surface.
+ * @param height The new height of the surface.
+ */
+ public void dispatchSurfaceChanged(int format, int width, int height) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.dispatchSurfaceChanged(mToken, format, width, height, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private void flushPendingEventsLocked() {
+ mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT);
+
+ final int count = mPendingEvents.size();
+ for (int i = 0; i < count; i++) {
+ int seq = mPendingEvents.keyAt(i);
+ Message msg = mHandler.obtainMessage(
+ InputEventHandler.MSG_FLUSH_INPUT_EVENT, seq, 0);
+ msg.setAsynchronous(true);
+ msg.sendToTarget();
+ }
+ }
+
+ private void releaseInternal() {
+ mToken = null;
+ synchronized (mHandler) {
+ if (mInputChannel != null) {
+ if (mSender != null) {
+ flushPendingEventsLocked();
+ mSender.dispose();
+ mSender = null;
+ }
+ mInputChannel.dispose();
+ mInputChannel = null;
+ }
+ }
+ synchronized (mSessionCallbackRecordMap) {
+ mSessionCallbackRecordMap.delete(mSeq);
+ }
}
void startAdService() {
@@ -69,5 +347,324 @@
throw e.rethrowFromSystemServer();
}
}
+
+ private final class InputEventHandler extends Handler {
+ public static final int MSG_SEND_INPUT_EVENT = 1;
+ public static final int MSG_TIMEOUT_INPUT_EVENT = 2;
+ public static final int MSG_FLUSH_INPUT_EVENT = 3;
+
+ InputEventHandler(Looper looper) {
+ super(looper, null, true);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SEND_INPUT_EVENT: {
+ sendInputEventAndReportResultOnMainLooper((PendingEvent) msg.obj);
+ return;
+ }
+ case MSG_TIMEOUT_INPUT_EVENT: {
+ finishedInputEvent(msg.arg1, false, true);
+ return;
+ }
+ case MSG_FLUSH_INPUT_EVENT: {
+ finishedInputEvent(msg.arg1, false, false);
+ return;
+ }
+ }
+ }
+ }
+
+ // Assumes the event has already been removed from the queue.
+ void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) {
+ p.mHandled = handled;
+ if (p.mEventHandler.getLooper().isCurrentThread()) {
+ // Already running on the callback handler thread so we can send the callback
+ // immediately.
+ p.run();
+ } else {
+ // Post the event to the callback handler thread.
+ // In this case, the callback will be responsible for recycling the event.
+ Message msg = Message.obtain(p.mEventHandler, p);
+ msg.setAsynchronous(true);
+ msg.sendToTarget();
+ }
+ }
+
+ // Must be called on the main looper
+ private void sendInputEventAndReportResultOnMainLooper(PendingEvent p) {
+ synchronized (mHandler) {
+ int result = sendInputEventOnMainLooperLocked(p);
+ if (result == DISPATCH_IN_PROGRESS) {
+ return;
+ }
+ }
+
+ invokeFinishedInputEventCallback(p, false);
+ }
+
+ private int sendInputEventOnMainLooperLocked(PendingEvent p) {
+ if (mInputChannel != null) {
+ if (mSender == null) {
+ mSender = new TvInputEventSender(mInputChannel, mHandler.getLooper());
+ }
+
+ final InputEvent event = p.mEvent;
+ final int seq = event.getSequenceNumber();
+ if (mSender.sendInputEvent(seq, event)) {
+ mPendingEvents.put(seq, p);
+ Message msg = mHandler.obtainMessage(
+ InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
+ msg.setAsynchronous(true);
+ mHandler.sendMessageDelayed(msg, INPUT_SESSION_NOT_RESPONDING_TIMEOUT);
+ return DISPATCH_IN_PROGRESS;
+ }
+
+ Log.w(TAG, "Unable to send input event to session: " + mToken + " dropping:"
+ + event);
+ }
+ return DISPATCH_NOT_HANDLED;
+ }
+
+ void finishedInputEvent(int seq, boolean handled, boolean timeout) {
+ final PendingEvent p;
+ synchronized (mHandler) {
+ int index = mPendingEvents.indexOfKey(seq);
+ if (index < 0) {
+ return; // spurious, event already finished or timed out
+ }
+
+ p = mPendingEvents.valueAt(index);
+ mPendingEvents.removeAt(index);
+
+ if (timeout) {
+ Log.w(TAG, "Timeout waiting for session to handle input event after "
+ + INPUT_SESSION_NOT_RESPONDING_TIMEOUT + " ms: " + mToken);
+ } else {
+ mHandler.removeMessages(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
+ }
+ }
+
+ invokeFinishedInputEventCallback(p, handled);
+ }
+
+ private void recyclePendingEventLocked(PendingEvent p) {
+ p.recycle();
+ mPendingEventPool.release(p);
+ }
+
+ /**
+ * Callback that is invoked when an input event that was dispatched to this session has been
+ * finished.
+ *
+ * @hide
+ */
+ public interface FinishedInputEventCallback {
+ /**
+ * Called when the dispatched input event is finished.
+ *
+ * @param token A token passed to {@link #dispatchInputEvent}.
+ * @param handled {@code true} if the dispatched input event was handled properly.
+ * {@code false} otherwise.
+ */
+ void onFinishedInputEvent(Object token, boolean handled);
+ }
+
+ private final class TvInputEventSender extends InputEventSender {
+ TvInputEventSender(InputChannel inputChannel, Looper looper) {
+ super(inputChannel, looper);
+ }
+
+ @Override
+ public void onInputEventFinished(int seq, boolean handled) {
+ finishedInputEvent(seq, handled, false);
+ }
+ }
+
+ private final class PendingEvent implements Runnable {
+ public InputEvent mEvent;
+ public Object mEventToken;
+ public Session.FinishedInputEventCallback mCallback;
+ public Handler mEventHandler;
+ public boolean mHandled;
+
+ public void recycle() {
+ mEvent = null;
+ mEventToken = null;
+ mCallback = null;
+ mEventHandler = null;
+ mHandled = false;
+ }
+
+ @Override
+ public void run() {
+ mCallback.onFinishedInputEvent(mEventToken, mHandled);
+
+ synchronized (mEventHandler) {
+ recyclePendingEventLocked(this);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Interface used to receive the created session.
+ * @hide
+ */
+ public abstract static class SessionCallback {
+ /**
+ * This is called after {@link TvAdManager#createSession} has been processed.
+ *
+ * @param session A {@link TvAdManager.Session} instance created. This can be
+ * {@code null} if the creation request failed.
+ */
+ public void onSessionCreated(@Nullable Session session) {
+ }
+
+ /**
+ * This is called when {@link TvAdManager.Session} is released.
+ * This typically happens when the process hosting the session has crashed or been killed.
+ *
+ * @param session the {@link TvAdManager.Session} instance released.
+ */
+ public void onSessionReleased(@NonNull Session session) {
+ }
+
+ /**
+ * This is called when {@link TvAdService.Session#layoutSurface} is called to
+ * change the layout of surface.
+ *
+ * @param session A {@link TvAdManager.Session} associated with this callback.
+ * @param left Left position.
+ * @param top Top position.
+ * @param right Right position.
+ * @param bottom Bottom position.
+ */
+ public void onLayoutSurface(Session session, int left, int top, int right, int bottom) {
+ }
+
+ }
+
+ /**
+ * Callback used to monitor status of the TV AD service.
+ * @hide
+ */
+ public abstract static class TvAdServiceCallback {
+ /**
+ * This is called when a TV AD service is added to the system.
+ *
+ * <p>Normally it happens when the user installs a new TV AD service package that implements
+ * {@link TvAdService} interface.
+ *
+ * @param serviceId The ID of the TV AD service.
+ */
+ public void onAdServiceAdded(@NonNull String serviceId) {
+ }
+
+ /**
+ * This is called when a TV AD service is removed from the system.
+ *
+ * <p>Normally it happens when the user uninstalls the previously installed TV AD service
+ * package.
+ *
+ * @param serviceId The ID of the TV AD service.
+ */
+ public void onAdServiceRemoved(@NonNull String serviceId) {
+ }
+
+ /**
+ * This is called when a TV AD service is updated on the system.
+ *
+ * <p>Normally it happens when a previously installed TV AD service package is re-installed
+ * or a newer version of the package exists becomes available/unavailable.
+ *
+ * @param serviceId The ID of the TV AD service.
+ */
+ public void onAdServiceUpdated(@NonNull String serviceId) {
+ }
+
+ }
+
+ private static final class SessionCallbackRecord {
+ private final SessionCallback mSessionCallback;
+ private final Handler mHandler;
+ private Session mSession;
+
+ SessionCallbackRecord(SessionCallback sessionCallback, Handler handler) {
+ mSessionCallback = sessionCallback;
+ mHandler = handler;
+ }
+
+ void postSessionCreated(final Session session) {
+ mSession = session;
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onSessionCreated(session);
+ }
+ });
+ }
+
+ void postSessionReleased() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onSessionReleased(mSession);
+ }
+ });
+ }
+
+ void postLayoutSurface(final int left, final int top, final int right,
+ final int bottom) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onLayoutSurface(mSession, left, top, right, bottom);
+ }
+ });
+ }
+ }
+
+ private static final class TvAdServiceCallbackRecord {
+ private final TvAdServiceCallback mCallback;
+ private final Executor mExecutor;
+
+ TvAdServiceCallbackRecord(TvAdServiceCallback callback, Executor executor) {
+ mCallback = callback;
+ mExecutor = executor;
+ }
+
+ public TvAdServiceCallback getCallback() {
+ return mCallback;
+ }
+
+ public void postAdServiceAdded(final String serviceId) {
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onAdServiceAdded(serviceId);
+ }
+ });
+ }
+
+ public void postAdServiceRemoved(final String serviceId) {
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onAdServiceRemoved(serviceId);
+ }
+ });
+ }
+
+ public void postAdServiceUpdated(final String serviceId) {
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onAdServiceUpdated(serviceId);
+ }
+ });
+ }
}
}
diff --git a/media/java/android/media/tv/ad/TvAdService.java b/media/java/android/media/tv/ad/TvAdService.java
index 6897a78..6995703 100644
--- a/media/java/android/media/tv/ad/TvAdService.java
+++ b/media/java/android/media/tv/ad/TvAdService.java
@@ -16,8 +16,37 @@
package android.media.tv.ad;
+import android.annotation.CallSuper;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SuppressLint;
import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.InputChannel;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.internal.os.SomeArgs;
+
+import java.util.ArrayList;
+import java.util.List;
/**
* The TvAdService class represents a TV client-side advertisement service.
@@ -36,9 +65,123 @@
public static final String SERVICE_META_DATA = "android.media.tv.ad.service";
/**
+ * This is the interface name that a service implementing a TV AD service should
+ * say that it supports -- that is, this is the action it uses for its intent filter. To be
+ * supported, the service must also require the
+ * android.Manifest.permission#BIND_TV_AD_SERVICE permission so that other
+ * applications cannot abuse it.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE = "android.media.tv.ad.TvAdService";
+
+ private final Handler mServiceHandler = new ServiceHandler();
+ private final RemoteCallbackList<ITvAdServiceCallback> mCallbacks = new RemoteCallbackList<>();
+
+ @Override
+ @Nullable
+ public final IBinder onBind(@NonNull Intent intent) {
+ ITvAdService.Stub tvAdServiceBinder = new ITvAdService.Stub() {
+ @Override
+ public void registerCallback(ITvAdServiceCallback cb) {
+ if (cb != null) {
+ mCallbacks.register(cb);
+ }
+ }
+
+ @Override
+ public void unregisterCallback(ITvAdServiceCallback cb) {
+ if (cb != null) {
+ mCallbacks.unregister(cb);
+ }
+ }
+
+ @Override
+ public void createSession(InputChannel channel, ITvAdSessionCallback cb,
+ String serviceId, String type) {
+ if (cb == null) {
+ return;
+ }
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = channel;
+ args.arg2 = cb;
+ args.arg3 = serviceId;
+ args.arg4 = type;
+ mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args)
+ .sendToTarget();
+ }
+
+ @Override
+ public void sendAppLinkCommand(Bundle command) {
+ onAppLinkCommand(command);
+ }
+ };
+ return tvAdServiceBinder;
+ }
+
+ /**
+ * Called when app link command is received.
+ */
+ public void onAppLinkCommand(@NonNull Bundle command) {
+ }
+
+
+ /**
+ * Returns a concrete implementation of {@link Session}.
+ *
+ * <p>May return {@code null} if this TV AD service fails to create a session for some
+ * reason.
+ *
+ * @param serviceId The ID of the TV AD associated with the session.
+ * @param type The type of the TV AD associated with the session.
+ */
+ @Nullable
+ public abstract Session onCreateSession(@NonNull String serviceId, @NonNull String type);
+
+ /**
* Base class for derived classes to implement to provide a TV AD session.
*/
public abstract static class Session implements KeyEvent.Callback {
+ private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState();
+
+ private final Object mLock = new Object();
+ // @GuardedBy("mLock")
+ private ITvAdSessionCallback mSessionCallback;
+ // @GuardedBy("mLock")
+ private final List<Runnable> mPendingActions = new ArrayList<>();
+ private final Context mContext;
+ final Handler mHandler;
+ private final WindowManager mWindowManager;
+ private Surface mSurface;
+
+
+ /**
+ * Creates a new Session.
+ *
+ * @param context The context of the application
+ */
+ public Session(@NonNull Context context) {
+ mContext = context;
+ mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ mHandler = new Handler(context.getMainLooper());
+ }
+
+ /**
+ * Releases TvAdService session.
+ */
+ public abstract void onRelease();
+
+ void release() {
+ onRelease();
+ if (mSurface != null) {
+ mSurface.release();
+ mSurface = null;
+ }
+ synchronized (mLock) {
+ mSessionCallback = null;
+ mPendingActions.clear();
+ }
+ }
+
/**
* Starts TvAdService session.
*/
@@ -48,21 +191,264 @@
void startAdService() {
onStartAdService();
}
- }
- /**
- * Implements the internal ITvAdService interface.
- */
- public static class ITvAdSessionWrapper extends ITvAdSession.Stub {
- private final Session mSessionImpl;
-
- public ITvAdSessionWrapper(Session mSessionImpl) {
- this.mSessionImpl = mSessionImpl;
+ @Override
+ public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
+ return false;
}
@Override
- public void startAdService() {
- mSessionImpl.startAdService();
+ public boolean onKeyLongPress(int keyCode, @NonNull KeyEvent event) {
+ return false;
}
+
+ @Override
+ public boolean onKeyMultiple(int keyCode, int count, @NonNull KeyEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
+ return false;
+ }
+
+ /**
+ * Implement this method to handle touch screen motion events on the current session.
+ *
+ * @param event The motion event being received.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to
+ * be handled by the next receiver, return {@code false}.
+ * @see View#onTouchEvent
+ */
+ public boolean onTouchEvent(@NonNull MotionEvent event) {
+ return false;
+ }
+
+ /**
+ * Implement this method to handle trackball events on the current session.
+ *
+ * @param event The motion event being received.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to
+ * be handled by the next receiver, return {@code false}.
+ * @see View#onTrackballEvent
+ */
+ public boolean onTrackballEvent(@NonNull MotionEvent event) {
+ return false;
+ }
+
+ /**
+ * Implement this method to handle generic motion events on the current session.
+ *
+ * @param event The motion event being received.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to
+ * be handled by the next receiver, return {@code false}.
+ * @see View#onGenericMotionEvent
+ */
+ public boolean onGenericMotionEvent(@NonNull MotionEvent event) {
+ return false;
+ }
+
+ /**
+ * Assigns a size and position to the surface passed in {@link #onSetSurface}. The position
+ * is relative to the overlay view that sits on top of this surface.
+ *
+ * @param left Left position in pixels, relative to the overlay view.
+ * @param top Top position in pixels, relative to the overlay view.
+ * @param right Right position in pixels, relative to the overlay view.
+ * @param bottom Bottom position in pixels, relative to the overlay view.
+ */
+ @CallSuper
+ public void layoutSurface(final int left, final int top, final int right,
+ final int bottom) {
+ if (left > right || top > bottom) {
+ throw new IllegalArgumentException("Invalid parameter");
+ }
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "layoutSurface (l=" + left + ", t=" + top
+ + ", r=" + right + ", b=" + bottom + ",)");
+ }
+ if (mSessionCallback != null) {
+ mSessionCallback.onLayoutSurface(left, top, right, bottom);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in layoutSurface", e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Called when the application sets the surface.
+ *
+ * <p>The TV AD service should render AD UI onto the given surface. When called with
+ * {@code null}, the AD service should immediately free any references to the currently set
+ * surface and stop using it.
+ *
+ * @param surface The surface to be used for AD UI rendering. Can be {@code null}.
+ * @return {@code true} if the surface was set successfully, {@code false} otherwise.
+ */
+ public abstract boolean onSetSurface(@Nullable Surface surface);
+
+ /**
+ * Called after any structural changes (format or size) have been made to the surface passed
+ * in {@link #onSetSurface}. This method is always called at least once, after
+ * {@link #onSetSurface} is called with non-null surface.
+ *
+ * @param format The new {@link PixelFormat} of the surface.
+ * @param width The new width of the surface.
+ * @param height The new height of the surface.
+ */
+ public void onSurfaceChanged(@PixelFormat.Format int format, int width, int height) {
+ }
+
+ /**
+ * Takes care of dispatching incoming input events and tells whether the event was handled.
+ */
+ int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
+ if (DEBUG) Log.d(TAG, "dispatchInputEvent(" + event + ")");
+ if (event instanceof KeyEvent) {
+ KeyEvent keyEvent = (KeyEvent) event;
+ if (keyEvent.dispatch(this, mDispatcherState, this)) {
+ return TvAdManager.Session.DISPATCH_HANDLED;
+ }
+
+ // TODO: special handlings of navigation keys and media keys
+ } else if (event instanceof MotionEvent) {
+ MotionEvent motionEvent = (MotionEvent) event;
+ final int source = motionEvent.getSource();
+ if (motionEvent.isTouchEvent()) {
+ if (onTouchEvent(motionEvent)) {
+ return TvAdManager.Session.DISPATCH_HANDLED;
+ }
+ } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+ if (onTrackballEvent(motionEvent)) {
+ return TvAdManager.Session.DISPATCH_HANDLED;
+ }
+ } else {
+ if (onGenericMotionEvent(motionEvent)) {
+ return TvAdManager.Session.DISPATCH_HANDLED;
+ }
+ }
+ }
+ // TODO: handle overlay view
+ return TvAdManager.Session.DISPATCH_NOT_HANDLED;
+ }
+
+
+ private void initialize(ITvAdSessionCallback callback) {
+ synchronized (mLock) {
+ mSessionCallback = callback;
+ for (Runnable runnable : mPendingActions) {
+ runnable.run();
+ }
+ mPendingActions.clear();
+ }
+ }
+
+ /**
+ * Calls {@link #onSetSurface}.
+ */
+ void setSurface(Surface surface) {
+ onSetSurface(surface);
+ if (mSurface != null) {
+ mSurface.release();
+ }
+ mSurface = surface;
+ // TODO: Handle failure.
+ }
+
+ /**
+ * Calls {@link #onSurfaceChanged}.
+ */
+ void dispatchSurfaceChanged(int format, int width, int height) {
+ if (DEBUG) {
+ Log.d(TAG, "dispatchSurfaceChanged(format=" + format + ", width=" + width
+ + ", height=" + height + ")");
+ }
+ onSurfaceChanged(format, width, height);
+ }
+
+ private void executeOrPostRunnableOnMainThread(Runnable action) {
+ synchronized (mLock) {
+ if (mSessionCallback == null) {
+ // The session is not initialized yet.
+ mPendingActions.add(action);
+ } else {
+ if (mHandler.getLooper().isCurrentThread()) {
+ action.run();
+ } else {
+ // Posts the runnable if this is not called from the main thread
+ mHandler.post(action);
+ }
+ }
+ }
+ }
+ }
+
+
+ @SuppressLint("HandlerLeak")
+ private final class ServiceHandler extends Handler {
+ private static final int DO_CREATE_SESSION = 1;
+ private static final int DO_NOTIFY_SESSION_CREATED = 2;
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case DO_CREATE_SESSION: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ InputChannel channel = (InputChannel) args.arg1;
+ ITvAdSessionCallback cb = (ITvAdSessionCallback) args.arg2;
+ String serviceId = (String) args.arg3;
+ String type = (String) args.arg4;
+ args.recycle();
+ TvAdService.Session sessionImpl = onCreateSession(serviceId, type);
+ if (sessionImpl == null) {
+ try {
+ // Failed to create a session.
+ cb.onSessionCreated(null);
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in onSessionCreated", e);
+ }
+ return;
+ }
+ ITvAdSession stub =
+ new ITvAdSessionWrapper(TvAdService.this, sessionImpl, channel);
+
+ SomeArgs someArgs = SomeArgs.obtain();
+ someArgs.arg1 = sessionImpl;
+ someArgs.arg2 = stub;
+ someArgs.arg3 = cb;
+ mServiceHandler.obtainMessage(
+ DO_NOTIFY_SESSION_CREATED, someArgs).sendToTarget();
+ return;
+ }
+ case DO_NOTIFY_SESSION_CREATED: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ Session sessionImpl = (Session) args.arg1;
+ ITvAdSession stub = (ITvAdSession) args.arg2;
+ ITvAdSessionCallback cb = (ITvAdSessionCallback) args.arg3;
+ try {
+ cb.onSessionCreated(stub);
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in onSessionCreated", e);
+ }
+ if (sessionImpl != null) {
+ sessionImpl.initialize(cb);
+ }
+ args.recycle();
+ return;
+ }
+ default: {
+ Log.w(TAG, "Unhandled message code: " + msg.what);
+ return;
+ }
+ }
+ }
+
}
}
diff --git a/media/java/android/media/tv/ad/TvAdServiceInfo.java b/media/java/android/media/tv/ad/TvAdServiceInfo.java
index ed04f1f..45dc89d 100644
--- a/media/java/android/media/tv/ad/TvAdServiceInfo.java
+++ b/media/java/android/media/tv/ad/TvAdServiceInfo.java
@@ -24,6 +24,7 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.os.Parcel;
import android.os.Parcelable;
@@ -63,8 +64,7 @@
if (context == null) {
throw new IllegalArgumentException("context cannot be null.");
}
- // TODO: use a constant
- Intent intent = new Intent("android.media.tv.ad.TvAdService").setComponent(component);
+ Intent intent = new Intent(TvAdService.SERVICE_INTERFACE).setComponent(component);
ResolveInfo resolveInfo = context.getPackageManager().resolveService(
intent, PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
if (resolveInfo == null) {
@@ -80,6 +80,7 @@
mService = resolveInfo;
mId = id;
+ mTypes.addAll(types);
}
private TvAdServiceInfo(ResolveInfo service, String id, List<String> types) {
@@ -147,9 +148,8 @@
ResolveInfo resolveInfo, Context context, List<String> types) {
ServiceInfo serviceInfo = resolveInfo.serviceInfo;
PackageManager pm = context.getPackageManager();
- // TODO: use constant for the metadata
try (XmlResourceParser parser =
- serviceInfo.loadXmlMetaData(pm, "android.media.tv.ad.service")) {
+ serviceInfo.loadXmlMetaData(pm, TvAdService.SERVICE_META_DATA)) {
if (parser == null) {
throw new IllegalStateException(
"No " + "android.media.tv.ad.service"
@@ -171,7 +171,15 @@
+ XML_START_TAG_NAME + " tag for " + serviceInfo.name);
}
- // TODO: parse attributes
+ TypedArray sa = resources.obtainAttributes(attrs,
+ com.android.internal.R.styleable.TvAdService);
+ CharSequence[] textArr = sa.getTextArray(
+ com.android.internal.R.styleable.TvAdService_adServiceTypes);
+ for (CharSequence cs : textArr) {
+ types.add(cs.toString().toLowerCase());
+ }
+
+ sa.recycle();
} catch (IOException | XmlPullParserException e) {
throw new IllegalStateException(
"Failed reading meta-data for " + serviceInfo.packageName, e);
diff --git a/media/java/android/media/tv/ad/TvAdView.java b/media/java/android/media/tv/ad/TvAdView.java
index 1a3771a..5e67fe9 100644
--- a/media/java/android/media/tv/ad/TvAdView.java
+++ b/media/java/android/media/tv/ad/TvAdView.java
@@ -16,8 +16,20 @@
package android.media.tv.ad;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.graphics.PixelFormat;
+import android.os.Handler;
+import android.util.AttributeSet;
import android.util.Log;
+import android.util.Xml;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
import android.view.ViewGroup;
/**
@@ -28,18 +40,166 @@
private static final String TAG = "TvAdView";
private static final boolean DEBUG = false;
- // TODO: create session
- private TvAdManager.Session mSession;
+ private final TvAdManager mTvAdManager;
- public TvAdView(Context context) {
- super(context, /* attrs = */null, /* defStyleAttr = */0);
+ private final Handler mHandler = new Handler();
+ private TvAdManager.Session mSession;
+ private MySessionCallback mSessionCallback;
+
+ private final AttributeSet mAttrs;
+ private final int mDefStyleAttr;
+ private final XmlResourceParser mParser;
+
+ private SurfaceView mSurfaceView;
+ private Surface mSurface;
+
+ private boolean mSurfaceChanged;
+ private int mSurfaceFormat;
+ private int mSurfaceWidth;
+ private int mSurfaceHeight;
+
+ private boolean mUseRequestedSurfaceLayout;
+ private int mSurfaceViewLeft;
+ private int mSurfaceViewRight;
+ private int mSurfaceViewTop;
+ private int mSurfaceViewBottom;
+
+
+
+ private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ if (DEBUG) {
+ Log.d(TAG, "surfaceChanged(holder=" + holder + ", format=" + format
+ + ", width=" + width + ", height=" + height + ")");
+ }
+ mSurfaceFormat = format;
+ mSurfaceWidth = width;
+ mSurfaceHeight = height;
+ mSurfaceChanged = true;
+ dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight);
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ mSurface = holder.getSurface();
+ setSessionSurface(mSurface);
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ mSurface = null;
+ mSurfaceChanged = false;
+ setSessionSurface(null);
+ }
+ };
+
+
+ public TvAdView(@NonNull Context context) {
+ this(context, null, 0);
+ }
+
+ public TvAdView(@NonNull Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public TvAdView(@NonNull Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ int sourceResId = Resources.getAttributeSetSourceResId(attrs);
+ if (sourceResId != Resources.ID_NULL) {
+ Log.d(TAG, "Build local AttributeSet");
+ mParser = context.getResources().getXml(sourceResId);
+ mAttrs = Xml.asAttributeSet(mParser);
+ } else {
+ Log.d(TAG, "Use passed in AttributeSet");
+ mParser = null;
+ mAttrs = attrs;
+ }
+ mDefStyleAttr = defStyleAttr;
+ resetSurfaceView();
+ mTvAdManager = (TvAdManager) getContext().getSystemService(Context.TV_AD_SERVICE);
}
@Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ public void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (DEBUG) {
- Log.d(TAG,
- "onLayout (left=" + l + ", top=" + t + ", right=" + r + ", bottom=" + b + ",)");
+ Log.d(TAG, "onLayout (left=" + left + ", top=" + top + ", right=" + right
+ + ", bottom=" + bottom + ",)");
+ }
+ if (mUseRequestedSurfaceLayout) {
+ mSurfaceView.layout(mSurfaceViewLeft, mSurfaceViewTop, mSurfaceViewRight,
+ mSurfaceViewBottom);
+ } else {
+ mSurfaceView.layout(0, 0, right - left, bottom - top);
+ }
+ }
+
+ @Override
+ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ mSurfaceView.measure(widthMeasureSpec, heightMeasureSpec);
+ int width = mSurfaceView.getMeasuredWidth();
+ int height = mSurfaceView.getMeasuredHeight();
+ int childState = mSurfaceView.getMeasuredState();
+ setMeasuredDimension(resolveSizeAndState(width, widthMeasureSpec, childState),
+ resolveSizeAndState(height, heightMeasureSpec,
+ childState << MEASURED_HEIGHT_STATE_SHIFT));
+ }
+
+ @Override
+ public void onVisibilityChanged(@NonNull View changedView, int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+ mSurfaceView.setVisibility(visibility);
+ }
+
+ private void resetSurfaceView() {
+ if (mSurfaceView != null) {
+ mSurfaceView.getHolder().removeCallback(mSurfaceHolderCallback);
+ removeView(mSurfaceView);
+ }
+ mSurface = null;
+ mSurfaceView = new SurfaceView(getContext(), mAttrs, mDefStyleAttr) {
+ @Override
+ protected void updateSurface() {
+ super.updateSurface();
+ }};
+ // The surface view's content should be treated as secure all the time.
+ mSurfaceView.setSecure(true);
+ mSurfaceView.getHolder().addCallback(mSurfaceHolderCallback);
+ mSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
+
+ mSurfaceView.setZOrderOnTop(false);
+ mSurfaceView.setZOrderMediaOverlay(true);
+
+ addView(mSurfaceView);
+ }
+
+ private void setSessionSurface(Surface surface) {
+ if (mSession == null) {
+ return;
+ }
+ mSession.setSurface(surface);
+ }
+
+ private void dispatchSurfaceChanged(int format, int width, int height) {
+ if (mSession == null) {
+ return;
+ }
+ //mSession.dispatchSurfaceChanged(format, width, height);
+ }
+
+ /**
+ * Prepares the AD service of corresponding {@link TvAdService}.
+ *
+ * @param serviceId the AD service ID, which can be found in TvAdServiceInfo#getId().
+ */
+ public void prepareAdService(@NonNull String serviceId, @NonNull String type) {
+ if (DEBUG) {
+ Log.d(TAG, "prepareAdService");
+ }
+ mSessionCallback = new TvAdView.MySessionCallback(serviceId);
+ if (mTvAdManager != null) {
+ mTvAdManager.createSession(serviceId, type, mSessionCallback, mHandler);
}
}
@@ -54,4 +214,75 @@
mSession.startAdService();
}
}
+
+ private class MySessionCallback extends TvAdManager.SessionCallback {
+ final String mServiceId;
+
+ MySessionCallback(String serviceId) {
+ mServiceId = serviceId;
+ }
+
+ @Override
+ public void onSessionCreated(TvAdManager.Session session) {
+ if (DEBUG) {
+ Log.d(TAG, "onSessionCreated()");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onSessionCreated - session already created");
+ // This callback is obsolete.
+ if (session != null) {
+ session.release();
+ }
+ return;
+ }
+ mSession = session;
+ if (session != null) {
+ // mSurface may not be ready yet as soon as starting an application.
+ // In the case, we don't send Session.setSurface(null) unnecessarily.
+ // setSessionSurface will be called in surfaceCreated.
+ if (mSurface != null) {
+ setSessionSurface(mSurface);
+ if (mSurfaceChanged) {
+ dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight);
+ }
+ }
+ } else {
+ // Failed to create
+ // Todo: forward error to Tv App
+ mSessionCallback = null;
+ }
+ }
+
+ @Override
+ public void onSessionReleased(TvAdManager.Session session) {
+ if (DEBUG) {
+ Log.d(TAG, "onSessionReleased()");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onSessionReleased - session not created");
+ return;
+ }
+ mSessionCallback = null;
+ mSession = null;
+ }
+
+ @Override
+ public void onLayoutSurface(
+ TvAdManager.Session session, int left, int top, int right, int bottom) {
+ if (DEBUG) {
+ Log.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top + ", right="
+ + right + ", bottom=" + bottom + ",)");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onLayoutSurface - session not created");
+ return;
+ }
+ mSurfaceViewLeft = left;
+ mSurfaceViewTop = top;
+ mSurfaceViewRight = right;
+ mSurfaceViewBottom = bottom;
+ mUseRequestedSurfaceLayout = true;
+ requestLayout();
+ }
+ }
}
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
index 7739184..e3dba03 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
@@ -48,6 +48,7 @@
void onRequestCurrentChannelLcn(int seq);
void onRequestStreamVolume(int seq);
void onRequestTrackInfoList(int seq);
+ void onRequestSelectedTrackInfo(int seq);
void onRequestCurrentTvInputId(int seq);
void onRequestTimeShiftMode(int seq);
void onRequestAvailableSpeeds(int seq);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
index 41cbe4a..4316d05 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
@@ -102,6 +102,8 @@
int UserId);
void notifyAdResponse(in IBinder sessionToken, in AdResponse response, int UserId);
void notifyAdBufferConsumed(in IBinder sessionToken, in AdBuffer buffer, int userId);
+ void sendSelectedTrackInfo(in IBinder sessionToken, in List<TvTrackInfo> tracks,
+ int userId);
void createMediaView(in IBinder sessionToken, in IBinder windowToken, in Rect frame,
int userId);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
index 052bc3d..ba7cf13 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
@@ -78,6 +78,7 @@
void notifyBroadcastInfoResponse(in BroadcastInfoResponse response);
void notifyAdResponse(in AdResponse response);
void notifyAdBufferConsumed(in AdBuffer buffer);
+ void sendSelectedTrackInfo(in List<TvTrackInfo> tracks);
void createMediaView(in IBinder windowToken, in Rect frame);
void relayoutMediaView(in Rect frame);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
index 9e43e79..416b8f1 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
@@ -50,6 +50,7 @@
void onRequestCurrentTvInputId();
void onRequestTimeShiftMode();
void onRequestAvailableSpeeds();
+ void onRequestSelectedTrackInfo();
void onRequestStartRecording(in String requestId, in Uri programUri);
void onRequestStopRecording(in String recordingId);
void onRequestScheduleRecording(in String requestId, in String inputId, in Uri channelUri,
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
index 253ade8..518b08a 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
@@ -102,6 +102,7 @@
private static final int DO_NOTIFY_RECORDING_SCHEDULED = 45;
private static final int DO_SEND_TIME_SHIFT_MODE = 46;
private static final int DO_SEND_AVAILABLE_SPEEDS = 47;
+ private static final int DO_SEND_SELECTED_TRACK_INFO = 48;
private final HandlerCaller mCaller;
private Session mSessionImpl;
@@ -247,6 +248,10 @@
args.recycle();
break;
}
+ case DO_SEND_SELECTED_TRACK_INFO: {
+ mSessionImpl.sendSelectedTrackInfo((List<TvTrackInfo>) msg.obj);
+ break;
+ }
case DO_NOTIFY_VIDEO_AVAILABLE: {
mSessionImpl.notifyVideoAvailable();
break;
@@ -526,6 +531,12 @@
}
@Override
+ public void sendSelectedTrackInfo(List<TvTrackInfo> tracks) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageO(DO_SEND_SELECTED_TRACK_INFO, tracks));
+ }
+
+ @Override
public void notifyTracksChanged(List<TvTrackInfo> tracks) {
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_NOTIFY_TRACKS_CHANGED, tracks));
}
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index 7cce84a..bf4379f 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -33,7 +33,6 @@
import android.media.tv.TvInputManager;
import android.media.tv.TvRecordingInfo;
import android.media.tv.TvTrackInfo;
-import android.media.tv.interactive.TvInteractiveAppService.Session;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -506,6 +505,18 @@
}
@Override
+ public void onRequestSelectedTrackInfo(int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postRequestSelectedTrackInfo();
+ }
+ }
+
+ @Override
public void onRequestCurrentTvInputId(int seq) {
synchronized (mSessionCallbackRecordMap) {
SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
@@ -1209,6 +1220,18 @@
}
}
+ void sendSelectedTrackInfo(@NonNull List<TvTrackInfo> tracks) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.sendSelectedTrackInfo(mToken, tracks, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
void sendCurrentTvInputId(@Nullable String inputId) {
if (mToken == null) {
Log.w(TAG, "The session has been already released");
@@ -2108,6 +2131,15 @@
});
}
+ void postRequestSelectedTrackInfo() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onRequestSelectedTrackInfo(mSession);
+ }
+ });
+ }
+
void postRequestCurrentTvInputId() {
mHandler.post(new Runnable() {
@Override
@@ -2378,6 +2410,15 @@
}
/**
+ * This is called when {@link TvInteractiveAppService.Session#requestSelectedTrackInfo()} is
+ * called.
+ *
+ * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
+ */
+ public void onRequestSelectedTrackInfo(Session session) {
+ }
+
+ /**
* This is called when {@link TvInteractiveAppService.Session#requestCurrentTvInputId} is
* called.
*
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 2419404..7936403 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -112,7 +112,8 @@
PLAYBACK_COMMAND_TYPE_TUNE_PREV,
PLAYBACK_COMMAND_TYPE_STOP,
PLAYBACK_COMMAND_TYPE_SET_STREAM_VOLUME,
- PLAYBACK_COMMAND_TYPE_SELECT_TRACK
+ PLAYBACK_COMMAND_TYPE_SELECT_TRACK,
+ PLAYBACK_COMMAND_TYPE_FREEZE
})
public @interface PlaybackCommandType {}
@@ -142,8 +143,11 @@
* Playback command type: select the given track.
*/
public static final String PLAYBACK_COMMAND_TYPE_SELECT_TRACK = "select_track";
-
-
+ /**
+ * Playback command type: freeze the video playback on the current frame.
+ * @hide
+ */
+ public static final String PLAYBACK_COMMAND_TYPE_FREEZE = "freeze";
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -932,6 +936,16 @@
@NonNull Bundle data) {
}
+ /**
+ * Called when the TV App sends the selected track info as a response to
+ * requestSelectedTrackInfo.
+ *
+ * @param tracks
+ * @hide
+ */
+ public void onSelectedTrackInfo(List<TvTrackInfo> tracks) {
+ }
+
@Override
public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
return false;
@@ -1338,6 +1352,30 @@
}
/**
+ * Requests the currently selected {@link TvTrackInfo} from the TV App.
+ *
+ * <p> Normally, track info cannot be synchronized until the channel has
+ * been changed. This is used when the session of the TIAS is newly
+ * created and the normal synchronization has not happened yet.
+ * @hide
+ */
+ @CallSuper
+ public void requestSelectedTrackInfo() {
+ executeOrPostRunnableOnMainThread(() -> {
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "requestSelectedTrackInfo");
+ }
+ if (mSessionCallback != null) {
+ mSessionCallback.onRequestSelectedTrackInfo();
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in requestSelectedTrackInfo", e);
+ }
+ });
+ }
+
+ /**
* Requests starting of recording
*
* <p> This is used to request the active {@link android.media.tv.TvRecordingClient} to
@@ -1781,6 +1819,13 @@
onTvMessage(type, data);
}
+ void sendSelectedTrackInfo(List<TvTrackInfo> tracks) {
+ if (DEBUG) {
+ Log.d(TAG, "notifySelectedTrackInfo (tracks= " + tracks + ")");
+ }
+ onSelectedTrackInfo(tracks);
+ }
+
/**
* Calls {@link #onAdBufferConsumed}.
*/
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index cbaf5e4..40a12e4 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -582,6 +582,20 @@
}
/**
+ * Sends the currently selected track info to the TV Interactive App.
+ *
+ * @hide
+ */
+ public void sendSelectedTrackInfo(@Nullable List<TvTrackInfo> tracks) {
+ if (DEBUG) {
+ Log.d(TAG, "sendSelectedTrackInfo");
+ }
+ if (mSession != null) {
+ mSession.sendSelectedTrackInfo(tracks);
+ }
+ }
+
+ /**
* Sends current TV input ID to related TV interactive app.
*
* @param inputId The current TV input ID whose channel is tuned. {@code null} if no channel is
@@ -1197,6 +1211,16 @@
}
/**
+ * This is called when {@link TvInteractiveAppService.Session#requestSelectedTrackInfo()} is
+ * called.
+ *
+ * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+ * @hide
+ */
+ public void onRequestSelectedTrackInfo(@NonNull String iAppServiceId) {
+ }
+
+ /**
* This is called when {@link TvInteractiveAppService.Session#requestCurrentTvInputId()} is
* called.
*
@@ -1714,6 +1738,28 @@
}
@Override
+ public void onRequestSelectedTrackInfo(Session session) {
+ if (DEBUG) {
+ Log.d(TAG, "onRequestSelectedTrackInfo");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onRequestSelectedTrackInfo - session not created");
+ return;
+ }
+ synchronized (mCallbackLock) {
+ if (mCallbackExecutor != null) {
+ mCallbackExecutor.execute(() -> {
+ synchronized (mCallbackLock) {
+ if (mCallback != null) {
+ mCallback.onRequestSelectedTrackInfo(mIAppServiceId);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ @Override
public void onRequestCurrentTvInputId(Session session) {
if (DEBUG) {
Log.d(TAG, "onRequestCurrentTvInputId");
diff --git a/media/java/android/media/tv/tuner/Lnb.java b/media/java/android/media/tv/tuner/Lnb.java
index f9eaabd..0b9429d 100644
--- a/media/java/android/media/tv/tuner/Lnb.java
+++ b/media/java/android/media/tv/tuner/Lnb.java
@@ -264,6 +264,25 @@
}
}
+ /* package */ void closeInternal() {
+ synchronized (mLock) {
+ if (mIsClosed) {
+ return;
+ }
+ int res = nativeClose();
+ if (res != Tuner.RESULT_SUCCESS) {
+ TunerUtils.throwExceptionForResult(res, "Failed to close LNB");
+ } else {
+ mIsClosed = true;
+ if (mOwner != null) {
+ mOwner.releaseLnb();
+ mOwner = null;
+ }
+ mCallbackMap.clear();
+ }
+ }
+ }
+
/**
* Sets the LNB's power voltage.
*
@@ -330,22 +349,7 @@
public void close() {
acquireTRMSLock("close()");
try {
- synchronized (mLock) {
- if (mIsClosed) {
- return;
- }
- int res = nativeClose();
- if (res != Tuner.RESULT_SUCCESS) {
- TunerUtils.throwExceptionForResult(res, "Failed to close LNB");
- } else {
- mIsClosed = true;
- if (mOwner != null) {
- mOwner.releaseLnb();
- mOwner = null;
- }
- mCallbackMap.clear();
- }
- }
+ closeInternal();
} finally {
releaseTRMSLock();
}
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 09f09b9..f28c2c1 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -30,6 +30,7 @@
import android.hardware.tv.tuner.Constant;
import android.hardware.tv.tuner.Constant64Bit;
import android.hardware.tv.tuner.FrontendScanType;
+import android.media.MediaCodec;
import android.media.tv.TvInputService;
import android.media.tv.tuner.dvr.DvrPlayback;
import android.media.tv.tuner.dvr.DvrRecorder;
@@ -272,8 +273,12 @@
try {
System.loadLibrary("media_tv_tuner");
nativeInit();
+ // Load and initialize MediaCodec to avoid flaky cts test result.
+ Class.forName(MediaCodec.class.getName());
} catch (UnsatisfiedLinkError e) {
Log.d(TAG, "tuner JNI library not found!");
+ } catch (ClassNotFoundException e) {
+ Log.e(TAG, "MediaCodec class not found!", e);
}
}
@@ -914,7 +919,7 @@
if (DEBUG) {
Log.d(TAG, "calling mLnb.close() : " + mClientId);
}
- mLnb.close();
+ mLnb.closeInternal();
} else {
if (DEBUG) {
Log.d(TAG, "NOT calling mLnb.close() : " + mClientId);
@@ -2348,6 +2353,7 @@
@Nullable
public Lnb openLnbByName(@NonNull String name, @CallbackExecutor @NonNull Executor executor,
@NonNull LnbCallback cb) {
+ acquireTRMSLock("openLnbByName");
mLnbLock.lock();
try {
Objects.requireNonNull(name, "LNB name must not be null");
@@ -2356,7 +2362,7 @@
Lnb newLnb = nativeOpenLnbByName(name);
if (newLnb != null) {
if (mLnb != null) {
- mLnb.close();
+ mLnb.closeInternal();
mLnbHandle = null;
}
mLnb = newLnb;
@@ -2367,6 +2373,7 @@
}
return mLnb;
} finally {
+ releaseTRMSLock();
mLnbLock.unlock();
}
}
@@ -2784,8 +2791,8 @@
}
}
+ // Must be called while TRMS lock is being held
/* package */ void releaseLnb() {
- acquireTRMSLock("releaseLnb()");
mLnbLock.lock();
try {
if (mLnbHandle != null) {
@@ -2802,7 +2809,6 @@
}
mLnb = null;
} finally {
- releaseTRMSLock();
mLnbLock.unlock();
}
}
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 3fcb871..00b0e57 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -967,7 +967,7 @@
ScopedLocalRef<jobject> filter(env);
{
android::Mutex::Autolock autoLock(mLock);
- if (env->IsSameObject(filter.get(), nullptr)) {
+ if (env->IsSameObject(mFilterObj, nullptr)) {
ALOGE("FilterClientCallbackImpl::onFilterStatus:"
"Filter object has been freed. Ignoring callback.");
return;
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
index 262f5f1..096e8ad 100644
--- a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
@@ -50,8 +50,16 @@
private static final String TAG = "BluetoothMidiDevice";
private static final boolean DEBUG = false;
- private static final int DEFAULT_PACKET_SIZE = 20;
- private static final int MAX_PACKET_SIZE = 512;
+ // Bluetooth services should subtract 5 bytes from the MTU for headers.
+ private static final int HEADER_SIZE = 5;
+ // Min MTU size for BLE
+ private static final int MIN_L2CAP_MTU = 23;
+ // 23 (min L2CAP MTU) - 5 (header size)
+ private static final int DEFAULT_PACKET_SIZE = MIN_L2CAP_MTU - HEADER_SIZE;
+ // Max MTU size on Android
+ private static final int MAX_ANDROID_MTU = 517;
+ // 517 (max Android MTU) - 5 (header size)
+ private static final int MAX_PACKET_SIZE = MAX_ANDROID_MTU - HEADER_SIZE;
// Bluetooth MIDI Gatt service UUID
private static final UUID MIDI_SERVICE = UUID.fromString(
@@ -135,8 +143,8 @@
// switch to receiving notifications
mBluetoothGatt.readCharacteristic(characteristic);
- // Request higher MTU size
- if (!gatt.requestMtu(MAX_PACKET_SIZE)) {
+ // Request max MTU size
+ if (!gatt.requestMtu(MAX_ANDROID_MTU)) {
Log.e(TAG, "request mtu failed");
mPacketEncoder.setMaxPacketSize(DEFAULT_PACKET_SIZE);
mPacketDecoder.setMaxPacketSize(DEFAULT_PACKET_SIZE);
@@ -204,8 +212,15 @@
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
Log.d(TAG, "onMtuChanged callback received. mtu: " + mtu + ", status: " + status);
if (status == BluetoothGatt.GATT_SUCCESS) {
- mPacketEncoder.setMaxPacketSize(Math.min(mtu, MAX_PACKET_SIZE));
- mPacketDecoder.setMaxPacketSize(Math.min(mtu, MAX_PACKET_SIZE));
+ int packetSize = Math.min(mtu - HEADER_SIZE, MAX_PACKET_SIZE);
+ if (packetSize <= 0) {
+ Log.e(TAG, "onMtuChanged non-positive packet size: " + packetSize);
+ packetSize = DEFAULT_PACKET_SIZE;
+ } else if (packetSize < DEFAULT_PACKET_SIZE) {
+ Log.w(TAG, "onMtuChanged small packet size: " + packetSize);
+ }
+ mPacketEncoder.setMaxPacketSize(packetSize);
+ mPacketDecoder.setMaxPacketSize(packetSize);
} else {
mPacketEncoder.setMaxPacketSize(DEFAULT_PACKET_SIZE);
mPacketDecoder.setMaxPacketSize(DEFAULT_PACKET_SIZE);
diff --git a/nfc-extras/Android.bp b/nfc-extras/Android.bp
index cb9ac6f..1f187e8 100644
--- a/nfc-extras/Android.bp
+++ b/nfc-extras/Android.bp
@@ -23,9 +23,13 @@
default_applicable_licenses: ["frameworks_base_license"],
}
+// TODO(b/303286040): Deprecate this API surface since this is no longer supported (see ag/443092)
java_sdk_library {
name: "com.android.nfc_extras",
srcs: ["java/**/*.java"],
+ libs: [
+ "framework-nfc.impl"
+ ],
api_packages: ["com.android.nfc_extras"],
dist_group: "android",
}
diff --git a/nfc/Android.bp b/nfc/Android.bp
index bf9f47c..7136866 100644
--- a/nfc/Android.bp
+++ b/nfc/Android.bp
@@ -10,7 +10,15 @@
filegroup {
name: "framework-nfc-non-updatable-sources",
path: "java",
- srcs: [],
+ srcs: [
+ "java/android/nfc/NfcServiceManager.java",
+ "java/android/nfc/cardemulation/ApduServiceInfo.aidl",
+ "java/android/nfc/cardemulation/ApduServiceInfo.java",
+ "java/android/nfc/cardemulation/NfcFServiceInfo.aidl",
+ "java/android/nfc/cardemulation/NfcFServiceInfo.java",
+ "java/android/nfc/cardemulation/AidGroup.aidl",
+ "java/android/nfc/cardemulation/AidGroup.java",
+ ],
}
filegroup {
@@ -30,10 +38,22 @@
libs: [
"unsupportedappusage", // for android.compat.annotation.UnsupportedAppUsage
],
+ static_libs: [
+ "android.nfc.flags-aconfig-java",
+ "android.permission.flags-aconfig-java",
+ ],
srcs: [
":framework-nfc-updatable-sources",
+ ":framework-nfc-javastream-protos",
],
- defaults: ["framework-non-updatable-unbundled-defaults"],
+ defaults: ["framework-module-defaults"],
+ sdk_version: "module_current",
+ min_sdk_version: "34", // should be 35 (making it 34 for compiling for `-next`)
+ installable: true,
+ optimize: {
+ enabled: false,
+ },
+ hostdex: true, // for hiddenapi check
permitted_packages: [
"android.nfc",
"com.android.nfc",
@@ -41,11 +61,22 @@
hidden_api_packages: [
"com.android.nfc",
],
- aidl: {
- include_dirs: [
- // TODO (b/303286040): Remove these when we change to |framework-module-defaults|
- "frameworks/base/nfc/java",
- "frameworks/base/core/java",
- ],
+ impl_library_visibility: [
+ "//frameworks/base:__subpackages__",
+ "//cts/tests/tests/nfc",
+ "//packages/apps/Nfc:__subpackages__",
+ ],
+ jarjar_rules: ":nfc-jarjar-rules",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
},
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.nfcservices",
+ ],
+}
+
+filegroup {
+ name: "nfc-jarjar-rules",
+ srcs: ["jarjar-rules.txt"],
}
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index d802177..7573474 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -1 +1,456 @@
// Signature format: 2.0
+package android.nfc {
+
+ public final class AvailableNfcAntenna implements android.os.Parcelable {
+ ctor public AvailableNfcAntenna(int, int);
+ method public int describeContents();
+ method public int getLocationX();
+ method public int getLocationY();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.nfc.AvailableNfcAntenna> CREATOR;
+ }
+
+ public class FormatException extends java.lang.Exception {
+ ctor public FormatException();
+ ctor public FormatException(String);
+ ctor public FormatException(String, Throwable);
+ }
+
+ public final class NdefMessage implements android.os.Parcelable {
+ ctor public NdefMessage(byte[]) throws android.nfc.FormatException;
+ ctor public NdefMessage(android.nfc.NdefRecord, android.nfc.NdefRecord...);
+ ctor public NdefMessage(android.nfc.NdefRecord[]);
+ method public int describeContents();
+ method public int getByteArrayLength();
+ method public android.nfc.NdefRecord[] getRecords();
+ method public byte[] toByteArray();
+ method public void writeToParcel(android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.nfc.NdefMessage> CREATOR;
+ }
+
+ public final class NdefRecord implements android.os.Parcelable {
+ ctor public NdefRecord(short, byte[], byte[], byte[]);
+ ctor @Deprecated public NdefRecord(byte[]) throws android.nfc.FormatException;
+ method public static android.nfc.NdefRecord createApplicationRecord(String);
+ method public static android.nfc.NdefRecord createExternal(String, String, byte[]);
+ method public static android.nfc.NdefRecord createMime(String, byte[]);
+ method public static android.nfc.NdefRecord createTextRecord(String, String);
+ method public static android.nfc.NdefRecord createUri(android.net.Uri);
+ method public static android.nfc.NdefRecord createUri(String);
+ method public int describeContents();
+ method public byte[] getId();
+ method public byte[] getPayload();
+ method public short getTnf();
+ method public byte[] getType();
+ method @Deprecated public byte[] toByteArray();
+ method public String toMimeType();
+ method public android.net.Uri toUri();
+ method public void writeToParcel(android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.nfc.NdefRecord> CREATOR;
+ field public static final byte[] RTD_ALTERNATIVE_CARRIER;
+ field public static final byte[] RTD_HANDOVER_CARRIER;
+ field public static final byte[] RTD_HANDOVER_REQUEST;
+ field public static final byte[] RTD_HANDOVER_SELECT;
+ field public static final byte[] RTD_SMART_POSTER;
+ field public static final byte[] RTD_TEXT;
+ field public static final byte[] RTD_URI;
+ field public static final short TNF_ABSOLUTE_URI = 3; // 0x3
+ field public static final short TNF_EMPTY = 0; // 0x0
+ field public static final short TNF_EXTERNAL_TYPE = 4; // 0x4
+ field public static final short TNF_MIME_MEDIA = 2; // 0x2
+ field public static final short TNF_UNCHANGED = 6; // 0x6
+ field public static final short TNF_UNKNOWN = 5; // 0x5
+ field public static final short TNF_WELL_KNOWN = 1; // 0x1
+ }
+
+ public final class NfcAdapter {
+ method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean allowTransaction();
+ method public void disableForegroundDispatch(android.app.Activity);
+ method public void disableReaderMode(android.app.Activity);
+ method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean disallowTransaction();
+ method public void enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]);
+ method public void enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle);
+ method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context);
+ method @Nullable public android.nfc.NfcAntennaInfo getNfcAntennaInfo();
+ method @FlaggedApi("android.nfc.enable_nfc_charging") @Nullable public android.nfc.WlcLDeviceInfo getWlcLDeviceInfo();
+ method public boolean ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler);
+ method public boolean isEnabled();
+ method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean isObserveModeSupported();
+ method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionEnabled();
+ method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionSupported();
+ method public boolean isSecureNfcEnabled();
+ method public boolean isSecureNfcSupported();
+ method @FlaggedApi("android.nfc.enable_nfc_charging") public boolean isWlcEnabled();
+ method @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public void resetDiscoveryTechnology(@NonNull android.app.Activity);
+ method @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public void setDiscoveryTechnology(@NonNull android.app.Activity, int, int);
+ field public static final String ACTION_ADAPTER_STATE_CHANGED = "android.nfc.action.ADAPTER_STATE_CHANGED";
+ field public static final String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED";
+ field @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static final String ACTION_PREFERRED_PAYMENT_CHANGED = "android.nfc.action.PREFERRED_PAYMENT_CHANGED";
+ field public static final String ACTION_TAG_DISCOVERED = "android.nfc.action.TAG_DISCOVERED";
+ field public static final String ACTION_TECH_DISCOVERED = "android.nfc.action.TECH_DISCOVERED";
+ field @RequiresPermission(android.Manifest.permission.NFC_TRANSACTION_EVENT) public static final String ACTION_TRANSACTION_DETECTED = "android.nfc.action.TRANSACTION_DETECTED";
+ field public static final String EXTRA_ADAPTER_STATE = "android.nfc.extra.ADAPTER_STATE";
+ field public static final String EXTRA_AID = "android.nfc.extra.AID";
+ field public static final String EXTRA_DATA = "android.nfc.extra.DATA";
+ field public static final String EXTRA_ID = "android.nfc.extra.ID";
+ field public static final String EXTRA_NDEF_MESSAGES = "android.nfc.extra.NDEF_MESSAGES";
+ field public static final String EXTRA_PREFERRED_PAYMENT_CHANGED_REASON = "android.nfc.extra.PREFERRED_PAYMENT_CHANGED_REASON";
+ field public static final String EXTRA_READER_PRESENCE_CHECK_DELAY = "presence";
+ field public static final String EXTRA_SECURE_ELEMENT_NAME = "android.nfc.extra.SECURE_ELEMENT_NAME";
+ field public static final String EXTRA_TAG = "android.nfc.extra.TAG";
+ field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_DISABLE = 0; // 0x0
+ field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_KEEP = -1; // 0xffffffff
+ field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_NFC_PASSIVE_A = 1; // 0x1
+ field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_NFC_PASSIVE_B = 2; // 0x2
+ field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_NFC_PASSIVE_F = 4; // 0x4
+ field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_READER_DISABLE = 0; // 0x0
+ field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_READER_KEEP = -1; // 0xffffffff
+ field public static final int FLAG_READER_NFC_A = 1; // 0x1
+ field public static final int FLAG_READER_NFC_B = 2; // 0x2
+ field public static final int FLAG_READER_NFC_BARCODE = 16; // 0x10
+ field public static final int FLAG_READER_NFC_F = 4; // 0x4
+ field public static final int FLAG_READER_NFC_V = 8; // 0x8
+ field public static final int FLAG_READER_NO_PLATFORM_SOUNDS = 256; // 0x100
+ field public static final int FLAG_READER_SKIP_NDEF_CHECK = 128; // 0x80
+ field public static final int PREFERRED_PAYMENT_CHANGED = 2; // 0x2
+ field public static final int PREFERRED_PAYMENT_LOADED = 1; // 0x1
+ field public static final int PREFERRED_PAYMENT_UPDATED = 3; // 0x3
+ field public static final int STATE_OFF = 1; // 0x1
+ field public static final int STATE_ON = 3; // 0x3
+ field public static final int STATE_TURNING_OFF = 4; // 0x4
+ field public static final int STATE_TURNING_ON = 2; // 0x2
+ }
+
+ @Deprecated public static interface NfcAdapter.CreateBeamUrisCallback {
+ method @Deprecated public android.net.Uri[] createBeamUris(android.nfc.NfcEvent);
+ }
+
+ @Deprecated public static interface NfcAdapter.CreateNdefMessageCallback {
+ method @Deprecated public android.nfc.NdefMessage createNdefMessage(android.nfc.NfcEvent);
+ }
+
+ @Deprecated public static interface NfcAdapter.OnNdefPushCompleteCallback {
+ method @Deprecated public void onNdefPushComplete(android.nfc.NfcEvent);
+ }
+
+ public static interface NfcAdapter.OnTagRemovedListener {
+ method public void onTagRemoved();
+ }
+
+ public static interface NfcAdapter.ReaderCallback {
+ method public void onTagDiscovered(android.nfc.Tag);
+ }
+
+ public final class NfcAntennaInfo implements android.os.Parcelable {
+ ctor public NfcAntennaInfo(int, int, boolean, @NonNull java.util.List<android.nfc.AvailableNfcAntenna>);
+ method public int describeContents();
+ method @NonNull public java.util.List<android.nfc.AvailableNfcAntenna> getAvailableNfcAntennas();
+ method public int getDeviceHeight();
+ method public int getDeviceWidth();
+ method public boolean isDeviceFoldable();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.nfc.NfcAntennaInfo> CREATOR;
+ }
+
+ public final class NfcEvent {
+ field public final android.nfc.NfcAdapter nfcAdapter;
+ field public final int peerLlcpMajorVersion;
+ field public final int peerLlcpMinorVersion;
+ }
+
+ public final class NfcManager {
+ method public android.nfc.NfcAdapter getDefaultAdapter();
+ }
+
+ public final class Tag implements android.os.Parcelable {
+ method public int describeContents();
+ method public byte[] getId();
+ method public String[] getTechList();
+ method public void writeToParcel(android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.nfc.Tag> CREATOR;
+ }
+
+ public class TagLostException extends java.io.IOException {
+ ctor public TagLostException();
+ ctor public TagLostException(String);
+ }
+
+ @FlaggedApi("android.nfc.enable_nfc_charging") public final class WlcLDeviceInfo implements android.os.Parcelable {
+ ctor public WlcLDeviceInfo(double, double, double, int);
+ method public int describeContents();
+ method public double getBatteryLevel();
+ method public double getProductId();
+ method public int getState();
+ method public double getTemperature();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int CONNECTED_CHARGING = 2; // 0x2
+ field public static final int CONNECTED_DISCHARGING = 3; // 0x3
+ field @NonNull public static final android.os.Parcelable.Creator<android.nfc.WlcLDeviceInfo> CREATOR;
+ field public static final int DISCONNECTED = 1; // 0x1
+ }
+
+}
+
+package android.nfc.cardemulation {
+
+ public final class CardEmulation {
+ method public boolean categoryAllowsForegroundPreference(String);
+ method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public java.util.List<java.lang.String> getAidsForPreferredPaymentService();
+ method public java.util.List<java.lang.String> getAidsForService(android.content.ComponentName, String);
+ method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public CharSequence getDescriptionForPreferredPaymentService();
+ method public static android.nfc.cardemulation.CardEmulation getInstance(android.nfc.NfcAdapter);
+ method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public String getRouteDestinationForPreferredPaymentService();
+ method public int getSelectionModeForCategory(String);
+ method public boolean isDefaultServiceForAid(android.content.ComponentName, String);
+ method public boolean isDefaultServiceForCategory(android.content.ComponentName, String);
+ method public boolean registerAidsForService(android.content.ComponentName, String, java.util.List<java.lang.String>);
+ method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean registerPollingLoopFilterForService(@NonNull android.content.ComponentName, @NonNull String);
+ method public boolean removeAidsForService(android.content.ComponentName, String);
+ method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean setOffHostForService(@NonNull android.content.ComponentName, @NonNull String);
+ method public boolean setPreferredService(android.app.Activity, android.content.ComponentName);
+ method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setServiceObserveModeDefault(@NonNull android.content.ComponentName, boolean);
+ method public boolean supportsAidPrefixRegistration();
+ method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean unsetOffHostForService(@NonNull android.content.ComponentName);
+ method public boolean unsetPreferredService(android.app.Activity);
+ field @Deprecated public static final String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT";
+ field public static final String CATEGORY_OTHER = "other";
+ field public static final String CATEGORY_PAYMENT = "payment";
+ field public static final String EXTRA_CATEGORY = "category";
+ field public static final String EXTRA_SERVICE_COMPONENT = "component";
+ field public static final int SELECTION_MODE_ALWAYS_ASK = 1; // 0x1
+ field public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2; // 0x2
+ field public static final int SELECTION_MODE_PREFER_DEFAULT = 0; // 0x0
+ }
+
+ public abstract class HostApduService extends android.app.Service {
+ ctor public HostApduService();
+ method public final void notifyUnhandled();
+ method public final android.os.IBinder onBind(android.content.Intent);
+ method public abstract void onDeactivated(int);
+ method public abstract byte[] processCommandApdu(byte[], android.os.Bundle);
+ method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void processPollingFrames(@NonNull java.util.List<android.os.Bundle>);
+ method public final void sendResponseApdu(byte[]);
+ field public static final int DEACTIVATION_DESELECTED = 1; // 0x1
+ field public static final int DEACTIVATION_LINK_LOSS = 0; // 0x0
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_DATA_KEY = "android.nfc.cardemulation.DATA";
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_GAIN_KEY = "android.nfc.cardemulation.GAIN";
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TIMESTAMP_KEY = "android.nfc.cardemulation.TIMESTAMP";
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_A = 65; // 0x0041 'A'
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_B = 66; // 0x0042 'B'
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_F = 70; // 0x0046 'F'
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TYPE_KEY = "android.nfc.cardemulation.TYPE";
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_OFF = 88; // 0x0058 'X'
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_ON = 79; // 0x004f 'O'
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_UNKNOWN = 85; // 0x0055 'U'
+ field public static final String SERVICE_INTERFACE = "android.nfc.cardemulation.action.HOST_APDU_SERVICE";
+ field public static final String SERVICE_META_DATA = "android.nfc.cardemulation.host_apdu_service";
+ }
+
+ public abstract class HostNfcFService extends android.app.Service {
+ ctor public HostNfcFService();
+ method public final android.os.IBinder onBind(android.content.Intent);
+ method public abstract void onDeactivated(int);
+ method public abstract byte[] processNfcFPacket(byte[], android.os.Bundle);
+ method public final void sendResponsePacket(byte[]);
+ field public static final int DEACTIVATION_LINK_LOSS = 0; // 0x0
+ field public static final String SERVICE_INTERFACE = "android.nfc.cardemulation.action.HOST_NFCF_SERVICE";
+ field public static final String SERVICE_META_DATA = "android.nfc.cardemulation.host_nfcf_service";
+ }
+
+ public final class NfcFCardEmulation {
+ method public boolean disableService(android.app.Activity) throws java.lang.RuntimeException;
+ method public boolean enableService(android.app.Activity, android.content.ComponentName) throws java.lang.RuntimeException;
+ method public static android.nfc.cardemulation.NfcFCardEmulation getInstance(android.nfc.NfcAdapter);
+ method public String getNfcid2ForService(android.content.ComponentName) throws java.lang.RuntimeException;
+ method public String getSystemCodeForService(android.content.ComponentName) throws java.lang.RuntimeException;
+ method public boolean registerSystemCodeForService(android.content.ComponentName, String) throws java.lang.RuntimeException;
+ method public boolean setNfcid2ForService(android.content.ComponentName, String) throws java.lang.RuntimeException;
+ method public boolean unregisterSystemCodeForService(android.content.ComponentName) throws java.lang.RuntimeException;
+ }
+
+ public abstract class OffHostApduService extends android.app.Service {
+ ctor public OffHostApduService();
+ field public static final String SERVICE_INTERFACE = "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE";
+ field public static final String SERVICE_META_DATA = "android.nfc.cardemulation.off_host_apdu_service";
+ }
+
+}
+
+package android.nfc.tech {
+
+ public final class IsoDep implements android.nfc.tech.TagTechnology {
+ method public void close() throws java.io.IOException;
+ method public void connect() throws java.io.IOException;
+ method public static android.nfc.tech.IsoDep get(android.nfc.Tag);
+ method public byte[] getHiLayerResponse();
+ method public byte[] getHistoricalBytes();
+ method public int getMaxTransceiveLength();
+ method public android.nfc.Tag getTag();
+ method public int getTimeout();
+ method public boolean isConnected();
+ method public boolean isExtendedLengthApduSupported();
+ method public void setTimeout(int);
+ method public byte[] transceive(byte[]) throws java.io.IOException;
+ }
+
+ public final class MifareClassic implements android.nfc.tech.TagTechnology {
+ method public boolean authenticateSectorWithKeyA(int, byte[]) throws java.io.IOException;
+ method public boolean authenticateSectorWithKeyB(int, byte[]) throws java.io.IOException;
+ method public int blockToSector(int);
+ method public void close() throws java.io.IOException;
+ method public void connect() throws java.io.IOException;
+ method public void decrement(int, int) throws java.io.IOException;
+ method public static android.nfc.tech.MifareClassic get(android.nfc.Tag);
+ method public int getBlockCount();
+ method public int getBlockCountInSector(int);
+ method public int getMaxTransceiveLength();
+ method public int getSectorCount();
+ method public int getSize();
+ method public android.nfc.Tag getTag();
+ method public int getTimeout();
+ method public int getType();
+ method public void increment(int, int) throws java.io.IOException;
+ method public boolean isConnected();
+ method public byte[] readBlock(int) throws java.io.IOException;
+ method public void restore(int) throws java.io.IOException;
+ method public int sectorToBlock(int);
+ method public void setTimeout(int);
+ method public byte[] transceive(byte[]) throws java.io.IOException;
+ method public void transfer(int) throws java.io.IOException;
+ method public void writeBlock(int, byte[]) throws java.io.IOException;
+ field public static final int BLOCK_SIZE = 16; // 0x10
+ field public static final byte[] KEY_DEFAULT;
+ field public static final byte[] KEY_MIFARE_APPLICATION_DIRECTORY;
+ field public static final byte[] KEY_NFC_FORUM;
+ field public static final int SIZE_1K = 1024; // 0x400
+ field public static final int SIZE_2K = 2048; // 0x800
+ field public static final int SIZE_4K = 4096; // 0x1000
+ field public static final int SIZE_MINI = 320; // 0x140
+ field public static final int TYPE_CLASSIC = 0; // 0x0
+ field public static final int TYPE_PLUS = 1; // 0x1
+ field public static final int TYPE_PRO = 2; // 0x2
+ field public static final int TYPE_UNKNOWN = -1; // 0xffffffff
+ }
+
+ public final class MifareUltralight implements android.nfc.tech.TagTechnology {
+ method public void close() throws java.io.IOException;
+ method public void connect() throws java.io.IOException;
+ method public static android.nfc.tech.MifareUltralight get(android.nfc.Tag);
+ method public int getMaxTransceiveLength();
+ method public android.nfc.Tag getTag();
+ method public int getTimeout();
+ method public int getType();
+ method public boolean isConnected();
+ method public byte[] readPages(int) throws java.io.IOException;
+ method public void setTimeout(int);
+ method public byte[] transceive(byte[]) throws java.io.IOException;
+ method public void writePage(int, byte[]) throws java.io.IOException;
+ field public static final int PAGE_SIZE = 4; // 0x4
+ field public static final int TYPE_ULTRALIGHT = 1; // 0x1
+ field public static final int TYPE_ULTRALIGHT_C = 2; // 0x2
+ field public static final int TYPE_UNKNOWN = -1; // 0xffffffff
+ }
+
+ public final class Ndef implements android.nfc.tech.TagTechnology {
+ method public boolean canMakeReadOnly();
+ method public void close() throws java.io.IOException;
+ method public void connect() throws java.io.IOException;
+ method public static android.nfc.tech.Ndef get(android.nfc.Tag);
+ method public android.nfc.NdefMessage getCachedNdefMessage();
+ method public int getMaxSize();
+ method public android.nfc.NdefMessage getNdefMessage() throws android.nfc.FormatException, java.io.IOException;
+ method public android.nfc.Tag getTag();
+ method public String getType();
+ method public boolean isConnected();
+ method public boolean isWritable();
+ method public boolean makeReadOnly() throws java.io.IOException;
+ method public void writeNdefMessage(android.nfc.NdefMessage) throws android.nfc.FormatException, java.io.IOException;
+ field public static final String MIFARE_CLASSIC = "com.nxp.ndef.mifareclassic";
+ field public static final String NFC_FORUM_TYPE_1 = "org.nfcforum.ndef.type1";
+ field public static final String NFC_FORUM_TYPE_2 = "org.nfcforum.ndef.type2";
+ field public static final String NFC_FORUM_TYPE_3 = "org.nfcforum.ndef.type3";
+ field public static final String NFC_FORUM_TYPE_4 = "org.nfcforum.ndef.type4";
+ }
+
+ public final class NdefFormatable implements android.nfc.tech.TagTechnology {
+ method public void close() throws java.io.IOException;
+ method public void connect() throws java.io.IOException;
+ method public void format(android.nfc.NdefMessage) throws android.nfc.FormatException, java.io.IOException;
+ method public void formatReadOnly(android.nfc.NdefMessage) throws android.nfc.FormatException, java.io.IOException;
+ method public static android.nfc.tech.NdefFormatable get(android.nfc.Tag);
+ method public android.nfc.Tag getTag();
+ method public boolean isConnected();
+ }
+
+ public final class NfcA implements android.nfc.tech.TagTechnology {
+ method public void close() throws java.io.IOException;
+ method public void connect() throws java.io.IOException;
+ method public static android.nfc.tech.NfcA get(android.nfc.Tag);
+ method public byte[] getAtqa();
+ method public int getMaxTransceiveLength();
+ method public short getSak();
+ method public android.nfc.Tag getTag();
+ method public int getTimeout();
+ method public boolean isConnected();
+ method public void setTimeout(int);
+ method public byte[] transceive(byte[]) throws java.io.IOException;
+ }
+
+ public final class NfcB implements android.nfc.tech.TagTechnology {
+ method public void close() throws java.io.IOException;
+ method public void connect() throws java.io.IOException;
+ method public static android.nfc.tech.NfcB get(android.nfc.Tag);
+ method public byte[] getApplicationData();
+ method public int getMaxTransceiveLength();
+ method public byte[] getProtocolInfo();
+ method public android.nfc.Tag getTag();
+ method public boolean isConnected();
+ method public byte[] transceive(byte[]) throws java.io.IOException;
+ }
+
+ public final class NfcBarcode implements android.nfc.tech.TagTechnology {
+ method public void close() throws java.io.IOException;
+ method public void connect() throws java.io.IOException;
+ method public static android.nfc.tech.NfcBarcode get(android.nfc.Tag);
+ method public byte[] getBarcode();
+ method public android.nfc.Tag getTag();
+ method public int getType();
+ method public boolean isConnected();
+ field public static final int TYPE_KOVIO = 1; // 0x1
+ field public static final int TYPE_UNKNOWN = -1; // 0xffffffff
+ }
+
+ public final class NfcF implements android.nfc.tech.TagTechnology {
+ method public void close() throws java.io.IOException;
+ method public void connect() throws java.io.IOException;
+ method public static android.nfc.tech.NfcF get(android.nfc.Tag);
+ method public byte[] getManufacturer();
+ method public int getMaxTransceiveLength();
+ method public byte[] getSystemCode();
+ method public android.nfc.Tag getTag();
+ method public int getTimeout();
+ method public boolean isConnected();
+ method public void setTimeout(int);
+ method public byte[] transceive(byte[]) throws java.io.IOException;
+ }
+
+ public final class NfcV implements android.nfc.tech.TagTechnology {
+ method public void close() throws java.io.IOException;
+ method public void connect() throws java.io.IOException;
+ method public static android.nfc.tech.NfcV get(android.nfc.Tag);
+ method public byte getDsfId();
+ method public int getMaxTransceiveLength();
+ method public byte getResponseFlags();
+ method public android.nfc.Tag getTag();
+ method public boolean isConnected();
+ method public byte[] transceive(byte[]) throws java.io.IOException;
+ }
+
+ public interface TagTechnology extends java.io.Closeable {
+ method public void connect() throws java.io.IOException;
+ method public android.nfc.Tag getTag();
+ method public boolean isConnected();
+ }
+
+}
+
diff --git a/nfc/api/lint-baseline.txt b/nfc/api/lint-baseline.txt
new file mode 100644
index 0000000..ef9aab6
--- /dev/null
+++ b/nfc/api/lint-baseline.txt
@@ -0,0 +1,95 @@
+// Baseline format: 1.0
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_ADAPTER_STATE_CHANGED:
+ Field 'ACTION_ADAPTER_STATE_CHANGED' is missing @BroadcastBehavior
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_PREFERRED_PAYMENT_CHANGED:
+ Field 'ACTION_PREFERRED_PAYMENT_CHANGED' is missing @BroadcastBehavior
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_TRANSACTION_DETECTED:
+ Field 'ACTION_TRANSACTION_DETECTED' is missing @BroadcastBehavior
+
+
+MissingNullability: android.nfc.cardemulation.OffHostApduService#onBind(android.content.Intent):
+ Missing nullability on method `onBind` return
+MissingNullability: android.nfc.cardemulation.OffHostApduService#onBind(android.content.Intent) parameter #0:
+ Missing nullability on parameter `intent` in method `onBind`
+
+
+RequiresPermission: android.nfc.NfcAdapter#disableForegroundDispatch(android.app.Activity):
+ Method 'disableForegroundDispatch' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.NfcAdapter#enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]):
+ Method 'enableForegroundDispatch' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForAid(android.content.ComponentName, String):
+ Method 'isDefaultServiceForAid' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForCategory(android.content.ComponentName, String):
+ Method 'isDefaultServiceForCategory' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.cardemulation.CardEmulation#setOffHostForService(android.content.ComponentName, String):
+ Method 'setOffHostForService' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.nfc.tech.IsoDep#getTimeout():
+ Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.IsoDep#setTimeout(int):
+ Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.IsoDep#transceive(byte[]):
+ Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyA(int, byte[]):
+ Method 'authenticateSectorWithKeyA' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyB(int, byte[]):
+ Method 'authenticateSectorWithKeyB' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#decrement(int, int):
+ Method 'decrement' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#getTimeout():
+ Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#increment(int, int):
+ Method 'increment' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#readBlock(int):
+ Method 'readBlock' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#restore(int):
+ Method 'restore' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#setTimeout(int):
+ Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#transceive(byte[]):
+ Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#transfer(int):
+ Method 'transfer' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#writeBlock(int, byte[]):
+ Method 'writeBlock' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#getTimeout():
+ Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#readPages(int):
+ Method 'readPages' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#setTimeout(int):
+ Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#transceive(byte[]):
+ Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#writePage(int, byte[]):
+ Method 'writePage' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#getNdefMessage():
+ Method 'getNdefMessage' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#isWritable():
+ Method 'isWritable' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#makeReadOnly():
+ Method 'makeReadOnly' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#writeNdefMessage(android.nfc.NdefMessage):
+ Method 'writeNdefMessage' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NdefFormatable#format(android.nfc.NdefMessage):
+ Method 'format' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NdefFormatable#formatReadOnly(android.nfc.NdefMessage):
+ Method 'formatReadOnly' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcA#getTimeout():
+ Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcA#setTimeout(int):
+ Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcA#transceive(byte[]):
+ Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcB#transceive(byte[]):
+ Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcF#getTimeout():
+ Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcF#setTimeout(int):
+ Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcF#transceive(byte[]):
+ Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcV#transceive(byte[]):
+ Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.TagTechnology#close():
+ Method 'close' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.TagTechnology#connect():
+ Method 'connect' documentation mentions permissions without declaring @RequiresPermission
diff --git a/nfc/api/module-lib-current.txt b/nfc/api/module-lib-current.txt
index d802177..5ebe911 100644
--- a/nfc/api/module-lib-current.txt
+++ b/nfc/api/module-lib-current.txt
@@ -1 +1,10 @@
// Signature format: 2.0
+package android.nfc {
+
+ public class NfcFrameworkInitializer {
+ method public static void registerServiceWrappers();
+ method public static void setNfcServiceManager(@NonNull android.nfc.NfcServiceManager);
+ }
+
+}
+
diff --git a/nfc/api/module-lib-lint-baseline.txt b/nfc/api/module-lib-lint-baseline.txt
new file mode 100644
index 0000000..f7f8ee3
--- /dev/null
+++ b/nfc/api/module-lib-lint-baseline.txt
@@ -0,0 +1,93 @@
+// Baseline format: 1.0
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_ADAPTER_STATE_CHANGED:
+ Field 'ACTION_ADAPTER_STATE_CHANGED' is missing @BroadcastBehavior
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_PREFERRED_PAYMENT_CHANGED:
+ Field 'ACTION_PREFERRED_PAYMENT_CHANGED' is missing @BroadcastBehavior
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_REQUIRE_UNLOCK_FOR_NFC:
+ Field 'ACTION_REQUIRE_UNLOCK_FOR_NFC' is missing @BroadcastBehavior
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_TRANSACTION_DETECTED:
+ Field 'ACTION_TRANSACTION_DETECTED' is missing @BroadcastBehavior
+
+RequiresPermission: android.nfc.NfcAdapter#disableForegroundDispatch(android.app.Activity):
+ Method 'disableForegroundDispatch' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.NfcAdapter#enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]):
+ Method 'enableForegroundDispatch' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForAid(android.content.ComponentName, String):
+ Method 'isDefaultServiceForAid' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForCategory(android.content.ComponentName, String):
+ Method 'isDefaultServiceForCategory' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.cardemulation.CardEmulation#setOffHostForService(android.content.ComponentName, String):
+ Method 'setOffHostForService' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.nfc.tech.IsoDep#getTimeout():
+ Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.IsoDep#setTimeout(int):
+ Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.IsoDep#transceive(byte[]):
+ Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyA(int, byte[]):
+ Method 'authenticateSectorWithKeyA' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyB(int, byte[]):
+ Method 'authenticateSectorWithKeyB' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#decrement(int, int):
+ Method 'decrement' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#getTimeout():
+ Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#increment(int, int):
+ Method 'increment' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#readBlock(int):
+ Method 'readBlock' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#restore(int):
+ Method 'restore' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#setTimeout(int):
+ Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#transceive(byte[]):
+ Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#transfer(int):
+ Method 'transfer' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#writeBlock(int, byte[]):
+ Method 'writeBlock' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#getTimeout():
+ Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#readPages(int):
+ Method 'readPages' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#setTimeout(int):
+ Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#transceive(byte[]):
+ Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#writePage(int, byte[]):
+ Method 'writePage' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#getNdefMessage():
+ Method 'getNdefMessage' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#isWritable():
+ Method 'isWritable' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#makeReadOnly():
+ Method 'makeReadOnly' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#writeNdefMessage(android.nfc.NdefMessage):
+ Method 'writeNdefMessage' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NdefFormatable#format(android.nfc.NdefMessage):
+ Method 'format' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NdefFormatable#formatReadOnly(android.nfc.NdefMessage):
+ Method 'formatReadOnly' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcA#getTimeout():
+ Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcA#setTimeout(int):
+ Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcA#transceive(byte[]):
+ Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcB#transceive(byte[]):
+ Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcF#getTimeout():
+ Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcF#setTimeout(int):
+ Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcF#transceive(byte[]):
+ Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcV#transceive(byte[]):
+ Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.TagTechnology#close():
+ Method 'close' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.TagTechnology#connect():
+ Method 'connect' documentation mentions permissions without declaring @RequiresPermission
+
+SdkConstant: android.nfc.NfcAdapter#ACTION_REQUIRE_UNLOCK_FOR_NFC:
+ Field 'ACTION_REQUIRE_UNLOCK_FOR_NFC' is missing @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
diff --git a/nfc/api/removed.txt b/nfc/api/removed.txt
index d802177..fb82b5d 100644
--- a/nfc/api/removed.txt
+++ b/nfc/api/removed.txt
@@ -1 +1,17 @@
// Signature format: 2.0
+package android.nfc {
+
+ public final class NfcAdapter {
+ method @Deprecated @android.compat.annotation.UnsupportedAppUsage public void disableForegroundNdefPush(android.app.Activity);
+ method @Deprecated @android.compat.annotation.UnsupportedAppUsage public void enableForegroundNdefPush(android.app.Activity, android.nfc.NdefMessage);
+ method @Deprecated @android.compat.annotation.UnsupportedAppUsage public boolean invokeBeam(android.app.Activity);
+ method @Deprecated @android.compat.annotation.UnsupportedAppUsage public boolean isNdefPushEnabled();
+ method @Deprecated @android.compat.annotation.UnsupportedAppUsage public void setBeamPushUris(android.net.Uri[], android.app.Activity);
+ method @Deprecated @android.compat.annotation.UnsupportedAppUsage public void setBeamPushUrisCallback(android.nfc.NfcAdapter.CreateBeamUrisCallback, android.app.Activity);
+ method @Deprecated @android.compat.annotation.UnsupportedAppUsage public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, android.app.Activity...);
+ method @Deprecated @android.compat.annotation.UnsupportedAppUsage public void setNdefPushMessageCallback(android.nfc.NfcAdapter.CreateNdefMessageCallback, android.app.Activity, android.app.Activity...);
+ method @Deprecated @android.compat.annotation.UnsupportedAppUsage public void setOnNdefPushCompleteCallback(android.nfc.NfcAdapter.OnNdefPushCompleteCallback, android.app.Activity, android.app.Activity...);
+ }
+
+}
+
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index d802177..40672a1 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -1 +1,53 @@
// Signature format: 2.0
+package android.nfc {
+
+ public final class NfcAdapter {
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean addNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler, String[]);
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable();
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable(boolean);
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable();
+ method @FlaggedApi("android.nfc.enable_nfc_reader_option") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableReaderOption(boolean);
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableSecureNfc(boolean);
+ method @FlaggedApi("android.nfc.enable_nfc_charging") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableWlc(boolean);
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") public int getAdapterState();
+ method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public java.util.Map<java.lang.String,java.lang.Boolean> getTagIntentAppPreferenceForUser(int);
+ method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOn();
+ method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOnSupported();
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isTagIntentAppPreferenceSupported();
+ method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
+ method @FlaggedApi("android.nfc.enable_nfc_charging") public void registerWlcStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.WlcStateListener);
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler);
+ method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean setControllerAlwaysOn(boolean);
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setReaderMode(boolean);
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int setTagIntentAppPreferenceForUser(int, @NonNull String, boolean);
+ method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
+ method @FlaggedApi("android.nfc.enable_nfc_charging") public void unregisterWlcStateListener(@NonNull android.nfc.NfcAdapter.WlcStateListener);
+ field @FlaggedApi("android.nfc.enable_nfc_mainline") public static final String ACTION_REQUIRE_UNLOCK_FOR_NFC = "android.nfc.action.REQUIRE_UNLOCK_FOR_NFC";
+ field public static final int TAG_INTENT_APP_PREF_RESULT_PACKAGE_NOT_FOUND = -1; // 0xffffffff
+ field public static final int TAG_INTENT_APP_PREF_RESULT_SUCCESS = 0; // 0x0
+ field public static final int TAG_INTENT_APP_PREF_RESULT_UNAVAILABLE = -2; // 0xfffffffe
+ }
+
+ public static interface NfcAdapter.ControllerAlwaysOnListener {
+ method public void onControllerAlwaysOnChanged(boolean);
+ }
+
+ public static interface NfcAdapter.NfcUnlockHandler {
+ method public boolean onUnlockAttempted(android.nfc.Tag);
+ }
+
+ @FlaggedApi("android.nfc.enable_nfc_charging") public static interface NfcAdapter.WlcStateListener {
+ method public void onWlcStateChanged(@NonNull android.nfc.WlcLDeviceInfo);
+ }
+
+}
+
+package android.nfc.cardemulation {
+
+ public final class CardEmulation {
+ method @FlaggedApi("android.permission.flags.wallet_role_enabled") @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public android.nfc.cardemulation.ApduServiceInfo getPreferredPaymentService();
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<android.nfc.cardemulation.ApduServiceInfo> getServices(@NonNull String, int);
+ }
+
+}
+
diff --git a/nfc/api/system-lint-baseline.txt b/nfc/api/system-lint-baseline.txt
new file mode 100644
index 0000000..761c8e6
--- /dev/null
+++ b/nfc/api/system-lint-baseline.txt
@@ -0,0 +1,105 @@
+// Baseline format: 1.0
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_ADAPTER_STATE_CHANGED:
+ Field 'ACTION_ADAPTER_STATE_CHANGED' is missing @BroadcastBehavior
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_PREFERRED_PAYMENT_CHANGED:
+ Field 'ACTION_PREFERRED_PAYMENT_CHANGED' is missing @BroadcastBehavior
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_REQUIRE_UNLOCK_FOR_NFC:
+ Field 'ACTION_REQUIRE_UNLOCK_FOR_NFC' is missing @BroadcastBehavior
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_TRANSACTION_DETECTED:
+ Field 'ACTION_TRANSACTION_DETECTED' is missing @BroadcastBehavior
+
+
+MissingNullability: android.nfc.cardemulation.OffHostApduService#onBind(android.content.Intent):
+ Missing nullability on method `onBind` return
+MissingNullability: android.nfc.cardemulation.OffHostApduService#onBind(android.content.Intent) parameter #0:
+ Missing nullability on parameter `intent` in method `onBind`
+
+
+RequiresPermission: android.nfc.NfcAdapter#disableForegroundDispatch(android.app.Activity):
+ Method 'disableForegroundDispatch' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.NfcAdapter#enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]):
+ Method 'enableForegroundDispatch' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForAid(android.content.ComponentName, String):
+ Method 'isDefaultServiceForAid' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForCategory(android.content.ComponentName, String):
+ Method 'isDefaultServiceForCategory' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.cardemulation.CardEmulation#setOffHostForService(android.content.ComponentName, String):
+ Method 'setOffHostForService' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.nfc.tech.IsoDep#getTimeout():
+ Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.IsoDep#setTimeout(int):
+ Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.IsoDep#transceive(byte[]):
+ Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyA(int, byte[]):
+ Method 'authenticateSectorWithKeyA' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyB(int, byte[]):
+ Method 'authenticateSectorWithKeyB' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#decrement(int, int):
+ Method 'decrement' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#getTimeout():
+ Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#increment(int, int):
+ Method 'increment' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#readBlock(int):
+ Method 'readBlock' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#restore(int):
+ Method 'restore' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#setTimeout(int):
+ Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#transceive(byte[]):
+ Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#transfer(int):
+ Method 'transfer' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#writeBlock(int, byte[]):
+ Method 'writeBlock' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#getTimeout():
+ Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#readPages(int):
+ Method 'readPages' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#setTimeout(int):
+ Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#transceive(byte[]):
+ Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#writePage(int, byte[]):
+ Method 'writePage' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#getNdefMessage():
+ Method 'getNdefMessage' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#isWritable():
+ Method 'isWritable' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#makeReadOnly():
+ Method 'makeReadOnly' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#writeNdefMessage(android.nfc.NdefMessage):
+ Method 'writeNdefMessage' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NdefFormatable#format(android.nfc.NdefMessage):
+ Method 'format' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NdefFormatable#formatReadOnly(android.nfc.NdefMessage):
+ Method 'formatReadOnly' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcA#getTimeout():
+ Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcA#setTimeout(int):
+ Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcA#transceive(byte[]):
+ Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcB#transceive(byte[]):
+ Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcF#getTimeout():
+ Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcF#setTimeout(int):
+ Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcF#transceive(byte[]):
+ Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcV#transceive(byte[]):
+ Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.TagTechnology#close():
+ Method 'close' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.TagTechnology#connect():
+ Method 'connect' documentation mentions permissions without declaring @RequiresPermission
+
+SamShouldBeLast: android.nfc.NfcAdapter#enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle):
+ SAM-compatible parameters (such as parameter 2, "callback", in android.nfc.NfcAdapter.enableReaderMode) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.nfc.NfcAdapter#ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler):
+ SAM-compatible parameters (such as parameter 3, "tagRemovedListener", in android.nfc.NfcAdapter.ignore) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+
+SdkConstant: android.nfc.NfcAdapter#ACTION_REQUIRE_UNLOCK_FOR_NFC:
+ Field 'ACTION_REQUIRE_UNLOCK_FOR_NFC' is missing @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
diff --git a/nfc/api/system-removed.txt b/nfc/api/system-removed.txt
index d802177..c6eaa57 100644
--- a/nfc/api/system-removed.txt
+++ b/nfc/api/system-removed.txt
@@ -1 +1,12 @@
// Signature format: 2.0
+package android.nfc {
+
+ public final class NfcAdapter {
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disableNdefPush();
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableNdefPush();
+ method public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, int);
+ field public static final int FLAG_NDEF_PUSH_NO_CONFIRM = 1; // 0x1
+ }
+
+}
+
diff --git a/nfc/jarjar-rules.txt b/nfc/jarjar-rules.txt
new file mode 100644
index 0000000..4cd652d
--- /dev/null
+++ b/nfc/jarjar-rules.txt
@@ -0,0 +1,38 @@
+# Used by framework-nfc for proto debug dumping
+rule android.app.PendingIntentProto* com.android.nfc.x.@0
+rule android.content.ComponentNameProto* com.android.nfc.x.@0
+rule android.content.IntentProto* com.android.nfc.x.@0
+rule android.content.IntentFilterProto* com.android.nfc.x.@0
+rule android.content.AuthorityEntryProto* com.android.nfc.x.@0
+rule android.nfc.cardemulation.AidGroupProto* com.android.nfc.x.@0
+rule android.nfc.cardemulation.ApduServiceInfoProto* com.android.nfc.x.@0
+rule android.nfc.cardemulation.NfcFServiceInfoProto* com.android.nfc.x.@0
+rule android.nfc.NdefMessageProto* com.android.nfc.x.@0
+rule android.nfc.NdefRecordProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.CardEmulationManagerProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.RegisteredServicesCacheProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.RegisteredNfcFServicesCacheProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.PreferredServicesProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.EnabledNfcFServicesProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.RegisteredAidCacheProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.AidRoutingManagerProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.RegisteredT3tIdentifiersCacheProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.SystemCodeRoutingManagerProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.HostEmulationManagerProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.HostNfcFEmulationManagerProto* com.android.nfc.x.@0
+rule com.android.nfc.NfcServiceDumpProto* com.android.nfc.x.@0
+rule com.android.nfc.DiscoveryParamsProto* com.android.nfc.x.@0
+rule com.android.nfc.NfcDispatcherProto* com.android.nfc.x.@0
+rule android.os.PersistableBundleProto* com.android.nfc.x.@0
+
+# Used by framework-nfc for reading trunk stable flags
+rule android.nfc.FakeFeatureFlagsImpl* com.android.nfc.x.@0
+rule android.nfc.FeatureFlags* com.android.nfc.x.@0
+rule android.nfc.Flags* com.android.nfc.x.@0
+rule android.permission.flags.** com.android.nfc.x.@0
+
+# Used by framework-nfc for misc utilities
+rule android.os.PatternMatcher* com.android.nfc.x.@0
+
+rule com.android.incident.Privacy* com.android.nfc.x.@0
+rule com.android.incident.PrivacyFlags* com.android.nfc.x.@0
diff --git a/core/java/android/nfc/ApduList.aidl b/nfc/java/android/nfc/ApduList.aidl
similarity index 100%
rename from core/java/android/nfc/ApduList.aidl
rename to nfc/java/android/nfc/ApduList.aidl
diff --git a/core/java/android/nfc/ApduList.java b/nfc/java/android/nfc/ApduList.java
similarity index 100%
rename from core/java/android/nfc/ApduList.java
rename to nfc/java/android/nfc/ApduList.java
diff --git a/core/java/android/nfc/AvailableNfcAntenna.aidl b/nfc/java/android/nfc/AvailableNfcAntenna.aidl
similarity index 100%
rename from core/java/android/nfc/AvailableNfcAntenna.aidl
rename to nfc/java/android/nfc/AvailableNfcAntenna.aidl
diff --git a/core/java/android/nfc/AvailableNfcAntenna.java b/nfc/java/android/nfc/AvailableNfcAntenna.java
similarity index 100%
rename from core/java/android/nfc/AvailableNfcAntenna.java
rename to nfc/java/android/nfc/AvailableNfcAntenna.java
diff --git a/core/java/android/nfc/Constants.java b/nfc/java/android/nfc/Constants.java
similarity index 100%
rename from core/java/android/nfc/Constants.java
rename to nfc/java/android/nfc/Constants.java
diff --git a/core/java/android/nfc/ErrorCodes.java b/nfc/java/android/nfc/ErrorCodes.java
similarity index 100%
rename from core/java/android/nfc/ErrorCodes.java
rename to nfc/java/android/nfc/ErrorCodes.java
diff --git a/core/java/android/nfc/FormatException.java b/nfc/java/android/nfc/FormatException.java
similarity index 100%
rename from core/java/android/nfc/FormatException.java
rename to nfc/java/android/nfc/FormatException.java
diff --git a/core/java/android/nfc/IAppCallback.aidl b/nfc/java/android/nfc/IAppCallback.aidl
similarity index 100%
rename from core/java/android/nfc/IAppCallback.aidl
rename to nfc/java/android/nfc/IAppCallback.aidl
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl
similarity index 97%
rename from core/java/android/nfc/INfcAdapter.aidl
rename to nfc/java/android/nfc/INfcAdapter.aidl
index 286cf28..bec62c5 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/nfc/java/android/nfc/INfcAdapter.aidl
@@ -97,4 +97,7 @@
WlcLDeviceInfo getWlcLDeviceInfo();
void updateDiscoveryTechnology(IBinder b, int pollFlags, int listenFlags);
+
+ void notifyPollingLoop(in Bundle frame);
+ void notifyHceDeactivated();
}
diff --git a/core/java/android/nfc/INfcAdapterExtras.aidl b/nfc/java/android/nfc/INfcAdapterExtras.aidl
similarity index 100%
rename from core/java/android/nfc/INfcAdapterExtras.aidl
rename to nfc/java/android/nfc/INfcAdapterExtras.aidl
diff --git a/core/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl
similarity index 94%
rename from core/java/android/nfc/INfcCardEmulation.aidl
rename to nfc/java/android/nfc/INfcCardEmulation.aidl
index f4b4604..791bd8c 100644
--- a/core/java/android/nfc/INfcCardEmulation.aidl
+++ b/nfc/java/android/nfc/INfcCardEmulation.aidl
@@ -32,6 +32,7 @@
boolean setDefaultForNextTap(int userHandle, in ComponentName service);
boolean setServiceObserveModeDefault(int userId, in android.content.ComponentName service, boolean enable);
boolean registerAidGroupForService(int userHandle, in ComponentName service, in AidGroup aidGroup);
+ boolean registerPollingLoopFilterForService(int userHandle, in ComponentName service, in String pollingLoopFilter);
boolean setOffHostForService(int userHandle, in ComponentName service, in String offHostSecureElement);
boolean unsetOffHostForService(int userHandle, in ComponentName service);
AidGroup getAidGroupForService(int userHandle, in ComponentName service, String category);
diff --git a/core/java/android/nfc/INfcControllerAlwaysOnListener.aidl b/nfc/java/android/nfc/INfcControllerAlwaysOnListener.aidl
similarity index 100%
rename from core/java/android/nfc/INfcControllerAlwaysOnListener.aidl
rename to nfc/java/android/nfc/INfcControllerAlwaysOnListener.aidl
diff --git a/core/java/android/nfc/INfcDta.aidl b/nfc/java/android/nfc/INfcDta.aidl
similarity index 100%
rename from core/java/android/nfc/INfcDta.aidl
rename to nfc/java/android/nfc/INfcDta.aidl
diff --git a/core/java/android/nfc/INfcFCardEmulation.aidl b/nfc/java/android/nfc/INfcFCardEmulation.aidl
similarity index 100%
rename from core/java/android/nfc/INfcFCardEmulation.aidl
rename to nfc/java/android/nfc/INfcFCardEmulation.aidl
diff --git a/core/java/android/nfc/INfcTag.aidl b/nfc/java/android/nfc/INfcTag.aidl
similarity index 100%
rename from core/java/android/nfc/INfcTag.aidl
rename to nfc/java/android/nfc/INfcTag.aidl
diff --git a/core/java/android/nfc/INfcUnlockHandler.aidl b/nfc/java/android/nfc/INfcUnlockHandler.aidl
similarity index 100%
rename from core/java/android/nfc/INfcUnlockHandler.aidl
rename to nfc/java/android/nfc/INfcUnlockHandler.aidl
diff --git a/core/java/android/nfc/INfcWlcStateListener.aidl b/nfc/java/android/nfc/INfcWlcStateListener.aidl
similarity index 100%
rename from core/java/android/nfc/INfcWlcStateListener.aidl
rename to nfc/java/android/nfc/INfcWlcStateListener.aidl
diff --git a/core/java/android/nfc/ITagRemovedCallback.aidl b/nfc/java/android/nfc/ITagRemovedCallback.aidl
similarity index 100%
rename from core/java/android/nfc/ITagRemovedCallback.aidl
rename to nfc/java/android/nfc/ITagRemovedCallback.aidl
diff --git a/core/java/android/nfc/NdefMessage.aidl b/nfc/java/android/nfc/NdefMessage.aidl
similarity index 100%
rename from core/java/android/nfc/NdefMessage.aidl
rename to nfc/java/android/nfc/NdefMessage.aidl
diff --git a/core/java/android/nfc/NdefMessage.java b/nfc/java/android/nfc/NdefMessage.java
similarity index 100%
rename from core/java/android/nfc/NdefMessage.java
rename to nfc/java/android/nfc/NdefMessage.java
diff --git a/core/java/android/nfc/NdefRecord.aidl b/nfc/java/android/nfc/NdefRecord.aidl
similarity index 100%
rename from core/java/android/nfc/NdefRecord.aidl
rename to nfc/java/android/nfc/NdefRecord.aidl
diff --git a/core/java/android/nfc/NdefRecord.java b/nfc/java/android/nfc/NdefRecord.java
similarity index 100%
rename from core/java/android/nfc/NdefRecord.java
rename to nfc/java/android/nfc/NdefRecord.java
diff --git a/core/java/android/nfc/NfcActivityManager.java b/nfc/java/android/nfc/NfcActivityManager.java
similarity index 100%
rename from core/java/android/nfc/NfcActivityManager.java
rename to nfc/java/android/nfc/NfcActivityManager.java
diff --git a/core/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
similarity index 98%
rename from core/java/android/nfc/NfcAdapter.java
rename to nfc/java/android/nfc/NfcAdapter.java
index 75f5491..68c16e6 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -26,6 +26,7 @@
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.annotation.UserIdInt;
import android.app.Activity;
import android.app.PendingIntent;
@@ -812,8 +813,8 @@
if (context == null) {
throw new IllegalArgumentException("context cannot be null");
}
- Context applicationContext = context.getApplicationContext();
- if (applicationContext == null) {
+ context = context.getApplicationContext();
+ if (context == null) {
throw new IllegalArgumentException(
"context not associated with any application (using a mock context?)");
}
@@ -2752,6 +2753,64 @@
}
}
+ /**
+ * Notifies the system of a new polling loop.
+ *
+ * @param frame is the new frame.
+ *
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public void notifyPollingLoop(@NonNull Bundle frame) {
+ try {
+ if (sService == null) {
+ attemptDeadServiceRecovery(null);
+ }
+ sService.notifyPollingLoop(frame);
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return;
+ }
+ try {
+ sService.notifyPollingLoop(frame);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ }
+ }
+
+ /**
+ * Notifies the system of a an HCE session being deactivated.
+ * *
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public void notifyHceDeactivated() {
+ try {
+ if (sService == null) {
+ attemptDeadServiceRecovery(null);
+ }
+ sService.notifyHceDeactivated();
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return;
+ }
+ try {
+ sService.notifyHceDeactivated();
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ }
+ }
+
/**
* Sets NFC charging feature.
* <p>This API is for the Settings application.
@@ -2767,6 +2826,7 @@
}
try {
return sService.enableWlc(enable);
+
} catch (RemoteException e) {
attemptDeadServiceRecovery(e);
// Try one more time
diff --git a/core/java/android/nfc/NfcAntennaInfo.aidl b/nfc/java/android/nfc/NfcAntennaInfo.aidl
similarity index 100%
rename from core/java/android/nfc/NfcAntennaInfo.aidl
rename to nfc/java/android/nfc/NfcAntennaInfo.aidl
diff --git a/core/java/android/nfc/NfcAntennaInfo.java b/nfc/java/android/nfc/NfcAntennaInfo.java
similarity index 100%
rename from core/java/android/nfc/NfcAntennaInfo.java
rename to nfc/java/android/nfc/NfcAntennaInfo.java
diff --git a/core/java/android/nfc/NfcControllerAlwaysOnListener.java b/nfc/java/android/nfc/NfcControllerAlwaysOnListener.java
similarity index 100%
rename from core/java/android/nfc/NfcControllerAlwaysOnListener.java
rename to nfc/java/android/nfc/NfcControllerAlwaysOnListener.java
diff --git a/core/java/android/nfc/NfcEvent.java b/nfc/java/android/nfc/NfcEvent.java
similarity index 100%
rename from core/java/android/nfc/NfcEvent.java
rename to nfc/java/android/nfc/NfcEvent.java
diff --git a/core/java/android/nfc/NfcFrameworkInitializer.java b/nfc/java/android/nfc/NfcFrameworkInitializer.java
similarity index 100%
rename from core/java/android/nfc/NfcFrameworkInitializer.java
rename to nfc/java/android/nfc/NfcFrameworkInitializer.java
diff --git a/core/java/android/nfc/NfcManager.java b/nfc/java/android/nfc/NfcManager.java
similarity index 100%
rename from core/java/android/nfc/NfcManager.java
rename to nfc/java/android/nfc/NfcManager.java
diff --git a/core/java/android/nfc/NfcServiceManager.java b/nfc/java/android/nfc/NfcServiceManager.java
similarity index 100%
rename from core/java/android/nfc/NfcServiceManager.java
rename to nfc/java/android/nfc/NfcServiceManager.java
diff --git a/core/java/android/nfc/NfcWlcStateListener.java b/nfc/java/android/nfc/NfcWlcStateListener.java
similarity index 100%
rename from core/java/android/nfc/NfcWlcStateListener.java
rename to nfc/java/android/nfc/NfcWlcStateListener.java
diff --git a/core/java/android/nfc/Tag.aidl b/nfc/java/android/nfc/Tag.aidl
similarity index 100%
rename from core/java/android/nfc/Tag.aidl
rename to nfc/java/android/nfc/Tag.aidl
diff --git a/core/java/android/nfc/Tag.java b/nfc/java/android/nfc/Tag.java
similarity index 100%
rename from core/java/android/nfc/Tag.java
rename to nfc/java/android/nfc/Tag.java
diff --git a/core/java/android/nfc/TagLostException.java b/nfc/java/android/nfc/TagLostException.java
similarity index 100%
rename from core/java/android/nfc/TagLostException.java
rename to nfc/java/android/nfc/TagLostException.java
diff --git a/core/java/android/nfc/TechListParcel.aidl b/nfc/java/android/nfc/TechListParcel.aidl
similarity index 100%
rename from core/java/android/nfc/TechListParcel.aidl
rename to nfc/java/android/nfc/TechListParcel.aidl
diff --git a/core/java/android/nfc/TechListParcel.java b/nfc/java/android/nfc/TechListParcel.java
similarity index 100%
rename from core/java/android/nfc/TechListParcel.java
rename to nfc/java/android/nfc/TechListParcel.java
diff --git a/core/java/android/nfc/TransceiveResult.aidl b/nfc/java/android/nfc/TransceiveResult.aidl
similarity index 100%
rename from core/java/android/nfc/TransceiveResult.aidl
rename to nfc/java/android/nfc/TransceiveResult.aidl
diff --git a/core/java/android/nfc/TransceiveResult.java b/nfc/java/android/nfc/TransceiveResult.java
similarity index 100%
rename from core/java/android/nfc/TransceiveResult.java
rename to nfc/java/android/nfc/TransceiveResult.java
diff --git a/core/java/android/nfc/WlcLDeviceInfo.aidl b/nfc/java/android/nfc/WlcLDeviceInfo.aidl
similarity index 100%
rename from core/java/android/nfc/WlcLDeviceInfo.aidl
rename to nfc/java/android/nfc/WlcLDeviceInfo.aidl
diff --git a/core/java/android/nfc/WlcLDeviceInfo.java b/nfc/java/android/nfc/WlcLDeviceInfo.java
similarity index 100%
rename from core/java/android/nfc/WlcLDeviceInfo.java
rename to nfc/java/android/nfc/WlcLDeviceInfo.java
diff --git a/core/java/android/nfc/cardemulation/AidGroup.aidl b/nfc/java/android/nfc/cardemulation/AidGroup.aidl
similarity index 100%
rename from core/java/android/nfc/cardemulation/AidGroup.aidl
rename to nfc/java/android/nfc/cardemulation/AidGroup.aidl
diff --git a/core/java/android/nfc/cardemulation/AidGroup.java b/nfc/java/android/nfc/cardemulation/AidGroup.java
similarity index 100%
rename from core/java/android/nfc/cardemulation/AidGroup.java
rename to nfc/java/android/nfc/cardemulation/AidGroup.java
diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.aidl b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.aidl
similarity index 100%
rename from core/java/android/nfc/cardemulation/ApduServiceInfo.aidl
rename to nfc/java/android/nfc/cardemulation/ApduServiceInfo.aidl
diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
similarity index 94%
rename from core/java/android/nfc/cardemulation/ApduServiceInfo.java
rename to nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
index 41dee3a..426c5aa 100644
--- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -52,6 +52,7 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.regex.Pattern;
@@ -102,6 +103,8 @@
*/
private final HashMap<String, AidGroup> mDynamicAidGroups;
+ private final ArrayList<String> mPollingLoopFilters;
+
/**
* Whether this service should only be started when the device is unlocked.
*/
@@ -169,6 +172,7 @@
this.mDescription = description;
this.mStaticAidGroups = new HashMap<String, AidGroup>();
this.mDynamicAidGroups = new HashMap<String, AidGroup>();
+ this.mPollingLoopFilters = new ArrayList<String>();
this.mOffHostName = offHost;
this.mStaticOffHostName = staticOffHost;
this.mOnHost = onHost;
@@ -282,6 +286,7 @@
mStaticAidGroups = new HashMap<String, AidGroup>();
mDynamicAidGroups = new HashMap<String, AidGroup>();
+ mPollingLoopFilters = new ArrayList<String>();
mOnHost = onHost;
final int depth = parser.getDepth();
@@ -364,6 +369,15 @@
Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
}
a.recycle();
+ } else if (eventType == XmlPullParser.START_TAG
+ && "polling-loop-filter".equals(tagName) && currentGroup == null) {
+ final TypedArray a = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.PollingLoopFilter);
+ String plf =
+ a.getString(com.android.internal.R.styleable.PollingLoopFilter_name)
+ .toUpperCase(Locale.ROOT);
+ mPollingLoopFilters.add(plf);
+ a.recycle();
}
}
} catch (NameNotFoundException e) {
@@ -420,6 +434,16 @@
}
/**
+ * Returns the current polling loop filters for this service.
+ * @return List of polling loop filters.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+ @NonNull
+ public List<String> getPollingLoopFilters() {
+ return mPollingLoopFilters;
+ }
+
+ /**
* Returns a consolidated list of AIDs with prefixes from the AID groups
* registered by this service. Note that if a service has both
* a static (manifest-based) AID group for a category and a dynamic
@@ -596,6 +620,26 @@
}
/**
+ * Add a Polling Loop Filter. Custom NFC polling frames that match this filter will be
+ * delivered to {@link HostApduService#processPollingFrames(List)}.
+ * @param pollingLoopFilter this polling loop filter to add.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public void addPollingLoopFilter(@NonNull String pollingLoopFilter) {
+ mPollingLoopFilters.add(pollingLoopFilter.toUpperCase(Locale.ROOT));
+ }
+
+ /**
+ * Remove a Polling Loop Filter. Custom NFC polling frames that match this filter will no
+ * longer be delivered to {@link HostApduService#processPollingFrames(List)}.
+ * @param pollingLoopFilter this polling loop filter to add.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public void removePollingLoopFilter(@NonNull String pollingLoopFilter) {
+ mPollingLoopFilters.remove(pollingLoopFilter.toUpperCase(Locale.ROOT));
+ }
+
+ /**
* Sets the off host Secure Element.
* @param offHost Secure Element to set. Only accept strings with prefix SIM or prefix eSE.
* Ref: GSMA TS.26 - NFC Handset Requirements
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
similarity index 95%
rename from core/java/android/nfc/cardemulation/CardEmulation.java
rename to nfc/java/android/nfc/cardemulation/CardEmulation.java
index 81eab71..0943392 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -57,6 +57,8 @@
*/
public final class CardEmulation {
private static final Pattern AID_PATTERN = Pattern.compile("[0-9A-Fa-f]{10,32}\\*?\\#?");
+ private static final Pattern PLF_PATTERN = Pattern.compile("[0-9A-Fa-f]{1,32}");
+
static final String TAG = "CardEmulation";
/**
@@ -353,6 +355,34 @@
}
/**
+ * Register a polling loop filter for a HostApduService.
+ * @param service The HostApduService to register the filter for.
+ * @param pollingLoopFilter The filter to register.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public boolean registerPollingLoopFilterForService(@NonNull ComponentName service,
+ @NonNull String pollingLoopFilter) {
+ try {
+ return sService.registerPollingLoopFilterForService(mContext.getUser().getIdentifier(),
+ service, pollingLoopFilter);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.registerPollingLoopFilterForService(
+ mContext.getUser().getIdentifier(), service, pollingLoopFilter);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return false;
+ }
+ }
+ }
+
+ /**
* Registers a list of AIDs for a specific category for the
* specified service.
*
@@ -933,6 +963,24 @@
}
/**
+ * Tests the validity of the polling loop filter.
+ * @param pollingLoopFilter The polling loop filter to test.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public static boolean isValidPollingLoopFilter(@NonNull String pollingLoopFilter) {
+ // Verify hex characters
+ if (!PLF_PATTERN.matcher(pollingLoopFilter).matches()) {
+ Log.e(TAG, "Polling Loop Filter " + pollingLoopFilter
+ + " is not a valid Polling Loop Filter.");
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
* A valid AID according to ISO/IEC 7816-4:
* <ul>
* <li>Has >= 5 bytes and <=16 bytes (>=10 hex chars and <= 32 hex chars)
diff --git a/core/java/android/nfc/cardemulation/HostApduService.java b/nfc/java/android/nfc/cardemulation/HostApduService.java
similarity index 100%
rename from core/java/android/nfc/cardemulation/HostApduService.java
rename to nfc/java/android/nfc/cardemulation/HostApduService.java
diff --git a/core/java/android/nfc/cardemulation/HostNfcFService.java b/nfc/java/android/nfc/cardemulation/HostNfcFService.java
similarity index 100%
rename from core/java/android/nfc/cardemulation/HostNfcFService.java
rename to nfc/java/android/nfc/cardemulation/HostNfcFService.java
diff --git a/core/java/android/nfc/cardemulation/NfcFCardEmulation.java b/nfc/java/android/nfc/cardemulation/NfcFCardEmulation.java
similarity index 100%
rename from core/java/android/nfc/cardemulation/NfcFCardEmulation.java
rename to nfc/java/android/nfc/cardemulation/NfcFCardEmulation.java
diff --git a/core/java/android/nfc/cardemulation/NfcFServiceInfo.aidl b/nfc/java/android/nfc/cardemulation/NfcFServiceInfo.aidl
similarity index 100%
rename from core/java/android/nfc/cardemulation/NfcFServiceInfo.aidl
rename to nfc/java/android/nfc/cardemulation/NfcFServiceInfo.aidl
diff --git a/core/java/android/nfc/cardemulation/NfcFServiceInfo.java b/nfc/java/android/nfc/cardemulation/NfcFServiceInfo.java
similarity index 100%
rename from core/java/android/nfc/cardemulation/NfcFServiceInfo.java
rename to nfc/java/android/nfc/cardemulation/NfcFServiceInfo.java
diff --git a/core/java/android/nfc/cardemulation/OWNERS b/nfc/java/android/nfc/cardemulation/OWNERS
similarity index 100%
rename from core/java/android/nfc/cardemulation/OWNERS
rename to nfc/java/android/nfc/cardemulation/OWNERS
diff --git a/core/java/android/nfc/cardemulation/OffHostApduService.java b/nfc/java/android/nfc/cardemulation/OffHostApduService.java
similarity index 100%
rename from core/java/android/nfc/cardemulation/OffHostApduService.java
rename to nfc/java/android/nfc/cardemulation/OffHostApduService.java
diff --git a/core/java/android/nfc/cardemulation/Utils.java b/nfc/java/android/nfc/cardemulation/Utils.java
similarity index 100%
rename from core/java/android/nfc/cardemulation/Utils.java
rename to nfc/java/android/nfc/cardemulation/Utils.java
diff --git a/core/java/android/nfc/dta/NfcDta.java b/nfc/java/android/nfc/dta/NfcDta.java
similarity index 100%
rename from core/java/android/nfc/dta/NfcDta.java
rename to nfc/java/android/nfc/dta/NfcDta.java
diff --git a/core/java/android/nfc/dta/OWNERS b/nfc/java/android/nfc/dta/OWNERS
similarity index 100%
rename from core/java/android/nfc/dta/OWNERS
rename to nfc/java/android/nfc/dta/OWNERS
diff --git a/core/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig
similarity index 100%
rename from core/java/android/nfc/flags.aconfig
rename to nfc/java/android/nfc/flags.aconfig
diff --git a/core/java/android/nfc/package.html b/nfc/java/android/nfc/package.html
similarity index 100%
rename from core/java/android/nfc/package.html
rename to nfc/java/android/nfc/package.html
diff --git a/core/java/android/nfc/tech/BasicTagTechnology.java b/nfc/java/android/nfc/tech/BasicTagTechnology.java
similarity index 100%
rename from core/java/android/nfc/tech/BasicTagTechnology.java
rename to nfc/java/android/nfc/tech/BasicTagTechnology.java
diff --git a/core/java/android/nfc/tech/IsoDep.java b/nfc/java/android/nfc/tech/IsoDep.java
similarity index 100%
rename from core/java/android/nfc/tech/IsoDep.java
rename to nfc/java/android/nfc/tech/IsoDep.java
diff --git a/core/java/android/nfc/tech/MifareClassic.java b/nfc/java/android/nfc/tech/MifareClassic.java
similarity index 100%
rename from core/java/android/nfc/tech/MifareClassic.java
rename to nfc/java/android/nfc/tech/MifareClassic.java
diff --git a/core/java/android/nfc/tech/MifareUltralight.java b/nfc/java/android/nfc/tech/MifareUltralight.java
similarity index 100%
rename from core/java/android/nfc/tech/MifareUltralight.java
rename to nfc/java/android/nfc/tech/MifareUltralight.java
diff --git a/core/java/android/nfc/tech/Ndef.java b/nfc/java/android/nfc/tech/Ndef.java
similarity index 100%
rename from core/java/android/nfc/tech/Ndef.java
rename to nfc/java/android/nfc/tech/Ndef.java
diff --git a/core/java/android/nfc/tech/NdefFormatable.java b/nfc/java/android/nfc/tech/NdefFormatable.java
similarity index 100%
rename from core/java/android/nfc/tech/NdefFormatable.java
rename to nfc/java/android/nfc/tech/NdefFormatable.java
diff --git a/core/java/android/nfc/tech/NfcA.java b/nfc/java/android/nfc/tech/NfcA.java
similarity index 100%
rename from core/java/android/nfc/tech/NfcA.java
rename to nfc/java/android/nfc/tech/NfcA.java
diff --git a/core/java/android/nfc/tech/NfcB.java b/nfc/java/android/nfc/tech/NfcB.java
similarity index 100%
rename from core/java/android/nfc/tech/NfcB.java
rename to nfc/java/android/nfc/tech/NfcB.java
diff --git a/core/java/android/nfc/tech/NfcBarcode.java b/nfc/java/android/nfc/tech/NfcBarcode.java
similarity index 100%
rename from core/java/android/nfc/tech/NfcBarcode.java
rename to nfc/java/android/nfc/tech/NfcBarcode.java
diff --git a/core/java/android/nfc/tech/NfcF.java b/nfc/java/android/nfc/tech/NfcF.java
similarity index 100%
rename from core/java/android/nfc/tech/NfcF.java
rename to nfc/java/android/nfc/tech/NfcF.java
diff --git a/core/java/android/nfc/tech/NfcV.java b/nfc/java/android/nfc/tech/NfcV.java
similarity index 100%
rename from core/java/android/nfc/tech/NfcV.java
rename to nfc/java/android/nfc/tech/NfcV.java
diff --git a/core/java/android/nfc/tech/OWNERS b/nfc/java/android/nfc/tech/OWNERS
similarity index 100%
rename from core/java/android/nfc/tech/OWNERS
rename to nfc/java/android/nfc/tech/OWNERS
diff --git a/core/java/android/nfc/tech/TagTechnology.java b/nfc/java/android/nfc/tech/TagTechnology.java
similarity index 100%
rename from core/java/android/nfc/tech/TagTechnology.java
rename to nfc/java/android/nfc/tech/TagTechnology.java
diff --git a/core/java/android/nfc/tech/package.html b/nfc/java/android/nfc/tech/package.html
similarity index 100%
rename from core/java/android/nfc/tech/package.html
rename to nfc/java/android/nfc/tech/package.html
diff --git a/nfc/lint-baseline.xml b/nfc/lint-baseline.xml
new file mode 100644
index 0000000..1dfdd29
--- /dev/null
+++ b/nfc/lint-baseline.xml
@@ -0,0 +1,213 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `new android.nfc.cardemulation.AidGroup`"
+ errorLine1=" AidGroup aidGroup = new AidGroup(aids, category);"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+ line="377"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.AidGroup#getAids`"
+ errorLine1=" return (group != null ? group.getAids() : null);"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+ line="537"
+ column="43"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.AidGroup#getAids`"
+ errorLine1=" return (group != null ? group.getAids() : null);"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+ line="547"
+ column="47"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getAids`"
+ errorLine1=" return (serviceInfo != null ? serviceInfo.getAids() : null);"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+ line="714"
+ column="55"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getAids`"
+ errorLine1=" return (serviceInfo != null ? serviceInfo.getAids() : null);"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+ line="724"
+ column="59"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#isOnHost`"
+ errorLine1=" if (!serviceInfo.isOnHost()) {"
+ errorLine2=" ~~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+ line="755"
+ column="34"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`"
+ errorLine1=" return serviceInfo.getOffHostSecureElement() == null ?"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+ line="756"
+ column="40"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`"
+ errorLine1=' "OffHost" : serviceInfo.getOffHostSecureElement();'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+ line="757"
+ column="53"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#isOnHost`"
+ errorLine1=" if (!serviceInfo.isOnHost()) {"
+ errorLine2=" ~~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+ line="772"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`"
+ errorLine1=" return serviceInfo.getOffHostSecureElement() == null ?"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+ line="773"
+ column="44"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`"
+ errorLine1=' "Offhost" : serviceInfo.getOffHostSecureElement();'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+ line="774"
+ column="57"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getDescription`"
+ errorLine1=" return (serviceInfo != null ? serviceInfo.getDescription() : null);"
+ errorLine2=" ~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+ line="798"
+ column="55"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getDescription`"
+ errorLine1=" return (serviceInfo != null ? serviceInfo.getDescription() : null);"
+ errorLine2=" ~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+ line="808"
+ column="59"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
+ errorLine1=" if (!activity.isResumed()) {"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+ line="1032"
+ column="23"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
+ errorLine1=" if (!activity.isResumed()) {"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+ line="1066"
+ column="23"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
+ errorLine1=" resumed = activity.isResumed();"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/NfcActivityManager.java"
+ line="124"
+ column="32"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
+ errorLine1=" if (!activity.isResumed()) {"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/NfcAdapter.java"
+ line="2457"
+ column="23"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
+ errorLine1=" if (!activity.isResumed()) {"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/cardemulation/NfcFCardEmulation.java"
+ line="315"
+ column="23"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
+ errorLine1=" if (!activity.isResumed()) {"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/cardemulation/NfcFCardEmulation.java"
+ line="351"
+ column="23"/>
+ </issue>
+
+</issues>
\ No newline at end of file
diff --git a/core/tests/nfctests/Android.bp b/nfc/tests/Android.bp
similarity index 97%
rename from core/tests/nfctests/Android.bp
rename to nfc/tests/Android.bp
index f81be49..62566ee 100644
--- a/core/tests/nfctests/Android.bp
+++ b/nfc/tests/Android.bp
@@ -30,6 +30,7 @@
"truth",
],
libs: [
+ "framework-nfc.impl",
"android.test.runner",
],
srcs: ["src/**/*.java"],
diff --git a/core/tests/nfctests/AndroidManifest.xml b/nfc/tests/AndroidManifest.xml
similarity index 100%
rename from core/tests/nfctests/AndroidManifest.xml
rename to nfc/tests/AndroidManifest.xml
diff --git a/core/tests/nfctests/AndroidTest.xml b/nfc/tests/AndroidTest.xml
similarity index 100%
rename from core/tests/nfctests/AndroidTest.xml
rename to nfc/tests/AndroidTest.xml
diff --git a/core/tests/nfctests/src/android/nfc/NfcControllerAlwaysOnListenerTest.java b/nfc/tests/src/android/nfc/NfcControllerAlwaysOnListenerTest.java
similarity index 100%
rename from core/tests/nfctests/src/android/nfc/NfcControllerAlwaysOnListenerTest.java
rename to nfc/tests/src/android/nfc/NfcControllerAlwaysOnListenerTest.java
diff --git a/core/tests/nfctests/src/android/nfc/TechListParcelTest.java b/nfc/tests/src/android/nfc/TechListParcelTest.java
similarity index 100%
rename from core/tests/nfctests/src/android/nfc/TechListParcelTest.java
rename to nfc/tests/src/android/nfc/TechListParcelTest.java
diff --git a/packages/CredentialManager/Android.bp b/packages/CredentialManager/Android.bp
index 991fe41..c292b502 100644
--- a/packages/CredentialManager/Android.bp
+++ b/packages/CredentialManager/Android.bp
@@ -7,19 +7,14 @@
default_applicable_licenses: ["frameworks_base_license"],
}
-android_app {
- name: "CredentialManager",
- defaults: ["platform_app_defaults"],
- certificate: "platform",
+android_library {
+ name: "CredentialManager-handheld",
+
+ platform_apis: true,
+
srcs: ["src/**/*.kt"],
resource_dirs: ["res"],
- dex_preopt: {
- profile_guided: true,
- //TODO: b/312357299 - Update baseline profile
- profile: "profile.txt.prof",
- },
-
static_libs: [
"CredentialManagerShared",
"PlatformComposeCore",
@@ -42,6 +37,23 @@
"androidx.recyclerview_recyclerview",
"kotlinx-coroutines-core",
],
+}
+
+android_app {
+ name: "CredentialManager",
+ defaults: ["platform_app_defaults"],
+ certificate: "platform",
+
+ dex_preopt: {
+ profile_guided: true,
+ //TODO: b/312357299 - Update baseline profile
+ profile: "profile.txt.prof",
+ },
+
+ // Do not add new dependencies here. Add to CredentialManager-handheld instead.
+ static_libs: [
+ "CredentialManager-handheld",
+ ],
platform_apis: true,
privileged: true,
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 2f0c83b..5becc86 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
@@ -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.
@@ -23,8 +23,8 @@
android:shape="rectangle"
android:top="1dp">
<shape>
- <corners android:radius="28dp" />
- <solid android:color="@android:color/system_surface_container_high_light" />
+ <corners android:radius="16dp" />
+ <solid android:color="@color/dropdown_container" />
</shape>
</item>
</ripple>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/layout/autofill_dataset_left_with_item_tag_hint.xml b/packages/CredentialManager/res/layout/autofill_dataset_left_with_item_tag_hint.xml
deleted file mode 100644
index e4e9f7a..0000000
--- a/packages/CredentialManager/res/layout/autofill_dataset_left_with_item_tag_hint.xml
+++ /dev/null
@@ -1,37 +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.
- -->
-
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@android:id/content"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/autofill.Dataset">
- <ImageView
- android:id="@android:id/icon1"
- 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"
- style="@style/autofill.TextAppearance"/>
-
-</RelativeLayout>
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
new file mode 100644
index 0000000..cb6c6b4
--- /dev/null
+++ b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
@@ -0,0 +1,45 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/content"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxWidth="@dimen/autofill_dropdown_layout_width"
+ android:elevation="3dp">
+
+ <ImageView
+ android:id="@android:id/icon1"
+ 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="@dimen/autofill_dropdown_text_width"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_toEndOf="@android:id/icon1"
+ 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"
+ style="@style/autofill.TextSubtitle"/>
+
+</RelativeLayout>
diff --git a/packages/CredentialManager/res/values/colors.xml b/packages/CredentialManager/res/values/colors.xml
index 63b9f24..dcb7ef9 100644
--- a/packages/CredentialManager/res/values/colors.xml
+++ b/packages/CredentialManager/res/values/colors.xml
@@ -16,23 +16,8 @@
<!-- Color palette -->
<resources>
- <color name="autofill_light_colorPrimary">@color/primary_material_light</color>
- <color name="autofill_light_colorAccent">@color/accent_material_light</color>
- <color name="autofill_light_colorControlHighlight">@color/ripple_material_light</color>
- <color name="autofill_light_colorButtonNormal">@color/button_material_light</color>
-
- <!-- Text colors -->
- <color name="autofill_light_textColorPrimary">@color/abc_primary_text_material_light</color>
- <color name="autofill_light_textColorSecondary">@color/abc_secondary_text_material_light</color>
- <color name="autofill_light_textColorHint">@color/abc_hint_foreground_material_light</color>
- <color name="autofill_light_textColorHintInverse">@color/abc_hint_foreground_material_dark
- </color>
- <color name="autofill_light_textColorHighlight">@color/highlighted_text_material_light</color>
- <color name="autofill_light_textColorLink">@color/autofill_light_colorAccent</color>
-
<!-- These colors are used for Remote Views. -->
- <color name="background_dark_mode">#0E0C0B</color>
- <color name="background">#F1F3F4</color>
- <color name="text_primary_dark_mode">#DFDEDB</color>
- <color name="text_primary">#202124</color>
+ <color name="text_primary">#1A1B20</color>
+ <color name="text_secondary">#44474F</color>
+ <color name="dropdown_container">#F3F3FA</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 67003a3..2a4719d 100644
--- a/packages/CredentialManager/res/values/dimens.xml
+++ b/packages/CredentialManager/res/values/dimens.xml
@@ -17,6 +17,12 @@
-->
<resources>
- <dimen name="autofill_view_padding">16dp</dimen>
- <dimen name="autofill_icon_size">16dp</dimen>
+ <dimen name="autofill_view_top_padding">12dp</dimen>
+ <dimen name="autofill_view_right_padding">24dp</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_icon_size">24dp</dimen>
+ <dimen name="autofill_dropdown_layout_width">296dp</dimen>
+ <dimen name="autofill_dropdown_text_width">240dp</dimen>
</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/styles.xml b/packages/CredentialManager/res/values/styles.xml
index 4a5761a..7de941e 100644
--- a/packages/CredentialManager/res/values/styles.xml
+++ b/packages/CredentialManager/res/values/styles.xml
@@ -15,24 +15,13 @@
-->
<resources>
- <style name="autofill.TextAppearance.Small" parent="@style/autofill.TextAppearance">
- <item name="android:textSize">12sp</item>
- </style>
-
-
- <style name="autofill.Dataset" parent="">
- <item name="android:background">@drawable/autofill_light_selectable_item_background</item>
- </style>
-
- <style name="autofill.TextAppearance" parent="">
- <item name="android:textColor">@color/autofill_light_textColorPrimary</item>
- <item name="android:textColorHint">@color/autofill_light_textColorHint</item>
- <item name="android:textColorHighlight">@color/autofill_light_textColorHighlight</item>
- <item name="android:textColorLink">@color/autofill_light_textColorLink</item>
+ <style name="autofill.TextTitle" parent="">
+ <item name="android:fontFamily">google-sans-medium</item>
<item name="android:textSize">14sp</item>
</style>
- <style name="autofill.TextAppearance.Primary">
- <item name="android:textColor">@color/autofill_light_textColorPrimary</item>
+ <style name="autofill.TextSubtitle" parent="">
+ <item name="android:fontFamily">google-sans-text</item>
+ <item name="android:textSize">12sp</item>
</style>
</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index c409ba6..f8ffc9e 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -34,6 +34,7 @@
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.viewmodel.compose.viewModel
+import com.android.compose.theme.PlatformTheme
import com.android.credentialmanager.common.Constants
import com.android.credentialmanager.common.DialogState
import com.android.credentialmanager.common.ProviderActivityResult
@@ -43,7 +44,6 @@
import com.android.credentialmanager.createflow.hasContentToDisplay
import com.android.credentialmanager.getflow.GetCredentialScreen
import com.android.credentialmanager.getflow.hasContentToDisplay
-import com.android.credentialmanager.ui.theme.PlatformTheme
@ExperimentalMaterialApi
class CredentialSelectorActivity : ComponentActivity() {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index b2c23a4..03ac605 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -57,6 +57,7 @@
import com.android.credentialmanager.getflow.ProviderDisplayInfo
import com.android.credentialmanager.getflow.toProviderDisplayInfo
import com.android.credentialmanager.ktx.credentialEntry
+import com.android.credentialmanager.model.CredentialType
import com.android.credentialmanager.model.get.CredentialEntryInfo
import com.android.credentialmanager.model.get.ProviderInfo
import org.json.JSONException
@@ -172,7 +173,7 @@
CancellationSignal(),
Executors.newSingleThreadExecutor(),
outcome,
- autofillCallback
+ autofillCallback.asBinder()
)
}
@@ -313,12 +314,14 @@
var i = 0
var datasetAdded = false
- val duplicateDisplayNames: MutableMap<String, Boolean> = mutableMapOf()
+ val duplicateDisplayNamesForPasskeys: MutableMap<String, Boolean> = mutableMapOf()
providerDisplayInfo.sortedUserNameToCredentialEntryList.forEach {
val credentialEntry = it.sortedCredentialEntryList.first()
- credentialEntry.displayName?.let {displayName ->
- val duplicateEntry = duplicateDisplayNames.contains(displayName)
- duplicateDisplayNames[displayName] = duplicateEntry
+ if (credentialEntry.credentialType == CredentialType.PASSKEY) {
+ credentialEntry.displayName?.let { displayName ->
+ val duplicateEntry = duplicateDisplayNamesForPasskeys.contains(displayName)
+ duplicateDisplayNamesForPasskeys[displayName] = duplicateEntry
+ }
}
}
providerDisplayInfo.sortedUserNameToCredentialEntryList.forEach usernameLoop@{
@@ -355,12 +358,19 @@
} else {
spec = inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
}
- val displayName : String = primaryEntry.displayName ?: primaryEntry.userName
+ val displayName: String = if (primaryEntry.credentialType ==
+ CredentialType.PASSKEY && primaryEntry.displayName != null) {
+ primaryEntry.displayName!!
+ } else {
+ primaryEntry.userName
+ }
val sliceBuilder = InlineSuggestionUi
.newContentBuilder(pendingIntent)
.setTitle(displayName)
sliceBuilder.setStartIcon(icon)
- if (duplicateDisplayNames[displayName] == true) {
+ if (primaryEntry.credentialType ==
+ CredentialType.PASSKEY && duplicateDisplayNamesForPasskeys[displayName]
+ == true) {
sliceBuilder.setSubtitle(primaryEntry.userName)
}
inlinePresentation = InlinePresentation(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
index db69b8b..d319e4c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
@@ -24,11 +24,11 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import com.android.compose.rememberSystemUiController
+import com.android.compose.theme.LocalAndroidColorScheme
import com.android.credentialmanager.common.material.ModalBottomSheetLayout
import com.android.credentialmanager.common.material.ModalBottomSheetValue
import com.android.credentialmanager.common.material.rememberModalBottomSheetState
import com.android.credentialmanager.ui.theme.EntryShape
-import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
import kotlinx.coroutines.launch
@@ -54,7 +54,7 @@
setBottomSheetSystemBarsColor(sysUiController)
}
ModalBottomSheetLayout(
- sheetBackgroundColor = LocalAndroidColorScheme.current.colorSurfaceBright,
+ sheetBackgroundColor = LocalAndroidColorScheme.current.surfaceBright,
modifier = Modifier.background(Color.Transparent),
sheetState = state,
sheetContent = sheetContent,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
index 3976f9a..bdfe399 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
@@ -30,8 +30,8 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
+import com.android.compose.theme.LocalAndroidColorScheme
import com.android.credentialmanager.ui.theme.Shapes
-import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
/**
* Container card for the whole sheet.
@@ -50,7 +50,7 @@
modifier = modifier.fillMaxWidth().wrapContentHeight(),
border = null,
colors = CardDefaults.cardColors(
- containerColor = LocalAndroidColorScheme.current.colorSurfaceBright,
+ containerColor = LocalAndroidColorScheme.current.surfaceBright,
),
) {
if (topAppBar != null) {
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 1c394ec..a6253b8 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
@@ -56,9 +56,9 @@
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
+import com.android.compose.theme.LocalAndroidColorScheme
import com.android.credentialmanager.R
import com.android.credentialmanager.ui.theme.EntryShape
-import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
import com.android.credentialmanager.ui.theme.Shapes
@Composable
@@ -168,7 +168,7 @@
// Decorative purpose only.
contentDescription = null,
modifier = Modifier.size(24.dp),
- tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+ tint = LocalAndroidColorScheme.current.onSurfaceVariant,
)
}
}
@@ -182,7 +182,7 @@
Icon(
modifier = iconSize,
bitmap = iconImageBitmap,
- tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+ tint = LocalAndroidColorScheme.current.onSurfaceVariant,
// Decorative purpose only.
contentDescription = null,
)
@@ -206,7 +206,7 @@
Icon(
modifier = iconSize,
imageVector = iconImageVector,
- tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+ tint = LocalAndroidColorScheme.current.onSurfaceVariant,
// Decorative purpose only.
contentDescription = null,
)
@@ -218,7 +218,7 @@
Icon(
modifier = iconSize,
painter = iconPainter,
- tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+ tint = LocalAndroidColorScheme.current.onSurfaceVariant,
// Decorative purpose only.
contentDescription = null,
)
@@ -229,9 +229,9 @@
},
border = null,
colors = SuggestionChipDefaults.suggestionChipColors(
- containerColor = LocalAndroidColorScheme.current.colorSurfaceContainerHigh,
- labelColor = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
- iconContentColor = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+ containerColor = LocalAndroidColorScheme.current.surfaceContainerHigh,
+ labelColor = LocalAndroidColorScheme.current.onSurfaceVariant,
+ iconContentColor = LocalAndroidColorScheme.current.onSurfaceVariant,
),
)
}
@@ -294,7 +294,7 @@
Icon(
modifier = Modifier.size(24.dp),
painter = leadingIconPainter,
- tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+ tint = LocalAndroidColorScheme.current.onSurfaceVariant,
// Decorative purpose only.
contentDescription = null,
)
@@ -353,7 +353,7 @@
R.string.accessibility_back_arrow_button
),
modifier = Modifier.size(24.dp).autoMirrored(),
- tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+ tint = LocalAndroidColorScheme.current.onSurfaceVariant,
)
}
}
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 4dc7f00..e039dea 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
@@ -21,6 +21,7 @@
import android.widget.RemoteViews
import androidx.core.content.ContextCompat
import com.android.credentialmanager.model.get.CredentialEntryInfo
+import com.android.credentialmanager.model.CredentialType
import android.graphics.drawable.Icon
class RemoteViewsFactory {
@@ -29,48 +30,87 @@
private const val setAdjustViewBoundsMethodName = "setAdjustViewBounds"
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
): RemoteViews {
- val padding = context.resources.getDimensionPixelSize(com.android
- .credentialmanager.R.dimen.autofill_view_padding)
var layoutId: Int = com.android.credentialmanager.R.layout
- .autofill_dataset_left_with_item_tag_hint
+ .credman_dropdown_presentation_layout
val remoteViews = RemoteViews(context.packageName, layoutId)
- setRemoteViewsPaddings(remoteViews, padding)
- val textColorPrimary = getTextColorPrimary(isDarkMode(context), context);
- remoteViews.setTextColor(android.R.id.text1, textColorPrimary);
- remoteViews.setTextViewText(android.R.id.text1, credentialEntryInfo.userName)
-
+ if (credentialEntryInfo.credentialType == CredentialType.UNKNOWN) {
+ return remoteViews
+ }
+ setRemoteViewsPaddings(remoteViews, context)
+ if (credentialEntryInfo.credentialType == CredentialType.PASSKEY) {
+ val displayName = credentialEntryInfo.displayName ?: credentialEntryInfo.userName
+ remoteViews.setTextViewText(android.R.id.text1, displayName)
+ val secondaryText = if (credentialEntryInfo.displayName != null)
+ (credentialEntryInfo.userName + " " + bulletPoint + " "
+ + credentialEntryInfo.credentialTypeDisplayName
+ + " " + bulletPoint + " " + credentialEntryInfo.providerDisplayName)
+ else (credentialEntryInfo.credentialTypeDisplayName + " " + bulletPoint + " "
+ + credentialEntryInfo.providerDisplayName)
+ remoteViews.setTextViewText(android.R.id.text2, secondaryText)
+ } else {
+ remoteViews.setTextViewText(android.R.id.text1, credentialEntryInfo.userName)
+ remoteViews.setTextViewText(android.R.id.text2,
+ bulletPoint.repeat(passwordCharacterLength))
+ }
+ 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);
remoteViews.setInt(
android.R.id.icon1,
- setMaxHeightMethodName,
+ setMaxHeightMethodName,
context.resources.getDimensionPixelSize(
com.android.credentialmanager.R.dimen.autofill_icon_size));
- val drawableId = if (isDarkMode(context))
- com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_one_dark
- else com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_one
+ val drawableId =
+ com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_one
remoteViews.setInt(
android.R.id.content, setBackgroundResourceMethodName, drawableId);
return remoteViews
}
private fun setRemoteViewsPaddings(
- remoteViews: RemoteViews,
- padding: Int) {
- val halfPadding = padding / 2
+ remoteViews: RemoteViews, context: Context) {
+ 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,
- halfPadding,
- halfPadding,
- halfPadding,
- halfPadding)
+ iconToTextPadding,
+ /* top=*/topPadding,
+ /* right=*/rightPadding,
+ /* bottom=*/0)
+ remoteViews.setViewPadding(
+ android.R.id.text2,
+ iconToTextPadding,
+ /* top=*/0,
+ /* right=*/rightPadding,
+ /* bottom=*/bottomPadding)
}
private fun isDarkMode(context: Context): Boolean {
@@ -78,11 +118,5 @@
Configuration.UI_MODE_NIGHT_MASK
return currentNightMode == Configuration.UI_MODE_NIGHT_YES
}
-
- private fun getTextColorPrimary(darkMode: Boolean, context: Context): Int {
- return if (darkMode) ContextCompat.getColor(
- context, com.android.credentialmanager.R.color.text_primary_dark_mode)
- else ContextCompat.getColor(context, com.android.credentialmanager.R.color.text_primary)
- }
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt
index 2df0c7a9..342af3b 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt
@@ -24,20 +24,20 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
-import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
+import com.android.compose.theme.LocalAndroidColorScheme
@Composable
fun CredentialListSectionHeader(text: String, isFirstSection: Boolean) {
InternalSectionHeader(
text = text,
- color = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+ color = LocalAndroidColorScheme.current.onSurfaceVariant,
applyTopPadding = !isFirstSection
)
}
@Composable
fun MoreAboutPasskeySectionHeader(text: String) {
- InternalSectionHeader(text, LocalAndroidColorScheme.current.colorOnSurface)
+ InternalSectionHeader(text, LocalAndroidColorScheme.current.onSurface)
}
@Composable
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SystemUiControllerUtils.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SystemUiControllerUtils.kt
index a619523..b4075f1 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SystemUiControllerUtils.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SystemUiControllerUtils.kt
@@ -19,8 +19,8 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import com.android.compose.SystemUiController
+import com.android.compose.theme.LocalAndroidColorScheme
import com.android.credentialmanager.common.material.ModalBottomSheetDefaults
-import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
@Composable
fun setTransparentSystemBarsColor(sysUiController: SystemUiController) {
@@ -34,7 +34,7 @@
darkIcons = false
)
sysUiController.setNavigationBarColor(
- color = LocalAndroidColorScheme.current.colorSurfaceBright,
+ color = LocalAndroidColorScheme.current.surfaceBright,
darkIcons = false
)
}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
index 6b46636..9111e61 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
@@ -25,7 +25,7 @@
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
-import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
+import com.android.compose.theme.LocalAndroidColorScheme
/**
* The headline for a screen. E.g. "Create a passkey for X", "Choose a saved sign-in for X".
@@ -37,7 +37,7 @@
Text(
modifier = modifier.wrapContentSize(),
text = text,
- color = LocalAndroidColorScheme.current.colorOnSurface,
+ color = LocalAndroidColorScheme.current.onSurface,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.headlineSmall,
)
@@ -51,7 +51,7 @@
Text(
modifier = modifier.wrapContentSize(),
text = text,
- color = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+ color = LocalAndroidColorScheme.current.onSurfaceVariant,
style = MaterialTheme.typography.bodyMedium,
)
}
@@ -69,7 +69,7 @@
Text(
modifier = modifier.wrapContentSize(),
text = text,
- color = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+ color = LocalAndroidColorScheme.current.onSurfaceVariant,
style = MaterialTheme.typography.bodySmall,
overflow = TextOverflow.Ellipsis,
maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE,
@@ -85,7 +85,7 @@
Text(
modifier = modifier.wrapContentSize(),
text = text,
- color = LocalAndroidColorScheme.current.colorOnSurface,
+ color = LocalAndroidColorScheme.current.onSurface,
style = MaterialTheme.typography.titleLarge,
)
}
@@ -103,7 +103,7 @@
Text(
modifier = modifier.wrapContentSize(),
text = text,
- color = LocalAndroidColorScheme.current.colorOnSurface,
+ color = LocalAndroidColorScheme.current.onSurface,
style = MaterialTheme.typography.titleSmall,
overflow = TextOverflow.Ellipsis,
maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE,
@@ -159,7 +159,7 @@
modifier = modifier.wrapContentSize(),
text = text,
textAlign = TextAlign.Center,
- color = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+ color = LocalAndroidColorScheme.current.onSurfaceVariant,
style = MaterialTheme.typography.labelLarge,
)
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 14a9165..f261d1f 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -46,6 +46,7 @@
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.core.graphics.drawable.toBitmap
+import com.android.compose.theme.LocalAndroidColorScheme
import com.android.credentialmanager.CredentialSelectorViewModel
import com.android.credentialmanager.R
import com.android.credentialmanager.model.EntryInfo
@@ -70,7 +71,6 @@
import com.android.credentialmanager.logging.CreateCredentialEvent
import com.android.credentialmanager.model.creation.CreateOptionInfo
import com.android.credentialmanager.model.creation.RemoteInfo
-import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
import com.android.internal.logging.UiEventLogger.UiEventEnum
@Composable
@@ -460,7 +460,7 @@
item {
Divider(
thickness = 1.dp,
- color = LocalAndroidColorScheme.current.colorOutlineVariant,
+ color = LocalAndroidColorScheme.current.outlineVariant,
modifier = Modifier.padding(vertical = 16.dp)
)
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index a291f59..458a99a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -26,7 +26,7 @@
import com.android.internal.util.Preconditions
data class GetCredentialUiState(
- val isRequestForAllOptions: Boolean,
+ val isRequestForAllOptions: Boolean,
val providerInfoList: List<ProviderInfo>,
val requestDisplayInfo: RequestDisplayInfo,
val providerDisplayInfo: ProviderDisplayInfo = toProviderDisplayInfo(providerInfoList),
@@ -165,7 +165,7 @@
)
}
-private fun toActiveEntry(
+fun toActiveEntry(
providerDisplayInfo: ProviderDisplayInfo,
): EntryInfo? {
val sortedUserNameToCredentialEntryList =
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt
deleted file mode 100644
index a33904d..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.theme
-
-import android.annotation.ColorInt
-import android.content.Context
-import androidx.compose.runtime.staticCompositionLocalOf
-import androidx.compose.ui.graphics.Color
-import com.android.internal.R
-
-/** File copied from PlatformComposeCore. */
-
-/** CompositionLocal used to pass [AndroidColorScheme] down the tree. */
-val LocalAndroidColorScheme =
- staticCompositionLocalOf<AndroidColorScheme> {
- throw IllegalStateException(
- "No AndroidColorScheme configured. Make sure to use LocalAndroidColorScheme in a " +
- "Composable surrounded by a PlatformTheme {}."
- )
- }
-
-/**
- * The Android color scheme.
- *
- * Important: Use M3 colors from MaterialTheme.colorScheme whenever possible instead. In the future,
- * most of the colors in this class will be removed in favor of their M3 counterpart.
- */
-class AndroidColorScheme internal constructor(context: Context) {
- val colorSurfaceBright = getColor(context, R.attr.materialColorSurfaceBright)
- val colorSurfaceContainerHigh = getColor(context, R.attr.materialColorSurfaceContainerHigh)
- val colorOutlineVariant = getColor(context, R.attr.materialColorOutlineVariant)
- val colorOnSurface = getColor(context, R.attr.materialColorOnSurface)
- val colorOnSurfaceVariant = getColor(context, R.attr.materialColorOnSurfaceVariant)
-
- companion object {
- fun getColor(context: Context, attr: Int): Color {
- val ta = context.obtainStyledAttributes(intArrayOf(attr))
- @ColorInt val color = ta.getColor(0, 0)
- ta.recycle()
- return Color(color)
- }
- }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt
deleted file mode 100644
index c923845..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.theme
-
-import android.annotation.AttrRes
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.ReadOnlyComposable
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalContext
-
-/** Read the [Color] from the given [attribute]. */
-@Composable
-@ReadOnlyComposable
-fun colorAttr(@AttrRes attribute: Int): Color {
- return AndroidColorScheme.getColor(LocalContext.current, attribute)
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/PlatformTheme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/PlatformTheme.kt
deleted file mode 100644
index 2f1ce68..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/PlatformTheme.kt
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.theme
-
-import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.dynamicDarkColorScheme
-import androidx.compose.material3.dynamicLightColorScheme
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.remember
-import androidx.compose.ui.platform.LocalContext
-import com.android.credentialmanager.ui.theme.typography.TypeScaleTokens
-import com.android.credentialmanager.ui.theme.typography.TypefaceNames
-import com.android.credentialmanager.ui.theme.typography.TypefaceTokens
-import com.android.credentialmanager.ui.theme.typography.TypographyTokens
-import com.android.credentialmanager.ui.theme.typography.platformTypography
-
-/** File copied from PlatformComposeCore. */
-
-/**
- * The Material 3 theme that should wrap all Platform Composables.
- *
- * TODO(b/280685309): Merge with the official SysUI platform theme.
- */
-@Composable
-fun PlatformTheme(
- isDarkTheme: Boolean = isSystemInDarkTheme(),
- content: @Composable () -> Unit,
-) {
- val context = LocalContext.current
-
- val colorScheme =
- if (isDarkTheme) {
- dynamicDarkColorScheme(context)
- } else {
- dynamicLightColorScheme(context)
- }
- val androidColorScheme = AndroidColorScheme(context)
- val typefaceNames = remember(context) { TypefaceNames.get(context) }
- val typography =
- remember(typefaceNames) {
- platformTypography(TypographyTokens(TypeScaleTokens(TypefaceTokens(typefaceNames))))
- }
-
- MaterialTheme(colorScheme, typography = typography) {
- CompositionLocalProvider(
- LocalAndroidColorScheme provides androidColorScheme,
- ) {
- content()
- }
- }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/PlatformTypography.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/PlatformTypography.kt
deleted file mode 100644
index 984e4f1..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/PlatformTypography.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.theme.typography
-
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Typography
-
-/** File copied from PlatformComposeCore. */
-
-/**
- * The typography for Platform Compose code.
- *
- * Do not use directly and call [MaterialTheme.typography] instead to access the different text
- * styles.
- */
-internal fun platformTypography(typographyTokens: TypographyTokens): Typography {
- return Typography(
- displayLarge = typographyTokens.displayLarge,
- displayMedium = typographyTokens.displayMedium,
- displaySmall = typographyTokens.displaySmall,
- headlineLarge = typographyTokens.headlineLarge,
- headlineMedium = typographyTokens.headlineMedium,
- headlineSmall = typographyTokens.headlineSmall,
- titleLarge = typographyTokens.titleLarge,
- titleMedium = typographyTokens.titleMedium,
- titleSmall = typographyTokens.titleSmall,
- bodyLarge = typographyTokens.bodyLarge,
- bodyMedium = typographyTokens.bodyMedium,
- bodySmall = typographyTokens.bodySmall,
- labelLarge = typographyTokens.labelLarge,
- labelMedium = typographyTokens.labelMedium,
- labelSmall = typographyTokens.labelSmall,
- )
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypeScaleTokens.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypeScaleTokens.kt
deleted file mode 100644
index b2dd207..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypeScaleTokens.kt
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.theme.typography
-
-import androidx.compose.ui.unit.sp
-
-/** File copied from PlatformComposeCore. */
-internal class TypeScaleTokens(typefaceTokens: TypefaceTokens) {
- val bodyLargeFont = typefaceTokens.plain
- val bodyLargeLineHeight = 24.0.sp
- val bodyLargeSize = 16.sp
- val bodyLargeTracking = 0.0.sp
- val bodyLargeWeight = TypefaceTokens.WeightRegular
- val bodyMediumFont = typefaceTokens.plain
- val bodyMediumLineHeight = 20.0.sp
- val bodyMediumSize = 14.sp
- val bodyMediumTracking = 0.0.sp
- val bodyMediumWeight = TypefaceTokens.WeightRegular
- val bodySmallFont = typefaceTokens.plain
- val bodySmallLineHeight = 16.0.sp
- val bodySmallSize = 12.sp
- val bodySmallTracking = 0.1.sp
- val bodySmallWeight = TypefaceTokens.WeightRegular
- val displayLargeFont = typefaceTokens.brand
- val displayLargeLineHeight = 64.0.sp
- val displayLargeSize = 57.sp
- val displayLargeTracking = 0.0.sp
- val displayLargeWeight = TypefaceTokens.WeightRegular
- val displayMediumFont = typefaceTokens.brand
- val displayMediumLineHeight = 52.0.sp
- val displayMediumSize = 45.sp
- val displayMediumTracking = 0.0.sp
- val displayMediumWeight = TypefaceTokens.WeightRegular
- val displaySmallFont = typefaceTokens.brand
- val displaySmallLineHeight = 44.0.sp
- val displaySmallSize = 36.sp
- val displaySmallTracking = 0.0.sp
- val displaySmallWeight = TypefaceTokens.WeightRegular
- val headlineLargeFont = typefaceTokens.brand
- val headlineLargeLineHeight = 40.0.sp
- val headlineLargeSize = 32.sp
- val headlineLargeTracking = 0.0.sp
- val headlineLargeWeight = TypefaceTokens.WeightRegular
- val headlineMediumFont = typefaceTokens.brand
- val headlineMediumLineHeight = 36.0.sp
- val headlineMediumSize = 28.sp
- val headlineMediumTracking = 0.0.sp
- val headlineMediumWeight = TypefaceTokens.WeightRegular
- val headlineSmallFont = typefaceTokens.brand
- val headlineSmallLineHeight = 32.0.sp
- val headlineSmallSize = 24.sp
- val headlineSmallTracking = 0.0.sp
- val headlineSmallWeight = TypefaceTokens.WeightRegular
- val labelLargeFont = typefaceTokens.plain
- val labelLargeLineHeight = 20.0.sp
- val labelLargeSize = 14.sp
- val labelLargeTracking = 0.0.sp
- val labelLargeWeight = TypefaceTokens.WeightMedium
- val labelMediumFont = typefaceTokens.plain
- val labelMediumLineHeight = 16.0.sp
- val labelMediumSize = 12.sp
- val labelMediumTracking = 0.1.sp
- val labelMediumWeight = TypefaceTokens.WeightMedium
- val labelSmallFont = typefaceTokens.plain
- val labelSmallLineHeight = 16.0.sp
- val labelSmallSize = 11.sp
- val labelSmallTracking = 0.1.sp
- val labelSmallWeight = TypefaceTokens.WeightMedium
- val titleLargeFont = typefaceTokens.brand
- val titleLargeLineHeight = 28.0.sp
- val titleLargeSize = 22.sp
- val titleLargeTracking = 0.0.sp
- val titleLargeWeight = TypefaceTokens.WeightRegular
- val titleMediumFont = typefaceTokens.plain
- val titleMediumLineHeight = 24.0.sp
- val titleMediumSize = 16.sp
- val titleMediumTracking = 0.0.sp
- val titleMediumWeight = TypefaceTokens.WeightMedium
- val titleSmallFont = typefaceTokens.plain
- val titleSmallLineHeight = 20.0.sp
- val titleSmallSize = 14.sp
- val titleSmallTracking = 0.0.sp
- val titleSmallWeight = TypefaceTokens.WeightMedium
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypefaceTokens.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypefaceTokens.kt
deleted file mode 100644
index 3cc761f..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypefaceTokens.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-@file:OptIn(ExperimentalTextApi::class)
-
-package com.android.credentialmanager.ui.theme.typography
-
-import android.content.Context
-import androidx.compose.ui.text.ExperimentalTextApi
-import androidx.compose.ui.text.font.DeviceFontFamilyName
-import androidx.compose.ui.text.font.Font
-import androidx.compose.ui.text.font.FontFamily
-import androidx.compose.ui.text.font.FontWeight
-
-/** File copied from PlatformComposeCore. */
-internal class TypefaceTokens(typefaceNames: TypefaceNames) {
- companion object {
- val WeightMedium = FontWeight.Medium
- val WeightRegular = FontWeight.Normal
- }
-
- private val brandFont = DeviceFontFamilyName(typefaceNames.brand)
- private val plainFont = DeviceFontFamilyName(typefaceNames.plain)
-
- val brand =
- FontFamily(
- Font(brandFont, weight = WeightMedium),
- Font(brandFont, weight = WeightRegular),
- )
- val plain =
- FontFamily(
- Font(plainFont, weight = WeightMedium),
- Font(plainFont, weight = WeightRegular),
- )
-}
-
-internal data class TypefaceNames
-private constructor(
- val brand: String,
- val plain: String,
-) {
- private enum class Config(val configName: String, val default: String) {
- Brand("config_headlineFontFamily", "sans-serif"),
- Plain("config_bodyFontFamily", "sans-serif"),
- }
-
- companion object {
- fun get(context: Context): TypefaceNames {
- return TypefaceNames(
- brand = getTypefaceName(context, Config.Brand),
- plain = getTypefaceName(context, Config.Plain),
- )
- }
-
- private fun getTypefaceName(context: Context, config: Config): String {
- return context
- .getString(context.resources.getIdentifier(config.configName, "string", "android"))
- .takeIf { it.isNotEmpty() }
- ?: config.default
- }
- }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypographyTokens.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypographyTokens.kt
deleted file mode 100644
index aadab92..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypographyTokens.kt
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.theme.typography
-
-import androidx.compose.ui.text.TextStyle
-
-/** File copied from PlatformComposeCore. */
-internal class TypographyTokens(typeScaleTokens: TypeScaleTokens) {
- val bodyLarge =
- TextStyle(
- fontFamily = typeScaleTokens.bodyLargeFont,
- fontWeight = typeScaleTokens.bodyLargeWeight,
- fontSize = typeScaleTokens.bodyLargeSize,
- lineHeight = typeScaleTokens.bodyLargeLineHeight,
- letterSpacing = typeScaleTokens.bodyLargeTracking,
- )
- val bodyMedium =
- TextStyle(
- fontFamily = typeScaleTokens.bodyMediumFont,
- fontWeight = typeScaleTokens.bodyMediumWeight,
- fontSize = typeScaleTokens.bodyMediumSize,
- lineHeight = typeScaleTokens.bodyMediumLineHeight,
- letterSpacing = typeScaleTokens.bodyMediumTracking,
- )
- val bodySmall =
- TextStyle(
- fontFamily = typeScaleTokens.bodySmallFont,
- fontWeight = typeScaleTokens.bodySmallWeight,
- fontSize = typeScaleTokens.bodySmallSize,
- lineHeight = typeScaleTokens.bodySmallLineHeight,
- letterSpacing = typeScaleTokens.bodySmallTracking,
- )
- val displayLarge =
- TextStyle(
- fontFamily = typeScaleTokens.displayLargeFont,
- fontWeight = typeScaleTokens.displayLargeWeight,
- fontSize = typeScaleTokens.displayLargeSize,
- lineHeight = typeScaleTokens.displayLargeLineHeight,
- letterSpacing = typeScaleTokens.displayLargeTracking,
- )
- val displayMedium =
- TextStyle(
- fontFamily = typeScaleTokens.displayMediumFont,
- fontWeight = typeScaleTokens.displayMediumWeight,
- fontSize = typeScaleTokens.displayMediumSize,
- lineHeight = typeScaleTokens.displayMediumLineHeight,
- letterSpacing = typeScaleTokens.displayMediumTracking,
- )
- val displaySmall =
- TextStyle(
- fontFamily = typeScaleTokens.displaySmallFont,
- fontWeight = typeScaleTokens.displaySmallWeight,
- fontSize = typeScaleTokens.displaySmallSize,
- lineHeight = typeScaleTokens.displaySmallLineHeight,
- letterSpacing = typeScaleTokens.displaySmallTracking,
- )
- val headlineLarge =
- TextStyle(
- fontFamily = typeScaleTokens.headlineLargeFont,
- fontWeight = typeScaleTokens.headlineLargeWeight,
- fontSize = typeScaleTokens.headlineLargeSize,
- lineHeight = typeScaleTokens.headlineLargeLineHeight,
- letterSpacing = typeScaleTokens.headlineLargeTracking,
- )
- val headlineMedium =
- TextStyle(
- fontFamily = typeScaleTokens.headlineMediumFont,
- fontWeight = typeScaleTokens.headlineMediumWeight,
- fontSize = typeScaleTokens.headlineMediumSize,
- lineHeight = typeScaleTokens.headlineMediumLineHeight,
- letterSpacing = typeScaleTokens.headlineMediumTracking,
- )
- val headlineSmall =
- TextStyle(
- fontFamily = typeScaleTokens.headlineSmallFont,
- fontWeight = typeScaleTokens.headlineSmallWeight,
- fontSize = typeScaleTokens.headlineSmallSize,
- lineHeight = typeScaleTokens.headlineSmallLineHeight,
- letterSpacing = typeScaleTokens.headlineSmallTracking,
- )
- val labelLarge =
- TextStyle(
- fontFamily = typeScaleTokens.labelLargeFont,
- fontWeight = typeScaleTokens.labelLargeWeight,
- fontSize = typeScaleTokens.labelLargeSize,
- lineHeight = typeScaleTokens.labelLargeLineHeight,
- letterSpacing = typeScaleTokens.labelLargeTracking,
- )
- val labelMedium =
- TextStyle(
- fontFamily = typeScaleTokens.labelMediumFont,
- fontWeight = typeScaleTokens.labelMediumWeight,
- fontSize = typeScaleTokens.labelMediumSize,
- lineHeight = typeScaleTokens.labelMediumLineHeight,
- letterSpacing = typeScaleTokens.labelMediumTracking,
- )
- val labelSmall =
- TextStyle(
- fontFamily = typeScaleTokens.labelSmallFont,
- fontWeight = typeScaleTokens.labelSmallWeight,
- fontSize = typeScaleTokens.labelSmallSize,
- lineHeight = typeScaleTokens.labelSmallLineHeight,
- letterSpacing = typeScaleTokens.labelSmallTracking,
- )
- val titleLarge =
- TextStyle(
- fontFamily = typeScaleTokens.titleLargeFont,
- fontWeight = typeScaleTokens.titleLargeWeight,
- fontSize = typeScaleTokens.titleLargeSize,
- lineHeight = typeScaleTokens.titleLargeLineHeight,
- letterSpacing = typeScaleTokens.titleLargeTracking,
- )
- val titleMedium =
- TextStyle(
- fontFamily = typeScaleTokens.titleMediumFont,
- fontWeight = typeScaleTokens.titleMediumWeight,
- fontSize = typeScaleTokens.titleMediumSize,
- lineHeight = typeScaleTokens.titleMediumLineHeight,
- letterSpacing = typeScaleTokens.titleMediumTracking,
- )
- val titleSmall =
- TextStyle(
- fontFamily = typeScaleTokens.titleSmallFont,
- fontWeight = typeScaleTokens.titleSmallWeight,
- fontSize = typeScaleTokens.titleSmallSize,
- lineHeight = typeScaleTokens.titleSmallLineHeight,
- letterSpacing = typeScaleTokens.titleSmallTracking,
- )
-}
diff --git a/packages/InputDevices/res/values-sv/strings.xml b/packages/InputDevices/res/values-sv/strings.xml
index 3d0b945..b1e5d75 100644
--- a/packages/InputDevices/res/values-sv/strings.xml
+++ b/packages/InputDevices/res/values-sv/strings.xml
@@ -3,50 +3,50 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="8016145283189546017">"Indataenheter"</string>
<string name="keyboard_layouts_label" msgid="6688773268302087545">"Androids tangentbord"</string>
- <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"Engelskt (Storbritannien)"</string>
- <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"Engelskt (USA)"</string>
- <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Engelskt (USA), internationellt"</string>
- <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Engelskt (USA), colemak"</string>
- <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Engelskt (USA), dvorak"</string>
- <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Engelskt (USA), workman"</string>
- <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Tyskt"</string>
- <string name="keyboard_layout_french_label" msgid="813450119589383723">"Franskt"</string>
- <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Franskt (Kanada)"</string>
+ <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"engelska (Storbritannien)"</string>
+ <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"engelska (USA)"</string>
+ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"engelska (USA), internationell"</string>
+ <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"engelska (USA), colemak"</string>
+ <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"engelska (USA), dvorak"</string>
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"engelska (USA), workman"</string>
+ <string name="keyboard_layout_german_label" msgid="8451565865467909999">"tyska"</string>
+ <string name="keyboard_layout_french_label" msgid="813450119589383723">"franska"</string>
+ <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"franska (Kanada)"</string>
<string name="keyboard_layout_russian_label" msgid="8724879775815042968">"ryska"</string>
- <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"Ryskt, Mac"</string>
- <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"Spanskt"</string>
- <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"Franskt (Schweiz)"</string>
- <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Tyskt (Schweiz)"</string>
- <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgiskt"</string>
+ <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"ryska, Mac"</string>
+ <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"spanska"</string>
+ <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"franska (Schweiz)"</string>
+ <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"tyska (Schweiz)"</string>
+ <string name="keyboard_layout_belgian" msgid="2011984572838651558">"belgiska"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"bulgariska"</string>
- <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Bulgariska (fonetiskt)"</string>
- <string name="keyboard_layout_italian" msgid="6497079660449781213">"Italienskt"</string>
- <string name="keyboard_layout_danish" msgid="8036432066627127851">"Danskt"</string>
- <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norskt"</string>
- <string name="keyboard_layout_swedish" msgid="732959109088479351">"Svenskt"</string>
- <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Finskt"</string>
- <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Kroatiskt"</string>
- <string name="keyboard_layout_czech" msgid="1349256901452975343">"Tjeckiskt"</string>
- <string name="keyboard_layout_czech_qwerty" msgid="3331402534128515501">"Tjeckiskt QWERTY"</string>
- <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Estniskt"</string>
- <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Ungerskt"</string>
- <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Isländskt"</string>
- <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"Portugisiskt (Brasilien)"</string>
- <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"Portugisiskt"</string>
- <string name="keyboard_layout_slovak" msgid="2469379934672837296">"Slovakiskt"</string>
- <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"Slovenskt"</string>
- <string name="keyboard_layout_turkish" msgid="7736163250907964898">"Turkiskt"</string>
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"bulgariska (fonetiskt)"</string>
+ <string name="keyboard_layout_italian" msgid="6497079660449781213">"italienska"</string>
+ <string name="keyboard_layout_danish" msgid="8036432066627127851">"danska"</string>
+ <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"norska"</string>
+ <string name="keyboard_layout_swedish" msgid="732959109088479351">"svenska"</string>
+ <string name="keyboard_layout_finnish" msgid="5585659438924315466">"finska"</string>
+ <string name="keyboard_layout_croatian" msgid="4172229471079281138">"kroatiska"</string>
+ <string name="keyboard_layout_czech" msgid="1349256901452975343">"tjeckiska"</string>
+ <string name="keyboard_layout_czech_qwerty" msgid="3331402534128515501">"tjeckiska QWERTY"</string>
+ <string name="keyboard_layout_estonian" msgid="8775830985185665274">"estniska"</string>
+ <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"ungerska"</string>
+ <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"isländska"</string>
+ <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"portugisiska (Brasilien)"</string>
+ <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"portugisiska"</string>
+ <string name="keyboard_layout_slovak" msgid="2469379934672837296">"slovakiska"</string>
+ <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"slovenska"</string>
+ <string name="keyboard_layout_turkish" msgid="7736163250907964898">"turkiska"</string>
<string name="keyboard_layout_turkish_f" msgid="9130320856010776018">"turkiska, F"</string>
- <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"Ukrainskt"</string>
+ <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"ukrainska"</string>
<string name="keyboard_layout_arabic" msgid="5671970465174968712">"arabiska"</string>
<string name="keyboard_layout_greek" msgid="7289253560162386040">"grekiska"</string>
<string name="keyboard_layout_hebrew" msgid="7241473985890173812">"hebreiska"</string>
- <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Litauiska"</string>
- <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Spanska (latinamerikansk)"</string>
+ <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"litauiska"</string>
+ <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"spanska (latinamerikansk)"</string>
<string name="keyboard_layout_latvian" msgid="4405417142306250595">"lettiska"</string>
<string name="keyboard_layout_persian" msgid="3920643161015888527">"persiska"</string>
<string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"azerbajdzjanska"</string>
- <string name="keyboard_layout_polish" msgid="1121588624094925325">"Polska"</string>
+ <string name="keyboard_layout_polish" msgid="1121588624094925325">"polska"</string>
<string name="keyboard_layout_belarusian" msgid="7619281752698687588">"vitryska"</string>
<string name="keyboard_layout_mongolian" msgid="7678483495823936626">"mongoliska"</string>
<string name="keyboard_layout_georgian" msgid="4596185456863747454">"georgiska"</string>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java
index b5af845..9af799c 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java
@@ -31,7 +31,7 @@
import android.os.Process;
import android.util.Log;
-import androidx.annotation.Nullable;
+import androidx.annotation.NonNull;
import java.io.IOException;
import java.util.Arrays;
@@ -105,7 +105,7 @@
}
}
- @Nullable
+ @NonNull
private String[] getRequestedPermissions(String callingPackage) {
String[] requestedPermissions = null;
try {
@@ -115,7 +115,7 @@
// Should be unreachable because we've just fetched the packageName above.
Log.e(TAG, "Package not found for " + callingPackage);
}
- return requestedPermissions;
+ return requestedPermissions == null ? new String[]{} : requestedPermissions;
}
void startUnarchive() {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
index 2da8c8c..221ca4f 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
@@ -32,6 +32,7 @@
import android.os.Bundle;
import android.os.Flags;
import android.os.Process;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
@@ -131,7 +132,7 @@
final boolean isUpdate =
((dialogInfo.appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
final boolean isArchive =
- android.content.pm.Flags.archiving() && (
+ isArchivingEnabled() && (
(dialogInfo.deleteFlags & PackageManager.DELETE_ARCHIVE) != 0);
final UserHandle myUserHandle = Process.myUserHandle();
UserManager userManager = getContext().getSystemService(UserManager.class);
@@ -242,6 +243,11 @@
return dialogBuilder.create();
}
+ private static boolean isArchivingEnabled() {
+ return android.content.pm.Flags.archiving()
+ || SystemProperties.getBoolean("pm.archiving.enabled", false);
+ }
+
private boolean isCloneProfile(UserHandle userHandle) {
UserManager customUserManager = getContext()
.createContextAsUser(UserHandle.of(userHandle.getIdentifier()), 0)
diff --git a/packages/SettingsLib/ActionBarShadow/Android.bp b/packages/SettingsLib/ActionBarShadow/Android.bp
index 6f94458..77cbb00 100644
--- a/packages/SettingsLib/ActionBarShadow/Android.bp
+++ b/packages/SettingsLib/ActionBarShadow/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibActionBarShadow",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
diff --git a/packages/SettingsLib/ActionButtonsPreference/Android.bp b/packages/SettingsLib/ActionButtonsPreference/Android.bp
index 1228555..c36b82d 100644
--- a/packages/SettingsLib/ActionButtonsPreference/Android.bp
+++ b/packages/SettingsLib/ActionButtonsPreference/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibActionButtonsPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/ActivityEmbedding/Android.bp b/packages/SettingsLib/ActivityEmbedding/Android.bp
index 41de29a..838a9e5 100644
--- a/packages/SettingsLib/ActivityEmbedding/Android.bp
+++ b/packages/SettingsLib/ActivityEmbedding/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibActivityEmbedding",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
diff --git a/packages/SettingsLib/AdaptiveIcon/Android.bp b/packages/SettingsLib/AdaptiveIcon/Android.bp
index 044ba87..67b6fb5 100644
--- a/packages/SettingsLib/AdaptiveIcon/Android.bp
+++ b/packages/SettingsLib/AdaptiveIcon/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibAdaptiveIcon",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/AdaptiveIcon/lint-baseline.xml b/packages/SettingsLib/AdaptiveIcon/lint-baseline.xml
index 7f16517..8127e1a 100644
--- a/packages/SettingsLib/AdaptiveIcon/lint-baseline.xml
+++ b/packages/SettingsLib/AdaptiveIcon/lint-baseline.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
<issue
id="NewApi"
@@ -8,7 +8,7 @@
errorLine2=" ~~~~~~~~">
<location
file="frameworks/base/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveIcon.java"
- line="78"
+ line="79"
column="34"/>
</issue>
@@ -19,7 +19,7 @@
errorLine2=" ~~~~~~~~">
<location
file="frameworks/base/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveIcon.java"
- line="90"
+ line="91"
column="36"/>
</issue>
@@ -30,7 +30,7 @@
errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="frameworks/base/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java"
- line="43"
+ line="45"
column="46"/>
</issue>
@@ -41,7 +41,7 @@
errorLine2=" ~~~~~">
<location
file="frameworks/base/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java"
- line="65"
+ line="67"
column="9"/>
</issue>
@@ -52,7 +52,7 @@
errorLine2=" ~~~~~">
<location
file="frameworks/base/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java"
- line="72"
+ line="74"
column="9"/>
</issue>
@@ -63,7 +63,7 @@
errorLine2=" ~~~~~~~~~~~">
<location
file="frameworks/base/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java"
- line="80"
+ line="82"
column="9"/>
</issue>
@@ -74,8 +74,8 @@
errorLine2=" ~~~~~~~~">
<location
file="frameworks/base/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java"
- line="105"
+ line="107"
column="26"/>
</issue>
-</issues>
+</issues>
\ No newline at end of file
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 8b16d64..c2cb757 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -9,6 +9,9 @@
android_library {
name: "SettingsLib",
+ defaults: [
+ "SettingsLintDefaults",
+ ],
static_libs: [
"androidx.localbroadcastmanager_localbroadcastmanager",
@@ -60,9 +63,15 @@
"src/**/*.java",
"src/**/*.kt",
],
+}
+
+// defaults for lint option
+java_defaults {
+ name: "SettingsLintDefaults",
lint: {
- baseline_filename: "lint-baseline.xml",
- extra_check_modules: ["SettingsLibLintChecker"],
+ extra_check_modules: [
+ "SettingsLibLintChecker",
+ ],
},
}
diff --git a/packages/SettingsLib/AppPreference/Android.bp b/packages/SettingsLib/AppPreference/Android.bp
index 69b9d44..c5b2ef6 100644
--- a/packages/SettingsLib/AppPreference/Android.bp
+++ b/packages/SettingsLib/AppPreference/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibAppPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/BannerMessagePreference/Android.bp b/packages/SettingsLib/BannerMessagePreference/Android.bp
index da91344..07290de 100644
--- a/packages/SettingsLib/BannerMessagePreference/Android.bp
+++ b/packages/SettingsLib/BannerMessagePreference/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibBannerMessagePreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/BarChartPreference/Android.bp b/packages/SettingsLib/BarChartPreference/Android.bp
index be1e0cf..448ed56 100644
--- a/packages/SettingsLib/BarChartPreference/Android.bp
+++ b/packages/SettingsLib/BarChartPreference/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibBarChartPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/ButtonPreference/Android.bp b/packages/SettingsLib/ButtonPreference/Android.bp
index 35572fad..0382829 100644
--- a/packages/SettingsLib/ButtonPreference/Android.bp
+++ b/packages/SettingsLib/ButtonPreference/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibButtonPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
index 70f7554..87ec0b8 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibCollapsingToolbarBaseActivity",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/DisplayUtils/Android.bp b/packages/SettingsLib/DisplayUtils/Android.bp
index eab35a1..279bb70 100644
--- a/packages/SettingsLib/DisplayUtils/Android.bp
+++ b/packages/SettingsLib/DisplayUtils/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibDisplayUtils",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
diff --git a/packages/SettingsLib/EmergencyNumber/lint-baseline.xml b/packages/SettingsLib/EmergencyNumber/lint-baseline.xml
index e9c687f..13bf5f5 100644
--- a/packages/SettingsLib/EmergencyNumber/lint-baseline.xml
+++ b/packages/SettingsLib/EmergencyNumber/lint-baseline.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
<issue
id="NewApi"
@@ -8,7 +8,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~">
<location
file="frameworks/base/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java"
- line="77"
+ line="81"
column="41"/>
</issue>
@@ -19,7 +19,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~">
<location
file="frameworks/base/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java"
- line="78"
+ line="82"
column="45"/>
</issue>
@@ -30,18 +30,18 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
<location
file="frameworks/base/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java"
- line="78"
+ line="82"
column="62"/>
</issue>
<issue
id="NewApi"
message="Call requires API level 29 (current min is 21): `android.telephony.TelephonyManager#getEmergencyNumberList`"
- errorLine1=" Map<Integer, List<EmergencyNumber>> allLists = mTelephonyManager.getEmergencyNumberList("
+ errorLine1=" Map<Integer, List<EmergencyNumber>> allLists = mTelephonyManager.getEmergencyNumberList("
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="frameworks/base/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java"
- line="173"
+ line="177"
column="74"/>
</issue>
@@ -52,7 +52,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="frameworks/base/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java"
- line="196"
+ line="200"
column="41"/>
</issue>
@@ -63,7 +63,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="frameworks/base/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java"
- line="219"
+ line="223"
column="69"/>
</issue>
@@ -74,7 +74,7 @@
errorLine2=" ~~~~~~~~~">
<location
file="frameworks/base/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java"
- line="234"
+ line="238"
column="41"/>
</issue>
@@ -85,8 +85,8 @@
errorLine2=" ~~~~~~~~~~~~~~~~~">
<location
file="frameworks/base/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java"
- line="251"
+ line="255"
column="52"/>
</issue>
-</issues>
+</issues>
\ No newline at end of file
diff --git a/packages/SettingsLib/EntityHeaderWidgets/Android.bp b/packages/SettingsLib/EntityHeaderWidgets/Android.bp
index 17b662c..83f81c6 100644
--- a/packages/SettingsLib/EntityHeaderWidgets/Android.bp
+++ b/packages/SettingsLib/EntityHeaderWidgets/Android.bp
@@ -10,13 +10,16 @@
android_library {
name: "SettingsLibEntityHeaderWidgets",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
static_libs: [
- "androidx.annotation_annotation",
- "SettingsLibSettingsTheme"
+ "androidx.annotation_annotation",
+ "SettingsLibSettingsTheme",
],
sdk_version: "system_current",
diff --git a/packages/SettingsLib/FooterPreference/Android.bp b/packages/SettingsLib/FooterPreference/Android.bp
index b45cd65..d1ad80d 100644
--- a/packages/SettingsLib/FooterPreference/Android.bp
+++ b/packages/SettingsLib/FooterPreference/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibFooterPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/HelpUtils/Android.bp b/packages/SettingsLib/HelpUtils/Android.bp
index 041fce2..284106e 100644
--- a/packages/SettingsLib/HelpUtils/Android.bp
+++ b/packages/SettingsLib/HelpUtils/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibHelpUtils",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/IllustrationPreference/Android.bp b/packages/SettingsLib/IllustrationPreference/Android.bp
index 4d4759b..6407810 100644
--- a/packages/SettingsLib/IllustrationPreference/Android.bp
+++ b/packages/SettingsLib/IllustrationPreference/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibIllustrationPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/LayoutPreference/Android.bp b/packages/SettingsLib/LayoutPreference/Android.bp
index 53ded23..8cf636a 100644
--- a/packages/SettingsLib/LayoutPreference/Android.bp
+++ b/packages/SettingsLib/LayoutPreference/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibLayoutPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/MainSwitchPreference/Android.bp b/packages/SettingsLib/MainSwitchPreference/Android.bp
index d9f74da..b984aaf 100644
--- a/packages/SettingsLib/MainSwitchPreference/Android.bp
+++ b/packages/SettingsLib/MainSwitchPreference/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibMainSwitchPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
@@ -28,7 +31,4 @@
"com.android.extservices",
"com.android.healthfitness",
],
- lint: {
- baseline_filename: "lint-baseline.xml",
- },
}
diff --git a/packages/SettingsLib/MainSwitchPreference/lint-baseline.xml b/packages/SettingsLib/MainSwitchPreference/lint-baseline.xml
deleted file mode 100644
index cfa64a4..0000000
--- a/packages/SettingsLib/MainSwitchPreference/lint-baseline.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
-
- <issue
- id="NewApi"
- message="`@android:dimen/config_restrictedIconSize` requires API level 29 (current min is 28)"
- errorLine1=' <dimen name="settingslib_restricted_icon_size">@android:dimen/config_restrictedIconSize</dimen>'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml"
- line="21"
- column="52"/>
- </issue>
-
-</issues>
\ No newline at end of file
diff --git a/packages/SettingsLib/ProfileSelector/Android.bp b/packages/SettingsLib/ProfileSelector/Android.bp
index 155ed2e..6dc07b2 100644
--- a/packages/SettingsLib/ProfileSelector/Android.bp
+++ b/packages/SettingsLib/ProfileSelector/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibProfileSelector",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/RestrictedLockUtils/Android.bp b/packages/SettingsLib/RestrictedLockUtils/Android.bp
index 3b04bd9..8d722eb 100644
--- a/packages/SettingsLib/RestrictedLockUtils/Android.bp
+++ b/packages/SettingsLib/RestrictedLockUtils/Android.bp
@@ -16,6 +16,9 @@
android_library {
name: "SettingsLibRestrictedLockUtils",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/RestrictedLockUtils/lint-baseline.xml b/packages/SettingsLib/RestrictedLockUtils/lint-baseline.xml
index 26d05a6..45a07fe 100644
--- a/packages/SettingsLib/RestrictedLockUtils/lint-baseline.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/lint-baseline.xml
@@ -1,38 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
-
- <issue
- id="NewApi"
- message="Call requires API level 24 (current min is 23): `android.os.UserHandle#of`"
- errorLine1=" context.startActivityAsUser(intent, UserHandle.of(targetUserId));"
- errorLine2=" ~~">
- <location
- file="frameworks/base/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java"
- line="97"
- column="56"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 24 (current min is 23): `android.os.UserHandle#of`"
- errorLine1=" return um.getUserProfiles().contains(UserHandle.of(userId));"
- errorLine2=" ~~">
- <location
- file="frameworks/base/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java"
- line="140"
- column="57"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 26 (current min is 23): `android.app.admin.DevicePolicyManager#getDeviceOwnerComponentOnAnyUser`"
- errorLine1=" adminComponent = dpm.getDeviceOwnerComponentOnAnyUser();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java"
- line="75"
- column="34"/>
- </issue>
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
<issue
id="NewApi"
@@ -58,6 +25,28 @@
<issue
id="NewApi"
+ message="Call requires API level 26 (current min is 23): `android.app.admin.DevicePolicyManager#getDeviceOwnerComponentOnAnyUser`"
+ errorLine1=" adminComponent = dpm.getDeviceOwnerComponentOnAnyUser();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java"
+ line="75"
+ column="34"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 24 (current min is 23): `android.os.UserHandle#of`"
+ errorLine1=" context.startActivityAsUser(intent, UserHandle.of(targetUserId));"
+ errorLine2=" ~~">
+ <location
+ file="frameworks/base/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java"
+ line="97"
+ column="56"/>
+ </issue>
+
+ <issue
+ id="NewApi"
message="Call requires API level 29 (current min is 23): `android.content.Context#startActivityAsUser`"
errorLine1=" context.startActivityAsUser(intent, UserHandle.of(targetUserId));"
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
@@ -67,4 +56,15 @@
column="17"/>
</issue>
+ <issue
+ id="NewApi"
+ message="Call requires API level 24 (current min is 23): `android.os.UserHandle#of`"
+ errorLine1=" return um.getUserProfiles().contains(UserHandle.of(userId));"
+ errorLine2=" ~~">
+ <location
+ file="frameworks/base/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java"
+ line="120"
+ column="57"/>
+ </issue>
+
</issues>
\ No newline at end of file
diff --git a/packages/SettingsLib/SchedulesProvider/Android.bp b/packages/SettingsLib/SchedulesProvider/Android.bp
index 22e4e94..c0fc741 100644
--- a/packages/SettingsLib/SchedulesProvider/Android.bp
+++ b/packages/SettingsLib/SchedulesProvider/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibSchedulesProvider",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
diff --git a/packages/SettingsLib/SchedulesProvider/lint-baseline.xml b/packages/SettingsLib/SchedulesProvider/lint-baseline.xml
index 0744710..db6a882 100644
--- a/packages/SettingsLib/SchedulesProvider/lint-baseline.xml
+++ b/packages/SettingsLib/SchedulesProvider/lint-baseline.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
<issue
id="NewApi"
diff --git a/packages/SettingsLib/SearchProvider/Android.bp b/packages/SettingsLib/SearchProvider/Android.bp
index c385d38..61ed65c 100644
--- a/packages/SettingsLib/SearchProvider/Android.bp
+++ b/packages/SettingsLib/SearchProvider/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibSearchProvider",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
diff --git a/packages/SettingsLib/SearchProvider/lint-baseline.xml b/packages/SettingsLib/SearchProvider/lint-baseline.xml
index 53346e0..3cfca1d 100644
--- a/packages/SettingsLib/SearchProvider/lint-baseline.xml
+++ b/packages/SettingsLib/SearchProvider/lint-baseline.xml
@@ -1,27 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
-
- <issue
- id="NewApi"
- message="Call requires API level 23 (current min is 21): `new android.provider.SearchIndexableResource`"
- errorLine1=" super("
- errorLine2=" ~~~~~">
- <location
- file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
- line="107"
- column="13"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level 23 (current min is 21): `android.provider.SearchIndexableResource`"
- errorLine1=" public static final class SearchIndexableIntentResource extends SearchIndexableResource {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
- line="97"
- column="69"/>
- </issue>
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
<issue
id="NewApi"
@@ -36,6 +14,39 @@
<issue
id="NewApi"
+ message="Field requires API level 23 (current min is 21): `android.provider.SearchIndexablesContract#INDEXABLES_XML_RES_COLUMNS`"
+ errorLine1=" final MatrixCursor cursor = new MatrixCursor(INDEXABLES_XML_RES_COLUMNS);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
+ line="45"
+ column="54"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Field requires API level 23 (current min is 21): `android.provider.SearchIndexableData#rank`"
+ errorLine1=" .add(XmlResource.COLUMN_RANK, indexableResource.rank)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
+ line="50"
+ column="51"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Field requires API level 23 (current min is 21): `android.provider.SearchIndexableResource#xmlResId`"
+ errorLine1=" .add(XmlResource.COLUMN_XML_RESID, indexableResource.xmlResId)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
+ line="51"
+ column="56"/>
+ </issue>
+
+ <issue
+ id="NewApi"
message="Field requires API level 23 (current min is 21): `android.provider.SearchIndexableData#className`"
errorLine1=" .add(XmlResource.COLUMN_CLASS_NAME, indexableResource.className)"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -58,6 +69,39 @@
<issue
id="NewApi"
+ message="Field requires API level 23 (current min is 21): `android.provider.SearchIndexableData#intentTargetClass`"
+ errorLine1=" indexableResource.intentTargetClass);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
+ line="56"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Class requires API level 23 (current min is 21): `android.provider.SearchIndexableResource`"
+ errorLine1=" public static final class SearchIndexableIntentResource extends SearchIndexableResource {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
+ line="97"
+ column="69"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 23 (current min is 21): `new android.provider.SearchIndexableResource`"
+ errorLine1=" super("
+ errorLine2=" ~~~~~">
+ <location
+ file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
+ line="107"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="NewApi"
message="Field requires API level 23 (current min is 21): `android.provider.SearchIndexableData#intentAction`"
errorLine1=' this.intentAction = "android.intent.action.MAIN";'
errorLine2=" ~~~~~~~~~~~~~~~~~">
@@ -81,17 +125,6 @@
<issue
id="NewApi"
message="Field requires API level 23 (current min is 21): `android.provider.SearchIndexableData#intentTargetClass`"
- errorLine1=" indexableResource.intentTargetClass);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
- line="56"
- column="29"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level 23 (current min is 21): `android.provider.SearchIndexableData#intentTargetClass`"
errorLine1=" this.intentTargetClass = className;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
@@ -100,37 +133,4 @@
column="13"/>
</issue>
- <issue
- id="NewApi"
- message="Field requires API level 23 (current min is 21): `android.provider.SearchIndexableData#rank`"
- errorLine1=" .add(XmlResource.COLUMN_RANK, indexableResource.rank)"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
- line="50"
- column="51"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level 23 (current min is 21): `android.provider.SearchIndexableResource#xmlResId`"
- errorLine1=" .add(XmlResource.COLUMN_XML_RESID, indexableResource.xmlResId)"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
- line="51"
- column="56"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level 23 (current min is 21): `android.provider.SearchIndexablesContract#INDEXABLES_XML_RES_COLUMNS`"
- errorLine1=" final MatrixCursor cursor = new MatrixCursor(INDEXABLES_XML_RES_COLUMNS);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
- line="45"
- column="54"/>
- </issue>
-
</issues>
\ No newline at end of file
diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp b/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp
index 702387e..2fe446d 100644
--- a/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp
+++ b/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibSelectorWithWidgetPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/res/layout-v33/preference_selector_with_widget.xml b/packages/SettingsLib/SelectorWithWidgetPreference/res/layout-v33/preference_selector_with_widget.xml
index 0b27464..0764609 100644
--- a/packages/SettingsLib/SelectorWithWidgetPreference/res/layout-v33/preference_selector_with_widget.xml
+++ b/packages/SettingsLib/SelectorWithWidgetPreference/res/layout-v33/preference_selector_with_widget.xml
@@ -66,6 +66,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
+ android:ellipsize="end"
android:hyphenationFrequency="normalFast"
android:lineBreakWordStyle="phrase"
android:textAppearance="?android:attr/textAppearanceListItem"/>
diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/res/layout/preference_selector_with_widget.xml b/packages/SettingsLib/SelectorWithWidgetPreference/res/layout/preference_selector_with_widget.xml
index 8bb56ff..4f1a910 100644
--- a/packages/SettingsLib/SelectorWithWidgetPreference/res/layout/preference_selector_with_widget.xml
+++ b/packages/SettingsLib/SelectorWithWidgetPreference/res/layout/preference_selector_with_widget.xml
@@ -66,6 +66,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
+ android:ellipsize="end"
android:textAppearance="?android:attr/textAppearanceListItem"/>
<LinearLayout
diff --git a/packages/SettingsLib/SettingsSpinner/Android.bp b/packages/SettingsLib/SettingsSpinner/Android.bp
index 0eec505..8fed61f 100644
--- a/packages/SettingsLib/SettingsSpinner/Android.bp
+++ b/packages/SettingsLib/SettingsSpinner/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibSettingsSpinner",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/SettingsTransition/Android.bp b/packages/SettingsLib/SettingsTransition/Android.bp
index 06493c0..e04af6c 100644
--- a/packages/SettingsLib/SettingsTransition/Android.bp
+++ b/packages/SettingsLib/SettingsTransition/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibSettingsTransition",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index 8b136da..6f9556f 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.6.0-beta02"
+ extra["jetpackComposeVersion"] = "1.6.0-rc01"
}
subprojects {
diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
index 965fdcf..df5644b 100644
--- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
@@ -73,6 +73,10 @@
android:authorities="com.android.spa.gallery.debug.provider"
android:exported="false">
</provider>
-
+ <activity
+ android:name="com.android.settingslib.spa.gallery.GalleryDialogActivity"
+ android:exported="true"
+ android:theme="@style/Theme.SpaLib.Dialog">
+ </activity>
</application>
</manifest>
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDialogActivity.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDialogActivity.kt
new file mode 100644
index 0000000..e22ed35
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDialogActivity.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.spa.gallery
+
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextAlign
+import com.android.settingslib.spa.SpaBaseDialogActivity
+import com.android.settingslib.spa.widget.dialog.AlertDialogButton
+import com.android.settingslib.spa.widget.dialog.SettingsAlertDialogWithIcon
+
+class GalleryDialogActivity : SpaBaseDialogActivity() {
+ @Composable
+ override fun Content() {
+ SettingsAlertDialogWithIcon(
+ onDismissRequest = { finish() },
+ confirmButton = AlertDialogButton("confirm") { finish() },
+ dismissButton = AlertDialogButton("dismiss") { finish() },
+ title = "title",
+ text = {
+ Text(
+ "text",
+ modifier = Modifier.fillMaxWidth(),
+ textAlign = TextAlign.Center
+ )
+ }
+ )
+ }
+}
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index 9703c347..1f78a9c 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.0"
+agp = "8.2.1"
compose-compiler = "1.5.1"
dexmaker-mockito = "2.28.3"
jvm = "17"
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index 7eccfe5..618dc37 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.2.0-alpha12")
+ api("androidx.compose.material3:material3:1.2.0-beta02")
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.7.4")
+ api("androidx.navigation:navigation-compose:2.7.6")
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/res/values/themes.xml b/packages/SettingsLib/Spa/spa/res/values/themes.xml
index 25846ec..4b5a9bc 100644
--- a/packages/SettingsLib/Spa/spa/res/values/themes.xml
+++ b/packages/SettingsLib/Spa/spa/res/values/themes.xml
@@ -22,4 +22,6 @@
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
</style>
+
+ <style name="Theme.SpaLib.Dialog" parent="Theme.Material3.DayNight.Dialog"/>
</resources>
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/SpaBaseDialogActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/SpaBaseDialogActivity.kt
new file mode 100644
index 0000000..dfb780a
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/SpaBaseDialogActivity.kt
@@ -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.settingslib.spa
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.runtime.Composable
+import com.android.settingslib.spa.framework.common.LogCategory
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+
+abstract class SpaBaseDialogActivity : ComponentActivity() {
+ private val spaEnvironment get() = SpaEnvironmentFactory.instance
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK)
+ setContent {
+ SettingsTheme {
+ Content()
+ }
+ }
+ }
+
+ @Composable
+ abstract fun Content()
+
+ companion object {
+ private const val TAG = "SpaBaseDialogActivity"
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index 5605485..da1ee77 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -22,12 +22,14 @@
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.annotation.VisibleForTesting
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Modifier
import androidx.core.view.WindowCompat
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavGraph.Companion.findStartDestination
@@ -133,6 +135,7 @@
NavHost(
navController = navController,
startDestination = NullPageProvider.name,
+ modifier = Modifier.fillMaxSize(),
) {
composable(NullPageProvider.name) {}
for (spp in allProvider) {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
index 81bee5e..0281ab8 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
@@ -86,7 +86,7 @@
)
}
-object NullPageProvider : SettingsPageProvider {
+internal object NullPageProvider : SettingsPageProvider {
override val name = NULL_PAGE_NAME
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavGraphBuilder.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavGraphBuilder.kt
index 192b125..93ad644 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavGraphBuilder.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavGraphBuilder.kt
@@ -30,6 +30,7 @@
import androidx.navigation.NavDeepLink
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
+import com.android.settingslib.spa.framework.common.NullPageProvider
/**
* Add the [Composable] to the [NavGraphBuilder] with animation
@@ -49,11 +50,13 @@
arguments = arguments,
deepLinks = deepLinks,
enterTransition = {
- slideIntoContainer(
- towards = AnimatedContentTransitionScope.SlideDirection.Start,
- animationSpec = slideInEffect,
- initialOffset = offsetFunc,
- ) + fadeIn(animationSpec = fadeInEffect)
+ if (initialState.destination.route != NullPageProvider.name) {
+ slideIntoContainer(
+ towards = AnimatedContentTransitionScope.SlideDirection.Start,
+ animationSpec = slideInEffect,
+ initialOffset = offsetFunc,
+ ) + fadeIn(animationSpec = fadeInEffect)
+ } else null
},
exitTransition = {
slideOutOfContainer(
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
index 207c174..de080e3 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
@@ -99,11 +99,11 @@
}
@Composable
-private fun getDialogWidth(): Dp {
+fun getDialogWidth(): Dp {
val configuration = LocalConfiguration.current
return configuration.screenWidthDp.dp * when (configuration.orientation) {
- Configuration.ORIENTATION_LANDSCAPE -> 0.6f
- else -> 0.8f
+ Configuration.ORIENTATION_LANDSCAPE -> 0.65f
+ else -> 0.85f
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt
new file mode 100644
index 0000000..1695e4f
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.spa.widget.dialog
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.WarningAmber
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Button
+import androidx.compose.material3.Icon
+import androidx.compose.material3.OutlinedButton
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.window.DialogProperties
+
+@Composable
+fun SettingsAlertDialogWithIcon(
+ onDismissRequest: () -> Unit,
+ confirmButton: AlertDialogButton?,
+ dismissButton: AlertDialogButton?,
+ title: String?,
+ text: @Composable (() -> Unit)?,
+) {
+ AlertDialog(
+ onDismissRequest = onDismissRequest,
+ icon = { Icon(Icons.Default.WarningAmber, contentDescription = null) },
+ modifier = Modifier.width(getDialogWidth()),
+ confirmButton = {
+ confirmButton?.let {
+ Button(
+ onClick = {
+ it.onClick()
+ },
+ ) {
+ Text(it.text)
+ }
+ }
+ },
+ dismissButton = dismissButton?.let {
+ {
+ OutlinedButton(
+ onClick = {
+ it.onClick()
+ },
+ ) {
+ Text(it.text)
+ }
+ }
+ },
+ title = title?.let {
+ {
+ Text(
+ it,
+ modifier = Modifier.fillMaxWidth(),
+ textAlign = TextAlign.Center
+ )
+ }
+ },
+ text = text?.let {
+ {
+ Column(Modifier.verticalScroll(rememberScrollState())) {
+ text()
+ }
+ }
+ },
+ properties = DialogProperties(usePlatformDefaultWidth = false),
+ )
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt
index a9974dc..514ad669 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt
@@ -39,6 +39,9 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsTheme
@@ -68,6 +71,7 @@
) {
val contentPadding = PaddingValues(horizontal = SettingsDimension.itemPaddingEnd)
Button(
+ modifier = Modifier.semantics { role = Role.DropdownList },
onClick = { expanded = true },
colors = ButtonDefaults.buttonColors(
containerColor = SettingsTheme.colorScheme.spinnerHeaderContainer,
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
index abeffec..0a98791 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
@@ -24,6 +24,7 @@
import android.content.pm.PackageManager
import android.content.pm.PackageManager.ApplicationInfoFlags
import android.content.pm.ResolveInfo
+import android.os.SystemProperties
import com.android.internal.R
import com.android.settingslib.spaprivileged.framework.common.userManager
import kotlinx.coroutines.async
@@ -110,7 +111,7 @@
): List<ApplicationInfo> {
val disabledComponentsFlag = (PackageManager.MATCH_DISABLED_COMPONENTS or
PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong()
- val archivedPackagesFlag: Long = if (featureFlags.archiving())
+ val archivedPackagesFlag: Long = if (isArchivingEnabled(featureFlags))
PackageManager.MATCH_ARCHIVED_PACKAGES else 0L
val regularFlags = ApplicationInfoFlags.of(
disabledComponentsFlag or
@@ -148,6 +149,9 @@
}
}
+ private fun isArchivingEnabled(featureFlags: FeatureFlags) =
+ featureFlags.archiving() || SystemProperties.getBoolean("pm.archiving.enabled", false)
+
override fun showSystemPredicate(
userIdFlow: Flow<Int>,
showSystemFlow: Flow<Boolean>,
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index 1c830c1..74b556e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -162,6 +162,7 @@
uid = checkNotNull(applicationInfo).uid,
packageName = packageName) })
RestrictedSwitchPreference(switchModel, restrictions, restrictionsProviderFactory)
+ InfoPageAdditionalContent(record, isAllowed)
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
index 916d83a..3f7a852 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
@@ -77,6 +77,9 @@
* Sets whether the permission is allowed for the given app.
*/
fun setAllowed(record: T, newAllowed: Boolean)
+
+ @Composable
+ fun InfoPageAdditionalContent(record: T, isAllowed: () -> Boolean?){}
}
interface TogglePermissionAppListProvider {
diff --git a/packages/SettingsLib/Tile/Android.bp b/packages/SettingsLib/Tile/Android.bp
index 19c59dd2..54b9748 100644
--- a/packages/SettingsLib/Tile/Android.bp
+++ b/packages/SettingsLib/Tile/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibTile",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
diff --git a/packages/SettingsLib/Tile/lint-baseline.xml b/packages/SettingsLib/Tile/lint-baseline.xml
index 326ec0d..56b1bca 100644
--- a/packages/SettingsLib/Tile/lint-baseline.xml
+++ b/packages/SettingsLib/Tile/lint-baseline.xml
@@ -1,55 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
<issue
id="NewApi"
- message="Call requires API level 24 (current min is 21): `java.lang.Iterable#forEach`"
- errorLine1=" controllers.forEach(controller -> {"
- errorLine2=" ~~~~~~~">
- <location
- file="frameworks/base/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchesProvider.java"
- line="79"
- column="21"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 23 (current min is 21): `android.graphics.drawable.Icon#createWithResource`">
+ message="Call requires API level 29 (current min is 21): `android.os.Parcel#writeBoolean`"
+ errorLine1=" dest.writeBoolean(this instanceof ProviderTile);"
+ errorLine2=" ~~~~~~~~~~~~">
<location
file="frameworks/base/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java"
- line="312"/>
+ line="114"
+ column="14"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 23 (current min is 21): `android.graphics.drawable.Icon#setTint`">
+ message="Call requires API level 23 (current min is 21): `android.graphics.drawable.Icon#createWithResource`"
+ errorLine1=" final Icon icon = Icon.createWithResource(componentInfo.packageName, iconResId);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~">
<location
file="frameworks/base/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java"
- line="318"/>
+ line="326"
+ column="36"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 29 (current min is 21): `android.os.Parcel#readBoolean`">
+ message="Call requires API level 23 (current min is 21): `android.graphics.drawable.Icon#setTint`"
+ errorLine1=" icon.setTint(tintColor);"
+ errorLine2=" ~~~~~~~">
<location
file="frameworks/base/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java"
- line="373"/>
+ line="332"
+ column="22"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 29 (current min is 21): `android.os.Parcel#writeBoolean`">
+ message="Call requires API level 29 (current min is 21): `android.os.Parcel#readBoolean`"
+ errorLine1=" final boolean isProviderTile = source.readBoolean();"
+ errorLine2=" ~~~~~~~~~~~">
<location
file="frameworks/base/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java"
- line="108"/>
+ line="387"
+ column="51"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 21): `android.content.Context#getAttributionSource`">
+ message="Call requires API level 31 (current min is 21): `android.content.Context#getAttributionSource`"
+ errorLine1=" return provider.call(context.getAttributionSource(),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
<location
file="frameworks/base/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java"
- line="565"/>
+ line="601"
+ column="42"/>
</issue>
</issues>
\ No newline at end of file
diff --git a/packages/SettingsLib/TopIntroPreference/Android.bp b/packages/SettingsLib/TopIntroPreference/Android.bp
index 77b7ac1..e70201b 100644
--- a/packages/SettingsLib/TopIntroPreference/Android.bp
+++ b/packages/SettingsLib/TopIntroPreference/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibTopIntroPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/TwoTargetPreference/Android.bp b/packages/SettingsLib/TwoTargetPreference/Android.bp
index 5aa906e..70d9630 100644
--- a/packages/SettingsLib/TwoTargetPreference/Android.bp
+++ b/packages/SettingsLib/TwoTargetPreference/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibTwoTargetPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/UsageProgressBarPreference/Android.bp b/packages/SettingsLib/UsageProgressBarPreference/Android.bp
index 4cc90cc..0a83aab 100644
--- a/packages/SettingsLib/UsageProgressBarPreference/Android.bp
+++ b/packages/SettingsLib/UsageProgressBarPreference/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibUsageProgressBarPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/Utils/Android.bp b/packages/SettingsLib/Utils/Android.bp
index d5a56c8..5d2aef7 100644
--- a/packages/SettingsLib/Utils/Android.bp
+++ b/packages/SettingsLib/Utils/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibUtils",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/Utils/lint-baseline.xml b/packages/SettingsLib/Utils/lint-baseline.xml
index 3fcd56c..2f6cc3a 100644
--- a/packages/SettingsLib/Utils/lint-baseline.xml
+++ b/packages/SettingsLib/Utils/lint-baseline.xml
@@ -1,26 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
<issue
id="NewApi"
- message="Call requires API level 24 (current min is 21): `android.os.UserHandle#of`"
- errorLine1=" mContext.getPackageName(), 0, UserHandle.of(managedUserId)"
- errorLine2=" ~~">
+ message="Call requires API level 24 (current min is 23): `android.os.UserManager#isManagedProfile`"
+ errorLine1=" return context.getSystemService(UserManager.class).isManagedProfile(userId)"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
<location
- file="frameworks/base/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java"
- line="119"
- column="70"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 24 (current min is 21): `android.os.UserHandle#of`"
- errorLine1=" intent, 0, UserHandle.of(managedUserId));"
- errorLine2=" ~~">
- <location
- file="frameworks/base/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java"
- line="150"
- column="47"/>
+ file="frameworks/base/packages/SettingsLib/Utils/src/com/android/settingslib/utils/applications/AppUtils.java"
+ line="62"
+ column="60"/>
</issue>
<issue
@@ -36,35 +25,13 @@
<issue
id="NewApi"
- message="Call requires API level 24 (current min is 21): `android.os.UserManager#isManagedProfile`"
- errorLine1=" if (mUserManager.isManagedProfile(id)) {"
- errorLine2=" ~~~~~~~~~~~~~~~~">
+ message="Call requires API level 29 (current min is 21): `android.content.Context#startActivityAsUser`"
+ errorLine1=" activityContext.startActivityAsUser(intent, UserHandle.of(userId));"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="frameworks/base/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java"
- line="173"
- column="30"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 24 (current min is 23): `android.os.UserManager#isManagedProfile`"
- errorLine1=" return context.getSystemService(UserManager.class).isManagedProfile(userId)"
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SettingsLib/Utils/src/com/android/settingslib/utils/applications/AppUtils.java"
- line="62"
- column="60"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 26 (current min is 21): `android.app.admin.DevicePolicyManager#getDeviceOwnerComponentOnAnyUser`"
- errorLine1=" return mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java"
- line="163"
- column="37"/>
+ line="80"
+ column="29"/>
</issue>
<issue
@@ -80,13 +47,13 @@
<issue
id="NewApi"
- message="Call requires API level 29 (current min is 21): `android.content.Context#startActivityAsUser`"
- errorLine1=" activityContext.startActivityAsUser(intent, UserHandle.of(userId));"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 24 (current min is 21): `android.os.UserHandle#of`"
+ errorLine1=" mContext.getPackageName(), 0, UserHandle.of(managedUserId)"
+ errorLine2=" ~~">
<location
file="frameworks/base/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java"
- line="80"
- column="29"/>
+ line="119"
+ column="70"/>
</issue>
<issue
@@ -102,6 +69,28 @@
<issue
id="NewApi"
+ message="Call requires API level 24 (current min is 21): `android.os.UserHandle#of`"
+ errorLine1=" intent, 0, UserHandle.of(managedUserId));"
+ errorLine2=" ~~">
+ <location
+ file="frameworks/base/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java"
+ line="150"
+ column="47"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 26 (current min is 21): `android.app.admin.DevicePolicyManager#getDeviceOwnerComponentOnAnyUser`"
+ errorLine1=" return mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java"
+ line="163"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="NewApi"
message="Call requires API level 30 (current min is 21): `android.os.UserManager#getAllProfiles`"
errorLine1=" List<UserHandle> allProfiles = mUserManager.getAllProfiles();"
errorLine2=" ~~~~~~~~~~~~~~">
@@ -111,4 +100,15 @@
column="53"/>
</issue>
+ <issue
+ id="NewApi"
+ message="Call requires API level 24 (current min is 21): `android.os.UserManager#isManagedProfile`"
+ errorLine1=" if (mUserManager.isManagedProfile(id)) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java"
+ line="173"
+ column="30"/>
+ </issue>
+
</issues>
\ No newline at end of file
diff --git a/packages/SettingsLib/lint-baseline.xml b/packages/SettingsLib/lint-baseline.xml
deleted file mode 100644
index d6a23fd..0000000
--- a/packages/SettingsLib/lint-baseline.xml
+++ /dev/null
@@ -1,204 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.bluetooth.BluetoothDevice#setAlias`">
- <location
- file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java"
- line="584"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.net.wifi.WifiInfo#getSubscriptionId`">
- <location
- file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java"
- line="248"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.net.wifi.WifiInfo#getSubscriptionId`">
- <location
- file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java"
- line="278"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.net.wifi.WifiManager#registerSubsystemRestartTrackingCallback`">
- <location
- file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java"
- line="201"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.net.wifi.WifiManager#unregisterSubsystemRestartTrackingCallback`">
- <location
- file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java"
- line="208"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.os.UserManager#isUserForeground`">
- <location
- file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminController.java"
- line="78"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.TelephonyManager#isDataCapable`">
- <location
- file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/Utils.java"
- line="498"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.TelephonyManager#isDataCapable`">
- <location
- file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java"
- line="225"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.TelephonyManager#registerTelephonyCallback`">
- <location
- file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java"
- line="215"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.TelephonyManager#registerTelephonyCallback`">
- <location
- file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java"
- line="86"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.TelephonyManager#unregisterTelephonyCallback`">
- <location
- file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java"
- line="222"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.TelephonyManager#unregisterTelephonyCallback`">
- <location
- file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java"
- line="88"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 34 (current min is 30): `android.os.UserManager#isAdminUser`">
- <location
- file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java"
- line="66"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 34 (current min is 30): `android.os.UserManager#isAdminUser`">
- <location
- file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/development/DevelopmentSettingsEnabler.java"
- line="49"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 34 (current min is 30): `android.os.UserManager#isAdminUser`">
- <location
- file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSimStatusImeiInfoPreferenceController.java"
- line="33"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.net.wifi.WifiManager.SubsystemRestartTrackingCallback`">
- <location
- file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java"
- line="64"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener`">
- <location
- file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java"
- line="125"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyCallback.CarrierNetworkListener`">
- <location
- file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java"
- line="124"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyCallback.DataActivityListener`">
- <location
- file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java"
- line="123"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyCallback.DataConnectionStateListener`">
- <location
- file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java"
- line="122"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyCallback.DisplayInfoListener`">
- <location
- file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java"
- line="126"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyCallback.ServiceStateListener`">
- <location
- file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java"
- line="120"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyCallback.SignalStrengthsListener`">
- <location
- file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java"
- line="121"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyCallback`">
- <location
- file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java"
- line="79"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyCallback`">
- <location
- file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java"
- line="119"/>
- </issue>
-
-</issues>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index 640d4e9..071258d 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -60,7 +60,7 @@
<string name="wifi_not_in_range" msgid="1541760821805777772">"ವ್ಯಾಪ್ತಿಯಲ್ಲಿಲ್ಲ"</string>
<string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಸಂಪರ್ಕಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
<string name="wifi_no_internet" msgid="1774198889176926299">"ಯಾವುದೇ ಇಂಟರ್ನೆಟ್ ಪ್ರವೇಶವಿಲ್ಲ"</string>
- <string name="saved_network" msgid="7143698034077223645">"<xliff:g id="NAME">%1$s</xliff:g> ನಿಂದ ಉಳಿಸಲಾಗಿದೆ"</string>
+ <string name="saved_network" msgid="7143698034077223645">"<xliff:g id="NAME">%1$s</xliff:g> ನಿಂದ ಸೇವ್ ಮಾಡಲಾಗಿದೆ"</string>
<string name="connected_via_network_scorer" msgid="7665725527352893558">"%1$s ಮೂಲಕ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಸಂಪರ್ಕಿಸಲಾಗಿದೆ"</string>
<string name="connected_via_network_scorer_default" msgid="7973529709744526285">"ನೆಟ್ವರ್ಕ್ ರೇಟಿಂಗ್ ಒದಗಿಸುವವರ ಮೂಲಕ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಸಂಪರ್ಕಿಸಲಾಗಿದೆ"</string>
<string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ಆ್ಯಪ್ ಮೂಲಕ ಸಂಪರ್ಕಿಸಲಾಗಿದೆ"</string>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 5aa2bfc..a4b3af9 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -338,6 +338,9 @@
<!-- Message for telling the user the kind of BT device being displayed in list. [CHAR LIMIT=30 BACKUP_MESSAGE_ID=5165842622743212268] -->
<string name="bluetooth_talkback_input_peripheral">Input Peripheral</string>
+ <!-- Message for telling the user the kind of BT device being displayed in list. [CHAR LIMIT=30 BACKUP_MESSAGE_ID=26580326066627664] -->
+ <string name="bluetooth_talkback_hearing_aids">Hearing Aids</string>
+
<!-- Message for telling the user the kind of BT device being displayed in list. [CHAR LIMIT=30 BACKUP_MESSAGE_ID=5615463912185280812] -->
<string name="bluetooth_talkback_bluetooth">Bluetooth</string>
@@ -1079,6 +1082,10 @@
<!-- [CHAR_LIMIT=NONE] Label for battery on main page of settings -->
<string name="power_remaining_settings_home_page"><xliff:g id="percentage" example="10%">%1$s</xliff:g> - <xliff:g id="time_string" example="1 hour left based on your usage">%2$s</xliff:g></string>
+ <!-- [CHAR_LIMIT=NONE] Label for charging on hold on main page of settings -->
+ <string name="power_charging_on_hold_settings_home_page"><xliff:g id="level">%1$s</xliff:g> - Charging on hold to protect battery</string>
+ <!-- [CHAR_LIMIT=NONE] Label for incompatible charging accessory on main page of settings -->
+ <string name="power_incompatible_charging_settings_home_page"><xliff:g id="level">%1$s</xliff:g> - Checking charging accessory</string>
<!-- [CHAR_LIMIT=40] Label for estimated remaining duration of battery discharging -->
<string name="power_remaining_duration_only">About <xliff:g id="time_remaining">%1$s</xliff:g> left</string>
<!-- [CHAR_LIMIT=40] Label for battery level chart when discharging with duration -->
@@ -1139,11 +1146,13 @@
<!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed -->
<string name="battery_info_status_discharging">Not charging</string>
<!-- Battery Info screen. Value for a status item. A state which device is connected with any charger(e.g. USB, Adapter or Wireless) but not charging yet. Used for diagnostic info screens, precise translation isn't needed -->
- <string name="battery_info_status_not_charging">Connected, not charging</string>
+ <string name="battery_info_status_not_charging">Connected, but not charging</string>
<!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed -->
<string name="battery_info_status_full">Charged</string>
<!-- [CHAR_LIMIT=40] Battery Info screen. Value for a status item. A state which device is fully charged -->
<string name="battery_info_status_full_charged">Fully Charged</string>
+ <!-- [CHAR_LIMIT=None] Battery Info screen. Value for a status item. A state which device charging on hold -->
+ <string name="battery_info_status_charging_on_hold">Charging on hold</string>
<!-- Summary for settings preference disabled by administrator [CHAR LIMIT=50] -->
<string name="disabled_by_admin_summary_text">Controlled by admin</string>
diff --git a/packages/SettingsLib/search/Android.bp b/packages/SettingsLib/search/Android.bp
index 3b14712..1f8d1dd 100644
--- a/packages/SettingsLib/search/Android.bp
+++ b/packages/SettingsLib/search/Android.bp
@@ -12,14 +12,14 @@
visibility: ["//visibility:private"],
srcs: ["interface-src/**/*.java"],
host_supported: true,
- lint: {
- baseline_filename: "lint-baseline.xml",
- },
}
android_library {
name: "SettingsLib-search",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
static_libs: [
"SettingsLib-search-interface",
],
diff --git a/packages/SettingsLib/search/lint-baseline.xml b/packages/SettingsLib/search/lint-baseline.xml
index 7ec512b..61cdb05 100644
--- a/packages/SettingsLib/search/lint-baseline.xml
+++ b/packages/SettingsLib/search/lint-baseline.xml
@@ -1,16 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
-
- <issue
- id="NewApi"
- message="Call requires API level 23 (current min is 21): `new android.provider.SearchIndexableData`"
- errorLine1=" super(context);"
- errorLine2=" ~~~~~">
- <location
- file="frameworks/base/packages/SettingsLib/search/src/com/android/settingslib/search/SearchIndexableRaw.java"
- line="62"
- column="9"/>
- </issue>
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
<issue
id="NewApi"
@@ -23,4 +12,15 @@
column="41"/>
</issue>
+ <issue
+ id="NewApi"
+ message="Call requires API level 23 (current min is 21): `new android.provider.SearchIndexableData`"
+ errorLine1=" super(context);"
+ errorLine2=" ~~~~~">
+ <location
+ file="frameworks/base/packages/SettingsLib/search/src/com/android/settingslib/search/SearchIndexableRaw.java"
+ line="62"
+ column="9"/>
+ </issue>
+
</issues>
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index c2be571..fb14a17 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -1,6 +1,7 @@
package com.android.settingslib;
import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_USER_LABEL;
+import static android.webkit.Flags.updateServiceV2;
import android.annotation.ColorInt;
import android.app.admin.DevicePolicyManager;
@@ -34,6 +35,7 @@
import android.net.wifi.WifiInfo;
import android.os.BatteryManager;
import android.os.Build;
+import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
@@ -44,6 +46,9 @@
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
import android.util.Log;
+import android.webkit.IWebViewUpdateService;
+import android.webkit.WebViewFactory;
+import android.webkit.WebViewProviderInfo;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -65,6 +70,8 @@
public class Utils {
+ private static final String TAG = "Utils";
+
@VisibleForTesting
static final String STORAGE_MANAGER_ENABLED_PROPERTY =
"ro.storage_manager.enabled";
@@ -76,6 +83,7 @@
private static String sPermissionControllerPackageName;
private static String sServicesSystemSharedLibPackageName;
private static String sSharedSystemSharedLibPackageName;
+ private static String sDefaultWebViewPackageName;
static final int[] WIFI_PIE = {
com.android.internal.R.drawable.ic_wifi_signal_0,
@@ -445,6 +453,7 @@
|| packageName.equals(sServicesSystemSharedLibPackageName)
|| packageName.equals(sSharedSystemSharedLibPackageName)
|| packageName.equals(PrintManager.PRINT_SPOOLER_PACKAGE_NAME)
+ || (updateServiceV2() && packageName.equals(getDefaultWebViewPackageName()))
|| isDeviceProvisioningPackage(resources, packageName);
}
@@ -459,6 +468,29 @@
}
/**
+ * Fetch the package name of the default WebView provider.
+ */
+ @Nullable
+ private static String getDefaultWebViewPackageName() {
+ if (sDefaultWebViewPackageName != null) {
+ return sDefaultWebViewPackageName;
+ }
+
+ try {
+ IWebViewUpdateService service = WebViewFactory.getUpdateService();
+ if (service != null) {
+ WebViewProviderInfo provider = service.getDefaultWebViewPackage();
+ if (provider != null) {
+ sDefaultWebViewPackageName = provider.packageName;
+ }
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException when trying to fetch default WebView package Name", e);
+ }
+ return sDefaultWebViewPackageName;
+ }
+
+ /**
* Returns the Wifi icon resource for a given RSSI level.
*
* @param level The number of bars to show (0-4)
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 0ffcc45..f7f0673 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -117,6 +117,12 @@
}
}
+ if (cachedDevice.isHearingAidDevice()) {
+ return new Pair<>(getBluetoothDrawable(context,
+ com.android.internal.R.drawable.ic_bt_hearing_aid),
+ context.getString(R.string.bluetooth_talkback_hearing_aids));
+ }
+
List<LocalBluetoothProfile> profiles = cachedDevice.getProfiles();
int resId = 0;
for (LocalBluetoothProfile profile : profiles) {
@@ -125,7 +131,8 @@
// The device should show hearing aid icon if it contains any hearing aid related
// profiles
if (profile instanceof HearingAidProfile || profile instanceof HapClientProfile) {
- return new Pair<>(getBluetoothDrawable(context, profileResId), null);
+ return new Pair<>(getBluetoothDrawable(context, profileResId),
+ context.getString(R.string.bluetooth_talkback_hearing_aids));
}
if (resId == 0) {
resId = profileResId;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 245fe6e..560bc46 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -410,8 +410,13 @@
connectDevice();
}
- void setHearingAidInfo(HearingAidInfo hearingAidInfo) {
+ public void setHearingAidInfo(HearingAidInfo hearingAidInfo) {
mHearingAidInfo = hearingAidInfo;
+ dispatchAttributesChanged();
+ }
+
+ public HearingAidInfo getHearingAidInfo() {
+ return mHearingAidInfo;
}
/**
@@ -1788,4 +1793,40 @@
boolean getUnpairing() {
return mUnpairing;
}
+
+ ListenableFuture<Void> syncProfileForMemberDevice() {
+ return ThreadUtils.getBackgroundExecutor()
+ .submit(
+ () -> {
+ List<Pair<LocalBluetoothProfile, Boolean>> toSync =
+ Stream.of(
+ mProfileManager.getA2dpProfile(),
+ mProfileManager.getHeadsetProfile(),
+ mProfileManager.getHearingAidProfile(),
+ mProfileManager.getLeAudioProfile(),
+ mProfileManager.getLeAudioBroadcastAssistantProfile())
+ .filter(Objects::nonNull)
+ .map(profile -> new Pair<>(profile, profile.isEnabled(mDevice)))
+ .toList();
+
+ for (var t : toSync) {
+ LocalBluetoothProfile profile = t.first;
+ boolean enabledForMain = t.second;
+
+ for (var member : mMemberDevices) {
+ BluetoothDevice btDevice = member.getDevice();
+
+ if (enabledForMain != profile.isEnabled(btDevice)) {
+ Log.d(TAG, "Syncing profile " + profile + " to "
+ + enabledForMain + " for member device "
+ + btDevice.getAnonymizedAddress() + " of main device "
+ + mDevice.getAnonymizedAddress());
+ profile.setEnabled(btDevice, enabledForMain);
+ }
+ }
+ }
+ return null;
+ }
+ );
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index a6536a8c..89fe268 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -349,6 +349,7 @@
if (profileId == BluetoothProfile.HEADSET
|| profileId == BluetoothProfile.A2DP
|| profileId == BluetoothProfile.LE_AUDIO
+ || profileId == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
|| profileId == BluetoothProfile.CSIP_SET_COORDINATOR) {
return mCsipDeviceManager.onProfileConnectionStateChangedIfProcessed(cachedDevice,
state);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index a49314a..e67ec48 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -379,6 +379,7 @@
if (hasChanged) {
log("addMemberDevicesIntoMainDevice: After changed, CachedBluetoothDevice list: "
+ mCachedDevices);
+ preferredMainDevice.syncProfileForMemberDevice();
}
return hasChanged;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java
index 5bc27195..943e3fc2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java
@@ -70,10 +70,8 @@
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
- if (isAvailable()) {
- mWifiMacAddress = screen.findPreference(KEY_WIFI_MAC_ADDRESS);
- updateConnectivity();
- }
+ mWifiMacAddress = screen.findPreference(KEY_WIFI_MAC_ADDRESS);
+ updateConnectivity();
}
@Override
@@ -84,16 +82,16 @@
@SuppressLint("HardwareIds")
@Override
protected void updateConnectivity() {
+ if (mWifiManager == null || mWifiMacAddress == null) {
+ return;
+ }
+
final String[] macAddresses = mWifiManager.getFactoryMacAddresses();
String macAddress = null;
if (macAddresses != null && macAddresses.length > 0) {
macAddress = macAddresses[0];
}
- if (mWifiMacAddress == null) {
- return;
- }
-
if (TextUtils.isEmpty(macAddress) || macAddress.equals(WifiInfo.DEFAULT_MAC_ADDRESS)) {
mWifiMacAddress.setSummary(R.string.status_unavailable);
} else {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index 7409eea..f7ec80b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -16,6 +16,7 @@
package com.android.settingslib.bluetooth;
import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -87,6 +88,14 @@
}
@Test
+ public void getBtClassDrawableWithDescription_typeHearingAid_returnHearingAidDrawable() {
+ when(mCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true);
+ BluetoothUtils.getBtClassDrawableWithDescription(mContext, mCachedBluetoothDevice);
+
+ verify(mContext).getDrawable(com.android.internal.R.drawable.ic_bt_hearing_aid);
+ }
+
+ @Test
public void getBtRainbowDrawableWithDescription_normalHeadset_returnAdaptiveIcon() {
when(mBluetoothDevice.getMetadata(
BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn("false".getBytes());
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index ed545ab..9db8b47 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -18,7 +18,9 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -56,6 +58,8 @@
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
+import java.util.concurrent.ExecutionException;
+
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowBluetoothAdapter.class})
public class CachedBluetoothDeviceTest {
@@ -1815,6 +1819,52 @@
assertThat(mCachedDevice.isConnectedHearingAidDevice()).isFalse();
}
+ @Test
+ public void syncProfileForMemberDevice_hasDiff_shouldSync()
+ throws ExecutionException, InterruptedException {
+ mCachedDevice.addMemberDevice(mSubCachedDevice);
+ when(mProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
+ when(mProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile);
+ when(mProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile);
+
+ when(mA2dpProfile.isEnabled(mDevice)).thenReturn(true);
+ when(mHearingAidProfile.isEnabled(mDevice)).thenReturn(true);
+ when(mLeAudioProfile.isEnabled(mDevice)).thenReturn(true);
+
+ when(mA2dpProfile.isEnabled(mSubDevice)).thenReturn(true);
+ when(mHearingAidProfile.isEnabled(mSubDevice)).thenReturn(false);
+ when(mLeAudioProfile.isEnabled(mSubDevice)).thenReturn(false);
+
+ mCachedDevice.syncProfileForMemberDevice().get();
+
+ verify(mA2dpProfile, never()).setEnabled(any(BluetoothDevice.class), anyBoolean());
+ verify(mHearingAidProfile).setEnabled(any(BluetoothDevice.class), eq(true));
+ verify(mLeAudioProfile).setEnabled(any(BluetoothDevice.class), eq(true));
+ }
+
+ @Test
+ public void syncProfileForMemberDevice_noDiff_shouldNotSync()
+ throws ExecutionException, InterruptedException {
+ mCachedDevice.addMemberDevice(mSubCachedDevice);
+ when(mProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
+ when(mProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile);
+ when(mProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile);
+
+ when(mA2dpProfile.isEnabled(mDevice)).thenReturn(false);
+ when(mHearingAidProfile.isEnabled(mDevice)).thenReturn(false);
+ when(mLeAudioProfile.isEnabled(mDevice)).thenReturn(true);
+
+ when(mA2dpProfile.isEnabled(mSubDevice)).thenReturn(false);
+ when(mHearingAidProfile.isEnabled(mSubDevice)).thenReturn(false);
+ when(mLeAudioProfile.isEnabled(mSubDevice)).thenReturn(true);
+
+ mCachedDevice.syncProfileForMemberDevice().get();
+
+ verify(mA2dpProfile, never()).setEnabled(any(BluetoothDevice.class), anyBoolean());
+ verify(mHearingAidProfile, never()).setEnabled(any(BluetoothDevice.class), anyBoolean());
+ verify(mLeAudioProfile, never()).setEnabled(any(BluetoothDevice.class), anyBoolean());
+ }
+
private HearingAidInfo getLeftAshaHearingAidInfo() {
return new HearingAidInfo.Builder()
.setAshaDeviceSide(HearingAidProfile.DeviceSide.SIDE_LEFT)
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
index 24fd06e..e7487e8 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
@@ -19,6 +19,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
@@ -73,13 +74,13 @@
private final static String DEVICE_ADDRESS_2 = "AA:BB:CC:DD:EE:22";
private final BluetoothClass DEVICE_CLASS =
createBtClass(BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE);
+ private final Context mContext = ApplicationProvider.getApplicationContext();
private CachedBluetoothDevice mCachedDevice1;
private CachedBluetoothDevice mCachedDevice2;
private CachedBluetoothDeviceManager mCachedDeviceManager;
private HearingAidDeviceManager mHearingAidDeviceManager;
private AudioDeviceAttributes mHearingDeviceAttribute;
- private final Context mContext = ApplicationProvider.getApplicationContext();
@Spy
private HearingAidAudioRoutingHelper mHelper = new HearingAidAudioRoutingHelper(mContext);
@Mock
@@ -517,6 +518,8 @@
when(mHelper.getMatchedHearingDeviceAttributes(mCachedDevice1)).thenReturn(
mHearingDeviceAttribute);
when(mCachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(true);
+ doReturn(true).when(mHelper).setPreferredDeviceRoutingStrategies(anyList(),
+ eq(mHearingDeviceAttribute), anyInt());
mHearingAidDeviceManager.onActiveDeviceChanged(mCachedDevice1);
@@ -529,6 +532,8 @@
when(mHelper.getMatchedHearingDeviceAttributes(mCachedDevice1)).thenReturn(
mHearingDeviceAttribute);
when(mCachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(false);
+ doReturn(true).when(mHelper).setPreferredDeviceRoutingStrategies(anyList(), any(),
+ anyInt());
mHearingAidDeviceManager.onActiveDeviceChanged(mCachedDevice1);
diff --git a/packages/SettingsProvider/TEST_MAPPING b/packages/SettingsProvider/TEST_MAPPING
index 890510f..0eed2b7 100644
--- a/packages/SettingsProvider/TEST_MAPPING
+++ b/packages/SettingsProvider/TEST_MAPPING
@@ -11,5 +11,10 @@
}
]
}
+ ],
+ "postsubmit": [
+ {
+ "name": "CtsDeviceConfigTestCases"
+ }
]
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 5004f25..8ae50eb 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -241,6 +241,8 @@
Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO,
Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE,
Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME,
+ Settings.Secure.BLUETOOTH_LE_BROADCAST_IMPROVE_COMPATIBILITY,
+ Settings.Secure.BLUETOOTH_LE_BROADCAST_FALLBACK_ACTIVE_DEVICE_ADDRESS,
Settings.Secure.CUSTOM_BUGREPORT_HANDLER_APP,
Settings.Secure.CUSTOM_BUGREPORT_HANDLER_USER,
Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 0b0e182..285c8c9 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -387,6 +387,11 @@
VALIDATORS.put(Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO, ANY_STRING_VALIDATOR);
VALIDATORS.put(Secure.BLUETOOTH_LE_BROADCAST_CODE, ANY_STRING_VALIDATOR);
VALIDATORS.put(Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME, ANY_STRING_VALIDATOR);
+ VALIDATORS.put(
+ Secure.BLUETOOTH_LE_BROADCAST_IMPROVE_COMPATIBILITY,
+ new DiscreteValueValidator(new String[] {"0", "1"}));
+ VALIDATORS.put(
+ Secure.BLUETOOTH_LE_BROADCAST_FALLBACK_ACTIVE_DEVICE_ADDRESS, ANY_STRING_VALIDATOR);
VALIDATORS.put(Secure.CUSTOM_BUGREPORT_HANDLER_APP, ANY_STRING_VALIDATOR);
VALIDATORS.put(Secure.CUSTOM_BUGREPORT_HANDLER_USER, ANY_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.LOCK_SCREEN_WEATHER_ENABLED, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index b0abf92..2d442f4 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -1349,6 +1349,26 @@
final int nameCount = names.size();
HashMap<String, String> flagsToValues = new HashMap<>(names.size());
+ if (Flags.loadAconfigDefaults()) {
+ Map<String, Map<String, String>> allDefaults =
+ settingsState.getAconfigDefaultValues();
+
+ if (allDefaults != null) {
+ if (prefix != null) {
+ String namespace = prefix.substring(0, prefix.length() - 1);
+
+ Map<String, String> namespaceDefaults = allDefaults.get(namespace);
+ if (namespaceDefaults != null) {
+ flagsToValues.putAll(namespaceDefaults);
+ }
+ } else {
+ for (Map<String, String> namespaceDefaults : allDefaults.values()) {
+ flagsToValues.putAll(namespaceDefaults);
+ }
+ }
+ }
+ }
+
for (int i = 0; i < nameCount; i++) {
String name = names.get(i);
Setting setting = settingsState.getSettingLocked(name);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 73c2e22..6f3c88f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -69,6 +69,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -236,6 +237,10 @@
@GuardedBy("mLock")
private int mNextHistoricalOpIdx;
+ @GuardedBy("mLock")
+ @Nullable
+ private Map<String, Map<String, String>> mNamespaceDefaults;
+
public static final int SETTINGS_TYPE_GLOBAL = 0;
public static final int SETTINGS_TYPE_SYSTEM = 1;
public static final int SETTINGS_TYPE_SECURE = 2;
@@ -331,25 +336,21 @@
readStateSyncLocked();
if (Flags.loadAconfigDefaults()) {
- // Only load aconfig defaults if this is the first boot, the XML
- // file doesn't exist yet, or this device is on its first boot after
- // an OTA.
- boolean shouldLoadAconfigValues = isConfigSettingsKey(mKey)
- && (!file.exists()
- || mContext.getPackageManager().isDeviceUpgrading());
- if (shouldLoadAconfigValues) {
+ if (isConfigSettingsKey(mKey)) {
loadAconfigDefaultValuesLocked();
}
}
+
}
}
@GuardedBy("mLock")
private void loadAconfigDefaultValuesLocked() {
+ mNamespaceDefaults = new HashMap<>();
+
for (String fileName : sAconfigTextProtoFilesOnDevice) {
try (FileInputStream inputStream = new FileInputStream(fileName)) {
- byte[] contents = inputStream.readAllBytes();
- loadAconfigDefaultValues(contents);
+ loadAconfigDefaultValues(inputStream.readAllBytes(), mNamespaceDefaults);
} catch (IOException e) {
Slog.e(LOG_TAG, "failed to read protobuf", e);
}
@@ -358,27 +359,21 @@
@VisibleForTesting
@GuardedBy("mLock")
- public void loadAconfigDefaultValues(byte[] fileContents) {
+ public static void loadAconfigDefaultValues(byte[] fileContents,
+ @NonNull Map<String, Map<String, String>> defaultMap) {
try {
- parsed_flags parsedFlags = parsed_flags.parseFrom(fileContents);
-
- if (parsedFlags == null) {
- Slog.e(LOG_TAG, "failed to parse aconfig protobuf");
- return;
- }
-
+ parsed_flags parsedFlags =
+ parsed_flags.parseFrom(fileContents);
for (parsed_flag flag : parsedFlags.getParsedFlagList()) {
- String flagName = flag.getNamespace() + "/"
- + flag.getPackage() + "." + flag.getName();
- String value = flag.getState() == flag_state.ENABLED ? "true" : "false";
-
- Setting existingSetting = getSettingLocked(flagName);
- boolean isDefaultLoaded = existingSetting.getTag() != null
- && existingSetting.getTag().equals(BOOT_LOADED_DEFAULT_TAG);
- if (existingSetting.getValue() == null || isDefaultLoaded) {
- insertSettingLocked(flagName, value, BOOT_LOADED_DEFAULT_TAG,
- false, flag.getPackage());
+ if (!defaultMap.containsKey(flag.getNamespace())) {
+ Map<String, String> defaults = new HashMap<>();
+ defaultMap.put(flag.getNamespace(), defaults);
}
+ String flagName = flag.getNamespace()
+ + "/" + flag.getPackage() + "." + flag.getName();
+ String flagValue = flag.getState() == flag_state.ENABLED
+ ? "true" : "false";
+ defaultMap.get(flag.getNamespace()).put(flagName, flagValue);
}
} catch (IOException e) {
Slog.e(LOG_TAG, "failed to parse protobuf", e);
@@ -443,6 +438,13 @@
return names;
}
+ @Nullable
+ public Map<String, Map<String, String>> getAconfigDefaultValues() {
+ synchronized (mLock) {
+ return mNamespaceDefaults;
+ }
+ }
+
// The settings provider must hold its lock when calling here.
public Setting getSettingLocked(String name) {
if (TextUtils.isEmpty(name)) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
index ecac5ee..edbc0b3 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
+++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
@@ -14,3 +14,11 @@
bug: "311155098"
is_fixed_read_only: true
}
+
+flag {
+ name: "configurable_font_scale_default"
+ namespace: "large_screen_experiences_app_compat"
+ description: "Whether the font_scale is read from a device dependent configuration file"
+ bug: "319808237"
+ is_fixed_read_only: true
+}
\ No newline at end of file
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index d26c5ff..16de478 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -231,6 +231,7 @@
Settings.Global.ENABLE_ADB_INCREMENTAL_INSTALL_DEFAULT,
Settings.Global.ENABLE_MULTI_SLOT_TIMEOUT_MILLIS,
Settings.Global.ENHANCED_4G_MODE_ENABLED,
+ Settings.Global.ENABLE_16K_PAGES, // Added for 16K developer option
Settings.Global.EPHEMERAL_COOKIE_MAX_SIZE_BYTES,
Settings.Global.ERROR_LOGCAT_PREFIX,
Settings.Global.EUICC_PROVISIONED,
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 24625ea..e55bbec 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -30,6 +30,8 @@
import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintStream;
+import java.util.HashMap;
+import java.util.Map;
public class SettingsStateTest extends AndroidTestCase {
public static final String CRAZY_STRING =
@@ -93,7 +95,6 @@
SettingsState settingsState = new SettingsState(
getContext(), lock, mSettingsFile, configKey,
SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
-
parsed_flags flags = parsed_flags
.newBuilder()
.addParsedFlag(parsed_flag
@@ -117,18 +118,13 @@
.build();
synchronized (lock) {
- settingsState.loadAconfigDefaultValues(flags.toByteArray());
- settingsState.persistSettingsLocked();
- }
- settingsState.waitForHandler();
+ Map<String, Map<String, String>> defaults = new HashMap<>();
+ settingsState.loadAconfigDefaultValues(flags.toByteArray(), defaults);
+ Map<String, String> namespaceDefaults = defaults.get("test_namespace");
+ assertEquals(2, namespaceDefaults.keySet().size());
- synchronized (lock) {
- assertEquals("false",
- settingsState.getSettingLocked(
- "test_namespace/com.android.flags.flag1").getValue());
- assertEquals("true",
- settingsState.getSettingLocked(
- "test_namespace/com.android.flags.flag2").getValue());
+ assertEquals("false", namespaceDefaults.get("test_namespace/com.android.flags.flag1"));
+ assertEquals("true", namespaceDefaults.get("test_namespace/com.android.flags.flag2"));
}
}
@@ -150,21 +146,18 @@
.build();
synchronized (lock) {
- settingsState.loadAconfigDefaultValues(flags.toByteArray());
- settingsState.persistSettingsLocked();
- }
- settingsState.waitForHandler();
+ Map<String, Map<String, String>> defaults = new HashMap<>();
+ settingsState.loadAconfigDefaultValues(flags.toByteArray(), defaults);
- synchronized (lock) {
- assertEquals(null,
- settingsState.getSettingLocked(
- "test_namespace/com.android.flags.flag1").getValue());
+ Map<String, String> namespaceDefaults = defaults.get("test_namespace");
+ assertEquals(null, namespaceDefaults);
}
}
public void testInvalidAconfigProtoDoesNotCrash() {
+ Map<String, Map<String, String>> defaults = new HashMap<>();
SettingsState settingsState = getSettingStateObject();
- settingsState.loadAconfigDefaultValues("invalid protobuf".getBytes());
+ settingsState.loadAconfigDefaultValues("invalid protobuf".getBytes(), defaults);
}
public void testIsBinary() {
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 507d9c4..db1ca95 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -559,6 +559,9 @@
<!-- Permission required for CTS test - android.server.biometrics -->
<uses-permission android:name="android.permission.TEST_BIOMETRIC" />
+ <!-- Permission required for CTS test - android.server.biometrics -->
+ <uses-permission android:name="android.permission.MANAGE_BIOMETRIC_DIALOG" />
+
<!-- Permissions required for CTS test - NotificationManagerTest -->
<uses-permission android:name="android.permission.MANAGE_NOTIFICATION_LISTENERS" />
@@ -748,6 +751,7 @@
<uses-permission android:name="android.permission.BIND_TELECOM_CONNECTION_SERVICE" />
<uses-permission android:name="android.permission.MODIFY_CELL_BROADCASTS" />
<uses-permission android:name="android.permission.SATELLITE_COMMUNICATION" />
+ <uses-permission android:name="android.permission.ACCESS_LAST_KNOWN_CELL_ID" />
<!-- Permission required for CTS test - CtsPersistentDataBlockManagerTestCases -->
<uses-permission android:name="android.permission.ACCESS_PDB_STATE" />
@@ -891,6 +895,9 @@
<!-- Permission required for Cts test - CtsNotificationTestCases -->
<uses-permission android:name="android.permission.RECEIVE_SENSITIVE_NOTIFICATIONS" />
+ <!-- Permission required for BinaryTransparencyService shell API and host test -->
+ <uses-permission android:name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES" />
+
<application
android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 7443e4c..168e6e0 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -355,6 +355,8 @@
<uses-permission android:name="android.permission.MONITOR_KEYBOARD_BACKLIGHT" />
+ <uses-permission android:name="android.permission.MONITOR_STICKY_MODIFIER_STATE" />
+
<!-- Listen to (dis-)connection of external displays and enable / disable them. -->
<uses-permission android:name="android.permission.MANAGE_DISPLAYS" />
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
index 6c75b434..0df9bac 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
+++ b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
@@ -37,6 +37,7 @@
"androidx.preference_preference",
"androidx.viewpager_viewpager",
"SettingsLibDisplayUtils",
+ "SettingsLibSettingsTheme",
"com_android_a11y_menu_flags_lib",
],
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
index ca84265..648cc3b 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
@@ -40,7 +40,7 @@
android:exported="true"
android:label="@string/accessibility_menu_settings_name"
android:launchMode="singleTop"
- android:theme="@style/AccessibilityMenuSettings">
+ android:theme="@style/Theme.SettingsBase">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
index eadcd7c..f5db6a4 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
@@ -8,10 +8,3 @@
description: "Hides the AccessibilityMenuService UI before taking action instead of after."
bug: "292020123"
}
-
-flag {
- name: "a11y_menu_settings_back_button_fix_and_large_button_sizing"
- namespace: "accessibility"
- description: "Provides/restores back button functionality for the a11yMenu settings page. Also, fixes sizing problems with large shortcut buttons."
- bug: "298467628"
-}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/styles.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/styles.xml
index 1f57654..81b3152 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/styles.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/styles.xml
@@ -16,10 +16,6 @@
-->
<resources>
- <style name="AccessibilityMenuSettings" parent="android:Theme.DeviceDefault.DayNight">
- <item name="android:windowLightStatusBar">false</item>
- </style>
-
<!--Adds the theme to support SnackBar component and user configurable theme. -->
<style name="ServiceTheme" parent="android:Theme.DeviceDefault.DayNight">
<item name="android:colorControlNormal">@color/colorControlNormal</item>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml
index a2508cd..4169155 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml
@@ -16,11 +16,6 @@
-->
<resources>
- <!--The theme is for preference CollapsingToolbarBaseActivity settings-->
- <style name="AccessibilityMenuSettings" parent="android:Theme.DeviceDefault.DayNight">
- <item name="android:windowLightStatusBar">true</item>
- </style>
-
<!--Adds the theme to support SnackBar component and user configurable theme. -->
<style name="ServiceTheme" parent="android:Theme.DeviceDefault.Light">
<item name="android:colorControlNormal">@color/colorControlNormal</item>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
index bf51e23..ab8f97a 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
@@ -35,7 +35,6 @@
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceManager;
-import com.android.systemui.accessibility.accessibilitymenu.Flags;
import com.android.systemui.accessibility.accessibilitymenu.R;
/**
@@ -56,28 +55,17 @@
ActionBar actionBar = getActionBar();
actionBar.setDisplayShowCustomEnabled(true);
-
- if (Flags.a11yMenuSettingsBackButtonFixAndLargeButtonSizing()) {
- actionBar.setDisplayHomeAsUpEnabled(true);
- }
+ actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setCustomView(R.layout.preferences_action_bar);
((TextView) findViewById(R.id.action_bar_title)).setText(
getResources().getString(R.string.accessibility_menu_settings_name)
);
- actionBar.setDisplayOptions(
- ActionBar.DISPLAY_TITLE_MULTIPLE_LINES
- | ActionBar.DISPLAY_SHOW_TITLE
- | ActionBar.DISPLAY_HOME_AS_UP);
}
@Override
public boolean onNavigateUp() {
- if (Flags.a11yMenuSettingsBackButtonFixAndLargeButtonSizing()) {
- mCallback.onBackInvoked();
- return true;
- } else {
- return false;
- }
+ mCallback.onBackInvoked();
+ return true;
}
/**
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
index c4f372c..c2cf6e1 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
@@ -28,7 +28,6 @@
import android.widget.TextView;
import com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService;
-import com.android.systemui.accessibility.accessibilitymenu.Flags;
import com.android.systemui.accessibility.accessibilitymenu.R;
import com.android.systemui.accessibility.accessibilitymenu.activity.A11yMenuSettingsActivity.A11yMenuPreferenceFragment;
import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut;
@@ -81,10 +80,8 @@
if (convertView == null) {
convertView = mInflater.inflate(R.layout.grid_item, parent, false);
- if (Flags.a11yMenuSettingsBackButtonFixAndLargeButtonSizing()) {
- configureShortcutSize(convertView,
- A11yMenuPreferenceFragment.isLargeButtonsEnabled(mService));
- }
+ configureShortcutSize(convertView,
+ A11yMenuPreferenceFragment.isLargeButtonsEnabled(mService));
}
A11yMenuShortcut shortcutItem = (A11yMenuShortcut) getItem(position);
@@ -147,15 +144,6 @@
ImageButton shortcutIconButton = convertView.findViewById(R.id.shortcutIconBtn);
TextView shortcutLabel = convertView.findViewById(R.id.shortcutLabel);
- if (!Flags.a11yMenuSettingsBackButtonFixAndLargeButtonSizing()) {
- if (A11yMenuPreferenceFragment.isLargeButtonsEnabled(mService)) {
- ViewGroup.LayoutParams params = shortcutIconButton.getLayoutParams();
- params.width = (int) (params.width * LARGE_BUTTON_SCALE);
- params.height = (int) (params.height * LARGE_BUTTON_SCALE);
- shortcutLabel.setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, mLargeTextSize);
- }
- }
-
if (shortcutItem.getId() == A11yMenuShortcut.ShortcutId.UNSPECIFIED_ID_VALUE.ordinal()) {
// Sets empty shortcut icon and label when the shortcut is ADD_ITEM.
shortcutIconButton.setImageResource(android.R.color.transparent);
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig
index 21263a9..7ba889b 100644
--- a/packages/SystemUI/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/aconfig/accessibility.aconfig
@@ -10,6 +10,13 @@
}
flag {
+ name: "floating_menu_drag_to_hide"
+ namespace: "accessibility"
+ description: "Allows users to hide the FAB then use notification to dismiss or bring it back."
+ bug: "298718415"
+}
+
+flag {
name: "floating_menu_ime_displacement_animation"
namespace: "accessibility"
description: "Adds an animation for when the FAB is displaced by an IME becoming visible."
@@ -28,4 +35,11 @@
namespace: "accessibility"
description: "Animates the floating menu's transition between curved and jagged edges."
bug: "281140482"
-}
\ No newline at end of file
+}
+
+flag {
+ name: "create_windowless_window_magnifier"
+ namespace: "accessibility"
+ description: "Uses SurfaceControlViewHost to create the magnifier for window magnification."
+ bug: "280992417"
+}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 5fbffbe..3236130 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -15,6 +15,13 @@
}
flag {
+ name: "notification_async_group_header_inflation"
+ namespace: "systemui"
+ description: "Inflates the notification group summary header views from the background thread."
+ bug: "217799515"
+}
+
+flag {
name: "notification_async_hybrid_view_inflation"
namespace: "systemui"
description: "Inflates hybrid (single-line) notification views from the background thread."
@@ -208,6 +215,13 @@
}
flag {
+ name: "compose_bouncer"
+ namespace: "systemui"
+ description: "Use the new compose bouncer in SystemUI"
+ bug: "310005730"
+}
+
+flag {
name: "media_in_scene_container"
namespace: "systemui"
description: "Enable media in the scene container framework"
@@ -336,3 +350,10 @@
description: "Relocate Smartspace to bottom of the Lock Screen"
bug: "316212788"
}
+
+flag {
+ name: "pin_input_field_styled_focus_state"
+ namespace: "systemui"
+ description: "Enables styled focus states on pin input field if keyboard is connected"
+ bug: "316106516"
+}
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
index fd04b5ee0..f4ffb3c 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -22,6 +22,8 @@
import android.view.WindowInsets
import androidx.activity.ComponentActivity
import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.bouncer.ui.BouncerDialogFactory
+import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
@@ -85,6 +87,12 @@
throwComposeUnavailableError()
}
+ override fun createBouncer(
+ context: Context,
+ viewModel: BouncerViewModel,
+ dialogFactory: BouncerDialogFactory,
+ ): View = throwComposeUnavailableError()
+
private fun throwComposeUnavailableError(): Nothing {
error(
"Compose is not available. Make sure to check isComposeAvailable() before calling any" +
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index d31547b..43745f9 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -28,6 +28,9 @@
import androidx.lifecycle.LifecycleOwner
import com.android.compose.theme.PlatformTheme
import com.android.compose.ui.platform.DensityAwareComposeView
+import com.android.systemui.bouncer.ui.BouncerDialogFactory
+import com.android.systemui.bouncer.ui.composable.BouncerContent
+import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout
import com.android.systemui.common.ui.compose.windowinsets.DisplayCutoutProvider
@@ -171,4 +174,14 @@
private fun Int.toDp(context: Context): Dp {
return (this.toFloat() / context.resources.displayMetrics.density).dp
}
+
+ override fun createBouncer(
+ context: Context,
+ viewModel: BouncerViewModel,
+ dialogFactory: BouncerDialogFactory,
+ ): View {
+ return ComposeView(context).apply {
+ setContent { PlatformTheme { BouncerContent(viewModel, dialogFactory) } }
+ }
+ }
}
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/BouncerSceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/BouncerSceneModule.kt
index 1860c9f..2b1268e 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/BouncerSceneModule.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/BouncerSceneModule.kt
@@ -16,34 +16,14 @@
package com.android.systemui.scene
-import android.app.AlertDialog
-import android.content.Context
-import com.android.systemui.bouncer.ui.composable.BouncerDialogFactory
import com.android.systemui.bouncer.ui.composable.BouncerScene
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.scene.shared.model.Scene
-import com.android.systemui.statusbar.phone.SystemUIDialog
import dagger.Binds
import dagger.Module
-import dagger.Provides
import dagger.multibindings.IntoSet
@Module
interface BouncerSceneModule {
@Binds @IntoSet fun bouncerScene(scene: BouncerScene): Scene
-
- companion object {
-
- @Provides
- @SysUISingleton
- fun bouncerSceneDialogFactory(@Application context: Context): BouncerDialogFactory {
- return object : BouncerDialogFactory {
- override fun invoke(): AlertDialog {
- return SystemUIDialog(context)
- }
- }
- }
- }
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
index 6591543..d949396 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
@@ -81,6 +81,7 @@
import com.android.compose.modifiers.thenIf
import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
+import com.android.systemui.bouncer.ui.BouncerDialogFactory
import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout
import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
@@ -824,10 +825,6 @@
}
}
-interface BouncerDialogFactory {
- operator fun invoke(): AlertDialog
-}
-
/**
* Calculates an alpha for the user switcher and bouncer such that it's at `1` when the offset of
* the two reaches a stopping point but `0` in the middle of the transition.
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index d638ffe..428bc39 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -24,6 +24,7 @@
import androidx.compose.ui.Modifier
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.bouncer.ui.BouncerDialogFactory
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.shared.model.Direction
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 d47527a..c073b79b 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
@@ -1,30 +1,13 @@
package com.android.systemui.communal.ui.compose
import androidx.compose.animation.core.tween
-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.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.offset
-import androidx.compose.foundation.layout.width
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Close
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
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.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.input.pointer.pointerInteropFilter
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.ElementKey
@@ -41,7 +24,6 @@
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.transform
@@ -66,7 +48,6 @@
* This is a temporary container to allow the communal UI to use [SceneTransitionLayout] for gesture
* handling and transitions before the full Flexiglass layout is ready.
*/
-@OptIn(ExperimentalComposeUiApi::class, ExperimentalCoroutinesApi::class)
@Composable
fun CommunalContainer(
modifier: Modifier = Modifier,
@@ -83,13 +64,12 @@
transitions = sceneTransitions,
)
- // Don't show hub mode UI if keyguard is present. This is important since we're in the shade,
- // which can be opened from many locations.
+ // Don't show hub mode UI if keyguard is not present. This is important since we're in the
+ // shade, which can be opened from many locations.
val isKeyguardShowing by viewModel.isKeyguardVisible.collectAsState(initial = false)
+ val isCommunalAvailable by viewModel.isCommunalAvailable.collectAsState()
- // Failsafe to hide the whole SceneTransitionLayout in case of bugginess.
- var showSceneTransitionLayout by remember { mutableStateOf(true) }
- if (!showSceneTransitionLayout || !isKeyguardShowing) {
+ if (!isKeyguardShowing || !isCommunalAvailable) {
return
}
@@ -102,80 +82,30 @@
onDispose { viewModel.setTransitionState(null) }
}
- Box(modifier = modifier.fillMaxSize()) {
- SceneTransitionLayout(
- state = sceneTransitionLayoutState,
- modifier = Modifier.fillMaxSize(),
- edgeDetector = FixedSizeEdgeDetector(ContainerDimensions.EdgeSwipeSize),
+ SceneTransitionLayout(
+ state = sceneTransitionLayoutState,
+ modifier = modifier.fillMaxSize(),
+ edgeDetector = FixedSizeEdgeDetector(ContainerDimensions.EdgeSwipeSize),
+ ) {
+ scene(
+ TransitionSceneKey.Blank,
+ userActions =
+ mapOf(
+ Swipe(SwipeDirection.Left, fromEdge = Edge.Right) to TransitionSceneKey.Communal
+ )
) {
- scene(
- TransitionSceneKey.Blank,
- userActions =
- mapOf(
- Swipe(SwipeDirection.Left, fromEdge = Edge.Right) to
- TransitionSceneKey.Communal
- )
- ) {
- BlankScene { showSceneTransitionLayout = false }
- }
-
- scene(
- TransitionSceneKey.Communal,
- userActions =
- mapOf(
- Swipe(SwipeDirection.Right, fromEdge = Edge.Left) to
- TransitionSceneKey.Blank
- ),
- ) {
- CommunalScene(viewModel, modifier = modifier)
- }
+ // This scene shows nothing only allowing for transitions to the communal scene.
+ Box(modifier = Modifier.fillMaxSize())
}
- // TODO(b/308813166): remove once CommunalContainer is moved lower in z-order and doesn't
- // block touches anymore.
- Box(
- modifier =
- Modifier.fillMaxSize()
- // Offsetting to the left so that edge swipe to open the hub still works. This
- // does mean that the very right edge of the hub won't refresh the screen
- // timeout, but should be good enough for a temporary solution.
- .offset(x = -ContainerDimensions.EdgeSwipeSize)
- .pointerInteropFilter {
- viewModel.onUserActivity()
- if (
- sceneTransitionLayoutState.transitionState.currentScene ==
- TransitionSceneKey.Blank
- ) {
- viewModel.onOuterTouch(it)
- return@pointerInteropFilter true
- }
- false
- }
- )
- }
-}
-
-/**
- * Blank scene that shows over keyguard/dream. This scene will eventually show nothing at all and is
- * only used to allow for transitions to the communal scene.
- */
-@Composable
-private fun BlankScene(
- modifier: Modifier = Modifier,
- hideSceneTransitionLayout: () -> Unit,
-) {
- Box(modifier.fillMaxSize()) {
- Column(
- Modifier.fillMaxHeight()
- .width(ContainerDimensions.EdgeSwipeSize)
- .align(Alignment.CenterEnd)
- .background(Color(0x55e9f2eb)),
- verticalArrangement = Arrangement.Center,
- horizontalAlignment = Alignment.CenterHorizontally
+ scene(
+ TransitionSceneKey.Communal,
+ userActions =
+ mapOf(
+ Swipe(SwipeDirection.Right, fromEdge = Edge.Left) to TransitionSceneKey.Blank
+ ),
) {
- IconButton(onClick = hideSceneTransitionLayout) {
- Icon(Icons.Filled.Close, contentDescription = "Close button")
- }
+ CommunalScene(viewModel, modifier = modifier)
}
}
}
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 d76f0ff..a390305 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
@@ -20,6 +20,7 @@
import android.os.Bundle
import android.util.SizeF
import android.widget.FrameLayout
+import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
@@ -38,6 +39,7 @@
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
+import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.foundation.shape.RoundedCornerShape
@@ -58,7 +60,9 @@
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -67,8 +71,10 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
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
@@ -83,13 +89,16 @@
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.window.Popup
+import androidx.core.view.setPadding
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.ui.compose.Dimensions.CardOutlineWidth
+import com.android.systemui.communal.ui.compose.extensions.allowGestures
+import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset
+import com.android.systemui.communal.ui.compose.extensions.observeTapsWithoutConsuming
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
-import com.android.systemui.media.controls.ui.MediaHierarchyManager
-import com.android.systemui.media.controls.ui.MediaHostState
import com.android.systemui.res.R
@Composable
@@ -106,22 +115,59 @@
var toolbarSize: IntSize? by remember { mutableStateOf(null) }
var gridCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) }
var isDraggingToRemove by remember { mutableStateOf(false) }
+ val gridState = rememberLazyGridState()
+ val contentListState = rememberContentListState(communalContent, viewModel)
+ val reorderingWidgets by viewModel.reorderingWidgets.collectAsState()
+ val selectedIndex = viewModel.selectedIndex.collectAsState()
+ val removeButtonEnabled by remember {
+ derivedStateOf { selectedIndex.value != null || reorderingWidgets }
+ }
+
+ val contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize)
+ val contentOffset = beforeContentPadding(contentPadding).toOffset()
Box(
modifier =
- modifier.fillMaxSize().background(LocalAndroidColorScheme.current.outlineVariant),
+ modifier
+ .fillMaxSize()
+ .background(LocalAndroidColorScheme.current.outlineVariant)
+ .pointerInput(gridState, contentOffset, contentListState) {
+ // If not in edit mode, don't allow selecting items.
+ if (!viewModel.isEditMode) return@pointerInput
+ observeTapsWithoutConsuming { offset ->
+ val adjustedOffset = offset - contentOffset
+ val index =
+ gridState.layoutInfo.visibleItemsInfo
+ .firstItemAtOffset(adjustedOffset)
+ ?.index
+ val newIndex =
+ if (index?.let(contentListState::isItemEditable) == true) {
+ index
+ } else {
+ null
+ }
+ viewModel.setSelectedIndex(newIndex)
+ }
+ },
) {
CommunalHubLazyGrid(
communalContent = communalContent,
viewModel = viewModel,
- contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize),
+ contentPadding = contentPadding,
+ contentOffset = contentOffset,
setGridCoordinates = { gridCoordinates = it },
- updateDragPositionForRemove = {
+ updateDragPositionForRemove = { offset ->
isDraggingToRemove =
- checkForDraggingToRemove(it, removeButtonCoordinates, gridCoordinates)
+ isPointerWithinCoordinates(
+ offset = gridCoordinates?.let { it.positionInWindow() + offset },
+ containerToCheck = removeButtonCoordinates
+ )
isDraggingToRemove
},
onOpenWidgetPicker = onOpenWidgetPicker,
+ gridState = gridState,
+ contentListState = contentListState,
+ selectedIndex = selectedIndex
)
if (viewModel.isEditMode && onOpenWidgetPicker != null && onEditDone != null) {
@@ -131,6 +177,14 @@
setRemoveButtonCoordinates = { removeButtonCoordinates = it },
onEditDone = onEditDone,
onOpenWidgetPicker = onOpenWidgetPicker,
+ onRemoveClicked = {
+ selectedIndex.value?.let { index ->
+ contentListState.onRemove(index)
+ contentListState.onSaveList()
+ viewModel.setSelectedIndex(null)
+ }
+ },
+ removeEnabled = removeButtonEnabled
)
} else {
IconButton(onClick = viewModel::onOpenWidgetEditor) {
@@ -160,16 +214,18 @@
communalContent: List<CommunalContentModel>,
viewModel: BaseCommunalViewModel,
contentPadding: PaddingValues,
+ selectedIndex: State<Int?>,
+ contentOffset: Offset,
+ gridState: LazyGridState,
+ contentListState: ContentListState,
setGridCoordinates: (coordinates: LayoutCoordinates) -> Unit,
updateDragPositionForRemove: (offset: Offset) -> Boolean,
onOpenWidgetPicker: (() -> Unit)? = null,
) {
var gridModifier = Modifier.align(Alignment.CenterStart)
- val gridState = rememberLazyGridState()
var list = communalContent
var dragDropState: GridDragDropState? = null
if (viewModel.isEditMode && viewModel is CommunalEditModeViewModel) {
- val contentListState = rememberContentListState(list, viewModel)
list = contentListState.list
// for drag & drop operations within the communal hub grid
dragDropState =
@@ -181,7 +237,7 @@
gridModifier =
gridModifier
.fillMaxSize()
- .dragContainer(dragDropState, beforeContentPadding(contentPadding), viewModel)
+ .dragContainer(dragDropState, contentOffset, viewModel)
.onGloballyPositioned { setGridCoordinates(it) }
// for widgets dropped from other activities
val dragAndDropTargetState =
@@ -220,8 +276,10 @@
list[index].size.dp().value,
)
if (viewModel.isEditMode && dragDropState != null) {
+ val selected by remember(index) { derivedStateOf { index == selectedIndex.value } }
DraggableItem(
dragDropState = dragDropState,
+ selected = selected,
enabled = list[index] is CommunalContentModel.Widget,
index = index,
size = size
@@ -255,11 +313,19 @@
@Composable
private fun Toolbar(
isDraggingToRemove: Boolean,
+ removeEnabled: Boolean,
+ onRemoveClicked: () -> Unit,
setToolbarSize: (toolbarSize: IntSize) -> Unit,
setRemoveButtonCoordinates: (coordinates: LayoutCoordinates) -> Unit,
onOpenWidgetPicker: () -> Unit,
- onEditDone: () -> Unit,
+ onEditDone: () -> Unit
) {
+ val removeButtonAlpha: Float by
+ animateFloatAsState(
+ targetValue = if (removeEnabled) 1f else 0.5f,
+ label = "RemoveButtonAlphaAnimation"
+ )
+
Row(
modifier =
Modifier.fillMaxWidth()
@@ -303,13 +369,18 @@
}
} else {
OutlinedButton(
- // Button is disabled to make it non-clickable
- enabled = false,
- onClick = {},
- colors = ButtonDefaults.outlinedButtonColors(disabledContentColor = colors.primary),
+ enabled = removeEnabled,
+ onClick = onRemoveClicked,
+ colors =
+ ButtonDefaults.outlinedButtonColors(
+ contentColor = colors.primary,
+ disabledContentColor = colors.primary
+ ),
border = BorderStroke(width = 1.0.dp, color = colors.primary),
contentPadding = Dimensions.ButtonPadding,
- modifier = Modifier.onGloballyPositioned { setRemoveButtonCoordinates(it) }
+ modifier =
+ Modifier.graphicsLayer { alpha = removeButtonAlpha }
+ .onGloballyPositioned { setRemoveButtonCoordinates(it) }
) {
RemoveButtonContent(spacerModifier)
}
@@ -387,7 +458,7 @@
) {
when (model) {
is CommunalContentModel.Widget -> WidgetContent(viewModel, model, size, modifier)
- is CommunalContentModel.WidgetPlaceholder -> WidgetPlaceholderContent(size)
+ is CommunalContentModel.WidgetPlaceholder -> HighlightedItem(size)
is CommunalContentModel.CtaTileInViewMode ->
CtaTileInViewModeContent(viewModel, size, modifier)
is CommunalContentModel.CtaTileInEditMode ->
@@ -398,13 +469,13 @@
}
}
-/** Presents a placeholder card for the new widget being dragged and dropping into the grid. */
+/** Creates an empty card used to highlight a particular spot on the grid. */
@Composable
-fun WidgetPlaceholderContent(size: SizeF) {
+fun HighlightedItem(size: SizeF, modifier: Modifier = Modifier) {
Card(
- modifier = Modifier.size(Dp(size.width), Dp(size.height)),
+ modifier = modifier.size(Dp(size.width), Dp(size.height)),
colors = CardDefaults.cardColors(containerColor = Color.Transparent),
- border = BorderStroke(3.dp, LocalAndroidColorScheme.current.tertiaryFixed),
+ border = BorderStroke(CardOutlineWidth, LocalAndroidColorScheme.current.tertiaryFixed),
shape = RoundedCornerShape(16.dp)
) {}
}
@@ -418,7 +489,7 @@
) {
val colors = LocalAndroidColorScheme.current
Card(
- modifier = modifier.height(size.height.dp),
+ modifier = modifier.height(size.height.dp).padding(CardOutlineWidth),
colors =
CardDefaults.cardColors(
containerColor = colors.primary,
@@ -490,7 +561,7 @@
}
val colors = LocalAndroidColorScheme.current
Card(
- modifier = modifier.height(size.height.dp),
+ modifier = modifier.height(size.height.dp).padding(CardOutlineWidth),
colors = CardDefaults.cardColors(containerColor = Color.Transparent),
border = BorderStroke(1.dp, colors.primary),
shape = RoundedCornerShape(200.dp),
@@ -529,8 +600,9 @@
modifier = modifier.height(size.height.dp),
contentAlignment = Alignment.Center,
) {
+ val paddingInPx = with(LocalDensity.current) { CardOutlineWidth.toPx().toInt() }
AndroidView(
- modifier = modifier,
+ modifier = modifier.allowGestures(allowed = !viewModel.isEditMode),
factory = { context ->
// The AppWidgetHostView will inherit the interaction handler from the
// AppWidgetHost. So set the interaction handler here before creating the view, and
@@ -540,9 +612,13 @@
model.appWidgetHost.setInteractionHandler(viewModel.getInteractionHandler())
val view =
model.appWidgetHost
- .createView(context, model.appWidgetId, model.providerInfo)
+ .createViewForCommunal(context, model.appWidgetId, model.providerInfo)
.apply { updateAppWidgetSize(Bundle.EMPTY, listOf(size)) }
model.appWidgetHost.setInteractionHandler(null)
+ // 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)
view
},
// For reusing composition in lazy lists.
@@ -576,10 +652,6 @@
AndroidView(
modifier = modifier,
factory = {
- viewModel.mediaHost.expansion = MediaHostState.EXPANDED
- viewModel.mediaHost.showsOnlyActiveMedia = false
- viewModel.mediaHost.falsingProtectionNeeded = false
- viewModel.mediaHost.init(MediaHierarchyManager.LOCATION_COMMUNAL_HUB)
viewModel.mediaHost.hostView.layoutParams =
FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
@@ -622,8 +694,8 @@
private fun beforeContentPadding(paddingValues: PaddingValues): ContentPaddingInPx {
return with(LocalDensity.current) {
ContentPaddingInPx(
- startPadding = paddingValues.calculateLeftPadding(LayoutDirection.Ltr).toPx(),
- topPadding = paddingValues.calculateTopPadding().toPx()
+ start = paddingValues.calculateLeftPadding(LayoutDirection.Ltr).toPx(),
+ top = paddingValues.calculateTopPadding().toPx()
)
}
}
@@ -632,18 +704,15 @@
* Check whether the pointer position that the item is being dragged at is within the coordinates of
* the remove button in the toolbar. Returns true if the item is removable.
*/
-private fun checkForDraggingToRemove(
- offset: Offset,
- removeButtonCoordinates: LayoutCoordinates?,
- gridCoordinates: LayoutCoordinates?,
+private fun isPointerWithinCoordinates(
+ offset: Offset?,
+ containerToCheck: LayoutCoordinates?
): Boolean {
- if (removeButtonCoordinates == null || gridCoordinates == null) {
+ if (offset == null || containerToCheck == null) {
return false
}
- val pointer = gridCoordinates.positionInWindow() + offset
- val removeButton = removeButtonCoordinates.positionInWindow()
- return pointer.x in removeButton.x..removeButton.x + removeButtonCoordinates.size.width &&
- pointer.y in removeButton.y..removeButton.y + removeButtonCoordinates.size.height
+ val container = containerToCheck.boundsInWindow()
+ return container.contains(offset)
}
private fun CommunalContentSize.dp(): Dp {
@@ -654,13 +723,16 @@
}
}
-data class ContentPaddingInPx(val startPadding: Float, val topPadding: Float)
+data class ContentPaddingInPx(val start: Float, val top: Float) {
+ fun toOffset(): Offset = Offset(start, top)
+}
object Dimensions {
val CardWidth = 464.dp
val CardHeightFull = 630.dp
val CardHeightHalf = 307.dp
val CardHeightThird = 199.dp
+ val CardOutlineWidth = 3.dp
val GridHeight = CardHeightFull
val Spacing = 16.dp
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
index 979991d..45f98b8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
@@ -21,12 +21,12 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.toMutableStateList
import com.android.systemui.communal.domain.model.CommunalContentModel
-import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
+import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
@Composable
fun rememberContentListState(
communalContent: List<CommunalContentModel>,
- viewModel: CommunalEditModeViewModel,
+ viewModel: BaseCommunalViewModel,
): ContentListState {
return remember(communalContent) {
ContentListState(
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 1138221..a195953 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
@@ -17,6 +17,10 @@
package com.android.systemui.communal.ui.compose
import android.util.SizeF
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
import androidx.compose.foundation.gestures.scrollBy
@@ -32,6 +36,7 @@
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
@@ -39,6 +44,7 @@
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
import kotlinx.coroutines.CoroutineScope
@@ -109,13 +115,10 @@
internal fun onDragStart(offset: Offset, contentOffset: Offset) {
state.layoutInfo.visibleItemsInfo
- .firstOrNull { item ->
- // grid item offset is based off grid content container so we need to deduct
- // before content padding from the initial pointer position
- contentListState.isItemEditable(item.index) &&
- (offset.x - contentOffset.x).toInt() in item.offset.x..item.offsetEnd.x &&
- (offset.y - contentOffset.y).toInt() in item.offset.y..item.offsetEnd.y
- }
+ .filter { item -> contentListState.isItemEditable(item.index) }
+ // grid item offset is based off grid content container so we need to deduct
+ // before content padding from the initial pointer position
+ .firstItemAtOffset(offset - contentOffset)
?.apply {
dragStartPointerOffset = offset - this.offset.toOffset()
draggingItemIndex = index
@@ -148,12 +151,11 @@
val middleOffset = startOffset + (endOffset - startOffset) / 2f
val targetItem =
- state.layoutInfo.visibleItemsInfo.find { item ->
- contentListState.isItemEditable(item.index) &&
- middleOffset.x.toInt() in item.offset.x..item.offsetEnd.x &&
- middleOffset.y.toInt() in item.offset.y..item.offsetEnd.y &&
- draggingItem.index != item.index
- }
+ state.layoutInfo.visibleItemsInfo
+ .asSequence()
+ .filter { item -> contentListState.isItemEditable(item.index) }
+ .filter { item -> draggingItem.index != item.index }
+ .firstItemAtOffset(middleOffset)
if (targetItem != null) {
val scrollToIndex =
@@ -208,32 +210,31 @@
fun Modifier.dragContainer(
dragDropState: GridDragDropState,
- beforeContentPadding: ContentPaddingInPx,
+ contentOffset: Offset,
viewModel: BaseCommunalViewModel,
): Modifier {
- return pointerInput(dragDropState, beforeContentPadding) {
- detectDragGesturesAfterLongPress(
- onDrag = { change, offset ->
- change.consume()
- dragDropState.onDrag(offset = offset)
- },
- onDragStart = { offset ->
- dragDropState.onDragStart(
- offset,
- Offset(beforeContentPadding.startPadding, beforeContentPadding.topPadding)
- )
- viewModel.onReorderWidgetStart()
- },
- onDragEnd = {
- dragDropState.onDragInterrupted()
- viewModel.onReorderWidgetEnd()
- },
- onDragCancel = {
- dragDropState.onDragInterrupted()
- viewModel.onReorderWidgetCancel()
- }
- )
- }
+ return this.then(
+ pointerInput(dragDropState, contentOffset) {
+ detectDragGesturesAfterLongPress(
+ onDrag = { change, offset ->
+ change.consume()
+ dragDropState.onDrag(offset = offset)
+ },
+ onDragStart = { offset ->
+ dragDropState.onDragStart(offset, contentOffset)
+ viewModel.onReorderWidgetStart()
+ },
+ onDragEnd = {
+ dragDropState.onDragInterrupted()
+ viewModel.onReorderWidgetEnd()
+ },
+ onDragCancel = {
+ dragDropState.onDragInterrupted()
+ viewModel.onReorderWidgetCancel()
+ }
+ )
+ }
+ )
}
/** Wrap LazyGrid item with additional modifier needed for drag and drop. */
@@ -243,6 +244,7 @@
dragDropState: GridDragDropState,
index: Int,
enabled: Boolean,
+ selected: Boolean,
size: SizeF,
modifier: Modifier = Modifier,
content: @Composable (isDragging: Boolean) -> Unit
@@ -250,21 +252,31 @@
if (!enabled) {
return Box(modifier = modifier) { content(false) }
}
+
val dragging = index == dragDropState.draggingItemIndex
+ val itemAlpha: Float by
+ animateFloatAsState(
+ targetValue = if (dragDropState.isDraggingToRemove) 0.5f else 1f,
+ label = "DraggableItemAlpha"
+ )
val draggingModifier =
if (dragging) {
Modifier.zIndex(1f).graphicsLayer {
translationX = dragDropState.draggingItemOffset.x
translationY = dragDropState.draggingItemOffset.y
- alpha = if (dragDropState.isDraggingToRemove) 0.5f else 1f
+ alpha = itemAlpha
}
} else {
Modifier.animateItemPlacement()
}
Box(modifier) {
- if (dragging) {
- WidgetPlaceholderContent(size)
+ AnimatedVisibility(
+ visible = (dragging || selected) && !dragDropState.isDraggingToRemove,
+ enter = fadeIn(),
+ exit = fadeOut()
+ ) {
+ HighlightedItem(size)
}
Box(modifier = draggingModifier, propagateMinConstraints = true) { content(dragging) }
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/LazyGridStateExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/LazyGridStateExt.kt
new file mode 100644
index 0000000..132093f
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/LazyGridStateExt.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.extensions
+
+import androidx.compose.foundation.lazy.grid.LazyGridItemInfo
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.unit.IntRect
+import androidx.compose.ui.unit.toRect
+
+/**
+ * Determine the item at the specified offset, or null if none exist.
+ *
+ * @param offset The offset in pixels, relative to the top start of the grid.
+ */
+fun Iterable<LazyGridItemInfo>.firstItemAtOffset(offset: Offset): LazyGridItemInfo? =
+ firstOrNull { item ->
+ isItemAtOffset(item, offset)
+ }
+
+/**
+ * Determine the item at the specified offset, or null if none exist.
+ *
+ * @param offset The offset in pixels, relative to the top start of the grid.
+ */
+fun Sequence<LazyGridItemInfo>.firstItemAtOffset(offset: Offset): LazyGridItemInfo? =
+ firstOrNull { item ->
+ isItemAtOffset(item, offset)
+ }
+
+private fun isItemAtOffset(item: LazyGridItemInfo, offset: Offset): Boolean {
+ val boundingBox = IntRect(item.offset, item.size)
+ return boundingBox.toRect().contains(offset)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/ModifierExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/ModifierExt.kt
new file mode 100644
index 0000000..b31008e
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/ModifierExt.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.communal.ui.compose.extensions
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.pointer.pointerInput
+
+/** Sets whether gestures are allowed on children of this element. */
+fun Modifier.allowGestures(allowed: Boolean): Modifier =
+ if (allowed) {
+ this
+ } else {
+ this.then(pointerInput(Unit) { consumeAllGestures() })
+ }
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
new file mode 100644
index 0000000..1407494
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.extensions
+
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.gestures.waitForUpOrCancellation
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.PointerInputChange
+import androidx.compose.ui.input.pointer.PointerInputScope
+import kotlinx.coroutines.coroutineScope
+
+/**
+ * Observe taps without actually consuming them, so child elements can still respond to them. Long
+ * presses are excluded.
+ */
+suspend fun PointerInputScope.observeTapsWithoutConsuming(
+ pass: PointerEventPass = PointerEventPass.Initial,
+ onTap: ((Offset) -> Unit)? = null,
+) = coroutineScope {
+ if (onTap == null) return@coroutineScope
+ awaitEachGesture {
+ awaitFirstDown(pass = pass)
+ val tapTimeout = viewConfiguration.longPressTimeoutMillis
+ val up = withTimeoutOrNull(tapTimeout) { waitForUpOrCancellation(pass = pass) }
+ if (up != null) {
+ onTap(up.position)
+ }
+ }
+}
+
+/** Consume all gestures on the initial pass so that child elements do not receive them. */
+suspend fun PointerInputScope.consumeAllGestures() = coroutineScope {
+ awaitEachGesture {
+ awaitPointerEvent(pass = PointerEventPass.Initial)
+ .changes
+ .forEach(PointerInputChange::consume)
+ }
+}
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 84d4246..56d6879 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,13 +17,16 @@
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.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.platform.LocalContext
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
@@ -66,6 +69,7 @@
override fun SceneScope.Content(modifier: Modifier) {
val isUdfpsVisible = viewModel.isUdfpsVisible
val burnIn = rememberBurnIn(clockInteractor)
+ val resources = LocalContext.current.resources
LockscreenLongPress(
viewModel = viewModel.longPress,
@@ -88,13 +92,27 @@
SmartSpace(
burnInParams = burnIn.parameters,
onTopChanged = burnIn.onSmartspaceTopChanged,
- modifier = Modifier.fillMaxWidth(),
+ modifier =
+ Modifier.fillMaxWidth()
+ .padding(
+ top = { viewModel.getSmartSpacePaddingTop(resources) }
+ ),
)
}
- with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
- with(notificationSection) {
- Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
+
+ if (viewModel.isLargeClockVisible) {
+ Spacer(modifier = Modifier.weight(weight = 1f))
+ with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
}
+
+ if (viewModel.areNotificationsVisible) {
+ with(notificationSection) {
+ Notifications(
+ modifier = Modifier.fillMaxWidth().weight(weight = 1f)
+ )
+ }
+ }
+
if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
with(ambientIndicationSectionOptional.get()) {
AmbientIndication(modifier = Modifier.fillMaxWidth())
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 4148462..d0aa444 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,13 +17,16 @@
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.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.platform.LocalContext
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
@@ -66,6 +69,7 @@
override fun SceneScope.Content(modifier: Modifier) {
val isUdfpsVisible = viewModel.isUdfpsVisible
val burnIn = rememberBurnIn(clockInteractor)
+ val resources = LocalContext.current.resources
LockscreenLongPress(
viewModel = viewModel.longPress,
@@ -88,13 +92,27 @@
SmartSpace(
burnInParams = burnIn.parameters,
onTopChanged = burnIn.onSmartspaceTopChanged,
- modifier = Modifier.fillMaxWidth(),
+ modifier =
+ Modifier.fillMaxWidth()
+ .padding(
+ top = { viewModel.getSmartSpacePaddingTop(resources) }
+ ),
)
}
- with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
- with(notificationSection) {
- Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
+
+ if (viewModel.isLargeClockVisible) {
+ Spacer(modifier = Modifier.weight(weight = 1f))
+ with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
}
+
+ if (viewModel.areNotificationsVisible) {
+ with(notificationSection) {
+ Notifications(
+ modifier = Modifier.fillMaxWidth().weight(weight = 1f)
+ )
+ }
+ }
+
if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
with(ambientIndicationSectionOptional.get()) {
AmbientIndication(modifier = Modifier.fillMaxWidth())
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
index f021bb6..f40b871 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
@@ -30,10 +30,10 @@
import com.android.compose.animation.scene.SceneScope
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.modifier.onTopPlacementChanged
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
-import com.android.systemui.res.R
import javax.inject.Inject
class ClockSection
@@ -79,7 +79,7 @@
modifier =
Modifier.padding(
horizontal =
- dimensionResource(R.dimen.keyguard_affordance_horizontal_offset)
+ dimensionResource(customizationR.dimen.clock_padding_start)
)
.padding(top = { viewModel.getSmallClockTopMargin(view.context) })
.onTopPlacementChanged(onTopChanged),
@@ -117,9 +117,7 @@
content {
AndroidView(
factory = { checkNotNull(currentClock).largeClock.view },
- modifier =
- Modifier.fillMaxWidth()
- .padding(top = { viewModel.getLargeClockTopMargin(view.context) })
+ modifier = Modifier.fillMaxWidth()
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index 900616f..42fcd13 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -16,23 +16,55 @@
package com.android.systemui.keyguard.ui.composable.section
+import android.content.Context
+import android.view.ViewGroup
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.notifications.ui.composable.NotificationStack
+import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.statusbar.notification.stack.AmbientState
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
+import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationStackAppearanceViewBinder
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import javax.inject.Inject
class NotificationSection
@Inject
constructor(
+ @Application context: Context,
private val viewModel: NotificationsPlaceholderViewModel,
+ controller: NotificationStackScrollLayoutController,
+ sceneContainerFlags: SceneContainerFlags,
+ sharedNotificationContainer: SharedNotificationContainer,
+ stackScrollLayout: NotificationStackScrollLayout,
+ notificationStackAppearanceViewModel: NotificationStackAppearanceViewModel,
+ ambientState: AmbientState,
) {
+ init {
+ if (sceneContainerFlags.flexiNotifsEnabled()) {
+ (stackScrollLayout.parent as? ViewGroup)?.removeView(stackScrollLayout)
+ sharedNotificationContainer.addNotificationStackScrollLayout(stackScrollLayout)
+
+ NotificationStackAppearanceViewBinder.bind(
+ context,
+ sharedNotificationContainer,
+ notificationStackAppearanceViewModel,
+ ambientState,
+ controller,
+ )
+ }
+ }
+
@Composable
fun SceneScope.Notifications(modifier: Modifier = Modifier) {
NotificationStack(
viewModel = viewModel,
- isScrimVisible = false,
modifier = modifier,
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 0eec024..e835d3e5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -22,6 +22,7 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
@@ -43,11 +44,10 @@
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
-import com.android.compose.animation.scene.ValueKey
-import com.android.compose.animation.scene.animateElementFloatAsState
+import com.android.compose.modifiers.height
import com.android.systemui.notifications.ui.composable.Notifications.Form
-import com.android.systemui.notifications.ui.composable.Notifications.SharedValues.SharedExpansionValue
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
+import kotlin.math.roundToInt
object Notifications {
object Elements {
@@ -56,10 +56,6 @@
val ShelfSpace = ElementKey("ShelfSpace")
}
- object SharedValues {
- val SharedExpansionValue = ValueKey("SharedExpansionValue")
- }
-
enum class Form {
HunFromTop,
Stack,
@@ -84,32 +80,52 @@
)
}
-/** Adds the space where notification stack will appear in the scene. */
+/** Adds the space where notification stack should appear in the scene. */
@Composable
fun SceneScope.NotificationStack(
viewModel: NotificationsPlaceholderViewModel,
- isScrimVisible: Boolean,
+ modifier: Modifier = Modifier,
+) {
+ NotificationPlaceholder(
+ viewModel = viewModel,
+ form = Form.Stack,
+ modifier = modifier,
+ )
+}
+
+/**
+ * Adds the space where notification stack should appear in the scene, with a scrim and nested
+ * scrolling.
+ */
+@Composable
+fun SceneScope.NotificationScrollingStack(
+ viewModel: NotificationsPlaceholderViewModel,
modifier: Modifier = Modifier,
) {
val cornerRadius by viewModel.cornerRadiusDp.collectAsState()
- Box(modifier = modifier) {
- if (isScrimVisible) {
- Box(
- modifier =
- Modifier.element(Notifications.Elements.NotificationScrim)
- .fillMaxSize()
- .graphicsLayer {
- shape = RoundedCornerShape(cornerRadius.dp)
- clip = true
- }
- .background(MaterialTheme.colorScheme.surface)
- )
- }
+ val contentHeight by viewModel.intrinsicContentHeight.collectAsState()
+
+ val expansionFraction by viewModel.expandFraction.collectAsState(0f)
+
+ Box(
+ modifier =
+ modifier
+ .verticalNestedScrollToScene()
+ .fillMaxWidth()
+ .element(Notifications.Elements.NotificationScrim)
+ .graphicsLayer {
+ shape = RoundedCornerShape(cornerRadius.dp)
+ clip = true
+ alpha = expansionFraction
+ }
+ .background(MaterialTheme.colorScheme.surface)
+ .debugBackground(viewModel, Color(0.5f, 0.5f, 0f, 0.2f))
+ ) {
NotificationPlaceholder(
viewModel = viewModel,
form = Form.Stack,
- modifier = Modifier.fillMaxSize(),
+ modifier = Modifier.fillMaxWidth().height { contentHeight.roundToInt() }
)
}
}
@@ -166,6 +182,7 @@
debugLog(viewModel) { "STACK onSizeChanged: size=$size" }
}
.onPlaced { coordinates: LayoutCoordinates ->
+ viewModel.onContentTopChanged(coordinates.positionInWindow().y)
debugLog(viewModel) {
"STACK onPlaced:" +
" size=${coordinates.size}" +
@@ -181,13 +198,6 @@
)
}
) {
- val animatedExpansion by
- animateElementFloatAsState(
- value = if (form == Form.HunFromTop) 0f else 1f,
- key = SharedExpansionValue
- )
- debugLog(viewModel) { "STACK composed: expansion=$animatedExpansion" }
-
content {
if (viewModel.isPlaceholderTextVisible) {
Box(Modifier.fillMaxSize()) {
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 bded98d..747faab 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
@@ -25,6 +25,7 @@
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.notifications.ui.composable.HeadsUpNotificationSpace
+import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.Edge
import com.android.systemui.scene.shared.model.SceneKey
@@ -66,6 +67,7 @@
modifier: Modifier,
) {
Box(modifier = modifier) {
+ Box(modifier = Modifier.fillMaxSize().element(Notifications.Elements.NotificationScrim))
HeadsUpNotificationSpace(
viewModel = notificationsViewModel,
modifier = Modifier.padding(16.dp).fillMaxSize(),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
index 6bb525a..0c2c519 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
@@ -3,12 +3,12 @@
import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
-import com.android.systemui.notifications.ui.composable.Notifications
-import com.android.systemui.scene.ui.composable.Shade
+import com.android.systemui.qs.ui.composable.QuickSettings
+import com.android.systemui.shade.ui.composable.ShadeHeader
fun TransitionBuilder.goneToShadeTransition() {
spec = tween(durationMillis = 500)
- translate(Shade.rootElementKey, Edge.Top, true)
- fade(Notifications.Elements.NotificationScrim)
+ fractionRange(start = .58f) { fade(ShadeHeader.Elements.CollapsedContent) }
+ translate(QuickSettings.Elements.Content, Edge.Top, true)
}
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 9c0f1fe..1545372 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
@@ -22,11 +22,9 @@
import androidx.compose.foundation.layout.Box
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.height
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
@@ -44,10 +42,12 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.media.controls.ui.MediaCarouselController
+import com.android.systemui.media.controls.ui.MediaHierarchyManager
import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.media.controls.ui.MediaHostState
import com.android.systemui.media.controls.ui.composable.MediaCarousel
import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
-import com.android.systemui.notifications.ui.composable.NotificationStack
+import com.android.systemui.notifications.ui.composable.NotificationScrollingStack
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Direction
@@ -128,6 +128,12 @@
modifier = modifier,
)
+ init {
+ mediaHost.expansion = MediaHostState.EXPANDED
+ mediaHost.showsOnlyActiveMedia = true
+ mediaHost.init(MediaHierarchyManager.LOCATION_QQS)
+ }
+
private fun destinationScenes(
up: SceneKey,
): Map<UserAction, SceneModel> {
@@ -148,35 +154,27 @@
mediaHost: MediaHost,
modifier: Modifier = Modifier,
) {
+ val localDensity = LocalDensity.current
val layoutWidth = remember { mutableStateOf(0) }
- Box(modifier.element(Shade.Elements.Scrim)) {
- Spacer(
- modifier =
- Modifier.element(Shade.Elements.ScrimBackground)
- .fillMaxSize()
- .background(MaterialTheme.colorScheme.scrim, shape = Shade.Shapes.Scrim)
- )
+ Box(
+ modifier =
+ modifier.element(Shade.Elements.Scrim).background(MaterialTheme.colorScheme.scrim),
+ )
+ Box {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
- modifier =
- Modifier.fillMaxSize()
- .clickable(onClick = { viewModel.onContentClicked() })
- .padding(
- start = Shade.Dimensions.HorizontalPadding,
- end = Shade.Dimensions.HorizontalPadding,
- bottom = 48.dp
- )
+ modifier = Modifier.fillMaxWidth().clickable(onClick = { viewModel.onContentClicked() })
) {
CollapsedShadeHeader(
viewModel = viewModel.shadeHeaderViewModel,
createTintedIconManager = createTintedIconManager,
createBatteryMeterViewController = createBatteryMeterViewController,
statusBarIconController = statusBarIconController,
+ modifier = Modifier.padding(horizontal = Shade.Dimensions.HorizontalPadding)
)
- Spacer(modifier = Modifier.height(16.dp))
QuickSettings(
- modifier = Modifier.wrapContentHeight(),
+ modifier = Modifier.height(130.dp),
viewModel.qsSceneAdapter,
)
@@ -202,16 +200,15 @@
},
mediaHost = mediaHost,
layoutWidth = layoutWidth.value,
- layoutHeight = with(LocalDensity.current) { mediaHeight.toPx() }.toInt(),
+ layoutHeight = with(localDensity) { mediaHeight.toPx() }.toInt(),
carouselController = mediaCarouselController,
)
}
Spacer(modifier = Modifier.height(16.dp))
- NotificationStack(
+ NotificationScrollingStack(
viewModel = viewModel.notifications,
- isScrimVisible = true,
- modifier = Modifier.weight(1f),
+ modifier = Modifier.fillMaxWidth().weight(1f),
)
}
}
diff --git a/packages/SystemUI/compose/features/tests/AndroidManifest.xml b/packages/SystemUI/compose/features/tests/AndroidManifest.xml
index 8fe9656..fc337fb 100644
--- a/packages/SystemUI/compose/features/tests/AndroidManifest.xml
+++ b/packages/SystemUI/compose/features/tests/AndroidManifest.xml
@@ -30,11 +30,6 @@
android:enabled="false"
tools:replace="android:authorities"
tools:node="remove" />
- <provider android:name="com.google.android.systemui.keyguard.KeyguardSliceProviderGoogle"
- android:authorities="com.android.systemui.test.keyguard.disabled"
- android:enabled="false"
- tools:replace="android:authorities"
- tools:node="remove" />
<provider android:name="com.android.systemui.keyguard.CustomizationProvider"
android:authorities="com.android.systemui.test.keyguard.quickaffordance.disabled"
android:enabled="false"
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PunchHole.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PunchHole.kt
index 0acc76f..b346a70 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PunchHole.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PunchHole.kt
@@ -20,24 +20,26 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.geometry.toRect
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.CompositingStrategy
import androidx.compose.ui.graphics.Outline
-import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.drawOutline
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.DrawScope
-import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.graphics.drawscope.translate
-import androidx.compose.ui.graphics.withSaveLayer
import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
import androidx.compose.ui.node.DelegatingNode
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.GlobalPositionAwareModifierNode
+import androidx.compose.ui.node.LayoutModifierNode
import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.toSize
@@ -88,28 +90,32 @@
var size: () -> Size,
var offset: () -> Offset,
var shape: () -> Shape,
-) : Modifier.Node(), DrawModifierNode {
+) : Modifier.Node(), DrawModifierNode, LayoutModifierNode {
private var lastSize: Size = Size.Unspecified
private var lastLayoutDirection: LayoutDirection = LayoutDirection.Ltr
private var lastOutline: Outline? = null
- override fun ContentDrawScope.draw() {
- val holeSize = size()
- if (holeSize == Size.Zero) {
- drawContent()
- return
- }
-
- drawIntoCanvas { canvas ->
- canvas.withSaveLayer(size.toRect(), Paint()) {
- drawContent()
-
- val offset = offset()
- translate(offset.x, offset.y) { drawHole(holeSize) }
+ override fun MeasureScope.measure(
+ measurable: Measurable,
+ constraints: Constraints
+ ): MeasureResult {
+ return measurable.measure(constraints).run {
+ layout(width, height) {
+ placeWithLayer(0, 0) { compositingStrategy = CompositingStrategy.Offscreen }
}
}
}
+ override fun ContentDrawScope.draw() {
+ drawContent()
+
+ val holeSize = size()
+ if (holeSize != Size.Zero) {
+ val offset = offset()
+ translate(offset.x, offset.y) { drawHole(holeSize) }
+ }
+ }
+
private fun DrawScope.drawHole(size: Size) {
if (shape == RectangleShape) {
drawRect(Color.Black, size = size, blendMode = BlendMode.DstOut)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
index c055919..ff05478 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
@@ -73,7 +73,7 @@
private val positionalThreshold
get() = with(layoutImpl.density) { 56.dp.toPx() }
- internal var gestureWithPriority: Any? = null
+ internal var currentSource: Any? = null
/** The [UserAction]s associated to the current swipe. */
private var actionUpOrLeft: UserAction? = null
@@ -315,10 +315,13 @@
with(layoutImpl.state) { coroutineScope.onChangeScene(targetScene.key) }
}
- animateOffset(
+ swipeTransition.animateOffset(
+ coroutineScope = coroutineScope,
initialVelocity = velocity,
targetOffset = targetOffset,
- targetScene = targetScene.key
+ onAnimationCompleted = {
+ layoutState.finishTransition(swipeTransition, idleScene = targetScene.key)
+ }
)
}
@@ -410,34 +413,6 @@
}
}
- private fun animateOffset(
- initialVelocity: Float,
- targetOffset: Float,
- targetScene: SceneKey,
- ) {
- swipeTransition.startOffsetAnimation {
- coroutineScope.launch {
- if (!swipeTransition.isAnimatingOffset) {
- swipeTransition.offsetAnimatable.snapTo(swipeTransition.dragOffset)
- }
- swipeTransition.isAnimatingOffset = true
-
- swipeTransition.offsetAnimatable.animateTo(
- targetOffset,
- // TODO(b/290184746): Make this spring spec configurable.
- spring(
- stiffness = Spring.StiffnessMediumLow,
- visibilityThreshold = OffsetVisibilityThreshold
- ),
- initialVelocity = initialVelocity,
- )
-
- swipeTransition.finishOffsetAnimation()
- layoutState.finishTransition(swipeTransition, targetScene)
- }
- }
- }
-
internal class SwipeTransition(
val _fromScene: Scene,
val _toScene: Scene,
@@ -479,12 +454,14 @@
private var offsetAnimationJob: Job? = null
/** Ends any previous [offsetAnimationJob] and runs the new [job]. */
- fun startOffsetAnimation(job: () -> Job) {
+ private fun startOffsetAnimation(job: () -> Job) {
cancelOffsetAnimation()
offsetAnimationJob = job()
}
/** 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()
@@ -496,6 +473,43 @@
dragOffset = offsetAnimatable.value
}
}
+
+ // TODO(b/290184746): Make this spring spec configurable.
+ private val animationSpec =
+ spring(
+ stiffness = Spring.StiffnessMediumLow,
+ visibilityThreshold = OffsetVisibilityThreshold
+ )
+
+ fun animateOffset(
+ // TODO(b/317063114) The CoroutineScope should be removed.
+ coroutineScope: CoroutineScope,
+ initialVelocity: Float,
+ targetOffset: Float,
+ onAnimationCompleted: () -> Unit,
+ ) {
+ startOffsetAnimation {
+ coroutineScope.launch {
+ animateOffset(targetOffset, initialVelocity)
+ onAnimationCompleted()
+ }
+ }
+ }
+
+ private suspend fun animateOffset(targetOffset: Float, initialVelocity: Float) {
+ if (!isAnimatingOffset) {
+ offsetAnimatable.snapTo(dragOffset)
+ }
+ isAnimatingOffset = true
+
+ offsetAnimatable.animateTo(
+ targetValue = targetOffset,
+ animationSpec = animationSpec,
+ initialVelocity = initialVelocity,
+ )
+
+ finishOffsetAnimation()
+ }
}
companion object {
@@ -506,20 +520,22 @@
private class SceneDraggableHandler(
private val gestureHandler: SceneGestureHandler,
) : DraggableHandler {
+ private val source = this
+
override fun onDragStarted(startedPosition: Offset, overSlop: Float, pointersDown: Int) {
- gestureHandler.gestureWithPriority = this
+ gestureHandler.currentSource = source
gestureHandler.onDragStarted(pointersDown, startedPosition, overSlop)
}
override fun onDelta(pixels: Float) {
- if (gestureHandler.gestureWithPriority == this) {
+ if (gestureHandler.currentSource == source) {
gestureHandler.onDrag(delta = pixels)
}
}
override fun onDragStopped(velocity: Float) {
- if (gestureHandler.gestureWithPriority == this) {
- gestureHandler.gestureWithPriority = null
+ if (gestureHandler.currentSource == source) {
+ gestureHandler.currentSource = null
gestureHandler.onDragStopped(velocity = velocity, canChangeScene = true)
}
}
@@ -572,6 +588,8 @@
return nextScene != null
}
+ val source = this
+
return PriorityNestedScrollConnection(
orientation = orientation,
canStartPreScroll = { offsetAvailable, offsetBeforeStart ->
@@ -642,7 +660,7 @@
canContinueScroll = { true },
canScrollOnFling = false,
onStart = { offsetAvailable ->
- gestureHandler.gestureWithPriority = this
+ gestureHandler.currentSource = source
gestureHandler.onDragStarted(
pointersDown = 1,
startedPosition = null,
@@ -650,7 +668,7 @@
)
},
onScroll = { offsetAvailable ->
- if (gestureHandler.gestureWithPriority != this) {
+ if (gestureHandler.currentSource != source) {
return@PriorityNestedScrollConnection 0f
}
@@ -661,7 +679,7 @@
offsetAvailable
},
onStop = { velocityAvailable ->
- if (gestureHandler.gestureWithPriority != this) {
+ if (gestureHandler.currentSource != source) {
return@PriorityNestedScrollConnection 0f
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
index 2c96d0e..8e35988 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
@@ -36,24 +36,25 @@
fun LargeTopAppBarNestedScrollConnection(
height: () -> Float,
onHeightChanged: (Float) -> Unit,
- heightRange: ClosedFloatingPointRange<Float>,
+ minHeight: () -> Float,
+ maxHeight: () -> Float,
): PriorityNestedScrollConnection {
- val minHeight = heightRange.start
- val maxHeight = heightRange.endInclusive
return PriorityNestedScrollConnection(
orientation = Orientation.Vertical,
// When swiping up, the LargeTopAppBar will shrink (to [minHeight]) and the content will
// expand. Then, you can then scroll down the content.
canStartPreScroll = { offsetAvailable, offsetBeforeStart ->
- offsetAvailable < 0 && offsetBeforeStart == 0f && height() > minHeight
+ offsetAvailable < 0 && offsetBeforeStart == 0f && height() > minHeight()
},
// When swiping down, the content will scroll up until it reaches the top. Then, the
// LargeTopAppBar will expand until it reaches its [maxHeight].
- canStartPostScroll = { offsetAvailable, _ -> offsetAvailable > 0 && height() < maxHeight },
+ canStartPostScroll = { offsetAvailable, _ ->
+ offsetAvailable > 0 && height() < maxHeight()
+ },
canStartPostFling = { false },
canContinueScroll = {
val currentHeight = height()
- minHeight < currentHeight && currentHeight < maxHeight
+ minHeight() < currentHeight && currentHeight < maxHeight()
},
canScrollOnFling = true,
onStart = { /* do nothing */},
@@ -61,10 +62,10 @@
val currentHeight = height()
val amountConsumed =
if (offsetAvailable > 0) {
- val amountLeft = maxHeight - currentHeight
+ val amountLeft = maxHeight() - currentHeight
offsetAvailable.coerceAtMost(amountLeft)
} else {
- val amountLeft = minHeight - currentHeight
+ val amountLeft = minHeight() - currentHeight
offsetAvailable.coerceAtLeast(amountLeft)
}
onHeightChanged(currentHeight + amountConsumed)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
index e2974cd..ac7717b 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
@@ -34,7 +34,8 @@
LargeTopAppBarNestedScrollConnection(
height = { height },
onHeightChanged = { height = it },
- heightRange = heightRange,
+ minHeight = { heightRange.start },
+ maxHeight = { heightRange.endInclusive },
)
private fun NestedScrollConnection.scroll(
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 41bde52..3dfe65a 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -22,6 +22,7 @@
import android.os.UserHandle
import android.provider.Settings
import androidx.annotation.OpenForTesting
+import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
import com.android.systemui.log.core.LogcatOnlyMessageBuffer
import com.android.systemui.log.core.Logger
@@ -120,8 +121,9 @@
override fun onPluginAttached(
manager: PluginLifecycleManager<ClockProviderPlugin>
): Boolean {
- manager.isDebug = !keepAllLoaded
-
+ manager.setLogFunc({ tag, msg ->
+ (clockBuffers?.infraMessageBuffer as LogBuffer?)?.log(tag, LogLevel.DEBUG, msg)
+ })
if (keepAllLoaded) {
// Always load new plugins if requested
return true
diff --git a/packages/SystemUI/docs/imgs/ribbon.png b/packages/SystemUI/docs/imgs/ribbon.png
index 9f57652..3379d3d 100644
--- a/packages/SystemUI/docs/imgs/ribbon.png
+++ b/packages/SystemUI/docs/imgs/ribbon.png
Binary files differ
diff --git a/packages/SystemUI/docs/scene.md b/packages/SystemUI/docs/scene.md
index 3e4a1b4..715d160 100644
--- a/packages/SystemUI/docs/scene.md
+++ b/packages/SystemUI/docs/scene.md
@@ -20,14 +20,16 @@
from one scene to another) are also pulled out and separated from the
content of the UI.
-In addition to the above, some of the **secondary goals** are: 4. Make
-**customization easier**: by separating scenes to standalone pieces, it becomes
-possible for variant owners and OEMs to exclude or replace certain scenes or to
-add brand-new scenes. 5. **Enable modularization**: by separating scenes to
-standalone pieces, it becomes possible to break down System UI into smaller
-codebases, each one of which could be built on its own. Note: this isn't part of
-the scene framework itself but is something that can be done more easily once
-the scene framework is in place.
+In addition to the above, some of the **secondary goals** are:
+
+4. Make **customization easier**: by separating scenes to standalone pieces, it
+becomes possible for variant owners and OEMs to exclude or replace certain scenes
+or to add brand-new scenes.
+5. **Enable modularization**: by separating scenes to standalone pieces, it
+becomes possible to break down System UI into smaller codebases, each one of
+which could be built on its own. Note: this isn't part of the scene framework
+itself but is something that can be done more easily once the scene framework
+is in place.
## Terminology
@@ -70,15 +72,17 @@
running: `console $ adb shell statusbar cmd migrate_keyguard_status_bar_view
true`
3. Set a collection of **aconfig flags** to `true` by running the following
- commands: `console $ adb shell device_config put systemui
- com.android.systemui.scene_container true $ adb shell device_config put
- systemui com.android.systemui.keyguard_bottom_area_refactor true $ adb shell
- device_config put systemui
- com.android.systemui.keyguard_shade_migration_nssl true $ adb shell
- device_config put systemui com.android.systemui.media_in_scene_container
- true`
-4. **Restart** System UI by issuing the following command: `console $ adb shell
- am crash com.android.systemui`
+ commands:
+ ```console
+ $ adb shell device_config put systemui com.android.systemui.scene_container true
+ $ adb shell device_config put systemui com.android.systemui.keyguard_bottom_area_refactor true
+ $ adb shell device_config put systemui com.android.systemui.keyguard_shade_migration_nssl true
+ $ adb shell device_config put systemui com.android.systemui.media_in_scene_container true
+ ```
+4. **Restart** System UI by issuing the following command:
+ ```console
+ $ adb shell am crash com.android.systemui
+ ```
5. **Verify** that the scene framework was turned on. There are two ways to do
this:
@@ -96,10 +100,15 @@
# Look for the log statements from the framework:
-$ adb logcat -v time SceneFramework:* *:S ```
+```console
+$ adb logcat -v time SceneFramework:* *:S
+```
-To **disable** the framework, simply turn off the main aconfig flag: `console $
-adb shell device_config put systemui com.android.systemui.scene_container false`
+To **disable** the framework, simply turn off the main aconfig flag:
+
+```console
+$ adb shell device_config put systemui com.android.systemui.scene_container false
+```
## Defining a scene
@@ -118,28 +127,28 @@
content of your UI changes with and throughout a transition to learn more please
see the [Scene transition animations](#Scene-transition-animations) section
-For example: ```kotlin @SysUISingleton class YourScene @Inject constructor( //
-your dependencies here ) : ComposableScene { override val key =
-SceneKey.YourScene
+For example:
-```
-override val destinationScenes: StateFlow<Map<UserAction, SceneModel>> =
- MutableStateFlow<Map<UserAction, SceneModel>>(
- mapOf(
- // This is where scene navigation is defined, more on that below.
- )
- ).asStateFlow()
+```kotlin
+@SysUISingleton class YourScene @Inject constructor( /* your dependencies here */ ) : ComposableScene {
+ override val key = SceneKey.YourScene
-@Composable
-override fun SceneScope.Content(
- modifier: Modifier,
-) {
- // This is where the UI is defined using Jetpack Compose.
+ override val destinationScenes: StateFlow<Map<UserAction, SceneModel>> =
+ MutableStateFlow<Map<UserAction, SceneModel>>(
+ mapOf(
+ // This is where scene navigation is defined, more on that below.
+ )
+ ).asStateFlow()
+
+ @Composable
+ override fun SceneScope.Content(
+ modifier: Modifier,
+ ) {
+ // This is where the UI is defined using Jetpack Compose.
+ }
}
```
-} ```
-
### Injecting scenes
Scenes are injected into the Dagger dependency graph from the
@@ -200,20 +209,21 @@
}
```
-Going through the example code: * The `spec` is the animation that should be
-invoked, in the example above, we use a `tween` animation with a duration of 500
-milliseconds * Then there's a series of function calls: `punchHole` applies a
-clip mask to the `Scrim` element in the destination scene (in this case it's the
-`Shade` scene) which has the position and size determined by the `bounds`
-parameter and the shape passed into the `shape` parameter. This lets the
-`Lockscreen` scene render "through" the `Shade` scene * The `translate` call
-shifts the `Scrim` element to/from the `Top` edge of the scene container * The
-first `fractionRange` wrapper tells the system to apply its contained functions
+Going through the example code:
+
+* The `spec` is the animation that should be invoked, in the example above, we use a `tween`
+animation with a duration of 500 milliseconds
+* Then there's a series of function calls: `punchHole` applies a clip mask to the `Scrim`
+element in the destination scene (in this case it's the `Shade` scene) which has the
+position and size determined by the `bounds` parameter and the shape passed into the `shape`
+parameter. This lets the `Lockscreen` scene render "through" the `Shade` scene
+* The `translate` call shifts the `Scrim` element to/from the `Top` edge of the scene container
+* The first `fractionRange` wrapper tells the system to apply its contained functions
only during the first half of the transition. Inside of it, we see a `fade` of
the `ScrimBackground` element and a `translate` o the `CollpasedGrid` element
-to/from the `Top` edge * The second `fractionRange` only starts at the second
-half of the transition (e.g. when the previous one ends) and applies a `fade` on
-the `Notifications` element
+to/from the `Top` edge
+* The second `fractionRange` only starts at the second half of the transition (e.g. when
+the previous one ends) and applies a `fade` on the `Notifications` element
You can find the actual documentation for this API
[here](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt).
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index e893169..c4bcb53 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -35,6 +35,7 @@
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository;
import com.android.systemui.res.R;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
@@ -107,7 +108,7 @@
mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener,
mEmergencyButtonController, mFalsingCollector, featureFlags,
- mSelectedUserInteractor) {
+ mSelectedUserInteractor, new FakeKeyboardRepository()) {
@Override
public void onResume(int reason) {
super.onResume(reason);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 78b854e..c2efc05 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -33,6 +33,7 @@
import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.DevicePostureController
import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED
@@ -141,7 +142,8 @@
postureController,
featureFlags,
mSelectedUserInteractor,
- uiEventLogger
+ uiEventLogger,
+ FakeKeyboardRepository()
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index c8461d2..030d41d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -45,18 +45,23 @@
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
import com.android.systemui.classifier.FalsingA11yDelegate
import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
import com.android.systemui.log.SessionTracker
import com.android.systemui.plugins.ActivityStarter.OnDismissAction
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
@@ -66,6 +71,7 @@
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.testKosmos
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.kotlin.JavaAdapter
import com.android.systemui.util.mockito.any
@@ -156,7 +162,7 @@
private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController
private lateinit var keyguardPasswordView: KeyguardPasswordView
private lateinit var testableResources: TestableResources
- private lateinit var sceneTestUtils: SceneTestUtils
+ private lateinit var kosmos: Kosmos
private lateinit var sceneInteractor: SceneInteractor
private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
private lateinit var deviceEntryInteractor: DeviceEntryInteractor
@@ -197,7 +203,6 @@
whenever(deviceProvisionedController.isUserSetup(anyInt())).thenReturn(true)
featureFlags = FakeFeatureFlags()
- featureFlags.set(Flags.BOUNCER_USER_SWITCHER, false)
featureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false)
featureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
featureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
@@ -223,19 +228,15 @@
mSelectedUserInteractor,
)
- sceneTestUtils = SceneTestUtils(this)
- sceneInteractor = sceneTestUtils.sceneInteractor()
+ kosmos = testKosmos()
+ sceneInteractor = kosmos.sceneInteractor
keyguardTransitionInteractor =
- KeyguardTransitionInteractorFactory.create(sceneTestUtils.testScope.backgroundScope)
+ KeyguardTransitionInteractorFactory.create(kosmos.testScope.backgroundScope)
.keyguardTransitionInteractor
sceneTransitionStateFlow =
MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Lockscreen))
sceneInteractor.setTransitionState(sceneTransitionStateFlow)
- deviceEntryInteractor =
- sceneTestUtils.deviceEntryInteractor(
- authenticationInteractor = sceneTestUtils.authenticationInteractor(),
- sceneInteractor = sceneInteractor,
- )
+ deviceEntryInteractor = kosmos.deviceEntryInteractor
mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
underTest =
@@ -254,7 +255,7 @@
falsingManager,
userSwitcherController,
featureFlags,
- sceneTestUtils.sceneContainerFlags,
+ kosmos.fakeSceneContainerFlags,
globalSettings,
sessionTracker,
Optional.of(sideFpsController),
@@ -264,7 +265,7 @@
audioManager,
faceAuthInteractor,
mock(),
- { JavaAdapter(sceneTestUtils.testScope.backgroundScope) },
+ { JavaAdapter(kosmos.testScope.backgroundScope) },
mSelectedUserInteractor,
deviceProvisionedController,
faceAuthAccessibilityDelegate,
@@ -791,7 +792,8 @@
@Test
fun dismissesKeyguard_whenSceneChangesToGone() =
- sceneTestUtils.testScope.runTest {
+ kosmos.testScope.runTest {
+ kosmos.fakeSceneContainerFlags.enabled = true
// Upon init, we have never dismisses the keyguard.
underTest.onInit()
runCurrent()
@@ -818,6 +820,8 @@
// While listening, going from the bouncer scene to the gone scene, does dismiss the
// keyguard.
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ runCurrent()
sceneInteractor.changeScene(SceneModel(SceneKey.Gone, null), "reason")
sceneTransitionStateFlow.value =
ObservableTransitionState.Transition(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
index f775175..0959f1b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
@@ -16,7 +16,6 @@
package com.android.keyguard
-import android.telephony.PinResult
import android.telephony.TelephonyManager
import android.testing.TestableLooper
import android.view.LayoutInflater
@@ -28,9 +27,11 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
import com.android.systemui.res.R
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -39,7 +40,6 @@
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
@@ -75,8 +75,7 @@
`when`(messageAreaControllerFactory.create(Mockito.any(KeyguardMessageArea::class.java)))
.thenReturn(keyguardMessageAreaController)
`when`(telephonyManager.createForSubscriptionId(anyInt())).thenReturn(telephonyManager)
- `when`(telephonyManager.supplyIccLockPin(anyString()))
- .thenReturn(mock(PinResult::class.java))
+ `when`(telephonyManager.supplyIccLockPin(anyString())).thenReturn(mock())
simPinView =
LayoutInflater.from(context).inflate(R.layout.keyguard_sim_pin_view, null)
as KeyguardSimPinView
@@ -97,7 +96,8 @@
falsingCollector,
emergencyButtonController,
fakeFeatureFlags,
- mSelectedUserInteractor
+ mSelectedUserInteractor,
+ FakeKeyboardRepository()
)
underTest.init()
underTest.onViewAttached()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
index 45a60199..1281e44 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
import com.android.systemui.res.R
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.any
@@ -91,6 +92,7 @@
emergencyButtonController,
fakeFeatureFlags,
mSelectedUserInteractor,
+ FakeKeyboardRepository()
)
underTest.init()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
index 74f50d8..c86c747 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
@@ -17,14 +17,15 @@
package com.android.systemui.accessibility.data.repository
import android.os.UserHandle
+import android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
@@ -37,11 +38,9 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class ColorCorrectionRepositoryImplTest : SysuiTestCase() {
- companion object {
- val TEST_USER_1 = UserHandle.of(1)!!
- val TEST_USER_2 = UserHandle.of(2)!!
- }
+ private val testUser1 = UserHandle.of(1)!!
+ private val testUser2 = UserHandle.of(2)!!
private val testDispatcher = StandardTestDispatcher()
private val scope = TestScope(testDispatcher)
private val settings: FakeSettings = FakeSettings()
@@ -53,6 +52,7 @@
underTest =
ColorCorrectionRepositoryImpl(
testDispatcher,
+ scope.backgroundScope,
settings,
)
}
@@ -60,112 +60,113 @@
@Test
fun isEnabled_initiallyGetsSettingsValue() =
scope.runTest {
+ val actualValue by collectLastValue(underTest.isEnabled(testUser1))
+
settings.putIntForUser(
- ColorCorrectionRepositoryImpl.SETTING_NAME,
- 1,
- TEST_USER_1.identifier
+ SETTING_NAME,
+ ENABLED,
+ testUser1.identifier
)
-
- underTest =
- ColorCorrectionRepositoryImpl(
- testDispatcher,
- settings,
- )
-
- underTest.isEnabled(TEST_USER_1).launchIn(backgroundScope)
runCurrent()
- val actualValue: Boolean = underTest.isEnabled(TEST_USER_1).first()
Truth.assertThat(actualValue).isTrue()
}
@Test
fun isEnabled_settingUpdated_valueUpdated() =
scope.runTest {
- underTest.isEnabled(TEST_USER_1).launchIn(backgroundScope)
+ val flowValues: List<Boolean> by collectValues(underTest.isEnabled(testUser1))
settings.putIntForUser(
- ColorCorrectionRepositoryImpl.SETTING_NAME,
- ColorCorrectionRepositoryImpl.DISABLED,
- TEST_USER_1.identifier
+ SETTING_NAME,
+ DISABLED,
+ testUser1.identifier
)
runCurrent()
- Truth.assertThat(underTest.isEnabled(TEST_USER_1).first()).isFalse()
settings.putIntForUser(
- ColorCorrectionRepositoryImpl.SETTING_NAME,
- ColorCorrectionRepositoryImpl.ENABLED,
- TEST_USER_1.identifier
+ SETTING_NAME,
+ ENABLED,
+ testUser1.identifier
)
runCurrent()
- Truth.assertThat(underTest.isEnabled(TEST_USER_1).first()).isTrue()
settings.putIntForUser(
- ColorCorrectionRepositoryImpl.SETTING_NAME,
- ColorCorrectionRepositoryImpl.DISABLED,
- TEST_USER_1.identifier
+ SETTING_NAME,
+ DISABLED,
+ testUser1.identifier
)
runCurrent()
- Truth.assertThat(underTest.isEnabled(TEST_USER_1).first()).isFalse()
+
+ Truth.assertThat(flowValues.size).isEqualTo(3)
+ Truth.assertThat(flowValues).containsExactly(false, true, false).inOrder()
}
@Test
fun isEnabled_settingForUserOneOnly_valueUpdatedForUserOneOnly() =
scope.runTest {
- underTest.isEnabled(TEST_USER_1).launchIn(backgroundScope)
- settings.putIntForUser(
- ColorCorrectionRepositoryImpl.SETTING_NAME,
- ColorCorrectionRepositoryImpl.DISABLED,
- TEST_USER_1.identifier
- )
- underTest.isEnabled(TEST_USER_2).launchIn(backgroundScope)
- settings.putIntForUser(
- ColorCorrectionRepositoryImpl.SETTING_NAME,
- ColorCorrectionRepositoryImpl.DISABLED,
- TEST_USER_2.identifier
- )
-
- runCurrent()
- Truth.assertThat(underTest.isEnabled(TEST_USER_1).first()).isFalse()
- Truth.assertThat(underTest.isEnabled(TEST_USER_2).first()).isFalse()
+ val lastValueUser1 by collectLastValue(underTest.isEnabled(testUser1))
+ val lastValueUser2 by collectLastValue(underTest.isEnabled(testUser2))
settings.putIntForUser(
- ColorCorrectionRepositoryImpl.SETTING_NAME,
- ColorCorrectionRepositoryImpl.ENABLED,
- TEST_USER_1.identifier
+ SETTING_NAME,
+ DISABLED,
+ testUser1.identifier
+ )
+ settings.putIntForUser(
+ SETTING_NAME,
+ DISABLED,
+ testUser2.identifier
)
runCurrent()
- Truth.assertThat(underTest.isEnabled(TEST_USER_1).first()).isTrue()
- Truth.assertThat(underTest.isEnabled(TEST_USER_2).first()).isFalse()
+
+ Truth.assertThat(lastValueUser1).isFalse()
+ Truth.assertThat(lastValueUser2).isFalse()
+
+ settings.putIntForUser(
+ SETTING_NAME,
+ ENABLED,
+ testUser1.identifier
+ )
+ runCurrent()
+
+ Truth.assertThat(lastValueUser1).isTrue()
+ Truth.assertThat(lastValueUser2).isFalse()
}
@Test
fun setEnabled() =
scope.runTest {
- val success = underTest.setIsEnabled(true, TEST_USER_1)
+ val success = underTest.setIsEnabled(true, testUser1)
runCurrent()
Truth.assertThat(success).isTrue()
val actualValue =
settings.getIntForUser(
- ColorCorrectionRepositoryImpl.SETTING_NAME,
- TEST_USER_1.identifier
+ SETTING_NAME,
+ testUser1.identifier
)
- Truth.assertThat(actualValue).isEqualTo(ColorCorrectionRepositoryImpl.ENABLED)
+ Truth.assertThat(actualValue).isEqualTo(ENABLED)
}
@Test
fun setDisabled() =
scope.runTest {
- val success = underTest.setIsEnabled(false, TEST_USER_1)
+ val success = underTest.setIsEnabled(false, testUser1)
runCurrent()
Truth.assertThat(success).isTrue()
val actualValue =
settings.getIntForUser(
- ColorCorrectionRepositoryImpl.SETTING_NAME,
- TEST_USER_1.identifier
+ SETTING_NAME,
+ testUser1.identifier
)
- Truth.assertThat(actualValue).isEqualTo(ColorCorrectionRepositoryImpl.DISABLED)
+ Truth.assertThat(actualValue).isEqualTo(DISABLED)
}
+
+ companion object {
+ private const val SETTING_NAME = ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED
+ private const val DISABLED = 0
+ private const val ENABLED = 1
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
index 3f05fef..4853529 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
@@ -21,11 +21,11 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
@@ -39,6 +39,8 @@
@RunWith(AndroidJUnit4::class)
class ColorInversionRepositoryImplTest : SysuiTestCase() {
+ private val testUser1 = UserHandle.of(1)!!
+ private val testUser2 = UserHandle.of(2)!!
private val testDispatcher = StandardTestDispatcher()
private val scope = TestScope(testDispatcher)
private val settings: FakeSettings = FakeSettings()
@@ -50,6 +52,7 @@
underTest =
ColorInversionRepositoryImpl(
testDispatcher,
+ scope.backgroundScope,
settings,
)
}
@@ -57,76 +60,68 @@
@Test
fun isEnabled_initiallyGetsSettingsValue() =
scope.runTest {
- settings.putIntForUser(SETTING_NAME, 1, TEST_USER_1.identifier)
+ val actualValue by collectLastValue(underTest.isEnabled(testUser1))
- underTest =
- ColorInversionRepositoryImpl(
- testDispatcher,
- settings,
- )
-
- underTest.isEnabled(TEST_USER_1).launchIn(backgroundScope)
+ settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
runCurrent()
- val actualValue: Boolean = underTest.isEnabled(TEST_USER_1).first()
assertThat(actualValue).isTrue()
}
@Test
fun isEnabled_settingUpdated_valueUpdated() =
scope.runTest {
- underTest.isEnabled(TEST_USER_1).launchIn(backgroundScope)
+ val flowValues: List<Boolean> by
+ collectValues(underTest.isEnabled(testUser1))
- settings.putIntForUser(SETTING_NAME, DISABLED, TEST_USER_1.identifier)
+ settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
runCurrent()
- assertThat(underTest.isEnabled(TEST_USER_1).first()).isFalse()
+ settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
+ runCurrent()
+ settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
+ runCurrent()
- settings.putIntForUser(SETTING_NAME, ENABLED, TEST_USER_1.identifier)
- runCurrent()
- assertThat(underTest.isEnabled(TEST_USER_1).first()).isTrue()
-
- settings.putIntForUser(SETTING_NAME, DISABLED, TEST_USER_1.identifier)
- runCurrent()
- assertThat(underTest.isEnabled(TEST_USER_1).first()).isFalse()
+ assertThat(flowValues.size).isEqualTo(3)
+ assertThat(flowValues).containsExactly(false, true, false).inOrder()
}
@Test
fun isEnabled_settingForUserOneOnly_valueUpdatedForUserOneOnly() =
scope.runTest {
- underTest.isEnabled(TEST_USER_1).launchIn(backgroundScope)
- settings.putIntForUser(SETTING_NAME, DISABLED, TEST_USER_1.identifier)
- underTest.isEnabled(TEST_USER_2).launchIn(backgroundScope)
- settings.putIntForUser(SETTING_NAME, DISABLED, TEST_USER_2.identifier)
+ val lastValueUser1 by collectLastValue(underTest.isEnabled(testUser1))
+ val lastValueUser2 by collectLastValue(underTest.isEnabled(testUser2))
+ settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
+ settings.putIntForUser(SETTING_NAME, DISABLED, testUser2.identifier)
runCurrent()
- assertThat(underTest.isEnabled(TEST_USER_1).first()).isFalse()
- assertThat(underTest.isEnabled(TEST_USER_2).first()).isFalse()
+ assertThat(lastValueUser1).isFalse()
+ assertThat(lastValueUser2).isFalse()
- settings.putIntForUser(SETTING_NAME, ENABLED, TEST_USER_1.identifier)
+ settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
runCurrent()
- assertThat(underTest.isEnabled(TEST_USER_1).first()).isTrue()
- assertThat(underTest.isEnabled(TEST_USER_2).first()).isFalse()
+ assertThat(lastValueUser1).isTrue()
+ assertThat(lastValueUser2).isFalse()
}
@Test
fun setEnabled() =
scope.runTest {
- val success = underTest.setIsEnabled(true, TEST_USER_1)
+ val success = underTest.setIsEnabled(true, testUser1)
runCurrent()
assertThat(success).isTrue()
- val actualValue = settings.getIntForUser(SETTING_NAME, TEST_USER_1.identifier)
+ val actualValue = settings.getIntForUser(SETTING_NAME, testUser1.identifier)
assertThat(actualValue).isEqualTo(ENABLED)
}
@Test
fun setDisabled() =
scope.runTest {
- val success = underTest.setIsEnabled(false, TEST_USER_1)
+ val success = underTest.setIsEnabled(false, testUser1)
runCurrent()
assertThat(success).isTrue()
- val actualValue = settings.getIntForUser(SETTING_NAME, TEST_USER_1.identifier)
+ val actualValue = settings.getIntForUser(SETTING_NAME, testUser1.identifier)
assertThat(actualValue).isEqualTo(DISABLED)
}
@@ -134,7 +129,5 @@
private const val SETTING_NAME = ACCESSIBILITY_DISPLAY_INVERSION_ENABLED
private const val DISABLED = 0
private const val ENABLED = 1
- private val TEST_USER_1 = UserHandle.of(1)!!
- private val TEST_USER_2 = UserHandle.of(2)!!
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
index b9759cc..caf9219 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
@@ -29,10 +29,13 @@
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
@@ -59,8 +62,8 @@
@Mock private lateinit var tableLogger: TableLogBuffer
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
- private val testUtils = SceneTestUtils(this)
- private val testScope = testUtils.testScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private val clock = FakeSystemClock()
private val userRepository = FakeUserRepository()
private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository
@@ -82,8 +85,8 @@
underTest =
AuthenticationRepositoryImpl(
applicationScope = testScope.backgroundScope,
- backgroundDispatcher = testUtils.testDispatcher,
- flags = testUtils.sceneContainerFlags,
+ backgroundDispatcher = kosmos.testDispatcher,
+ flags = kosmos.sceneContainerFlags,
clock = clock,
getSecurityMode = getSecurityMode,
userRepository = userRepository,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index 10c16bd..cb8cebf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -22,6 +22,7 @@
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
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.None
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Password
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern
@@ -29,7 +30,8 @@
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
import com.android.systemui.authentication.shared.model.AuthenticationWipeModel
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -45,9 +47,9 @@
@RunWith(AndroidJUnit4::class)
class AuthenticationInteractorTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val underTest = utils.authenticationInteractor()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.authenticationInteractor
private val onAuthenticationResult by
testScope.collectLastValue(underTest.onAuthenticationResult)
@@ -62,7 +64,7 @@
assertThat(authMethod).isEqualTo(Pin)
assertThat(underTest.getAuthenticationMethod()).isEqualTo(Pin)
- utils.authenticationRepository.setAuthenticationMethod(Password)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
assertThat(authMethod).isEqualTo(Password)
assertThat(underTest.getAuthenticationMethod()).isEqualTo(Password)
@@ -74,7 +76,7 @@
val authMethod by collectLastValue(underTest.authenticationMethod)
runCurrent()
- utils.authenticationRepository.setAuthenticationMethod(None)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(None)
assertThat(authMethod).isEqualTo(None)
assertThat(underTest.getAuthenticationMethod()).isEqualTo(None)
@@ -83,7 +85,7 @@
@Test
fun authenticate_withCorrectPin_succeeds() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
assertSucceeded(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
}
@@ -91,7 +93,7 @@
@Test
fun authenticate_withIncorrectPin_fails() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
assertFailed(underTest.authenticate(listOf(9, 8, 7, 6, 5, 4)))
}
@@ -99,7 +101,7 @@
@Test(expected = IllegalArgumentException::class)
fun authenticate_withEmptyPin_throwsException() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
underTest.authenticate(listOf())
}
@@ -107,7 +109,7 @@
fun authenticate_withCorrectMaxLengthPin_succeeds() =
testScope.runTest {
val correctMaxLengthPin = List(16) { 9 }
- utils.authenticationRepository.apply {
+ kosmos.fakeAuthenticationRepository.apply {
setAuthenticationMethod(Pin)
overrideCredential(correctMaxLengthPin)
}
@@ -124,7 +126,7 @@
// If the policy changes, there is work to do in SysUI.
assertThat(DevicePolicyManager.MAX_PASSWORD_LENGTH).isLessThan(17)
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
assertFailed(underTest.authenticate(List(17) { 9 }))
}
@@ -132,7 +134,7 @@
@Test
fun authenticate_withCorrectPassword_succeeds() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(Password)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
assertSucceeded(underTest.authenticate("password".toList()))
}
@@ -140,7 +142,7 @@
@Test
fun authenticate_withIncorrectPassword_fails() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(Password)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
assertFailed(underTest.authenticate("alohomora".toList()))
}
@@ -148,7 +150,7 @@
@Test
fun authenticate_withCorrectPattern_succeeds() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(Pattern)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pattern)
assertSucceeded(underTest.authenticate(FakeAuthenticationRepository.PATTERN))
}
@@ -156,7 +158,7 @@
@Test
fun authenticate_withIncorrectPattern_fails() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(Pattern)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pattern)
val wrongPattern =
listOf(
AuthenticationPatternCoordinate(x = 2, y = 0),
@@ -172,7 +174,7 @@
fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNull() =
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
- utils.authenticationRepository.apply {
+ kosmos.fakeAuthenticationRepository.apply {
setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(true)
}
@@ -182,14 +184,14 @@
assertSkipped(underTest.authenticate(shorterPin, tryAutoConfirm = true))
assertThat(underTest.lockoutEndTimestamp).isNull()
- assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0)
+ assertThat(kosmos.fakeAuthenticationRepository.lockoutStartedReportCount).isEqualTo(0)
}
@Test
fun tryAutoConfirm_withAutoConfirmWrongPinCorrectLength_returnsFalse() =
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
- utils.authenticationRepository.apply {
+ kosmos.fakeAuthenticationRepository.apply {
setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(true)
}
@@ -207,7 +209,7 @@
fun tryAutoConfirm_withAutoConfirmLongerPin_returnsFalse() =
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
- utils.authenticationRepository.apply {
+ kosmos.fakeAuthenticationRepository.apply {
setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(true)
}
@@ -225,7 +227,7 @@
fun tryAutoConfirm_withAutoConfirmCorrectPin_returnsTrue() =
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
- utils.authenticationRepository.apply {
+ kosmos.fakeAuthenticationRepository.apply {
setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(true)
}
@@ -241,7 +243,7 @@
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
- utils.authenticationRepository.apply {
+ kosmos.fakeAuthenticationRepository.apply {
setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(true)
reportLockoutStarted(42)
@@ -258,7 +260,7 @@
@Test
fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNull() =
testScope.runTest {
- utils.authenticationRepository.apply {
+ kosmos.fakeAuthenticationRepository.apply {
setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(false)
}
@@ -271,7 +273,7 @@
@Test
fun tryAutoConfirm_withoutCorrectPassword_returnsNull() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(Password)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
assertSkipped(underTest.authenticate("password".toList(), tryAutoConfirm = true))
}
@@ -280,7 +282,7 @@
fun isAutoConfirmEnabled_featureDisabled_returnsFalse() =
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
- utils.authenticationRepository.setAutoConfirmFeatureEnabled(false)
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(false)
assertThat(isAutoConfirmEnabled).isFalse()
}
@@ -289,7 +291,7 @@
fun isAutoConfirmEnabled_featureEnabled_returnsTrue() =
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
- utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
assertThat(isAutoConfirmEnabled).isTrue()
}
@@ -298,7 +300,7 @@
fun isAutoConfirmEnabled_featureEnabledButDisabledByLockout() =
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
- utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
// The feature is enabled.
assertThat(isAutoConfirmEnabled).isTrue()
@@ -308,7 +310,7 @@
assertFailed(underTest.authenticate(listOf(5, 6, 7))) // Wrong PIN
}
assertThat(underTest.lockoutEndTimestamp).isNotNull()
- assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1)
+ assertThat(kosmos.fakeAuthenticationRepository.lockoutStartedReportCount).isEqualTo(1)
// Lockout disabled auto-confirm.
assertThat(isAutoConfirmEnabled).isFalse()
@@ -336,7 +338,7 @@
val failedAuthenticationAttempts by
collectLastValue(underTest.failedAuthenticationAttempts)
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
assertSucceeded(underTest.authenticate(correctPin))
@@ -366,7 +368,7 @@
@Test
fun lockoutEndTimestamp() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
underTest.authenticate(correctPin)
@@ -384,7 +386,7 @@
val expectedLockoutEndTimestamp =
testScope.currentTime + FakeAuthenticationRepository.LOCKOUT_DURATION_MS
assertThat(underTest.lockoutEndTimestamp).isEqualTo(expectedLockoutEndTimestamp)
- assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1)
+ assertThat(kosmos.fakeAuthenticationRepository.lockoutStartedReportCount).isEqualTo(1)
// Correct PIN, but locked out, so doesn't attempt it:
assertSkipped(underTest.authenticate(correctPin), assertNoResultEvents = false)
@@ -409,7 +411,7 @@
fun upcomingWipe() =
testScope.runTest {
val upcomingWipe by collectLastValue(underTest.upcomingWipe)
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
val wrongPin = FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 }
@@ -418,7 +420,7 @@
var expectedFailedAttempts = 0
var remainingFailedAttempts =
- utils.authenticationRepository.getMaxFailedUnlockAttemptsForWipe()
+ kosmos.fakeAuthenticationRepository.getMaxFailedUnlockAttemptsForWipe()
assertThat(remainingFailedAttempts)
.isGreaterThan(LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE)
@@ -458,7 +460,7 @@
fun hintedPinLength_withoutAutoConfirm_isNull() =
testScope.runTest {
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
- utils.authenticationRepository.apply {
+ kosmos.fakeAuthenticationRepository.apply {
setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(false)
}
@@ -470,11 +472,13 @@
fun hintedPinLength_withAutoConfirmPinTooShort_isNull() =
testScope.runTest {
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
- utils.authenticationRepository.apply {
+ kosmos.fakeAuthenticationRepository.apply {
setAuthenticationMethod(Pin)
overrideCredential(
buildList {
- repeat(utils.authenticationRepository.hintedPinLength - 1) { add(it + 1) }
+ repeat(kosmos.fakeAuthenticationRepository.hintedPinLength - 1) {
+ add(it + 1)
+ }
}
)
setAutoConfirmFeatureEnabled(true)
@@ -487,28 +491,31 @@
fun hintedPinLength_withAutoConfirmPinAtRightLength_isSameLength() =
testScope.runTest {
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
- utils.authenticationRepository.apply {
+ kosmos.fakeAuthenticationRepository.apply {
setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(true)
overrideCredential(
buildList {
- repeat(utils.authenticationRepository.hintedPinLength) { add(it + 1) }
+ repeat(kosmos.fakeAuthenticationRepository.hintedPinLength) { add(it + 1) }
}
)
}
- assertThat(hintedPinLength).isEqualTo(utils.authenticationRepository.hintedPinLength)
+ assertThat(hintedPinLength)
+ .isEqualTo(kosmos.fakeAuthenticationRepository.hintedPinLength)
}
@Test
fun hintedPinLength_withAutoConfirmPinTooLong_isNull() =
testScope.runTest {
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
- utils.authenticationRepository.apply {
+ kosmos.fakeAuthenticationRepository.apply {
setAuthenticationMethod(Pin)
overrideCredential(
buildList {
- repeat(utils.authenticationRepository.hintedPinLength + 1) { add(it + 1) }
+ repeat(kosmos.fakeAuthenticationRepository.hintedPinLength + 1) {
+ add(it + 1)
+ }
}
)
setAutoConfirmFeatureEnabled(true)
@@ -520,10 +527,10 @@
@Test
fun authenticate_withTooShortPassword() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(Password)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
val tooShortPassword = buildList {
- repeat(utils.authenticationRepository.minPasswordLength - 1) { time ->
+ repeat(kosmos.fakeAuthenticationRepository.minPasswordLength - 1) { time ->
add("$time")
}
}
@@ -534,7 +541,7 @@
assertThat(authenticationResult).isEqualTo(AuthenticationResult.SUCCEEDED)
assertThat(onAuthenticationResult).isTrue()
assertThat(underTest.lockoutEndTimestamp).isNull()
- assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0)
+ assertThat(kosmos.fakeAuthenticationRepository.lockoutStartedReportCount).isEqualTo(0)
assertThat(failedAuthenticationAttempts).isEqualTo(0)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 1c1335f..343280d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -801,6 +801,20 @@
}
@Test
+ public void testOnBiometricPromptDismissedCallback_hideAuthenticationDialog() {
+ // GIVEN a callback is registered
+ AuthController.Callback callback = mock(AuthController.Callback.class);
+ mAuthController.addCallback(callback);
+
+ // WHEN dialog is shown and then dismissed
+ showDialog(new int[]{1} /* sensorIds */, false /* credentialAllowed */);
+ mAuthController.hideAuthenticationDialog(mAuthController.mCurrentDialog.getRequestId());
+
+ // THEN callback should be received
+ verify(callback).onBiometricPromptDismissed();
+ }
+
+ @Test
public void testSubscribesToLogContext() {
mAuthController.setBiometricContextListener(mContextListener);
verify(mLogContextInteractor).addBiometricContextListener(same(mContextListener));
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
index 15633d1..72e884e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
@@ -16,8 +16,10 @@
package com.android.systemui.biometrics
+import android.graphics.Bitmap
import android.hardware.biometrics.BiometricManager.Authenticators
import android.hardware.biometrics.ComponentInfoInternal
+import android.hardware.biometrics.PromptContentView
import android.hardware.biometrics.PromptInfo
import android.hardware.biometrics.SensorProperties
import android.hardware.biometrics.SensorPropertiesInternal
@@ -116,18 +118,24 @@
}
internal fun promptInfo(
+ logoRes: Int = -1,
+ logoBitmap: Bitmap? = null,
title: String = "title",
subtitle: String = "sub",
description: String = "desc",
+ contentView: PromptContentView? = null,
credentialTitle: String? = "cred title",
credentialSubtitle: String? = "cred sub",
credentialDescription: String? = "cred desc",
negativeButton: String = "neg",
): PromptInfo {
val info = PromptInfo()
+ info.logoRes = logoRes
+ info.logoBitmap = logoBitmap
info.title = title
info.subtitle = subtitle
info.description = description
+ info.contentView = contentView
credentialTitle?.let { info.deviceCredentialTitle = it }
credentialSubtitle?.let { info.deviceCredentialSubtitle = it }
credentialDescription?.let { info.deviceCredentialDescription = it }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index b0beab9..f7743e2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -38,6 +38,7 @@
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
import com.android.systemui.biometrics.ui.viewmodel.DefaultUdfpsTouchOverlayViewModel
import com.android.systemui.biometrics.ui.viewmodel.DeviceEntryUdfpsTouchOverlayViewModel
@@ -118,6 +119,7 @@
@Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@Mock private lateinit var shadeInteractor: ShadeInteractor
@Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
+ @Mock private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true }
private var overlayParams: UdfpsOverlayParams = UdfpsOverlayParams()
@@ -174,7 +176,8 @@
mSelectedUserInteractor,
{ deviceEntryUdfpsTouchOverlayViewModel },
{ defaultUdfpsTouchOverlayViewModel },
- shadeInteractor
+ shadeInteractor,
+ udfpsOverlayInteractor,
)
block()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index cec2d74..90c3c14 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -76,6 +76,7 @@
import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
import com.android.systemui.biometrics.udfps.InteractionEvent;
import com.android.systemui.biometrics.udfps.NormalizedTouchData;
@@ -214,6 +215,8 @@
@Mock
private AlternateBouncerInteractor mAlternateBouncerInteractor;
@Mock
+ private UdfpsOverlayInteractor mUdfpsOverlayInteractor;
+ @Mock
private UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
@Mock
private SelectedUserInteractor mSelectedUserInteractor;
@@ -342,8 +345,8 @@
mFpsUnlockTracker,
mKeyguardTransitionInteractor,
mDeviceEntryUdfpsTouchOverlayViewModel,
- mDefaultUdfpsTouchOverlayViewModel
-
+ mDefaultUdfpsTouchOverlayViewModel,
+ mUdfpsOverlayInteractor
);
verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
mOverlayController = mOverlayCaptor.getValue();
@@ -1219,6 +1222,40 @@
}
@Test
+ public void onDownTouchReceivedWithoutPreviousUp() throws RemoteException {
+ final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
+ 0L);
+ final TouchProcessorResult processorResultDown =
+ new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN,
+ -1 /* pointerId */, touchData);
+
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+ BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+ mFgExecutor.runAllReady();
+
+ verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+
+ // WHEN ACTION_DOWN is received and touch is within sensor
+ when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+ processorResultDown);
+ MotionEvent firstDownEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, firstDownEvent);
+ mBiometricExecutor.runAllReady();
+ firstDownEvent.recycle();
+
+ // And another ACTION_DOWN is received without an ACTION_UP before
+ MotionEvent secondDownEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, secondDownEvent);
+ mBiometricExecutor.runAllReady();
+ secondDownEvent.recycle();
+
+ // THEN the touch is still processed
+ verify(mFingerprintManager, times(2)).onPointerDown(anyLong(), anyInt(), anyInt(),
+ anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(),
+ anyBoolean());
+ }
+
+ @Test
public void onTouch_pilferPointerWhenAltBouncerShowing()
throws RemoteException {
final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
index 13b53a8..7d9c2f9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
@@ -27,6 +27,7 @@
import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.dump.DumpManager;
@@ -76,6 +77,7 @@
protected @Mock UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
protected @Mock SelectedUserInteractor mSelectedUserInteractor;
protected @Mock KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+ protected @Mock UdfpsOverlayInteractor mUdfpsOverlayInteractor;
protected FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
@@ -152,7 +154,8 @@
mUdfpsKeyguardAccessibilityDelegate,
mSelectedUserInteractor,
mKeyguardTransitionInteractor,
- mShadeInteractor);
+ mShadeInteractor,
+ mUdfpsOverlayInteractor);
return controller;
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
index 335ac9d..0e257bc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
@@ -24,6 +24,7 @@
import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATE_APPEAR_ON_SCREEN_OFF
import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepositoryImpl
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
@@ -32,6 +33,7 @@
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
import com.android.systemui.bouncer.ui.BouncerView
import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
@@ -45,9 +47,11 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.time.SystemClock
+import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
@@ -130,6 +134,13 @@
repository = transitionRepository,
)
.keyguardTransitionInteractor
+ mUdfpsOverlayInteractor =
+ UdfpsOverlayInteractor(
+ context,
+ mock(AuthController::class.java),
+ mock(SelectedUserInteractor::class.java),
+ testScope.backgroundScope,
+ )
return createUdfpsKeyguardViewController(/* useModernBouncer */ true)
}
@@ -239,6 +250,31 @@
}
@Test
+ fun shouldHandleTouchesChange() =
+ testScope.runTest {
+ val shouldHandleTouches by collectLastValue(mUdfpsOverlayInteractor.shouldHandleTouches)
+
+ // GIVEN view is attached + on the keyguard
+ mController.onViewAttached()
+ captureStatusBarStateListeners()
+ sendStatusBarStateChanged(StatusBarState.KEYGUARD)
+ whenever(mView.setPauseAuth(true)).thenReturn(true)
+ whenever(mView.unpausedAlpha).thenReturn(0)
+
+ // WHEN panelViewExpansion changes to expanded
+ val job = mController.listenForBouncerExpansion(this)
+ keyguardBouncerRepository.setPrimaryShow(true)
+ keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE)
+ runCurrent()
+
+ // THEN UDFPS auth is paused and should not handle touches
+ assertThat(mController.shouldPauseAuth()).isTrue()
+ assertThat(shouldHandleTouches!!).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
fun fadeFromDialogSuggestedAlpha() =
testScope.runTest {
// GIVEN view is attached and status bar expansion is 1f
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
new file mode 100644
index 0000000..ccf119a
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorTest.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.biometrics.domain.interactor
+
+import android.hardware.biometrics.SensorLocationInternal
+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.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.display.data.repository.displayRepository
+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.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FingerprintPropertyInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.fingerprintPropertyInteractor
+ private val repository = kosmos.fingerprintPropertyRepository
+ private val configurationRepository = kosmos.fakeConfigurationRepository
+ private val displayRepository = kosmos.displayRepository
+
+ @Test
+ fun sensorLocation_resolution1f() =
+ testScope.runTest {
+ val currSensorLocation by collectLastValue(underTest.sensorLocation)
+
+ displayRepository.emitDisplayChangeEvent(0)
+ runCurrent()
+ repository.setProperties(
+ sensorId = 0,
+ strength = SensorStrength.STRONG,
+ sensorType = FingerprintSensorType.UDFPS_OPTICAL,
+ sensorLocations =
+ mapOf(
+ Pair("", SensorLocationInternal("", 4, 4, 2)),
+ Pair("otherDisplay", SensorLocationInternal("", 1, 1, 1))
+ )
+ )
+ runCurrent()
+ configurationRepository.setScaleForResolution(1f)
+ runCurrent()
+
+ assertThat(currSensorLocation?.centerX).isEqualTo(4)
+ assertThat(currSensorLocation?.centerY).isEqualTo(4)
+ assertThat(currSensorLocation?.radius).isEqualTo(2)
+ }
+
+ @Test
+ fun sensorLocation_resolution2f() =
+ testScope.runTest {
+ val currSensorLocation by collectLastValue(underTest.sensorLocation)
+
+ displayRepository.emitDisplayChangeEvent(0)
+ runCurrent()
+ repository.setProperties(
+ sensorId = 0,
+ strength = SensorStrength.STRONG,
+ sensorType = FingerprintSensorType.UDFPS_OPTICAL,
+ sensorLocations =
+ mapOf(
+ Pair("", SensorLocationInternal("", 4, 4, 2)),
+ Pair("otherDisplay", SensorLocationInternal("", 1, 1, 1))
+ )
+ )
+ runCurrent()
+ configurationRepository.setScaleForResolution(2f)
+ runCurrent()
+
+ assertThat(currSensorLocation?.centerX).isEqualTo(4 * 2)
+ assertThat(currSensorLocation?.centerY).isEqualTo(4 * 2)
+ assertThat(currSensorLocation?.radius).isEqualTo(2 * 2)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt
index c2117ae..a67b093 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt
@@ -20,8 +20,11 @@
import androidx.test.filters.SmallTest
import com.android.internal.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+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.TestScope
@@ -36,8 +39,8 @@
@RunWith(AndroidJUnit4::class)
class EmergencyServicesRepositoryImplTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private lateinit var underTest: EmergencyServicesRepository
@@ -52,7 +55,7 @@
EmergencyServicesRepository(
resources = context.resources,
applicationScope = testScope.backgroundScope,
- configurationRepository = utils.configurationRepository,
+ configurationRepository = kosmos.configurationRepository,
)
}
@@ -71,7 +74,7 @@
private fun TestScope.setEmergencyCallWhileSimLocked(isEnabled: Boolean) {
overrideResource(R.bool.config_enable_emergency_call_while_sim_locked, isEnabled)
- utils.configurationRepository.onConfigurationChange()
+ kosmos.fakeConfigurationRepository.onConfigurationChange()
runCurrent()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
index 67ce86b..741cde8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
@@ -16,27 +16,32 @@
package com.android.systemui.bouncer.domain.interactor
-import android.app.ActivityTaskManager
import android.telecom.TelecomManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.app.activityTaskManager
import com.android.internal.R
+import com.android.internal.logging.fakeMetricsLogger
import com.android.internal.logging.nano.MetricsProto
-import com.android.internal.logging.testing.FakeMetricsLogger
-import com.android.internal.util.EmergencyAffordanceManager
+import com.android.internal.util.emergencyAffordanceManager
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
+import com.android.systemui.testKosmos
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.whenever
+import com.android.telecom.telecomManager
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,27 +58,26 @@
@RunWith(AndroidJUnit4::class)
class BouncerActionButtonInteractorTest : SysuiTestCase() {
- @Mock private lateinit var activityTaskManager: ActivityTaskManager
- @Mock private lateinit var emergencyAffordanceManager: EmergencyAffordanceManager
@Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
- @Mock private lateinit var tableLogger: TableLogBuffer
@Mock private lateinit var telecomManager: TelecomManager
- private lateinit var utils: SceneTestUtils
- private lateinit var testScope: TestScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val metricsLogger = kosmos.fakeMetricsLogger
+ private val activityTaskManager = kosmos.activityTaskManager
+ private val emergencyAffordanceManager = kosmos.emergencyAffordanceManager
+
private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository
- private val metricsLogger = FakeMetricsLogger()
private var currentUserId: Int = 0
private var needsEmergencyAffordance = true
- private lateinit var underTest: BouncerActionButtonInteractor
-
@Before
fun setUp() {
- utils = SceneTestUtils(this)
- testScope = utils.testScope
MockitoAnnotations.initMocks(this)
+ kosmos.fakeSceneContainerFlags.enabled = true
+
+ mobileConnectionsRepository = kosmos.fakeMobileConnectionsRepository
overrideResource(R.string.lockscreen_emergency_call, MESSAGE_EMERGENCY_CALL)
overrideResource(R.string.lockscreen_return_to_call, MESSAGE_RETURN_TO_CALL)
@@ -86,34 +90,18 @@
.thenReturn(needsEmergencyAffordance)
whenever(telecomManager.isInCall).thenReturn(false)
- utils.featureFlags.set(REFACTOR_GETCURRENTUSER, true)
+ kosmos.fakeFeatureFlagsClassic.set(REFACTOR_GETCURRENTUSER, true)
- mobileConnectionsRepository =
- FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogger)
+ kosmos.fakeTelephonyRepository.setHasTelephonyRadio(true)
- utils.telephonyRepository.setHasTelephonyRadio(true)
-
- underTest =
- utils.bouncerActionButtonInteractor(
- mobileConnectionsRepository = mobileConnectionsRepository,
- activityTaskManager = activityTaskManager,
- telecomManager = telecomManager,
- emergencyAffordanceManager = emergencyAffordanceManager,
- metricsLogger = metricsLogger,
- )
+ kosmos.telecomManager = telecomManager
}
@Test
fun noTelephonyRadio_noButton() =
testScope.runTest {
- utils.telephonyRepository.setHasTelephonyRadio(false)
- underTest =
- utils.bouncerActionButtonInteractor(
- mobileConnectionsRepository = mobileConnectionsRepository,
- activityTaskManager = activityTaskManager,
- telecomManager = telecomManager,
- )
-
+ kosmos.fakeTelephonyRepository.setHasTelephonyRadio(false)
+ val underTest = kosmos.bouncerActionButtonInteractor
val actionButton by collectLastValue(underTest.actionButton)
assertThat(actionButton).isNull()
}
@@ -121,12 +109,8 @@
@Test
fun noTelecomManager_noButton() =
testScope.runTest {
- underTest =
- utils.bouncerActionButtonInteractor(
- mobileConnectionsRepository = mobileConnectionsRepository,
- activityTaskManager = activityTaskManager,
- telecomManager = null,
- )
+ kosmos.telecomManager = null
+ val underTest = kosmos.bouncerActionButtonInteractor
val actionButton by collectLastValue(underTest.actionButton)
assertThat(actionButton).isNull()
}
@@ -134,8 +118,9 @@
@Test
fun duringCall_returnToCallButton() =
testScope.runTest {
+ val underTest = kosmos.bouncerActionButtonInteractor
val actionButton by collectLastValue(underTest.actionButton)
- utils.telephonyRepository.setIsInCall(true)
+ kosmos.fakeTelephonyRepository.setIsInCall(true)
assertThat(actionButton).isNotNull()
assertThat(actionButton?.label).isEqualTo(MESSAGE_RETURN_TO_CALL)
@@ -143,6 +128,7 @@
assertThat(actionButton?.onLongClick).isNull()
actionButton?.onClick?.invoke()
+ runCurrent()
assertThat(metricsLogger.logs.size).isEqualTo(1)
assertThat(metricsLogger.logs.element().category)
@@ -154,10 +140,13 @@
@Test
fun noCall_secureAuthMethod_emergencyCallButton() =
testScope.runTest {
+ val underTest = kosmos.bouncerActionButtonInteractor
val actionButton by collectLastValue(underTest.actionButton)
mobileConnectionsRepository.isAnySimSecure.value = false
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.telephonyRepository.setIsInCall(false)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeTelephonyRepository.setIsInCall(false)
assertThat(actionButton).isNotNull()
assertThat(actionButton?.label).isEqualTo(MESSAGE_EMERGENCY_CALL)
@@ -165,6 +154,7 @@
assertThat(actionButton?.onLongClick).isNotNull()
actionButton?.onClick?.invoke()
+ runCurrent()
assertThat(metricsLogger.logs.size).isEqualTo(1)
assertThat(metricsLogger.logs.element().category)
@@ -182,10 +172,14 @@
@Test
fun noCall_insecureAuthMethodButSecureSim_emergencyCallButton() =
testScope.runTest {
+ val underTest = kosmos.bouncerActionButtonInteractor
val actionButton by collectLastValue(underTest.actionButton)
mobileConnectionsRepository.isAnySimSecure.value = true
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
- utils.telephonyRepository.setIsInCall(false)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
+ kosmos.fakeTelephonyRepository.setIsInCall(false)
+ runCurrent()
assertThat(actionButton).isNotNull()
assertThat(actionButton?.label).isEqualTo(MESSAGE_EMERGENCY_CALL)
@@ -196,10 +190,13 @@
@Test
fun noCall_insecure_noButton() =
testScope.runTest {
+ val underTest = kosmos.bouncerActionButtonInteractor
val actionButton by collectLastValue(underTest.actionButton)
mobileConnectionsRepository.isAnySimSecure.value = false
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
- utils.telephonyRepository.setIsInCall(false)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
+ kosmos.fakeTelephonyRepository.setIsInCall(false)
assertThat(actionButton).isNull()
}
@@ -207,12 +204,15 @@
@Test
fun noCall_simSecureButEmergencyNotSupported_noButton() =
testScope.runTest {
+ val underTest = kosmos.bouncerActionButtonInteractor
val actionButton by collectLastValue(underTest.actionButton)
mobileConnectionsRepository.isAnySimSecure.value = true
overrideResource(R.bool.config_enable_emergency_call_while_sim_locked, false)
- utils.configurationRepository.onConfigurationChange()
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
- utils.telephonyRepository.setIsInCall(false)
+ kosmos.fakeConfigurationRepository.onConfigurationChange()
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
+ kosmos.fakeTelephonyRepository.setIsInCall(false)
runCurrent()
assertThat(actionButton).isNull()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index 93ba6a4..707777b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -20,14 +20,20 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.domain.interactor.AuthenticationResult
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -37,8 +43,6 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@@ -46,17 +50,16 @@
@RunWith(AndroidJUnit4::class)
class BouncerInteractorTest : SysuiTestCase() {
- @Mock private lateinit var mDeviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor
-
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val authenticationInteractor = utils.authenticationInteractor()
+ private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
+ private val testScope = kosmos.testScope
+ private val authenticationInteractor = kosmos.authenticationInteractor
private lateinit var underTest: BouncerInteractor
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+
overrideResource(R.string.keyguard_enter_your_pin, MESSAGE_ENTER_YOUR_PIN)
overrideResource(R.string.keyguard_enter_your_password, MESSAGE_ENTER_YOUR_PASSWORD)
overrideResource(R.string.keyguard_enter_your_pattern, MESSAGE_ENTER_YOUR_PATTERN)
@@ -64,11 +67,7 @@
overrideResource(R.string.kg_wrong_password, MESSAGE_WRONG_PASSWORD)
overrideResource(R.string.kg_wrong_pattern, MESSAGE_WRONG_PATTERN)
- underTest =
- utils.bouncerInteractor(
- authenticationInteractor = authenticationInteractor,
- deviceEntryFaceAuthInteractor = mDeviceEntryFaceAuthInteractor,
- )
+ underTest = kosmos.bouncerInteractor
}
@Test
@@ -76,7 +75,9 @@
testScope.runTest {
val message by collectLastValue(underTest.message)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
runCurrent()
underTest.clearMessage()
assertThat(message).isNull()
@@ -100,7 +101,9 @@
@Test
fun pinAuthMethod_sim_skipsAuthentication() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Sim)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Sim
+ )
runCurrent()
// We rely on TelephonyManager to authenticate the sim card.
@@ -115,9 +118,11 @@
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
runCurrent()
- utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
assertThat(isAutoConfirmEnabled).isTrue()
// Incomplete input.
@@ -143,7 +148,9 @@
testScope.runTest {
val message by collectLastValue(underTest.message)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
runCurrent()
// Incomplete input.
@@ -166,7 +173,7 @@
fun passwordAuthMethod() =
testScope.runTest {
val message by collectLastValue(underTest.message)
- utils.authenticationRepository.setAuthenticationMethod(
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
runCurrent()
@@ -186,7 +193,8 @@
assertThat(
underTest.authenticate(
buildList {
- repeat(utils.authenticationRepository.minPasswordLength - 1) { time ->
+ repeat(kosmos.fakeAuthenticationRepository.minPasswordLength - 1) { time
+ ->
add("$time")
}
}
@@ -204,7 +212,7 @@
fun patternAuthMethod() =
testScope.runTest {
val message by collectLastValue(underTest.message)
- utils.authenticationRepository.setAuthenticationMethod(
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pattern
)
runCurrent()
@@ -220,7 +228,8 @@
AuthenticationPatternCoordinate(0, 1),
)
assertThat(wrongPattern).isNotEqualTo(FakeAuthenticationRepository.PATTERN)
- assertThat(wrongPattern.size).isAtLeast(utils.authenticationRepository.minPatternLength)
+ assertThat(wrongPattern.size)
+ .isAtLeast(kosmos.fakeAuthenticationRepository.minPatternLength)
assertThat(underTest.authenticate(wrongPattern)).isEqualTo(AuthenticationResult.FAILED)
assertThat(message).isEqualTo(MESSAGE_WRONG_PATTERN)
@@ -231,7 +240,7 @@
val tooShortPattern =
FakeAuthenticationRepository.PATTERN.subList(
0,
- utils.authenticationRepository.minPatternLength - 1
+ kosmos.fakeAuthenticationRepository.minPatternLength - 1
)
assertThat(underTest.authenticate(tooShortPattern))
.isEqualTo(AuthenticationResult.SKIPPED)
@@ -251,7 +260,9 @@
val lockoutStartedEvents by collectValues(underTest.onLockoutStarted)
val message by collectLastValue(underTest.message)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
assertThat(lockoutStartedEvents).isEmpty()
// Try the wrong PIN repeatedly, until lockout is triggered:
@@ -297,16 +308,24 @@
@Test
fun intentionalUserInputEvent_registersTouchEvent() =
testScope.runTest {
- assertThat(utils.powerRepository.userTouchRegistered).isFalse()
+ assertThat(kosmos.fakePowerRepository.userTouchRegistered).isFalse()
underTest.onIntentionalUserInput()
- assertThat(utils.powerRepository.userTouchRegistered).isTrue()
+ assertThat(kosmos.fakePowerRepository.userTouchRegistered).isTrue()
}
@Test
fun intentionalUserInputEvent_notifiesFaceAuthInteractor() =
testScope.runTest {
+ val isFaceAuthRunning by
+ collectLastValue(kosmos.fakeDeviceEntryFaceAuthRepository.isAuthRunning)
+ kosmos.deviceEntryFaceAuthInteractor.onDeviceLifted()
+ runCurrent()
+ assertThat(isFaceAuthRunning).isTrue()
+
underTest.onIntentionalUserInput()
- verify(mDeviceEntryFaceAuthInteractor).onPrimaryBouncerUserInput()
+ runCurrent()
+
+ assertThat(isFaceAuthRunning).isFalse()
}
companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
index ee46f76..63f6c20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -16,10 +16,10 @@
package com.android.systemui.bouncer.domain.interactor
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.testing.TestableResources
import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardUpdateMonitor
@@ -60,7 +60,7 @@
@SmallTest
@RunWithLooper(setAsMainLooper = true)
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
class PrimaryBouncerInteractorTest : SysuiTestCase() {
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private lateinit var repository: KeyguardBouncerRepository
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 8c53c0e..09fdd11 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
@@ -28,8 +28,11 @@
import com.android.systemui.bouncer.data.repository.FakeSimBouncerRepository
import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor.Companion.INVALID_SUBSCRIPTION_ID
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -54,10 +57,10 @@
@Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock lateinit var euiccManager: EuiccManager
- private val utils = SceneTestUtils(this)
+ private val kosmos = testKosmos()
private val bouncerSimRepository = FakeSimBouncerRepository()
private val resources: Resources = context.resources
- private val testScope = utils.testScope
+ private val testScope = kosmos.testScope
private lateinit var underTest: SimBouncerInteractor
@@ -68,13 +71,13 @@
SimBouncerInteractor(
context,
testScope.backgroundScope,
- utils.testDispatcher,
+ kosmos.testDispatcher,
bouncerSimRepository,
telephonyManager,
resources,
keyguardUpdateMonitor,
euiccManager,
- utils.mobileConnectionsRepository,
+ kosmos.mobileConnectionsRepository,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
index 2f0843b..27b84b2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
@@ -20,9 +20,13 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
@@ -33,19 +37,16 @@
@RunWith(AndroidJUnit4::class)
class AuthMethodBouncerViewModelTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val bouncerInteractor =
- utils.bouncerInteractor(
- authenticationInteractor = utils.authenticationInteractor(),
- )
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val bouncerInteractor = kosmos.bouncerInteractor
private val underTest =
PinBouncerViewModel(
applicationContext = context,
viewModelScope = testScope.backgroundScope,
interactor = bouncerInteractor,
isInputEnabled = MutableStateFlow(true),
- simBouncerInteractor = utils.simBouncerInteractor,
+ simBouncerInteractor = kosmos.simBouncerInteractor,
authenticationMethod = AuthenticationMethodModel.Pin,
)
@@ -53,7 +54,9 @@
fun animateFailure() =
testScope.runTest {
val animateFailure by collectLastValue(underTest.animateFailure)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
assertThat(animateFailure).isFalse()
// Wrong PIN:
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index 47bbe6f4..cfe8c5d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -20,15 +20,21 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.None
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.authentication.shared.model.AuthenticationMethodModel.Sim
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import kotlin.time.Duration.Companion.seconds
@@ -41,6 +47,7 @@
import kotlinx.coroutines.test.currentTime
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -49,18 +56,17 @@
@RunWith(AndroidJUnit4::class)
class BouncerViewModelTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val authenticationInteractor = utils.authenticationInteractor()
- private val bouncerInteractor =
- utils.bouncerInteractor(
- authenticationInteractor = authenticationInteractor,
- )
- private val underTest =
- utils.bouncerViewModel(
- bouncerInteractor = bouncerInteractor,
- authenticationInteractor = authenticationInteractor,
- )
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val authenticationInteractor = kosmos.authenticationInteractor
+ private val bouncerInteractor = kosmos.bouncerInteractor
+ private lateinit var underTest: BouncerViewModel
+
+ @Before
+ fun setUp() {
+ kosmos.fakeSceneContainerFlags.enabled = true
+ underTest = kosmos.bouncerViewModel
+ }
@Test
fun authMethod_nonNullForSecureMethods_nullForNotSecureMethods() =
@@ -68,7 +74,7 @@
var authMethodViewModel: AuthMethodBouncerViewModel? = null
authMethodsToTest().forEach { authMethod ->
- utils.authenticationRepository.setAuthenticationMethod(authMethod)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod)
val job =
underTest.authMethodViewModel.onEach { authMethodViewModel = it }.launchIn(this)
runCurrent()
@@ -98,13 +104,13 @@
// First pass, populate our "seen" map:
authMethodsToTest().forEach { authMethod ->
- utils.authenticationRepository.setAuthenticationMethod(authMethod)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod)
authMethodViewModel?.let { seen[authMethod] = it }
}
// Second pass, assert same instances are not reused:
authMethodsToTest().forEach { authMethod ->
- utils.authenticationRepository.setAuthenticationMethod(authMethod)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod)
authMethodViewModel?.let {
assertThat(it.authenticationMethod).isEqualTo(authMethod)
assertThat(it).isNotSameInstanceAs(seen[authMethod])
@@ -116,11 +122,11 @@
fun authMethodUnchanged_reusesInstances() =
testScope.runTest {
authMethodsToTest().forEach { authMethod ->
- utils.authenticationRepository.setAuthenticationMethod(authMethod)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod)
val firstInstance: AuthMethodBouncerViewModel? =
collectLastValue(underTest.authMethodViewModel).invoke()
- utils.authenticationRepository.setAuthenticationMethod(authMethod)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod)
val secondInstance: AuthMethodBouncerViewModel? =
collectLastValue(underTest.authMethodViewModel).invoke()
@@ -139,7 +145,7 @@
fun message() =
testScope.runTest {
val message by collectLastValue(underTest.message)
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
assertThat(message?.isUpdateAnimated).isTrue()
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
@@ -157,8 +163,8 @@
testScope.runTest {
val authMethodViewModel by collectLastValue(underTest.authMethodViewModel)
val message by collectLastValue(underTest.message)
- utils.authenticationRepository.setAuthenticationMethod(Pin)
- assertThat(utils.authenticationRepository.lockoutEndTimestamp).isNull()
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
+ assertThat(kosmos.fakeAuthenticationRepository.lockoutEndTimestamp).isNull()
assertThat(authMethodViewModel?.lockoutMessageId).isNotNull()
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { times ->
@@ -192,7 +198,7 @@
authViewModel?.isInputEnabled ?: emptyFlow()
}
)
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
assertThat(isInputEnabled).isTrue()
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
@@ -210,7 +216,7 @@
testScope.runTest {
val authMethodViewModel by collectLastValue(underTest.authMethodViewModel)
val dialogViewModel by collectLastValue(underTest.dialogViewModel)
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
assertThat(authMethodViewModel?.lockoutMessageId).isNotNull()
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
@@ -228,17 +234,17 @@
fun isSideBySideSupported() =
testScope.runTest {
val isSideBySideSupported by collectLastValue(underTest.isSideBySideSupported)
- utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
assertThat(isSideBySideSupported).isTrue()
- utils.authenticationRepository.setAuthenticationMethod(Password)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
assertThat(isSideBySideSupported).isTrue()
- utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
assertThat(isSideBySideSupported).isTrue()
- utils.authenticationRepository.setAuthenticationMethod(Password)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
assertThat(isSideBySideSupported).isFalse()
}
@@ -246,12 +252,12 @@
fun isFoldSplitRequired() =
testScope.runTest {
val isFoldSplitRequired by collectLastValue(underTest.isFoldSplitRequired)
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
assertThat(isFoldSplitRequired).isTrue()
- utils.authenticationRepository.setAuthenticationMethod(Password)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
assertThat(isFoldSplitRequired).isFalse()
- utils.authenticationRepository.setAuthenticationMethod(Pattern)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pattern)
assertThat(isFoldSplitRequired).isTrue()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index 64e6e57..b3b6457 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -19,13 +19,19 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -43,20 +49,12 @@
@RunWith(AndroidJUnit4::class)
class PasswordBouncerViewModelTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val authenticationInteractor = utils.authenticationInteractor()
- private val sceneInteractor = utils.sceneInteractor()
- private val bouncerInteractor =
- utils.bouncerInteractor(
- authenticationInteractor = authenticationInteractor,
- )
- private val bouncerViewModel =
- utils.bouncerViewModel(
- bouncerInteractor = bouncerInteractor,
- authenticationInteractor = authenticationInteractor,
- actionButtonInteractor = utils.bouncerActionButtonInteractor(),
- )
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val authenticationInteractor = kosmos.authenticationInteractor
+ private val sceneInteractor = kosmos.sceneInteractor
+ private val bouncerInteractor = kosmos.bouncerInteractor
+ private val bouncerViewModel = kosmos.bouncerViewModel
private val isInputEnabled = MutableStateFlow(true)
private val underTest =
@@ -148,10 +146,10 @@
testScope.runTest {
val message by collectLastValue(bouncerViewModel.message)
val password by collectLastValue(underTest.password)
- utils.authenticationRepository.setAuthenticationMethod(
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
switchToScene(SceneKey.Bouncer)
// No input entered.
@@ -317,8 +315,10 @@
}
private fun TestScope.lockDeviceAndOpenPasswordBouncer() {
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Password)
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Password
+ )
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
switchToScene(SceneKey.Bouncer)
}
@@ -328,13 +328,13 @@
) {
if (isLockedOut) {
repeat(failedAttemptCount) {
- utils.authenticationRepository.reportAuthenticationAttempt(false)
+ kosmos.fakeAuthenticationRepository.reportAuthenticationAttempt(false)
}
- utils.authenticationRepository.reportLockoutStarted(
+ kosmos.fakeAuthenticationRepository.reportLockoutStarted(
30.seconds.inWholeMilliseconds.toInt()
)
} else {
- utils.authenticationRepository.reportAuthenticationAttempt(true)
+ kosmos.fakeAuthenticationRepository.reportAuthenticationAttempt(true)
}
isInputEnabled.value = !isLockedOut
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index 83d1938..c2680bc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -20,13 +20,20 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.authenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate as Point
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -44,20 +51,12 @@
@RunWith(AndroidJUnit4::class)
class PatternBouncerViewModelTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val authenticationInteractor = utils.authenticationInteractor()
- private val sceneInteractor = utils.sceneInteractor()
- private val bouncerInteractor =
- utils.bouncerInteractor(
- authenticationInteractor = authenticationInteractor,
- )
- private val bouncerViewModel =
- utils.bouncerViewModel(
- bouncerInteractor = bouncerInteractor,
- authenticationInteractor = authenticationInteractor,
- actionButtonInteractor = utils.bouncerActionButtonInteractor(),
- )
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val authenticationInteractor = kosmos.authenticationInteractor
+ private val sceneInteractor = kosmos.sceneInteractor
+ private val bouncerInteractor = kosmos.bouncerInteractor
+ private val bouncerViewModel = kosmos.bouncerViewModel
private val underTest =
PatternBouncerViewModel(
applicationContext = context,
@@ -313,7 +312,7 @@
underTest.onDragStart()
CORRECT_PATTERN.subList(
0,
- utils.authenticationRepository.minPatternLength - 1,
+ kosmos.authenticationRepository.minPatternLength - 1,
)
.forEach { coordinate ->
underTest.onDrag(
@@ -382,8 +381,10 @@
}
private fun TestScope.lockDeviceAndOpenPatternBouncer() {
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pattern)
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pattern
+ )
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
switchToScene(SceneKey.Bouncer)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index db98d76..1d660d6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -20,12 +20,20 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.data.repository.fakeSimBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -43,27 +51,19 @@
@RunWith(AndroidJUnit4::class)
class PinBouncerViewModelTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val sceneInteractor = utils.sceneInteractor()
- private val authenticationInteractor = utils.authenticationInteractor()
- private val bouncerInteractor =
- utils.bouncerInteractor(
- authenticationInteractor = authenticationInteractor,
- )
- private val bouncerViewModel =
- utils.bouncerViewModel(
- bouncerInteractor = bouncerInteractor,
- authenticationInteractor = authenticationInteractor,
- actionButtonInteractor = utils.bouncerActionButtonInteractor(),
- )
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val sceneInteractor = kosmos.sceneInteractor
+ private val authenticationInteractor = kosmos.authenticationInteractor
+ private val bouncerInteractor = kosmos.bouncerInteractor
+ private val bouncerViewModel = kosmos.bouncerViewModel
private val underTest =
PinBouncerViewModel(
applicationContext = context,
viewModelScope = testScope.backgroundScope,
interactor = bouncerInteractor,
isInputEnabled = MutableStateFlow(true).asStateFlow(),
- simBouncerInteractor = utils.simBouncerInteractor,
+ simBouncerInteractor = kosmos.simBouncerInteractor,
authenticationMethod = AuthenticationMethodModel.Pin,
)
@@ -94,7 +94,7 @@
viewModelScope = testScope.backgroundScope,
interactor = bouncerInteractor,
isInputEnabled = MutableStateFlow(true).asStateFlow(),
- simBouncerInteractor = utils.simBouncerInteractor,
+ simBouncerInteractor = kosmos.simBouncerInteractor,
authenticationMethod = AuthenticationMethodModel.Sim,
)
@@ -105,7 +105,7 @@
fun onErrorDialogDismissed_clearsDialogMessage() =
testScope.runTest {
val dialogMessage by collectLastValue(underTest.errorDialogMessage)
- utils.simBouncerRepository.setSimVerificationErrorMessage("abc")
+ kosmos.fakeSimBouncerRepository.setSimVerificationErrorMessage("abc")
assertThat(dialogMessage).isEqualTo("abc")
underTest.onErrorDialogDismissed()
@@ -122,10 +122,10 @@
viewModelScope = testScope.backgroundScope,
interactor = bouncerInteractor,
isInputEnabled = MutableStateFlow(true).asStateFlow(),
- simBouncerInteractor = utils.simBouncerInteractor,
+ simBouncerInteractor = kosmos.simBouncerInteractor,
authenticationMethod = AuthenticationMethodModel.Sim,
)
- utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
assertThat(hintedPinLength).isNull()
@@ -262,7 +262,7 @@
@Test
fun onAutoConfirm_whenCorrect() =
testScope.runTest {
- utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult)
lockDeviceAndOpenPinBouncer()
@@ -277,7 +277,7 @@
val currentScene by collectLastValue(sceneInteractor.desiredScene)
val message by collectLastValue(bouncerViewModel.message)
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
- utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
lockDeviceAndOpenPinBouncer()
FakeAuthenticationRepository.DEFAULT_PIN.dropLast(1).forEach { digit ->
@@ -317,7 +317,9 @@
testScope.runTest {
val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
assertThat(backspaceButtonAppearance).isEqualTo(ActionButtonAppearance.Shown)
}
@@ -326,8 +328,10 @@
fun backspaceButtonAppearance_withAutoConfirmButNoInput_isHidden() =
testScope.runTest {
val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
assertThat(backspaceButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden)
}
@@ -336,8 +340,10 @@
fun backspaceButtonAppearance_withAutoConfirmAndInput_isShownQuiet() =
testScope.runTest {
val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
underTest.onPinButtonClicked(1)
@@ -349,7 +355,9 @@
testScope.runTest {
val confirmButtonAppearance by collectLastValue(underTest.confirmButtonAppearance)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Shown)
}
@@ -358,8 +366,10 @@
fun confirmButtonAppearance_withAutoConfirm_isHidden() =
testScope.runTest {
val confirmButtonAppearance by collectLastValue(underTest.confirmButtonAppearance)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden)
}
@@ -369,10 +379,10 @@
testScope.runTest {
val isAnimationEnabled by collectLastValue(underTest.isDigitButtonAnimationEnabled)
- utils.authenticationRepository.setPinEnhancedPrivacyEnabled(true)
+ kosmos.fakeAuthenticationRepository.setPinEnhancedPrivacyEnabled(true)
assertThat(isAnimationEnabled).isFalse()
- utils.authenticationRepository.setPinEnhancedPrivacyEnabled(false)
+ kosmos.fakeAuthenticationRepository.setPinEnhancedPrivacyEnabled(false)
assertThat(isAnimationEnabled).isTrue()
}
@@ -390,8 +400,8 @@
}
private fun TestScope.lockDeviceAndOpenPinBouncer() {
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
switchToScene(SceneKey.Bouncer)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
new file mode 100644
index 0000000..820bfbf
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
@@ -0,0 +1,135 @@
+/*
+ * 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.data.repository
+
+import android.content.SharedPreferences
+import android.content.pm.UserInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.CommunalPrefsRepositoryImpl.Companion.FILE_NAME
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.settings.UserFileManager
+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.FakeSharedPreferences
+import com.google.common.truth.Truth.assertThat
+import java.io.File
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalPrefsRepositoryImplTest : SysuiTestCase() {
+ private lateinit var underTest: CommunalPrefsRepositoryImpl
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private lateinit var userRepository: FakeUserRepository
+ private lateinit var userFileManager: UserFileManager
+
+ @Before
+ fun setUp() {
+ userRepository = kosmos.fakeUserRepository
+ userRepository.setUserInfos(USER_INFOS)
+
+ userFileManager =
+ FakeUserFileManager(
+ mapOf(
+ USER_INFOS[0].id to FakeSharedPreferences(),
+ USER_INFOS[1].id to FakeSharedPreferences()
+ )
+ )
+ underTest =
+ CommunalPrefsRepositoryImpl(
+ testScope.backgroundScope,
+ kosmos.testDispatcher,
+ userRepository,
+ userFileManager,
+ )
+ }
+
+ @Test
+ fun isCtaDismissedValue_byDefault_isFalse() =
+ testScope.runTest {
+ val isCtaDismissed by collectLastValue(underTest.isCtaDismissed)
+ assertThat(isCtaDismissed).isFalse()
+ }
+
+ @Test
+ fun isCtaDismissedValue_onSet_isTrue() =
+ testScope.runTest {
+ val isCtaDismissed by collectLastValue(underTest.isCtaDismissed)
+
+ underTest.setCtaDismissedForCurrentUser()
+ assertThat(isCtaDismissed).isTrue()
+ }
+
+ @Test
+ fun isCtaDismissedValue_whenSwitchUser() =
+ testScope.runTest {
+ val isCtaDismissed by collectLastValue(underTest.isCtaDismissed)
+ underTest.setCtaDismissedForCurrentUser()
+
+ // dismissed true for primary user
+ assertThat(isCtaDismissed).isTrue()
+
+ // switch to secondary user
+ userRepository.setSelectedUserInfo(USER_INFOS[1])
+
+ // dismissed is false for secondary user
+ assertThat(isCtaDismissed).isFalse()
+
+ // switch back to primary user
+ userRepository.setSelectedUserInfo(USER_INFOS[0])
+
+ // dismissed is true for primary user
+ assertThat(isCtaDismissed).isTrue()
+ }
+
+ private class FakeUserFileManager(private val sharedPrefs: Map<Int, SharedPreferences>) :
+ UserFileManager {
+ override fun getFile(fileName: String, userId: Int): File {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getSharedPreferences(
+ fileName: String,
+ mode: Int,
+ userId: Int
+ ): SharedPreferences {
+ if (fileName != FILE_NAME) {
+ throw IllegalArgumentException("Preference files must be $FILE_NAME")
+ }
+ return sharedPrefs.getValue(userId)
+ }
+ }
+
+ companion object {
+ val USER_INFOS =
+ listOf(
+ UserInfo(/* id= */ 0, "zero", /* flags= */ 0),
+ UserInfo(/* id= */ 1, "secondary", /* flags= */ 0),
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
index 65176e1..81d5344 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
@@ -24,11 +24,12 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
-import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.data.repository.SceneContainerRepository
+import com.android.systemui.scene.data.repository.sceneContainerRepository
import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -51,8 +52,8 @@
@Before
fun setUp() {
- val sceneTestUtils = SceneTestUtils(this)
- sceneContainerRepository = sceneTestUtils.fakeSceneContainerRepository()
+ val kosmos = testKosmos()
+ sceneContainerRepository = kosmos.sceneContainerRepository
featureFlagsClassic = FakeFeatureFlagsClassic()
featureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
new file mode 100644
index 0000000..c4a8582
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.data.repository
+
+import android.content.pm.UserInfo
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryImpl.Companion.CURRENT_TUTORIAL_VERSION
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalTutorialRepositoryImplTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private lateinit var secureSettings: FakeSettings
+ private lateinit var userRepository: FakeUserRepository
+
+ private lateinit var underTest: CommunalTutorialRepositoryImpl
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ secureSettings = FakeSettings()
+ userRepository = FakeUserRepository()
+ val listOfUserInfo = listOf(MAIN_USER_INFO)
+ userRepository.setUserInfos(listOfUserInfo)
+
+ underTest =
+ CommunalTutorialRepositoryImpl(
+ kosmos.applicationCoroutineScope,
+ kosmos.testDispatcher,
+ userRepository,
+ secureSettings,
+ logcatLogBuffer("CommunalTutorialRepositoryImplTest"),
+ )
+ }
+
+ @Test
+ fun tutorialSettingState_defaultToNotStarted() =
+ testScope.runTest {
+ val tutorialSettingState by collectLastValue(underTest.tutorialSettingState)
+ assertThat(tutorialSettingState)
+ .isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED)
+ }
+
+ @Test
+ fun tutorialSettingState_whenTutorialSettingsUpdatedToStarted() =
+ testScope.runTest {
+ underTest.setTutorialState(Settings.Secure.HUB_MODE_TUTORIAL_STARTED)
+ val tutorialSettingState by collectLastValue(underTest.tutorialSettingState)
+ assertThat(tutorialSettingState).isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_STARTED)
+ }
+
+ @Test
+ fun tutorialSettingState_whenTutorialSettingsUpdatedToCompleted() =
+ testScope.runTest {
+ underTest.setTutorialState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+ val tutorialSettingState by collectLastValue(underTest.tutorialSettingState)
+ assertThat(tutorialSettingState).isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+ }
+
+ @Test
+ fun tutorialVersion_userCompletedCurrentVersion_stateCompleted() =
+ testScope.runTest {
+ // User completed the current version.
+ setTutorialStateSetting(CURRENT_TUTORIAL_VERSION)
+
+ // Verify tutorial state is completed.
+ val tutorialSettingState by collectLastValue(underTest.tutorialSettingState)
+ assertThat(tutorialSettingState).isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+ }
+
+ @Test
+ fun tutorialVersion_userCompletedPreviousVersion_stateNotStarted() =
+ testScope.runTest {
+ // User completed the previous version.
+ setTutorialStateSetting(CURRENT_TUTORIAL_VERSION - 1)
+
+ // Verify tutorial state is not started.
+ val tutorialSettingState by collectLastValue(underTest.tutorialSettingState)
+ assertThat(tutorialSettingState)
+ .isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED)
+ }
+
+ @Test
+ fun tutorialVersion_uponTutorialCompletion_writeCurrentVersion() =
+ testScope.runTest {
+ // Tutorial not started.
+ setTutorialStateSetting(Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED)
+
+ // Tutorial completed.
+ underTest.setTutorialState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+
+ // Verify tutorial setting state is updated to current version.
+ val settingState = getTutorialStateSetting()
+ assertThat(settingState).isEqualTo(CURRENT_TUTORIAL_VERSION)
+ }
+
+ private fun setTutorialStateSetting(
+ @Settings.Secure.HubModeTutorialState state: Int,
+ user: UserInfo = MAIN_USER_INFO
+ ) {
+ secureSettings.putIntForUser(Settings.Secure.HUB_MODE_TUTORIAL_STATE, state, user.id)
+ }
+
+ private fun getTutorialStateSetting(user: UserInfo = MAIN_USER_INFO): Int {
+ return secureSettings.getIntForUser(Settings.Secure.HUB_MODE_TUTORIAL_STATE, user.id)
+ }
+
+ companion object {
+ private val MAIN_USER_INFO =
+ UserInfo(/* id= */ 0, /* name= */ "primary", /* flags= */ UserInfo.FLAG_MAIN)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index 4079f12..fdb17c2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -16,14 +16,9 @@
package com.android.systemui.communal.data.repository
-import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProviderInfo
import android.content.ComponentName
-import android.content.Intent
-import android.content.Intent.ACTION_USER_UNLOCKED
-import android.os.UserHandle
-import android.os.UserManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -32,11 +27,14 @@
import com.android.systemui.communal.data.db.CommunalWidgetItem
import com.android.systemui.communal.shared.CommunalWidgetHost
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.core.FakeLogBuffer
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.res.R
-import com.android.systemui.settings.UserTracker
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -44,8 +42,6 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -65,13 +61,7 @@
@Mock private lateinit var appWidgetManager: AppWidgetManager
- @Mock private lateinit var appWidgetHost: AppWidgetHost
-
- @Mock private lateinit var userManager: UserManager
-
- @Mock private lateinit var userHandle: UserHandle
-
- @Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
@Mock private lateinit var stopwatchProviderInfo: AppWidgetProviderInfo
@@ -81,14 +71,12 @@
@Mock private lateinit var communalWidgetDao: CommunalWidgetDao
- private lateinit var communalRepository: FakeCommunalRepository
+ private val kosmos = testKosmos()
+
+ private val testScope = kosmos.testScope
private lateinit var logBuffer: LogBuffer
- private val testDispatcher = StandardTestDispatcher()
-
- private val testScope = TestScope(testDispatcher)
-
private val fakeAllowlist =
listOf(
"com.android.fake/WidgetProviderA",
@@ -96,68 +84,60 @@
"com.android.fake/WidgetProviderC",
)
+ private lateinit var underTest: CommunalWidgetRepositoryImpl
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- logBuffer = FakeLogBuffer.Factory.create()
- communalRepository = FakeCommunalRepository()
+ logBuffer = logcatLogBuffer(name = "CommunalWidgetRepoImplTest")
- communalEnabled(true)
setAppWidgetIds(emptyList())
overrideResource(R.array.config_communalWidgetAllowlist, fakeAllowlist.toTypedArray())
whenever(stopwatchProviderInfo.loadLabel(any())).thenReturn("Stopwatch")
- whenever(userTracker.userHandle).thenReturn(userHandle)
whenever(communalWidgetDao.getWidgets()).thenReturn(flowOf(emptyMap()))
whenever(appWidgetManagerOptional.isPresent).thenReturn(true)
whenever(appWidgetManagerOptional.get()).thenReturn(appWidgetManager)
+
+ underTest =
+ CommunalWidgetRepositoryImpl(
+ appWidgetManagerOptional,
+ appWidgetHost,
+ testScope.backgroundScope,
+ kosmos.testDispatcher,
+ communalWidgetHost,
+ communalWidgetDao,
+ logBuffer,
+ )
}
@Test
- fun neverQueryDbForWidgets_whenFeatureIsDisabled() =
+ fun neverQueryDbForWidgets_whenHostIsInactive() =
testScope.runTest {
- communalEnabled(false)
- val repository = initCommunalWidgetRepository()
- repository.communalWidgets.launchIn(backgroundScope)
+ underTest.updateAppWidgetHostActive(false)
+ underTest.communalWidgets.launchIn(testScope.backgroundScope)
runCurrent()
verify(communalWidgetDao, never()).getWidgets()
}
@Test
- fun neverQueryDbForWidgets_whenFeatureEnabled_andUserLocked() =
+ fun communalWidgets_whenHostIsActive_queryWidgetsFromDb() =
testScope.runTest {
- userUnlocked(false)
- val repository = initCommunalWidgetRepository()
- repository.communalWidgets.launchIn(backgroundScope)
- runCurrent()
+ underTest.updateAppWidgetHostActive(true)
- verify(communalWidgetDao, never()).getWidgets()
- }
-
- @Test
- fun communalWidgets_whenUserUnlocked_queryWidgetsFromDb() =
- testScope.runTest {
- userUnlocked(false)
- val repository = initCommunalWidgetRepository()
- val communalWidgets by collectLastValue(repository.communalWidgets)
- runCurrent()
val communalItemRankEntry = CommunalItemRank(uid = 1L, rank = 1)
val communalWidgetItemEntry = CommunalWidgetItem(uid = 1L, 1, "pk_name/cls_name", 1L)
whenever(communalWidgetDao.getWidgets())
.thenReturn(flowOf(mapOf(communalItemRankEntry to communalWidgetItemEntry)))
whenever(appWidgetManager.getAppWidgetInfo(anyInt())).thenReturn(providerInfoA)
- userUnlocked(true)
installedProviders(listOf(stopwatchProviderInfo))
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
- context,
- Intent(ACTION_USER_UNLOCKED)
- )
- runCurrent()
+ val communalWidgets by collectLastValue(underTest.communalWidgets)
+ runCurrent()
verify(communalWidgetDao).getWidgets()
assertThat(communalWidgets)
.containsExactly(
@@ -172,9 +152,7 @@
@Test
fun addWidget_allocateId_bindWidget_andAddToDb() =
testScope.runTest {
- userUnlocked(true)
- val repository = initCommunalWidgetRepository()
- runCurrent()
+ underTest.updateAppWidgetHostActive(true)
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
@@ -182,7 +160,7 @@
whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(true)
whenever(communalWidgetHost.allocateIdAndBindWidget(any<ComponentName>()))
.thenReturn(id)
- repository.addWidget(provider, priority) { true }
+ underTest.addWidget(provider, priority) { true }
runCurrent()
verify(communalWidgetHost).allocateIdAndBindWidget(provider)
@@ -192,16 +170,14 @@
@Test
fun addWidget_configurationFails_doNotAddWidgetToDb() =
testScope.runTest {
- userUnlocked(true)
- val repository = initCommunalWidgetRepository()
- runCurrent()
+ underTest.updateAppWidgetHostActive(true)
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
val priority = 1
whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(true)
whenever(communalWidgetHost.allocateIdAndBindWidget(provider)).thenReturn(id)
- repository.addWidget(provider, priority) { false }
+ underTest.addWidget(provider, priority) { false }
runCurrent()
verify(communalWidgetHost).allocateIdAndBindWidget(provider)
@@ -212,16 +188,14 @@
@Test
fun addWidget_configurationThrowsError_doNotAddWidgetToDb() =
testScope.runTest {
- userUnlocked(true)
- val repository = initCommunalWidgetRepository()
- runCurrent()
+ underTest.updateAppWidgetHostActive(true)
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
val priority = 1
whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(true)
whenever(communalWidgetHost.allocateIdAndBindWidget(provider)).thenReturn(id)
- repository.addWidget(provider, priority) { throw IllegalStateException("some error") }
+ underTest.addWidget(provider, priority) { throw IllegalStateException("some error") }
runCurrent()
verify(communalWidgetHost).allocateIdAndBindWidget(provider)
@@ -232,9 +206,7 @@
@Test
fun addWidget_configurationNotRequired_doesNotConfigure_addWidgetToDb() =
testScope.runTest {
- userUnlocked(true)
- val repository = initCommunalWidgetRepository()
- runCurrent()
+ underTest.updateAppWidgetHostActive(true)
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
@@ -243,7 +215,7 @@
whenever(communalWidgetHost.allocateIdAndBindWidget(any<ComponentName>()))
.thenReturn(id)
var configured = false
- repository.addWidget(provider, priority) {
+ underTest.addWidget(provider, priority) {
configured = true
true
}
@@ -257,12 +229,10 @@
@Test
fun deleteWidget_removeWidgetId_andDeleteFromDb() =
testScope.runTest {
- userUnlocked(true)
- val repository = initCommunalWidgetRepository()
- runCurrent()
+ underTest.updateAppWidgetHostActive(true)
val id = 1
- repository.deleteWidget(id)
+ underTest.deleteWidget(id)
runCurrent()
verify(communalWidgetDao).deleteWidgetById(id)
@@ -272,108 +242,37 @@
@Test
fun reorderWidgets_queryDb() =
testScope.runTest {
- userUnlocked(true)
- val repository = initCommunalWidgetRepository()
- runCurrent()
+ underTest.updateAppWidgetHostActive(true)
val widgetIdToPriorityMap = mapOf(104 to 1, 103 to 2, 101 to 3)
- repository.updateWidgetOrder(widgetIdToPriorityMap)
+ underTest.updateWidgetOrder(widgetIdToPriorityMap)
runCurrent()
verify(communalWidgetDao).updateWidgetOrder(widgetIdToPriorityMap)
}
@Test
- fun broadcastReceiver_communalDisabled_doNotRegisterUserUnlockedBroadcastReceiver() =
+ fun appWidgetHost_startListening() =
testScope.runTest {
- communalEnabled(false)
- val repository = initCommunalWidgetRepository()
- repository.communalWidgets.launchIn(backgroundScope)
- runCurrent()
- assertThat(fakeBroadcastDispatcher.numReceiversRegistered).isEqualTo(0)
- }
-
- @Test
- fun broadcastReceiver_featureEnabledAndUserLocked_registerBroadcastReceiver() =
- testScope.runTest {
- userUnlocked(false)
- val repository = initCommunalWidgetRepository()
- repository.communalWidgets.launchIn(backgroundScope)
- runCurrent()
- assertThat(fakeBroadcastDispatcher.numReceiversRegistered).isEqualTo(1)
- }
-
- @Test
- fun appWidgetHost_userUnlocked_startListening() =
- testScope.runTest {
- userUnlocked(false)
- val repository = initCommunalWidgetRepository()
- repository.communalWidgets.launchIn(backgroundScope)
- runCurrent()
verify(appWidgetHost, never()).startListening()
- userUnlocked(true)
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
- context,
- Intent(ACTION_USER_UNLOCKED)
- )
- runCurrent()
+ underTest.updateAppWidgetHostActive(true)
verify(appWidgetHost).startListening()
}
@Test
- fun appWidgetHost_userLockedAgain_stopListening() =
+ fun appWidgetHost_stopListening() =
testScope.runTest {
- userUnlocked(false)
- val repository = initCommunalWidgetRepository()
- repository.communalWidgets.launchIn(backgroundScope)
- runCurrent()
-
- userUnlocked(true)
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
- context,
- Intent(ACTION_USER_UNLOCKED)
- )
- runCurrent()
+ underTest.updateAppWidgetHostActive(true)
verify(appWidgetHost).startListening()
- verify(appWidgetHost, never()).stopListening()
- userUnlocked(false)
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
- context,
- Intent(ACTION_USER_UNLOCKED)
- )
- runCurrent()
+ underTest.updateAppWidgetHostActive(false)
verify(appWidgetHost).stopListening()
}
- private fun initCommunalWidgetRepository(): CommunalWidgetRepositoryImpl {
- return CommunalWidgetRepositoryImpl(
- appWidgetManagerOptional,
- appWidgetHost,
- testScope.backgroundScope,
- testDispatcher,
- fakeBroadcastDispatcher,
- communalRepository,
- communalWidgetHost,
- communalWidgetDao,
- userManager,
- userTracker,
- logBuffer,
- )
- }
-
- private fun communalEnabled(enabled: Boolean) {
- communalRepository.setIsCommunalEnabled(enabled)
- }
-
- private fun userUnlocked(userUnlocked: Boolean) {
- whenever(userManager.isUserUnlockingOrUnlocked(userHandle)).thenReturn(userUnlocked)
- }
-
private fun installedProviders(providers: List<AppWidgetProviderInfo>) {
whenever(appWidgetManager.installedProviders).thenReturn(providers)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
new file mode 100644
index 0000000..ed29aa4
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.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.systemui.communal.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.FakeCommunalRepository
+import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.communalInteractor
+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.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * This class is a variation of the [CommunalInteractorTest] for cases where communal is disabled.
+ */
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class CommunalInteractorCommunalDisabledTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private lateinit var communalRepository: FakeCommunalRepository
+ private lateinit var widgetRepository: FakeCommunalWidgetRepository
+ private lateinit var keyguardRepository: FakeKeyguardRepository
+
+ private lateinit var underTest: CommunalInteractor
+
+ @Before
+ fun setUp() {
+ communalRepository = kosmos.fakeCommunalRepository
+ widgetRepository = kosmos.fakeCommunalWidgetRepository
+ keyguardRepository = kosmos.fakeKeyguardRepository
+
+ communalRepository.setIsCommunalEnabled(false)
+
+ underTest = kosmos.communalInteractor
+ }
+
+ @Test
+ fun isCommunalEnabled_false() =
+ testScope.runTest { assertThat(underTest.isCommunalEnabled).isFalse() }
+
+ @Test
+ fun isCommunalAvailable_whenStorageUnlock_false() =
+ testScope.runTest {
+ val isCommunalAvailable by collectLastValue(underTest.isCommunalAvailable)
+
+ assertThat(isCommunalAvailable).isFalse()
+
+ keyguardRepository.setIsEncryptedOrLockdown(false)
+ runCurrent()
+
+ assertThat(isCommunalAvailable).isFalse()
+ }
+
+ @Test
+ fun updateAppWidgetHostActive_whenStorageUnlock_false() =
+ testScope.runTest {
+ assertThat(widgetRepository.isHostActive()).isFalse()
+
+ keyguardRepository.setIsEncryptedOrLockdown(false)
+ runCurrent()
+
+ assertThat(widgetRepository.isHostActive()).isFalse()
+ }
+}
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 744b65f..7769223 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
@@ -24,22 +24,36 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository
import com.android.systemui.communal.data.repository.FakeCommunalRepository
import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
+import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.fakeCommunalPrefsRepository
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository
+import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.communalInteractor
+import com.android.systemui.keyguard.domain.interactor.editWidgetsActivityStarter
+import com.android.systemui.kosmos.testScope
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
+import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
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.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -47,13 +61,17 @@
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
+/**
+ * This class of test cases assume that communal is enabled. For disabled cases, see
+ * [CommunalInteractorCommunalDisabledTest].
+ */
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class CommunalInteractorTest : SysuiTestCase() {
- private lateinit var testScope: TestScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private lateinit var tutorialRepository: FakeCommunalTutorialRepository
private lateinit var communalRepository: FakeCommunalRepository
@@ -61,41 +79,63 @@
private lateinit var widgetRepository: FakeCommunalWidgetRepository
private lateinit var smartspaceRepository: FakeSmartspaceRepository
private lateinit var keyguardRepository: FakeKeyguardRepository
+ private lateinit var communalPrefsRepository: FakeCommunalPrefsRepository
private lateinit var editWidgetsActivityStarter: EditWidgetsActivityStarter
private lateinit var underTest: CommunalInteractor
@Before
fun setUp() {
- MockitoAnnotations.initMocks(this)
+ tutorialRepository = kosmos.fakeCommunalTutorialRepository
+ communalRepository = kosmos.fakeCommunalRepository
+ mediaRepository = kosmos.fakeCommunalMediaRepository
+ widgetRepository = kosmos.fakeCommunalWidgetRepository
+ smartspaceRepository = kosmos.fakeSmartspaceRepository
+ keyguardRepository = kosmos.fakeKeyguardRepository
+ editWidgetsActivityStarter = kosmos.editWidgetsActivityStarter
+ communalPrefsRepository = kosmos.fakeCommunalPrefsRepository
- testScope = TestScope()
-
- val withDeps = CommunalInteractorFactory.create()
-
- tutorialRepository = withDeps.tutorialRepository
- communalRepository = withDeps.communalRepository
- mediaRepository = withDeps.mediaRepository
- widgetRepository = withDeps.widgetRepository
- smartspaceRepository = withDeps.smartspaceRepository
- keyguardRepository = withDeps.keyguardRepository
- editWidgetsActivityStarter = withDeps.editWidgetsActivityStarter
-
- underTest = withDeps.communalInteractor
+ underTest = kosmos.communalInteractor
}
@Test
- fun communalEnabled() =
+ fun communalEnabled_true() =
+ testScope.runTest { assertThat(underTest.isCommunalEnabled).isTrue() }
+
+ @Test
+ fun isCommunalAvailable_trueWhenStorageUnlock() =
testScope.runTest {
- communalRepository.setIsCommunalEnabled(true)
- assertThat(underTest.isCommunalEnabled).isTrue()
+ val isAvailable by collectLastValue(underTest.isCommunalAvailable)
+ assertThat(isAvailable).isFalse()
+
+ keyguardRepository.setIsEncryptedOrLockdown(false)
+ runCurrent()
+
+ assertThat(isAvailable).isTrue()
}
@Test
- fun communalDisabled() =
+ fun isCommunalAvailable_whenStorageUnlock_true() =
testScope.runTest {
- communalRepository.setIsCommunalEnabled(false)
- assertThat(underTest.isCommunalEnabled).isFalse()
+ val isAvailable by collectLastValue(underTest.isCommunalAvailable)
+ assertThat(isAvailable).isFalse()
+
+ keyguardRepository.setIsEncryptedOrLockdown(false)
+ runCurrent()
+
+ assertThat(isAvailable).isTrue()
+ }
+
+ @Test
+ fun updateAppWidgetHostActive_uponStorageUnlock_true() =
+ testScope.runTest {
+ collectLastValue(underTest.isCommunalAvailable)
+ assertThat(widgetRepository.isHostActive()).isFalse()
+
+ keyguardRepository.setIsEncryptedOrLockdown(false)
+ runCurrent()
+
+ assertThat(widgetRepository.isHostActive()).isTrue()
}
@Test
@@ -327,10 +367,9 @@
}
@Test
- fun cta_visibilityTrue_shows() =
+ fun ctaTile_showsByDefault() =
testScope.runTest {
tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
- communalRepository.setCtaTileInViewModeVisibility(true)
val ctaTileContent by collectLastValue(underTest.ctaTileContent)
@@ -342,10 +381,10 @@
}
@Test
- fun ctaTile_visibilityFalse_doesNotShow() =
+ fun ctaTile_afterDismiss_doesNotShow() =
testScope.runTest {
tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
- communalRepository.setCtaTileInViewModeVisibility(false)
+ communalPrefsRepository.setCtaDismissedForCurrentUser()
val ctaTileContent by collectLastValue(underTest.ctaTileContent)
@@ -379,6 +418,131 @@
}
@Test
+ fun transitionProgress_onTargetScene_fullProgress() =
+ testScope.runTest {
+ val targetScene = CommunalSceneKey.Blank
+ val transitionProgressFlow = underTest.transitionProgressToScene(targetScene)
+ val transitionProgress by collectLastValue(transitionProgressFlow)
+
+ val transitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(
+ ObservableCommunalTransitionState.Idle(targetScene)
+ )
+ underTest.setTransitionState(transitionState)
+
+ // We're on the target scene.
+ assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Idle(targetScene))
+ }
+
+ @Test
+ fun transitionProgress_notOnTargetScene_noProgress() =
+ testScope.runTest {
+ val targetScene = CommunalSceneKey.Blank
+ val currentScene = CommunalSceneKey.Communal
+ val transitionProgressFlow = underTest.transitionProgressToScene(targetScene)
+ val transitionProgress by collectLastValue(transitionProgressFlow)
+
+ val transitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(
+ ObservableCommunalTransitionState.Idle(currentScene)
+ )
+ underTest.setTransitionState(transitionState)
+
+ // Transition progress is still idle, but we're not on the target scene.
+ assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Idle(currentScene))
+ }
+
+ @Test
+ fun transitionProgress_transitioningToTrackedScene() =
+ testScope.runTest {
+ val currentScene = CommunalSceneKey.Communal
+ val targetScene = CommunalSceneKey.Blank
+ val transitionProgressFlow = underTest.transitionProgressToScene(targetScene)
+ val transitionProgress by collectLastValue(transitionProgressFlow)
+
+ var transitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(
+ ObservableCommunalTransitionState.Idle(currentScene)
+ )
+ underTest.setTransitionState(transitionState)
+
+ // Progress starts at 0.
+ assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Idle(currentScene))
+
+ val progress = MutableStateFlow(0f)
+ transitionState =
+ MutableStateFlow(
+ ObservableCommunalTransitionState.Transition(
+ fromScene = currentScene,
+ toScene = targetScene,
+ progress = progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+ underTest.setTransitionState(transitionState)
+
+ // Partially transition.
+ progress.value = .4f
+ assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Transition(.4f))
+
+ // Transition is at full progress.
+ progress.value = 1f
+ assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Transition(1f))
+
+ // Transition finishes.
+ transitionState = MutableStateFlow(ObservableCommunalTransitionState.Idle(targetScene))
+ underTest.setTransitionState(transitionState)
+ assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Idle(targetScene))
+ }
+
+ @Test
+ fun transitionProgress_transitioningAwayFromTrackedScene() =
+ testScope.runTest {
+ val currentScene = CommunalSceneKey.Blank
+ val targetScene = CommunalSceneKey.Communal
+ val transitionProgressFlow = underTest.transitionProgressToScene(currentScene)
+ val transitionProgress by collectLastValue(transitionProgressFlow)
+
+ var transitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(
+ ObservableCommunalTransitionState.Idle(currentScene)
+ )
+ underTest.setTransitionState(transitionState)
+
+ // Progress starts at 0.
+ assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Idle(currentScene))
+
+ val progress = MutableStateFlow(0f)
+ transitionState =
+ MutableStateFlow(
+ ObservableCommunalTransitionState.Transition(
+ fromScene = currentScene,
+ toScene = targetScene,
+ progress = progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+ underTest.setTransitionState(transitionState)
+
+ // Partially transition.
+ progress.value = .4f
+
+ // This is a transition we don't care about the progress of.
+ assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.OtherTransition)
+
+ // Transition is at full progress.
+ progress.value = 1f
+ assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.OtherTransition)
+
+ // Transition finishes.
+ transitionState = MutableStateFlow(ObservableCommunalTransitionState.Idle(targetScene))
+ underTest.setTransitionState(transitionState)
+ assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Idle(targetScene))
+ }
+
+ @Test
fun isCommunalShowing() =
testScope.runTest {
var isCommunalShowing = collectLastValue(underTest.isCommunalShowing)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt
new file mode 100644
index 0000000..721fc49
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt
@@ -0,0 +1,205 @@
+/*
+ * 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.log
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
+import com.android.systemui.communal.shared.log.CommunalUiEvent
+import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class CommunalLoggerStartableTest : SysuiTestCase() {
+ @Mock private lateinit var uiEventLogger: UiEventLogger
+
+ private lateinit var testScope: TestScope
+ private lateinit var communalInteractor: CommunalInteractor
+ private lateinit var underTest: CommunalLoggerStartable
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ val withDeps = CommunalInteractorFactory.create()
+ testScope = withDeps.testScope
+ communalInteractor = withDeps.communalInteractor
+
+ underTest =
+ CommunalLoggerStartable(
+ testScope.backgroundScope,
+ communalInteractor,
+ uiEventLogger,
+ )
+ underTest.start()
+ }
+
+ @Test
+ fun transitionStateLogging_enterCommunalHub() =
+ testScope.runTest {
+ // Transition state is default (non-communal)
+ val transitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(idle(CommunalSceneKey.DEFAULT))
+ communalInteractor.setTransitionState(transitionState)
+ runCurrent()
+
+ // Verify nothing is logged from the default state
+ verify(uiEventLogger, never()).log(any())
+
+ // Start transition to communal
+ transitionState.value = transition(to = CommunalSceneKey.Communal)
+ runCurrent()
+
+ // Verify UiEvent logged
+ verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_START)
+
+ // Finish transition to communal
+ transitionState.value = idle(CommunalSceneKey.Communal)
+ runCurrent()
+
+ // Verify UiEvent logged
+ verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_FINISH)
+ verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SHOWN)
+ }
+
+ @Test
+ fun transitionStateLogging_enterCommunalHub_canceled() =
+ testScope.runTest {
+ // Transition state is default (non-communal)
+ val transitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(idle(CommunalSceneKey.DEFAULT))
+ communalInteractor.setTransitionState(transitionState)
+ runCurrent()
+
+ // Verify nothing is logged from the default state
+ verify(uiEventLogger, never()).log(any())
+
+ // Start transition to communal
+ transitionState.value = transition(to = CommunalSceneKey.Communal)
+ runCurrent()
+
+ // Verify UiEvent logged
+ verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_START)
+
+ // Cancel the transition
+ transitionState.value = idle(CommunalSceneKey.DEFAULT)
+ runCurrent()
+
+ // Verify UiEvent logged
+ verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_CANCEL)
+
+ // Verify neither SHOWN nor GONE is logged
+ verify(uiEventLogger, never()).log(CommunalUiEvent.COMMUNAL_HUB_SHOWN)
+ verify(uiEventLogger, never()).log(CommunalUiEvent.COMMUNAL_HUB_GONE)
+ }
+
+ @Test
+ fun transitionStateLogging_exitCommunalHub() =
+ testScope.runTest {
+ // Transition state is communal
+ val transitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(idle(CommunalSceneKey.Communal))
+ communalInteractor.setTransitionState(transitionState)
+ runCurrent()
+
+ // Verify SHOWN is logged when it's the default state
+ verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SHOWN)
+
+ // Start transition from communal
+ transitionState.value = transition(from = CommunalSceneKey.Communal)
+ runCurrent()
+
+ // Verify UiEvent logged
+ verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_START)
+
+ // Finish transition to communal
+ transitionState.value = idle(CommunalSceneKey.DEFAULT)
+ runCurrent()
+
+ // Verify UiEvent logged
+ verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_FINISH)
+ verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_GONE)
+ }
+
+ @Test
+ fun transitionStateLogging_exitCommunalHub_canceled() =
+ testScope.runTest {
+ // Transition state is communal
+ val transitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(idle(CommunalSceneKey.Communal))
+ communalInteractor.setTransitionState(transitionState)
+ runCurrent()
+
+ // Clear the initial SHOWN event from the logger
+ clearInvocations(uiEventLogger)
+
+ // Start transition from communal
+ transitionState.value = transition(from = CommunalSceneKey.Communal)
+ runCurrent()
+
+ // Verify UiEvent logged
+ verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_START)
+
+ // Cancel the transition
+ transitionState.value = idle(CommunalSceneKey.Communal)
+ runCurrent()
+
+ // Verify UiEvent logged
+ verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_CANCEL)
+
+ // Verify neither SHOWN nor GONE is logged
+ verify(uiEventLogger, never()).log(CommunalUiEvent.COMMUNAL_HUB_SHOWN)
+ verify(uiEventLogger, never()).log(CommunalUiEvent.COMMUNAL_HUB_GONE)
+ }
+
+ private fun transition(
+ from: CommunalSceneKey = CommunalSceneKey.DEFAULT,
+ to: CommunalSceneKey = CommunalSceneKey.DEFAULT,
+ ): ObservableCommunalTransitionState.Transition {
+ return ObservableCommunalTransitionState.Transition(
+ fromScene = from,
+ toScene = to,
+ progress = emptyFlow(),
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = emptyFlow(),
+ )
+ }
+
+ private fun idle(sceneKey: CommunalSceneKey): ObservableCommunalTransitionState.Idle {
+ return ObservableCommunalTransitionState.Idle(sceneKey)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
new file mode 100644
index 0000000..e904236
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.ui.widgets
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
+import com.android.systemui.communal.widgets.CommunalAppWidgetHostView
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalAppWidgetHostTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private lateinit var underTest: CommunalAppWidgetHost
+
+ @Before
+ fun setUp() {
+ underTest = CommunalAppWidgetHost(context = context, hostId = 116)
+ }
+
+ @Test
+ fun createViewForCommunal_returnCommunalAppWidgetView() =
+ testScope.runTest {
+ val appWidgetId = 789
+ val view =
+ underTest.createViewForCommunal(
+ context = context,
+ appWidgetId = appWidgetId,
+ appWidget = null
+ )
+ assertThat(view).isInstanceOf(CommunalAppWidgetHostView::class.java)
+ assertThat(view).isNotNull()
+ assertThat(view.appWidgetId).isEqualTo(appWidgetId)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index ff6fd43..09243e5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -19,9 +19,7 @@
import android.app.Activity.RESULT_CANCELED
import android.app.Activity.RESULT_OK
import android.app.smartspace.SmartspaceTarget
-import android.appwidget.AppWidgetHost
import android.content.ComponentName
-import android.os.PowerManager
import android.provider.Settings
import android.widget.RemoteViews
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -37,17 +35,16 @@
import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.ui.MediaHost
-import com.android.systemui.shade.ShadeViewController
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import javax.inject.Provider
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -64,9 +61,7 @@
@RunWith(AndroidJUnit4::class)
class CommunalEditModeViewModelTest : SysuiTestCase() {
@Mock private lateinit var mediaHost: MediaHost
- @Mock private lateinit var shadeViewController: ShadeViewController
- @Mock private lateinit var powerManager: PowerManager
- @Mock private lateinit var appWidgetHost: AppWidgetHost
+ @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
@Mock private lateinit var uiEventLogger: UiEventLogger
private val kosmos = testKosmos()
@@ -97,8 +92,6 @@
CommunalEditModeViewModel(
withDeps.communalInteractor,
appWidgetHost,
- Provider { shadeViewController },
- powerManager,
mediaHost,
uiEventLogger,
)
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 8e3f664..f9cfc37 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
@@ -17,13 +17,13 @@
package com.android.systemui.communal.view.viewmodel
import android.app.smartspace.SmartspaceTarget
-import android.os.PowerManager
import android.provider.Settings
import android.widget.RemoteViews
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository
import com.android.systemui.communal.data.repository.FakeCommunalRepository
import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
@@ -35,13 +35,12 @@
import com.android.systemui.communal.widgets.WidgetInteractionHandler
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.media.controls.ui.MediaHierarchyManager
import com.android.systemui.media.controls.ui.MediaHost
-import com.android.systemui.shade.ShadeViewController
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import javax.inject.Provider
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
@@ -51,6 +50,7 @@
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@@ -58,8 +58,6 @@
@RunWith(AndroidJUnit4::class)
class CommunalViewModelTest : SysuiTestCase() {
@Mock private lateinit var mediaHost: MediaHost
- @Mock private lateinit var shadeViewController: ShadeViewController
- @Mock private lateinit var powerManager: PowerManager
private lateinit var testScope: TestScope
@@ -69,6 +67,7 @@
private lateinit var widgetRepository: FakeCommunalWidgetRepository
private lateinit var smartspaceRepository: FakeSmartspaceRepository
private lateinit var mediaRepository: FakeCommunalMediaRepository
+ private lateinit var communalPrefsRepository: FakeCommunalPrefsRepository
private lateinit var underTest: CommunalViewModel
@@ -85,6 +84,7 @@
widgetRepository = withDeps.widgetRepository
smartspaceRepository = withDeps.smartspaceRepository
mediaRepository = withDeps.mediaRepository
+ communalPrefsRepository = withDeps.communalPrefsRepository
underTest =
CommunalViewModel(
@@ -92,13 +92,18 @@
withDeps.communalInteractor,
WidgetInteractionHandler(mock()),
withDeps.tutorialInteractor,
- Provider { shadeViewController },
- powerManager,
mediaHost,
)
}
@Test
+ fun init_initsMediaHost() =
+ testScope.runTest {
+ // MediaHost is initialized as soon as the class is created.
+ verify(mediaHost).init(MediaHierarchyManager.LOCATION_COMMUNAL_HUB)
+ }
+
+ @Test
fun tutorial_tutorialNotCompletedAndKeyguardVisible_showTutorialContent() =
testScope.runTest {
// Keyguard showing, and tutorial not started.
@@ -147,9 +152,6 @@
// Media playing.
mediaRepository.mediaActive()
- // CTA Tile not dismissed.
- communalRepository.setCtaTileInViewModeVisibility(true)
-
val communalContent by collectLastValue(underTest.communalContent)
// Order is smart space, then UMO, widget content and cta tile.
@@ -169,7 +171,6 @@
fun dismissCta_hidesCtaTileAndShowsPopup_thenHidesPopupAfterTimeout() =
testScope.runTest {
tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
- communalRepository.setCtaTileInViewModeVisibility(true)
val communalContent by collectLastValue(underTest.communalContent)
val isPopupOnDismissCtaShowing by collectLastValue(underTest.isPopupOnDismissCtaShowing)
@@ -193,7 +194,6 @@
fun popup_onDismiss_hidesImmediately() =
testScope.runTest {
tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
- communalRepository.setCtaTileInViewModeVisibility(true)
val isPopupOnDismissCtaShowing by collectLastValue(underTest.isPopupOnDismissCtaShowing)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
index 565049b..b54c5bd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
@@ -7,9 +7,11 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.whenever
@@ -36,8 +38,8 @@
@Mock private lateinit var keyguardBypassController: KeyguardBypassController
@Mock private lateinit var keyguardStateController: KeyguardStateController
- private val testUtils = SceneTestUtils(this)
- private val testScope = testUtils.testScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private val userRepository = FakeUserRepository()
private val keyguardRepository = FakeKeyguardRepository()
@@ -52,7 +54,7 @@
underTest =
DeviceEntryRepositoryImpl(
applicationScope = testScope.backgroundScope,
- backgroundDispatcher = testUtils.testDispatcher,
+ backgroundDispatcher = kosmos.testDispatcher,
userRepository = userRepository,
lockPatternUtils = lockPatternUtils,
keyguardBypassController = keyguardBypassController,
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 ea19cb7..52305b1 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
@@ -20,19 +20,25 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
-import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeTrustRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
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
@@ -41,21 +47,19 @@
@RunWith(AndroidJUnit4::class)
class DeviceEntryInteractorTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val repository: FakeDeviceEntryRepository = utils.deviceEntryRepository
- private val faceAuthRepository = FakeDeviceEntryFaceAuthRepository()
- private val trustRepository = FakeTrustRepository()
- private val sceneInteractor = utils.sceneInteractor()
- private val authenticationInteractor = utils.authenticationInteractor()
- private val underTest =
- utils.deviceEntryInteractor(
- repository = repository,
- authenticationInteractor = authenticationInteractor,
- sceneInteractor = sceneInteractor,
- faceAuthRepository = faceAuthRepository,
- trustRepository = trustRepository,
- )
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val faceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository
+ private val trustRepository = kosmos.fakeTrustRepository
+ private val sceneInteractor = kosmos.sceneInteractor
+ private val authenticationInteractor = kosmos.authenticationInteractor
+ private lateinit var underTest: DeviceEntryInteractor
+
+ @Before
+ fun setUp() {
+ kosmos.fakeSceneContainerFlags.enabled = true
+ underTest = kosmos.deviceEntryInteractor
+ }
@Test
fun canSwipeToEnter_startsNull() =
@@ -67,8 +71,10 @@
@Test
fun isUnlocked_whenAuthMethodIsNoneAndLockscreenDisabled_isTrue() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
- utils.deviceEntryRepository.apply {
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
+ kosmos.fakeDeviceEntryRepository.apply {
setLockscreenEnabled(false)
// Toggle isUnlocked, twice.
@@ -101,8 +107,10 @@
@Test
fun isUnlocked_whenAuthMethodIsSimAndUnlocked_isFalse() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Sim)
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Sim
+ )
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
val isUnlocked by collectLastValue(underTest.isUnlocked)
assertThat(isUnlocked).isFalse()
@@ -159,10 +167,10 @@
@Test
fun isDeviceEntered_onBouncer_isFalse() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pattern
)
- utils.deviceEntryRepository.setLockscreenEnabled(true)
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
switchToScene(SceneKey.Lockscreen)
runCurrent()
switchToScene(SceneKey.Bouncer)
@@ -184,8 +192,10 @@
@Test
fun canSwipeToEnter_onLockscreenWithPin_isFalse() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.deviceEntryRepository.setLockscreenEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
switchToScene(SceneKey.Lockscreen)
val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter)
@@ -205,15 +215,15 @@
}
private fun setupSwipeDeviceEntryMethod() {
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
- utils.deviceEntryRepository.setLockscreenEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
}
@Test
fun canSwipeToEnter_whenTrustedByTrustManager_isTrue() =
testScope.runTest {
val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter)
- utils.authenticationRepository.setAuthenticationMethod(
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
switchToScene(SceneKey.Lockscreen)
@@ -230,7 +240,7 @@
fun canSwipeToEnter_whenAuthenticatedByFace_isTrue() =
testScope.runTest {
val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter)
- utils.authenticationRepository.setAuthenticationMethod(
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
switchToScene(SceneKey.Lockscreen)
@@ -246,9 +256,9 @@
@Test
fun isAuthenticationRequired_lockedAndSecured_true() =
testScope.runTest {
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
runCurrent()
- utils.authenticationRepository.setAuthenticationMethod(
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
@@ -258,9 +268,11 @@
@Test
fun isAuthenticationRequired_lockedAndNotSecured_false() =
testScope.runTest {
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
runCurrent()
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
assertThat(underTest.isAuthenticationRequired()).isFalse()
}
@@ -268,9 +280,9 @@
@Test
fun isAuthenticationRequired_unlockedAndSecured_false() =
testScope.runTest {
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
runCurrent()
- utils.authenticationRepository.setAuthenticationMethod(
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
@@ -280,9 +292,11 @@
@Test
fun isAuthenticationRequired_unlockedAndNotSecured_false() =
testScope.runTest {
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
runCurrent()
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
assertThat(underTest.isAuthenticationRequired()).isFalse()
}
@@ -290,7 +304,7 @@
@Test
fun isBypassEnabled_enabledInRepository_true() =
testScope.runTest {
- utils.deviceEntryRepository.setBypassEnabled(true)
+ kosmos.fakeDeviceEntryRepository.setBypassEnabled(true)
assertThat(underTest.isBypassEnabled.value).isTrue()
}
@@ -301,8 +315,10 @@
switchToScene(SceneKey.Lockscreen)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
runCurrent()
underTest.attemptDeviceEntry()
@@ -317,7 +333,10 @@
switchToScene(SceneKey.Lockscreen)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
+ runCurrent()
underTest.attemptDeviceEntry()
@@ -331,8 +350,11 @@
switchToScene(SceneKey.Lockscreen)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
- utils.deviceEntryRepository.setLockscreenEnabled(true)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
+ runCurrent()
underTest.attemptDeviceEntry()
@@ -342,7 +364,7 @@
@Test
fun isBypassEnabled_disabledInRepository_false() =
testScope.runTest {
- utils.deviceEntryRepository.setBypassEnabled(false)
+ kosmos.fakeDeviceEntryRepository.setBypassEnabled(false)
assertThat(underTest.isBypassEnabled.value).isFalse()
}
@@ -350,8 +372,10 @@
fun successfulAuthenticationChallengeAttempt_updatesIsUnlockedState() =
testScope.runTest {
val isUnlocked by collectLastValue(underTest.isUnlocked)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.deviceEntryRepository.setLockscreenEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
assertThat(isUnlocked).isFalse()
authenticationInteractor.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
new file mode 100644
index 0000000..32943a1
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.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.deviceentry.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.kosmos.testScope
+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 DeviceUnlockedInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val authenticationRepository = kosmos.fakeAuthenticationRepository
+ private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
+
+ val underTest =
+ DeviceUnlockedInteractor(
+ applicationScope = testScope.backgroundScope,
+ authenticationInteractor = kosmos.authenticationInteractor,
+ deviceEntryRepository = deviceEntryRepository,
+ )
+
+ @Test
+ fun isDeviceUnlocked_whenUnlockedAndAuthMethodIsNone_isTrue() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(underTest.isDeviceUnlocked)
+
+ deviceEntryRepository.setUnlocked(true)
+ authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+
+ assertThat(isUnlocked).isTrue()
+ }
+
+ @Test
+ fun isDeviceUnlocked_whenUnlockedAndAuthMethodIsPin_isTrue() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(underTest.isDeviceUnlocked)
+
+ deviceEntryRepository.setUnlocked(true)
+ authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+
+ assertThat(isUnlocked).isTrue()
+ }
+
+ @Test
+ fun isDeviceUnlocked_whenUnlockedAndAuthMethodIsSim_isFalse() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(underTest.isDeviceUnlocked)
+
+ deviceEntryRepository.setUnlocked(true)
+ authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Sim)
+
+ assertThat(isUnlocked).isFalse()
+ }
+
+ @Test
+ fun isDeviceUnlocked_whenLockedAndAuthMethodIsNone_isTrue() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(underTest.isDeviceUnlocked)
+
+ deviceEntryRepository.setUnlocked(false)
+ authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+
+ assertThat(isUnlocked).isTrue()
+ }
+
+ @Test
+ fun isDeviceUnlocked_whenLockedAndAuthMethodIsPin_isFalse() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(underTest.isDeviceUnlocked)
+
+ deviceEntryRepository.setUnlocked(false)
+ authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+
+ assertThat(isUnlocked).isFalse()
+ }
+
+ @Test
+ fun isDeviceUnlocked_whenLockedAndAuthMethodIsSim_isFalse() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(underTest.isDeviceUnlocked)
+
+ deviceEntryRepository.setUnlocked(false)
+ authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Sim)
+
+ assertThat(isUnlocked).isFalse()
+ }
+}
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 c4ebbdc..6f62afc 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
@@ -35,6 +35,7 @@
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
@@ -52,6 +53,8 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.verify
@@ -68,6 +71,8 @@
@Mock private lateinit var authController: AuthController
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var dreamOverlayCallbackController: DreamOverlayCallbackController
+ @Mock private lateinit var userTracker: UserTracker
+ @Captor private lateinit var updateCallbackCaptor: ArgumentCaptor<KeyguardUpdateMonitorCallback>
private val mainDispatcher = StandardTestDispatcher()
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -93,6 +98,7 @@
testScope.backgroundScope,
systemClock,
facePropertyRepository,
+ userTracker,
)
}
@@ -553,4 +559,25 @@
job.cancel()
}
+
+ @Test
+ fun isEncryptedOrLockdown() =
+ testScope.runTest {
+ whenever(userTracker.userId).thenReturn(0)
+ whenever(keyguardUpdateMonitor.isEncryptedOrLockdown(0)).thenReturn(true)
+
+ // Default value for isEncryptedOrLockdown is true
+ val isEncryptedOrLockdown by collectLastValue(underTest.isEncryptedOrLockdown)
+ assertThat(isEncryptedOrLockdown).isTrue()
+
+ verify(keyguardUpdateMonitor).registerCallback(updateCallbackCaptor.capture())
+ val updateCallback = updateCallbackCaptor.value
+
+ // Strong auth state updated
+ whenever(keyguardUpdateMonitor.isEncryptedOrLockdown(0)).thenReturn(false)
+ updateCallback.onStrongAuthStateChanged(0)
+
+ // Verify no longer encrypted or lockdown
+ assertThat(isEncryptedOrLockdown).isFalse()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index 11939c1..2c3afb1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -26,12 +26,16 @@
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
+import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -48,10 +52,10 @@
@RunWith(AndroidJUnit4::class)
class KeyguardInteractorTest : SysuiTestCase() {
- private val testUtils = SceneTestUtils(this)
- private val testScope = testUtils.testScope
- private val repository = testUtils.keyguardRepository
- private val sceneInteractor = testUtils.sceneInteractor()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val repository = kosmos.fakeKeyguardRepository
+ private val sceneInteractor = kosmos.sceneInteractor
private val commandQueue = FakeCommandQueue()
private val bouncerRepository = FakeKeyguardBouncerRepository()
private val shadeRepository = FakeShadeRepository()
@@ -63,7 +67,7 @@
repository = repository,
commandQueue = commandQueue,
powerInteractor = PowerInteractorFactory.create().powerInteractor,
- sceneContainerFlags = testUtils.sceneContainerFlags,
+ sceneContainerFlags = kosmos.fakeSceneContainerFlags,
bouncerRepository = bouncerRepository,
configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()),
shadeRepository = shadeRepository,
@@ -183,6 +187,7 @@
@Test
fun animationDozingTransitions() =
testScope.runTest {
+ kosmos.fakeSceneContainerFlags.enabled = true
val isAnimate by collectLastValue(underTest.animateDozingTransitions)
underTest.setAnimateDozingTransitions(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 4f7d944..6828041 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -21,23 +21,24 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.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.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertEquals
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -46,18 +47,11 @@
@kotlinx.coroutines.ExperimentalCoroutinesApi
class KeyguardTransitionInteractorTest : SysuiTestCase() {
- private lateinit var underTest: KeyguardTransitionInteractor
- private lateinit var repository: FakeKeyguardTransitionRepository
- private val testScope = TestScope()
+ val kosmos = testKosmos()
- @Before
- fun setUp() {
- repository = FakeKeyguardTransitionRepository()
- underTest = KeyguardTransitionInteractorFactory.create(
- scope = testScope.backgroundScope,
- repository = repository,
- ).keyguardTransitionInteractor
- }
+ val underTest = kosmos.keyguardTransitionInteractor
+ val repository = kosmos.fakeKeyguardTransitionRepository
+ val testScope = kosmos.testScope
@Test
fun transitionCollectorsReceivesOnlyAppropriateEvents() = runTest {
@@ -114,48 +108,50 @@
}
@Test
- fun finishedKeyguardStateTests() = testScope.runTest {
- val finishedSteps by collectValues(underTest.finishedKeyguardState)
- runCurrent()
- val steps = mutableListOf<TransitionStep>()
-
- steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0f, STARTED))
- steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0.5f, RUNNING))
- steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 1f, FINISHED))
- steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0f, STARTED))
- steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0.9f, RUNNING))
- steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 1f, FINISHED))
- steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
-
- steps.forEach {
- repository.sendTransitionStep(it)
+ fun finishedKeyguardStateTests() =
+ testScope.runTest {
+ val finishedSteps by collectValues(underTest.finishedKeyguardState)
runCurrent()
- }
+ val steps = mutableListOf<TransitionStep>()
- assertThat(finishedSteps).isEqualTo(listOf(LOCKSCREEN, PRIMARY_BOUNCER, AOD))
- }
+ steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0f, STARTED))
+ steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0.5f, RUNNING))
+ steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 1f, FINISHED))
+ steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0f, STARTED))
+ steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0.9f, RUNNING))
+ steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 1f, FINISHED))
+ steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
+
+ steps.forEach {
+ repository.sendTransitionStep(it)
+ runCurrent()
+ }
+
+ assertThat(finishedSteps).isEqualTo(listOf(LOCKSCREEN, PRIMARY_BOUNCER, AOD))
+ }
@Test
- fun startedKeyguardStateTests() = testScope.runTest {
- val startedStates by collectValues(underTest.startedKeyguardState)
- runCurrent()
- val steps = mutableListOf<TransitionStep>()
-
- steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0f, STARTED))
- steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0.5f, RUNNING))
- steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 1f, FINISHED))
- steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0f, STARTED))
- steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0.9f, RUNNING))
- steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 1f, FINISHED))
- steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
-
- steps.forEach {
- repository.sendTransitionStep(it)
+ fun startedKeyguardStateTests() =
+ testScope.runTest {
+ val startedStates by collectValues(underTest.startedKeyguardState)
runCurrent()
- }
+ val steps = mutableListOf<TransitionStep>()
- assertThat(startedStates).isEqualTo(listOf(LOCKSCREEN, PRIMARY_BOUNCER, AOD, GONE))
- }
+ steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0f, STARTED))
+ steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0.5f, RUNNING))
+ steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 1f, FINISHED))
+ steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0f, STARTED))
+ steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0.9f, RUNNING))
+ steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 1f, FINISHED))
+ steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
+
+ steps.forEach {
+ repository.sendTransitionStep(it)
+ runCurrent()
+ }
+
+ assertThat(startedStates).isEqualTo(listOf(LOCKSCREEN, PRIMARY_BOUNCER, AOD, GONE))
+ }
@Test
fun finishedKeyguardTransitionStepTests() = runTest {
@@ -178,7 +174,7 @@
// Ignore the default state.
assertThat(finishedSteps.subList(1, finishedSteps.size))
- .isEqualTo(listOf(steps[2], steps[5]))
+ .isEqualTo(listOf(steps[2], steps[5]))
}
@Test
@@ -233,500 +229,1067 @@
}
@Test
- fun isInTransitionToState() = testScope.runTest {
- val results by collectValues(underTest.isInTransitionToState(GONE))
+ fun isInTransitionToAnyState() =
+ testScope.runTest {
+ val inTransition by collectValues(underTest.isInTransitionToAnyState)
- sendSteps(
+ assertEquals(
+ listOf(
+ true, // The repo is seeded with a transition from OFF to LOCKSCREEN.
+ false,
+ ),
+ inTransition
+ )
+
+ sendSteps(
+ TransitionStep(LOCKSCREEN, GONE, 0f, STARTED),
+ )
+
+ assertEquals(
+ listOf(
+ true,
+ false,
+ true,
+ ),
+ inTransition
+ )
+
+ sendSteps(
+ TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING),
+ )
+
+ assertEquals(
+ listOf(
+ true,
+ false,
+ true,
+ ),
+ inTransition
+ )
+
+ sendSteps(
+ TransitionStep(LOCKSCREEN, GONE, 1f, FINISHED),
+ )
+
+ assertEquals(
+ listOf(
+ true,
+ false,
+ true,
+ false,
+ ),
+ inTransition
+ )
+ }
+
+ @Test
+ fun isInTransitionToAnyState_finishedStateIsStartedStateAfterCancels() =
+ testScope.runTest {
+ val inTransition by collectValues(underTest.isInTransitionToAnyState)
+
+ assertEquals(
+ listOf(
+ true,
+ false,
+ ),
+ inTransition
+ )
+
+ // Start FINISHED in GONE.
+ sendSteps(
+ TransitionStep(LOCKSCREEN, GONE, 0f, STARTED),
+ TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING),
+ TransitionStep(LOCKSCREEN, GONE, 1f, FINISHED),
+ )
+
+ assertEquals(
+ listOf(
+ true,
+ false,
+ true,
+ false,
+ ),
+ inTransition
+ )
+
+ sendSteps(
+ TransitionStep(GONE, DOZING, 0f, STARTED),
+ )
+
+ assertEquals(
+ listOf(
+ true,
+ false,
+ true,
+ false,
+ true,
+ ),
+ inTransition
+ )
+
+ sendSteps(
+ TransitionStep(GONE, DOZING, 0.5f, RUNNING),
+ TransitionStep(GONE, DOZING, 0.6f, CANCELED),
+ TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED),
+ TransitionStep(DOZING, LOCKSCREEN, 0.5f, RUNNING),
+ TransitionStep(DOZING, LOCKSCREEN, 0.6f, CANCELED),
+ TransitionStep(LOCKSCREEN, GONE, 0f, STARTED),
+ )
+
+ assertEquals(
+ listOf(
+ true,
+ false,
+ true,
+ false,
+ // We should have been in transition throughout the entire transition, including
+ // both cancellations, and we should still be in transition despite now
+ // transitioning to GONE, the state we're also FINISHED in.
+ true,
+ ),
+ inTransition
+ )
+
+ sendSteps(
+ TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING),
+ TransitionStep(LOCKSCREEN, GONE, 1f, FINISHED),
+ )
+
+ assertEquals(
+ listOf(
+ true,
+ false,
+ true,
+ false,
+ true,
+ false,
+ ),
+ inTransition
+ )
+ }
+
+ @Test
+ fun isInTransitionToState() =
+ testScope.runTest {
+ val results by collectValues(underTest.isInTransitionToState(GONE))
+
+ sendSteps(
TransitionStep(AOD, DOZING, 0f, STARTED),
TransitionStep(AOD, DOZING, 0.5f, RUNNING),
TransitionStep(AOD, DOZING, 1f, FINISHED),
- )
+ )
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- ))
-
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, STARTED),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, RUNNING),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, FINISHED),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(GONE, DOZING, 0f, STARTED),
TransitionStep(GONE, DOZING, 0f, RUNNING),
TransitionStep(GONE, DOZING, 1f, FINISHED),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, STARTED),
TransitionStep(DOZING, GONE, 0f, RUNNING),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- true,
- ))
- }
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ true,
+ )
+ )
+ }
@Test
- fun isInTransitionFromState() = testScope.runTest {
- val results by collectValues(underTest.isInTransitionFromState(DOZING))
+ fun isInTransitionFromState() =
+ testScope.runTest {
+ val results by collectValues(underTest.isInTransitionFromState(DOZING))
- sendSteps(
+ sendSteps(
TransitionStep(AOD, DOZING, 0f, STARTED),
TransitionStep(AOD, DOZING, 0.5f, RUNNING),
TransitionStep(AOD, DOZING, 1f, FINISHED),
- )
+ )
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- ))
-
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, STARTED),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, RUNNING),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, FINISHED),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(GONE, DOZING, 0f, STARTED),
TransitionStep(GONE, DOZING, 0f, RUNNING),
TransitionStep(GONE, DOZING, 1f, FINISHED),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, STARTED),
TransitionStep(DOZING, GONE, 0f, RUNNING),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- true,
- ))
- }
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ true,
+ )
+ )
+ }
@Test
- fun isInTransitionFromStateWhere() = testScope.runTest {
- val results by collectValues(underTest.isInTransitionFromStateWhere {
- it == DOZING
- })
+ fun isInTransitionFromStateWhere() =
+ testScope.runTest {
+ val results by collectValues(underTest.isInTransitionFromStateWhere { it == DOZING })
- sendSteps(
+ sendSteps(
TransitionStep(AOD, DOZING, 0f, STARTED),
TransitionStep(AOD, DOZING, 0.5f, RUNNING),
TransitionStep(AOD, DOZING, 1f, FINISHED),
- )
+ )
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- ))
-
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, STARTED),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, RUNNING),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, FINISHED),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(GONE, DOZING, 0f, STARTED),
TransitionStep(GONE, DOZING, 0f, RUNNING),
TransitionStep(GONE, DOZING, 1f, FINISHED),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, STARTED),
TransitionStep(DOZING, GONE, 0f, RUNNING),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- true,
- ))
- }
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ true,
+ )
+ )
+ }
@Test
- fun isInTransitionWhere() = testScope.runTest {
- val results by collectValues(underTest.isInTransitionWhere(
- fromStatePredicate = { it == DOZING },
- toStatePredicate = { it == GONE },
- ))
+ fun isInTransitionWhere() =
+ testScope.runTest {
+ val results by
+ collectValues(
+ underTest.isInTransitionWhere(
+ fromStatePredicate = { it == DOZING },
+ toStatePredicate = { it == GONE },
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(AOD, DOZING, 0f, STARTED),
TransitionStep(AOD, DOZING, 0.5f, RUNNING),
TransitionStep(AOD, DOZING, 1f, FINISHED),
- )
+ )
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- ))
-
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, STARTED),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, RUNNING),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, FINISHED),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(GONE, DOZING, 0f, STARTED),
TransitionStep(GONE, DOZING, 0f, RUNNING),
TransitionStep(GONE, DOZING, 1f, FINISHED),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, STARTED),
TransitionStep(DOZING, GONE, 0f, RUNNING),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- true,
- ))
- }
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ true,
+ )
+ )
+ }
@Test
- fun isFinishedInStateWhere() = testScope.runTest {
- val results by collectValues(underTest.isFinishedInStateWhere { it == GONE } )
+ fun isInTransitionWhere_withCanceledStep() =
+ testScope.runTest {
+ val results by
+ collectValues(
+ underTest.isInTransitionWhere(
+ fromStatePredicate = { it == DOZING },
+ toStatePredicate = { it == GONE },
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(AOD, DOZING, 0f, STARTED),
TransitionStep(AOD, DOZING, 0.5f, RUNNING),
TransitionStep(AOD, DOZING, 1f, FINISHED),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false, // Finished in DOZING, not GONE.
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ )
+ )
- sendSteps(TransitionStep(DOZING, GONE, 0f, STARTED))
+ sendSteps(
+ TransitionStep(DOZING, GONE, 0f, STARTED),
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ )
+ )
- sendSteps(TransitionStep(DOZING, GONE, 0f, RUNNING))
+ sendSteps(
+ TransitionStep(DOZING, GONE, 0f, RUNNING),
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ )
+ )
- sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
+ sendSteps(
+ TransitionStep(DOZING, GONE, 0f, CANCELED),
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(GONE, DOZING, 0f, STARTED),
TransitionStep(GONE, DOZING, 0f, RUNNING),
- )
+ TransitionStep(GONE, DOZING, 1f, FINISHED),
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ )
+ )
- sendSteps(TransitionStep(GONE, DOZING, 1f, FINISHED))
-
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- ))
-
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, STARTED),
TransitionStep(DOZING, GONE, 0f, RUNNING),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- ))
-
- sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
-
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- true,
- ))
- }
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ true,
+ )
+ )
+ }
@Test
- fun isFinishedInState() = testScope.runTest {
- val results by collectValues(underTest.isFinishedInState(GONE))
+ fun isFinishedInStateWhere() =
+ testScope.runTest {
+ val results by collectValues(underTest.isFinishedInStateWhere { it == GONE })
- sendSteps(
+ sendSteps(
TransitionStep(AOD, DOZING, 0f, STARTED),
TransitionStep(AOD, DOZING, 0.5f, RUNNING),
TransitionStep(AOD, DOZING, 1f, FINISHED),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false, // Finished in DOZING, not GONE.
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false, // Finished in DOZING, not GONE.
+ )
+ )
- sendSteps(TransitionStep(DOZING, GONE, 0f, STARTED))
+ sendSteps(TransitionStep(DOZING, GONE, 0f, STARTED))
- assertThat(results).isEqualTo(listOf(
- false,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ )
+ )
- sendSteps(TransitionStep(DOZING, GONE, 0f, RUNNING))
+ sendSteps(TransitionStep(DOZING, GONE, 0f, RUNNING))
- assertThat(results).isEqualTo(listOf(
- false,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ )
+ )
- sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
+ sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(GONE, DOZING, 0f, STARTED),
TransitionStep(GONE, DOZING, 0f, RUNNING),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ )
+ )
- sendSteps(TransitionStep(GONE, DOZING, 1f, FINISHED))
+ sendSteps(TransitionStep(GONE, DOZING, 1f, FINISHED))
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, STARTED),
TransitionStep(DOZING, GONE, 0f, RUNNING),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ )
+ )
- sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
+ sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- true,
- ))
- }
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ true,
+ )
+ )
+ }
@Test
- fun finishedKeyguardState_emitsAgainIfCancelledAndReversed() = testScope.runTest {
- val finishedStates by collectValues(underTest.finishedKeyguardState)
+ fun isFinishedInState() =
+ testScope.runTest {
+ val results by collectValues(underTest.isFinishedInState(GONE))
- // We default FINISHED in LOCKSCREEN.
- assertEquals(listOf(
- LOCKSCREEN
- ), finishedStates)
+ sendSteps(
+ TransitionStep(AOD, DOZING, 0f, STARTED),
+ TransitionStep(AOD, DOZING, 0.5f, RUNNING),
+ TransitionStep(AOD, DOZING, 1f, FINISHED),
+ )
- sendSteps(
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false, // Finished in DOZING, not GONE.
+ )
+ )
+
+ sendSteps(TransitionStep(DOZING, GONE, 0f, STARTED))
+
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ )
+ )
+
+ sendSteps(TransitionStep(DOZING, GONE, 0f, RUNNING))
+
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ )
+ )
+
+ sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
+
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ )
+ )
+
+ sendSteps(
+ TransitionStep(GONE, DOZING, 0f, STARTED),
+ TransitionStep(GONE, DOZING, 0f, RUNNING),
+ )
+
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ )
+ )
+
+ sendSteps(TransitionStep(GONE, DOZING, 1f, FINISHED))
+
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ )
+ )
+
+ sendSteps(
+ TransitionStep(DOZING, GONE, 0f, STARTED),
+ TransitionStep(DOZING, GONE, 0f, RUNNING),
+ )
+
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ )
+ )
+
+ sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
+
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ true,
+ )
+ )
+ }
+
+ @Test
+ fun finishedKeyguardState_emitsAgainIfCancelledAndReversed() =
+ testScope.runTest {
+ val finishedStates by collectValues(underTest.finishedKeyguardState)
+
+ // We default FINISHED in LOCKSCREEN.
+ assertEquals(listOf(LOCKSCREEN), finishedStates)
+
+ sendSteps(
TransitionStep(LOCKSCREEN, AOD, 0f, STARTED),
TransitionStep(LOCKSCREEN, AOD, 0.5f, RUNNING),
TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED),
- )
+ )
- // We're FINISHED in AOD.
- assertEquals(listOf(
- LOCKSCREEN,
- AOD,
- ), finishedStates)
+ // We're FINISHED in AOD.
+ assertEquals(
+ listOf(
+ LOCKSCREEN,
+ AOD,
+ ),
+ finishedStates
+ )
- // Transition back to LOCKSCREEN.
- sendSteps(
+ // Transition back to LOCKSCREEN.
+ sendSteps(
TransitionStep(AOD, LOCKSCREEN, 0f, STARTED),
TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING),
TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED),
- )
+ )
- // We're FINISHED in LOCKSCREEN.
- assertEquals(listOf(
- LOCKSCREEN,
- AOD,
- LOCKSCREEN,
- ), finishedStates)
+ // We're FINISHED in LOCKSCREEN.
+ assertEquals(
+ listOf(
+ LOCKSCREEN,
+ AOD,
+ LOCKSCREEN,
+ ),
+ finishedStates
+ )
- sendSteps(
+ sendSteps(
TransitionStep(LOCKSCREEN, GONE, 0f, STARTED),
TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING),
- )
+ )
- // We've STARTED a transition to GONE but not yet finished it so we're still FINISHED in
- // LOCKSCREEN.
- assertEquals(listOf(
- LOCKSCREEN,
- AOD,
- LOCKSCREEN,
- ), finishedStates)
+ // We've STARTED a transition to GONE but not yet finished it so we're still FINISHED in
+ // LOCKSCREEN.
+ assertEquals(
+ listOf(
+ LOCKSCREEN,
+ AOD,
+ LOCKSCREEN,
+ ),
+ finishedStates
+ )
- sendSteps(
+ sendSteps(
TransitionStep(LOCKSCREEN, GONE, 0.6f, CANCELED),
- )
+ )
- // We've CANCELED a transition to GONE, we're still FINISHED in LOCKSCREEN.
- assertEquals(listOf(
- LOCKSCREEN,
- AOD,
- LOCKSCREEN,
- ), finishedStates)
+ // We've CANCELED a transition to GONE, we're still FINISHED in LOCKSCREEN.
+ assertEquals(
+ listOf(
+ LOCKSCREEN,
+ AOD,
+ LOCKSCREEN,
+ ),
+ finishedStates
+ )
- sendSteps(
+ sendSteps(
TransitionStep(GONE, LOCKSCREEN, 0.6f, STARTED),
TransitionStep(GONE, LOCKSCREEN, 0.9f, RUNNING),
TransitionStep(GONE, LOCKSCREEN, 1f, FINISHED),
- )
+ )
- // Expect another emission of LOCKSCREEN, as we have FINISHED a second transition to
- // LOCKSCREEN after the cancellation.
- assertEquals(listOf(
- LOCKSCREEN,
- AOD,
- LOCKSCREEN,
- LOCKSCREEN,
- ), finishedStates)
- }
+ // Expect another emission of LOCKSCREEN, as we have FINISHED a second transition to
+ // LOCKSCREEN after the cancellation.
+ assertEquals(
+ listOf(
+ LOCKSCREEN,
+ AOD,
+ LOCKSCREEN,
+ LOCKSCREEN,
+ ),
+ finishedStates
+ )
+ }
+
+ @Test
+ fun testCurrentState() =
+ testScope.runTest {
+ val currentStates by collectValues(underTest.currentKeyguardState)
+
+ // We init the repo with a transition from OFF -> LOCKSCREEN.
+ assertEquals(
+ listOf(
+ OFF,
+ LOCKSCREEN,
+ ),
+ currentStates
+ )
+
+ sendSteps(
+ TransitionStep(LOCKSCREEN, AOD, 0f, STARTED),
+ )
+
+ // The current state should continue to be LOCKSCREEN as we transition to AOD.
+ assertEquals(
+ listOf(
+ OFF,
+ LOCKSCREEN,
+ ),
+ currentStates
+ )
+
+ sendSteps(
+ TransitionStep(LOCKSCREEN, AOD, 0.5f, RUNNING),
+ )
+
+ // The current state should continue to be LOCKSCREEN as we transition to AOD.
+ assertEquals(
+ listOf(
+ OFF,
+ LOCKSCREEN,
+ ),
+ currentStates
+ )
+
+ sendSteps(
+ TransitionStep(LOCKSCREEN, AOD, 0.6f, CANCELED),
+ )
+
+ // Once CANCELED, we're still currently in LOCKSCREEN...
+ assertEquals(
+ listOf(
+ OFF,
+ LOCKSCREEN,
+ ),
+ currentStates
+ )
+
+ sendSteps(
+ TransitionStep(AOD, LOCKSCREEN, 0.6f, STARTED),
+ )
+
+ // ...until STARTING back to LOCKSCREEN, at which point the "current" state should be
+ // the
+ // one we're transitioning from, despite never FINISHING in that state.
+ assertEquals(
+ listOf(
+ OFF,
+ LOCKSCREEN,
+ AOD,
+ ),
+ currentStates
+ )
+
+ sendSteps(
+ TransitionStep(AOD, LOCKSCREEN, 0.8f, RUNNING),
+ TransitionStep(AOD, LOCKSCREEN, 0.8f, FINISHED),
+ )
+
+ // FINSHING in LOCKSCREEN should update the current state to LOCKSCREEN.
+ assertEquals(
+ listOf(
+ OFF,
+ LOCKSCREEN,
+ AOD,
+ LOCKSCREEN,
+ ),
+ currentStates
+ )
+ }
+
+ @Test
+ fun testCurrentState_multipleCancellations_backToLastFinishedState() =
+ testScope.runTest {
+ val currentStates by collectValues(underTest.currentKeyguardState)
+
+ // We init the repo with a transition from OFF -> LOCKSCREEN.
+ assertEquals(
+ listOf(
+ OFF,
+ LOCKSCREEN,
+ ),
+ currentStates
+ )
+
+ sendSteps(
+ TransitionStep(LOCKSCREEN, GONE, 0f, STARTED),
+ TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING),
+ TransitionStep(LOCKSCREEN, GONE, 1f, FINISHED),
+ )
+
+ assertEquals(
+ listOf(
+ // Default transition from OFF -> LOCKSCREEN
+ OFF,
+ LOCKSCREEN,
+ // Transitioned to GONE
+ GONE,
+ ),
+ currentStates
+ )
+
+ sendSteps(
+ TransitionStep(GONE, DOZING, 0f, STARTED),
+ TransitionStep(GONE, DOZING, 0.5f, RUNNING),
+ TransitionStep(GONE, DOZING, 0.6f, CANCELED),
+ )
+
+ assertEquals(
+ listOf(
+ OFF,
+ LOCKSCREEN,
+ GONE,
+ // Current state should not be DOZING until the post-cancelation transition is
+ // STARTED
+ ),
+ currentStates
+ )
+
+ sendSteps(
+ TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED),
+ )
+
+ assertEquals(
+ listOf(
+ OFF,
+ LOCKSCREEN,
+ GONE,
+ // DOZING -> LS STARTED, DOZING is now the current state.
+ DOZING,
+ ),
+ currentStates
+ )
+
+ sendSteps(
+ TransitionStep(DOZING, LOCKSCREEN, 0.5f, RUNNING),
+ TransitionStep(DOZING, LOCKSCREEN, 0.6f, CANCELED),
+ )
+
+ assertEquals(
+ listOf(
+ OFF,
+ LOCKSCREEN,
+ GONE,
+ DOZING,
+ ),
+ currentStates
+ )
+
+ sendSteps(
+ TransitionStep(LOCKSCREEN, GONE, 0f, STARTED),
+ )
+
+ assertEquals(
+ listOf(
+ OFF,
+ LOCKSCREEN,
+ GONE,
+ DOZING,
+ // LS -> GONE STARTED, LS is now the current state.
+ LOCKSCREEN,
+ ),
+ currentStates
+ )
+
+ sendSteps(
+ TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING),
+ TransitionStep(LOCKSCREEN, GONE, 1f, FINISHED),
+ )
+
+ assertEquals(
+ listOf(
+ OFF,
+ LOCKSCREEN,
+ GONE,
+ DOZING,
+ LOCKSCREEN,
+ // FINISHED in GONE, GONE is now the current state.
+ GONE,
+ ),
+ currentStates
+ )
+ }
private suspend fun sendSteps(vararg steps: TransitionStep) {
steps.forEach {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt
index e7037a6..9daf186 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt
@@ -94,7 +94,7 @@
testScope,
)
- assertThat(values.size).isEqualTo(5)
+ assertThat(values.size).isEqualTo(6)
values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt
new file mode 100644
index 0000000..c7ab529
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionStep
+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
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AlternateBouncerToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() {
+ private val kosmos =
+ testKosmos().apply {
+ fakeFeatureFlagsClassic.apply {
+ set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
+ set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+ }
+ }
+ private val testScope = kosmos.testScope
+ private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val underTest = kosmos.alternateBouncerToPrimaryBouncerTransitionViewModel
+
+ @Test
+ fun deviceEntryParentViewDisappear() =
+ testScope.runTest {
+ val values by collectValues(underTest.deviceEntryParentViewAlpha)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ listOf(
+ step(0f, TransitionState.STARTED),
+ step(0f),
+ step(0.1f),
+ step(0.2f),
+ step(0.3f),
+ step(1f),
+ ),
+ testScope,
+ )
+
+ values.forEach { assertThat(it).isEqualTo(0f) }
+ }
+
+ private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ value = value,
+ transitionState = state,
+ ownerName = "AlternateBouncerToPrimaryBouncerTransitionViewModelTest"
+ )
+ }
+}
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 e141c2b..f1690daf 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
@@ -95,7 +95,7 @@
testScope,
)
- assertThat(values.size).isEqualTo(6)
+ assertThat(values.size).isEqualTo(7)
values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
}
@@ -117,7 +117,7 @@
testScope,
)
- assertThat(values.size).isEqualTo(3)
+ assertThat(values.size).isEqualTo(4)
values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
}
@@ -232,7 +232,7 @@
testScope,
)
- assertThat(values.size).isEqualTo(4)
+ assertThat(values.size).isEqualTo(5)
values.forEach { assertThat(it).isIn(Range.closed(-100f, 0f)) }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
index 897ce6d..f763a67 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
@@ -59,9 +59,9 @@
testScope,
)
- // Only three values should be present, since the dream overlay runs for a small
+ // Only five values should be present, since the dream overlay runs for a small
// fraction of the overall animation time
- assertThat(values.size).isEqualTo(4)
+ assertThat(values.size).isEqualTo(5)
values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
}
@@ -84,7 +84,7 @@
testScope,
)
- assertThat(values.size).isEqualTo(4)
+ assertThat(values.size).isEqualTo(5)
values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 5b88ebe6..fd2fd2f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -203,4 +203,38 @@
assertThat(isVisible?.isAnimating).isEqualTo(false)
}
+
+ @Test
+ fun alpha_glanceableHubOpen_isZero() =
+ testScope.runTest {
+ val alpha by collectLastValue(underTest.alpha)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope,
+ )
+
+ assertThat(alpha).isEqualTo(0f)
+ }
+
+ @Test
+ fun alpha_glanceableHubClosed_isOne() =
+ testScope.runTest {
+ val alpha by collectLastValue(underTest.alpha)
+
+ // Transition to the glanceable hub and back.
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope,
+ )
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.LOCKSCREEN,
+ testScope,
+ )
+
+ assertThat(alpha).isEqualTo(1.0f)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
new file mode 100644
index 0000000..d4dd2ac
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.keyguard.KeyguardClockSwitch
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.authController
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LockscreenContentViewModelTest : SysuiTestCase() {
+
+ private val kosmos: Kosmos = testKosmos()
+
+ lateinit var underTest: LockscreenContentViewModel
+
+ @Before
+ fun setup() {
+ with(kosmos) {
+ fakeFeatureFlagsClassic.set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, true)
+ underTest = lockscreenContentViewModel
+ }
+ }
+
+ @Test
+ fun isUdfpsVisible_withUdfps_true() =
+ with(kosmos) {
+ testScope.runTest {
+ whenever(kosmos.authController.isUdfpsSupported).thenReturn(true)
+ assertThat(underTest.isUdfpsVisible).isTrue()
+ }
+ }
+
+ @Test
+ fun isUdfpsVisible_withoutUdfps_false() =
+ with(kosmos) {
+ testScope.runTest {
+ whenever(kosmos.authController.isUdfpsSupported).thenReturn(false)
+ assertThat(underTest.isUdfpsVisible).isFalse()
+ }
+ }
+
+ @Test
+ fun isLargeClockVisible_withLargeClock_true() =
+ with(kosmos) {
+ testScope.runTest {
+ kosmos.fakeKeyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE)
+ assertThat(underTest.isLargeClockVisible).isTrue()
+ }
+ }
+
+ @Test
+ fun isLargeClockVisible_withSmallClock_false() =
+ with(kosmos) {
+ testScope.runTest {
+ kosmos.fakeKeyguardClockRepository.setClockSize(KeyguardClockSwitch.SMALL)
+ assertThat(underTest.isLargeClockVisible).isFalse()
+ }
+ }
+
+ @Test
+ fun areNotificationsVisible_withSmallClock_true() =
+ with(kosmos) {
+ testScope.runTest {
+ kosmos.fakeKeyguardClockRepository.setClockSize(KeyguardClockSwitch.SMALL)
+ assertThat(underTest.areNotificationsVisible).isTrue()
+ }
+ }
+
+ @Test
+ fun areNotificationsVisible_withLargeClock_false() =
+ with(kosmos) {
+ testScope.runTest {
+ kosmos.fakeKeyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE)
+ assertThat(underTest.areNotificationsVisible).isFalse()
+ }
+ }
+}
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 74d309c..aa15d0b 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
@@ -21,11 +21,19 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -37,9 +45,9 @@
@RunWith(AndroidJUnit4::class)
class LockscreenSceneViewModelTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val sceneInteractor = utils.sceneInteractor()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val sceneInteractor = kosmos.sceneInteractor
private val underTest = createLockscreenSceneViewModel()
@@ -47,9 +55,11 @@
fun upTransitionSceneKey_canSwipeToUnlock_gone() =
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
- utils.deviceEntryRepository.setLockscreenEnabled(true)
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
@@ -59,8 +69,10 @@
fun upTransitionSceneKey_cannotSwipeToUnlock_bouncer() =
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer)
@@ -69,7 +81,7 @@
@Test
fun leftTransitionSceneKey_communalIsEnabled_communal() =
testScope.runTest {
- utils.communalRepository.setIsCommunalEnabled(true)
+ kosmos.fakeCommunalRepository.setIsCommunalEnabled(true)
val underTest = createLockscreenSceneViewModel()
assertThat(underTest.leftDestinationSceneKey).isEqualTo(SceneKey.Communal)
@@ -78,7 +90,7 @@
@Test
fun leftTransitionSceneKey_communalIsDisabled_null() =
testScope.runTest {
- utils.communalRepository.setIsCommunalEnabled(false)
+ kosmos.fakeCommunalRepository.setIsCommunalEnabled(false)
val underTest = createLockscreenSceneViewModel()
assertThat(underTest.leftDestinationSceneKey).isNull()
@@ -87,17 +99,13 @@
private fun createLockscreenSceneViewModel(): LockscreenSceneViewModel {
return LockscreenSceneViewModel(
applicationScope = testScope.backgroundScope,
- deviceEntryInteractor =
- utils.deviceEntryInteractor(
- authenticationInteractor = utils.authenticationInteractor(),
- sceneInteractor = utils.sceneInteractor(),
- ),
- communalInteractor = utils.communalInteractor(),
+ deviceEntryInteractor = kosmos.deviceEntryInteractor,
+ communalInteractor = kosmos.communalInteractor,
longPress =
KeyguardLongPressViewModel(
interactor = mock(),
),
- notifications = utils.notificationsPlaceholderViewModel(),
+ notifications = kosmos.notificationsPlaceholderViewModel,
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
index 4843f8b..74025fd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
@@ -73,9 +73,9 @@
testScope = testScope,
)
- // Only three values should be present, since the dream overlay runs for a small
+ // Only five values should be present, since the dream overlay runs for a small
// fraction of the overall animation time
- assertThat(values.size).isEqualTo(4)
+ assertThat(values.size).isEqualTo(5)
values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
}
@@ -98,10 +98,10 @@
testScope = testScope,
)
- assertThat(values.size).isEqualTo(5)
+ assertThat(values.size).isEqualTo(6)
values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
// Validate finished value
- assertThat(values[4]).isEqualTo(0f)
+ assertThat(values[5]).isEqualTo(0f)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
index a1b8aab..6fcb0c1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
@@ -74,9 +74,9 @@
),
testScope = testScope,
)
- // Only 3 values should be present, since the dream overlay runs for a small fraction
+ // Only 5 values should be present, since the dream overlay runs for a small fraction
// of the overall animation time
- assertThat(values.size).isEqualTo(4)
+ assertThat(values.size).isEqualTo(5)
values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
index 2111ad5..639114c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
@@ -33,6 +33,7 @@
import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@@ -54,6 +55,7 @@
fun lockscreenFadeIn() =
testScope.runTest {
val values by collectValues(underTest.lockscreenAlpha)
+ runCurrent()
keyguardTransitionRepository.sendTransitionSteps(
listOf(
@@ -83,6 +85,7 @@
100
)
val values by collectValues(underTest.lockscreenTranslationY)
+ runCurrent()
keyguardTransitionRepository.sendTransitionSteps(
listOf(
@@ -95,7 +98,7 @@
testScope,
)
- assertThat(values.size).isEqualTo(4)
+ assertThat(values.size).isEqualTo(5)
values.forEach { assertThat(it).isIn(Range.closed(-100f, 0f)) }
}
@@ -107,6 +110,7 @@
100
)
val values by collectValues(underTest.lockscreenTranslationY)
+ runCurrent()
keyguardTransitionRepository.sendTransitionStep(step(0.5f, TransitionState.CANCELED))
@@ -117,6 +121,7 @@
fun deviceEntryParentViewFadeIn() =
testScope.runTest {
val values by collectValues(underTest.deviceEntryParentViewAlpha)
+ runCurrent()
keyguardTransitionRepository.sendTransitionSteps(
listOf(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
index 90b8362..30b87bb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -33,6 +33,7 @@
import com.android.systemui.util.mockito.whenever
import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -65,6 +66,7 @@
fun bouncerAlpha() =
testScope.runTest {
val values by collectValues(underTest.bouncerAlpha)
+ runCurrent()
keyguardTransitionRepository.sendTransitionSteps(
listOf(
@@ -83,6 +85,7 @@
fun bouncerAlpha_runDimissFromKeyguard() =
testScope.runTest {
val values by collectValues(underTest.bouncerAlpha)
+ runCurrent()
whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
@@ -95,7 +98,7 @@
testScope,
)
- assertThat(values.size).isEqualTo(1)
+ assertThat(values.size).isEqualTo(2)
values.forEach { assertThat(it).isEqualTo(0f) }
}
@@ -103,11 +106,12 @@
fun lockscreenAlpha() =
testScope.runTest {
val values by collectValues(underTest.lockscreenAlpha)
+ runCurrent()
keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
keyguardTransitionRepository.sendTransitionStep(step(1f))
- assertThat(values.size).isEqualTo(1)
+ assertThat(values.size).isEqualTo(2)
values.forEach { assertThat(it).isEqualTo(0f) }
}
@@ -115,13 +119,14 @@
fun lockscreenAlpha_runDimissFromKeyguard() =
testScope.runTest {
val values by collectValues(underTest.lockscreenAlpha)
+ runCurrent()
sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(true)
keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
keyguardTransitionRepository.sendTransitionStep(step(1f))
- assertThat(values.size).isEqualTo(1)
+ assertThat(values.size).isEqualTo(2)
values.forEach { assertThat(it).isEqualTo(1f) }
}
@@ -129,13 +134,14 @@
fun lockscreenAlpha_leaveShadeOpen() =
testScope.runTest {
val values by collectValues(underTest.lockscreenAlpha)
+ runCurrent()
sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(true)
keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
keyguardTransitionRepository.sendTransitionStep(step(1f))
- assertThat(values.size).isEqualTo(1)
+ assertThat(values.size).isEqualTo(2)
values.forEach { assertThat(it).isEqualTo(1f) }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
index 070e07a..eb845b2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
@@ -17,11 +17,9 @@
package com.android.systemui.qs.pipeline.data.repository
import android.Manifest.permission.BIND_QUICK_SETTINGS_TILE
-import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.Context
import android.content.Intent
-import android.content.IntentFilter
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.ResolveInfoFlags
@@ -33,44 +31,36 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.data.repository.fakePackageChangeRepository
+import com.android.systemui.common.data.repository.packageChangeRepository
+import com.android.systemui.common.data.shared.model.PackageChangeModel
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argThat
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
-@OptIn(ExperimentalCoroutinesApi::class)
class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
- private val testDispatcher = StandardTestDispatcher()
- private val testScope = TestScope(testDispatcher)
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
@Mock private lateinit var context: Context
@Mock private lateinit var packageManager: PackageManager
- @Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
private lateinit var underTest: InstalledTilesComponentRepositoryImpl
@@ -92,63 +82,12 @@
underTest =
InstalledTilesComponentRepositoryImpl(
context,
- testDispatcher,
+ kosmos.testDispatcher,
+ kosmos.packageChangeRepository
)
}
@Test
- fun registersAndUnregistersBroadcastReceiver() =
- testScope.runTest {
- val user = 10
- val job = launch { underTest.getInstalledTilesComponents(user).collect {} }
- runCurrent()
-
- verify(context)
- .registerReceiverAsUser(
- capture(receiverCaptor),
- eq(UserHandle.of(user)),
- any(),
- nullable(),
- nullable(),
- )
-
- verify(context, never()).unregisterReceiver(receiverCaptor.value)
-
- job.cancel()
- runCurrent()
- verify(context).unregisterReceiver(receiverCaptor.value)
- }
-
- @Test
- fun intentFilterForCorrectActionsAndScheme() =
- testScope.runTest {
- val filterCaptor = argumentCaptor<IntentFilter>()
-
- backgroundScope.launch { underTest.getInstalledTilesComponents(0).collect {} }
- runCurrent()
-
- verify(context)
- .registerReceiverAsUser(
- any(),
- any(),
- capture(filterCaptor),
- nullable(),
- nullable(),
- )
-
- with(filterCaptor.value) {
- assertThat(matchAction(Intent.ACTION_PACKAGE_CHANGED)).isTrue()
- assertThat(matchAction(Intent.ACTION_PACKAGE_ADDED)).isTrue()
- assertThat(matchAction(Intent.ACTION_PACKAGE_REMOVED)).isTrue()
- assertThat(matchAction(Intent.ACTION_PACKAGE_REPLACED)).isTrue()
- assertThat(countActions()).isEqualTo(4)
-
- assertThat(hasDataScheme("package")).isTrue()
- assertThat(countDataSchemes()).isEqualTo(1)
- }
- }
-
- @Test
fun componentsLoadedOnStart() =
testScope.runTest {
val userId = 0
@@ -169,7 +108,7 @@
}
@Test
- fun componentAdded_foundAfterBroadcast() =
+ fun componentAdded_foundAfterPackageChange() =
testScope.runTest {
val userId = 0
val resolveInfo =
@@ -186,7 +125,7 @@
)
)
.thenReturn(listOf(resolveInfo))
- getRegisteredReceiver().onReceive(context, Intent(Intent.ACTION_PACKAGE_ADDED))
+ kosmos.fakePackageChangeRepository.notifyChange(PackageChangeModel.Empty)
assertThat(componentNames).containsExactly(TEST_COMPONENT)
}
@@ -275,19 +214,6 @@
assertThat(componentNames).containsExactly(TEST_COMPONENT)
}
- private fun getRegisteredReceiver(): BroadcastReceiver {
- verify(context)
- .registerReceiverAsUser(
- capture(receiverCaptor),
- any(),
- any(),
- nullable(),
- nullable(),
- )
-
- return receiverCaptor.value
- }
-
companion object {
private val INTENT = Intent(TileService.ACTION_QS_TILE)
private val FLAGS =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
index bd1c310..c104977 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
@@ -16,32 +16,45 @@
package com.android.systemui.qs.tiles.base.actions
+import android.app.PendingIntent
+import android.content.ComponentName
import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ResolveInfoFlags
+import android.content.pm.ResolveInfo
+import android.os.UserHandle
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.util.mockito.argThat
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatcher
import org.mockito.Mock
import org.mockito.Mockito.any
import org.mockito.Mockito.eq
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
class QSTileIntentUserInputHandlerTest : SysuiTestCase() {
-
- @Mock private lateinit var activityStarted: ActivityStarter
+ @Mock private lateinit var packageManager: PackageManager
+ @Mock private lateinit var activityStarter: ActivityStarter
lateinit var underTest: QSTileIntentUserInputHandler
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- underTest = QSTileIntentUserInputHandlerImpl(activityStarted)
+ underTest = QSTileIntentUserInputHandlerImpl(activityStarter, packageManager, user)
}
@Test
@@ -50,6 +63,103 @@
underTest.handle(null, intent)
- verify(activityStarted).postStartActivityDismissingKeyguard(eq(intent), eq(0), any())
+ verify(activityStarter).postStartActivityDismissingKeyguard(eq(intent), eq(0), any())
+ }
+
+ @Test
+ fun testPassesActivityPendingIntentToStarterAsPendingIntent() {
+ val pendingIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(true) }
+
+ underTest.handle(null, pendingIntent, true)
+
+ verify(activityStarter).postStartActivityDismissingKeyguard(eq(pendingIntent), any())
+ }
+
+ @Test
+ fun testPassesActivityPendingIntentToStarterAsPendingIntentWhenNotRequestingActivityStart() {
+ val pendingIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(true) }
+
+ underTest.handle(null, pendingIntent, false)
+
+ verify(activityStarter).postStartActivityDismissingKeyguard(eq(pendingIntent), any())
+ }
+
+ @Test
+ fun testPassNonActivityPendingIntentAndRequestStartingActivity_findsIntentAndStarts() {
+ val pendingIntent =
+ mock<PendingIntent> {
+ whenever(isActivity).thenReturn(false)
+ whenever(creatorPackage).thenReturn(ORIGINAL_PACKAGE)
+ }
+ setUpQueryResult(listOf(createActivityInfo(testResolvedComponent, exported = true)))
+
+ underTest.handle(null, pendingIntent, true)
+
+ val expectedIntent =
+ Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_LAUNCHER)
+ .setPackage(null)
+ .addFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+ )
+ .setComponent(testResolvedComponent)
+
+ verify(activityStarter)
+ .postStartActivityDismissingKeyguard(
+ argThat(IntentMatcher(expectedIntent)),
+ eq(0),
+ any()
+ )
+ }
+
+ @Test
+ fun testPassNonActivityPendingIntentAndDoNotRequestStartingActivity_doesNotStartActivity() {
+ val pendingIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(false) }
+
+ underTest.handle(null, pendingIntent, false)
+
+ verify(activityStarter, never())
+ .postStartActivityDismissingKeyguard(any(Intent::class.java), eq(0), any())
+ }
+
+ private fun createActivityInfo(
+ componentName: ComponentName,
+ exported: Boolean = false,
+ ): ActivityInfo {
+ return ActivityInfo().apply {
+ packageName = componentName.packageName
+ name = componentName.className
+ this.exported = exported
+ }
+ }
+
+ private fun setUpQueryResult(infos: List<ActivityInfo>) {
+ `when`(
+ packageManager.queryIntentActivitiesAsUser(
+ any(Intent::class.java),
+ any(ResolveInfoFlags::class.java),
+ eq(user.identifier)
+ )
+ )
+ .thenReturn(infos.map { ResolveInfo().apply { activityInfo = it } })
+ }
+
+ private class IntentMatcher(intent: Intent) : ArgumentMatcher<Intent> {
+ private val expectedIntent = intent
+ override fun matches(argument: Intent?): Boolean {
+ return argument?.action.equals(expectedIntent.action) &&
+ argument?.`package`.equals(expectedIntent.`package`) &&
+ argument?.component?.equals(expectedIntent.component)!! &&
+ argument?.categories?.equals(expectedIntent.categories)!! &&
+ argument?.flags?.equals(expectedIntent.flags)!!
+ }
+ }
+
+ companion object {
+ private const val ORIGINAL_PACKAGE = "original_pkg"
+ private const val TEST_PACKAGE = "test_pkg"
+ private const val TEST_COMPONENT_CLASS_NAME = "test_component_class_name"
+ private val testResolvedComponent = ComponentName(TEST_PACKAGE, TEST_COMPONENT_CLASS_NAME)
+ private val user = UserHandle.of(0)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractorTest.kt
index e44c8493..be2da17 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractorTest.kt
@@ -18,42 +18,25 @@
import android.app.AlarmManager.AlarmClockInfo
import android.app.PendingIntent
-import android.content.Intent
import android.provider.AlarmClock
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click
import com.android.systemui.qs.tiles.impl.alarm.domain.model.AlarmTileModel
-import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.nullable
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.Mockito.verify
@SmallTest
@RunWith(AndroidJUnit4::class)
class AlarmTileUserActionInteractorTest : SysuiTestCase() {
- private lateinit var activityStarter: ActivityStarter
- private lateinit var intentCaptor: ArgumentCaptor<Intent>
- private lateinit var pendingIntentCaptor: ArgumentCaptor<PendingIntent>
-
- lateinit var underTest: AlarmTileUserActionInteractor
-
- @Before
- fun setup() {
- activityStarter = mock<ActivityStarter>()
- intentCaptor = ArgumentCaptor.forClass(Intent::class.java)
- pendingIntentCaptor = ArgumentCaptor.forClass(PendingIntent::class.java)
- underTest = AlarmTileUserActionInteractor(activityStarter)
- }
+ private val inputHandler = FakeQSTileIntentUserInputHandler()
+ private val underTest = AlarmTileUserActionInteractor(inputHandler)
@Test
fun handleClickWithDefaultIntent() = runTest {
@@ -62,21 +45,21 @@
underTest.handleInput(click(inputModel))
- verify(activityStarter)
- .postStartActivityDismissingKeyguard(capture(intentCaptor), eq(0), nullable())
- assertThat(intentCaptor.value.action).isEqualTo(AlarmClock.ACTION_SHOW_ALARMS)
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+ assertThat(it.intent.action).isEqualTo(AlarmClock.ACTION_SHOW_ALARMS)
+ }
}
@Test
fun handleClickWithPendingIntent() = runTest {
- val expectedIntent: PendingIntent = mock<PendingIntent>()
+ val expectedIntent = mock<PendingIntent>()
val alarmInfo = AlarmClockInfo(1L, expectedIntent)
val inputModel = AlarmTileModel.NextAlarmSet(true, alarmInfo)
underTest.handleInput(click(inputModel))
- verify(activityStarter)
- .postStartActivityDismissingKeyguard(capture(pendingIntentCaptor), nullable())
- assertThat(pendingIntentCaptor.value).isEqualTo(expectedIntent)
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOnePendingIntentInput {
+ assertThat(it.pendingIntent).isEqualTo(expectedIntent)
+ }
}
}
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 6403ed1..be523b8 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
@@ -22,13 +22,15 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
+import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.scene.shared.model.UserAction
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
+import com.android.systemui.statusbar.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
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
@@ -36,6 +38,7 @@
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
@@ -47,9 +50,9 @@
@RunWith(AndroidJUnit4::class)
class QuickSettingsSceneViewModelTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val sceneInteractor = utils.sceneInteractor()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val sceneInteractor = kosmos.sceneInteractor
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
private val qsFlexiglassAdapter = FakeQSSceneAdapter { mock() }
@@ -90,7 +93,7 @@
QuickSettingsSceneViewModel(
shadeHeaderViewModel = shadeHeaderViewModel,
qsSceneAdapter = qsFlexiglassAdapter,
- notifications = utils.notificationsPlaceholderViewModel(),
+ notifications = kosmos.notificationsPlaceholderViewModel,
)
}
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 530d127d..1cd764e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -24,26 +24,41 @@
import androidx.test.filters.SmallTest
import com.android.internal.R
import com.android.internal.util.EmergencyAffordanceManager
+import com.android.internal.util.emergencyAffordanceManager
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
+import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.bouncerViewModel
+import com.android.systemui.classifier.domain.interactor.falsingInteractor
+import com.android.systemui.classifier.falsingCollector
+import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
-import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.pipeline.MediaDataManager
-import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.model.SysUiState
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
+import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.domain.startable.SceneContainerStartable
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
@@ -51,16 +66,21 @@
import com.android.systemui.settings.FakeDisplayTracker
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
+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
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
+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.android.telecom.telecomManager
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -101,28 +121,13 @@
@RunWith(AndroidJUnit4::class)
class SceneFrameworkIntegrationTest : SysuiTestCase() {
- @Mock private lateinit var emergencyAffordanceManager: EmergencyAffordanceManager
- @Mock private lateinit var tableLogger: TableLogBuffer
- @Mock private lateinit var telecomManager: TelecomManager
-
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val sceneContainerConfig = utils.fakeSceneContainerConfig()
- private val sceneRepository =
- utils.fakeSceneContainerRepository(
- containerConfig = sceneContainerConfig,
- )
- private val sceneInteractor =
- utils.sceneInteractor(
- repository = sceneRepository,
- )
- private val authenticationInteractor = utils.authenticationInteractor()
- private val deviceEntryInteractor =
- utils.deviceEntryInteractor(
- authenticationInteractor = authenticationInteractor,
- sceneInteractor = sceneInteractor,
- )
- private val communalInteractor = utils.communalInteractor()
+ private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
+ private val testScope = kosmos.testScope
+ private val sceneContainerConfig = kosmos.sceneContainerConfig
+ private val sceneInteractor = kosmos.sceneInteractor
+ private val authenticationInteractor = kosmos.authenticationInteractor
+ private val deviceEntryInteractor = kosmos.deviceEntryInteractor
+ private val communalInteractor = kosmos.communalInteractor
private val transitionState =
MutableStateFlow<ObservableTransitionState>(
@@ -131,14 +136,11 @@
private val sceneContainerViewModel =
SceneContainerViewModel(
sceneInteractor = sceneInteractor,
- falsingInteractor = utils.falsingInteractor(),
+ falsingInteractor = kosmos.falsingInteractor,
)
.apply { setTransitionState(transitionState) }
- private val bouncerInteractor =
- utils.bouncerInteractor(
- authenticationInteractor = authenticationInteractor,
- )
+ private val bouncerInteractor = kosmos.bouncerInteractor
private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository
private lateinit var bouncerActionButtonInteractor: BouncerActionButtonInteractor
@@ -153,7 +155,7 @@
KeyguardLongPressViewModel(
interactor = mock(),
),
- notifications = utils.notificationsPlaceholderViewModel(),
+ notifications = kosmos.notificationsPlaceholderViewModel,
)
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
@@ -170,61 +172,51 @@
FakeMobileConnectionsRepository(),
),
constants = mock(),
- utils.featureFlags,
+ flags = kosmos.fakeFeatureFlagsClassic,
scope = testScope.backgroundScope,
)
private lateinit var shadeHeaderViewModel: ShadeHeaderViewModel
private lateinit var shadeSceneViewModel: ShadeSceneViewModel
- private val keyguardRepository = utils.keyguardRepository
- private val keyguardInteractor =
- utils.keyguardInteractor(
- repository = keyguardRepository,
- )
- private val powerInteractor = PowerInteractorFactory.create().powerInteractor
+ private val keyguardInteractor = kosmos.keyguardInteractor
+ private val powerInteractor = kosmos.powerInteractor
private var bouncerSceneJob: Job? = null
private val qsFlexiglassAdapter = FakeQSSceneAdapter(inflateDelegate = { mock() })
@Mock private lateinit var mediaDataManager: MediaDataManager
- @Mock private lateinit var mediaHost: MediaHost
+
+ private lateinit var emergencyAffordanceManager: EmergencyAffordanceManager
+ private lateinit var telecomManager: TelecomManager
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+
overrideResource(R.bool.config_enable_emergency_call_while_sim_locked, true)
+ telecomManager = checkNotNull(kosmos.telecomManager)
whenever(telecomManager.isInCall).thenReturn(false)
+ emergencyAffordanceManager = kosmos.emergencyAffordanceManager
whenever(emergencyAffordanceManager.needsEmergencyAffordance()).thenReturn(true)
- utils.featureFlags.apply {
+ kosmos.fakeFeatureFlagsClassic.apply {
set(Flags.NEW_NETWORK_SLICE_UI, false)
set(Flags.REFACTOR_GETCURRENTUSER, true)
}
- mobileConnectionsRepository =
- FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogger)
- mobileConnectionsRepository.isAnySimSecure.value = true
+ mobileConnectionsRepository = kosmos.fakeMobileConnectionsRepository
+ mobileConnectionsRepository.isAnySimSecure.value = false
- utils.telephonyRepository.apply {
+ kosmos.fakeTelephonyRepository.apply {
setHasTelephonyRadio(true)
setCallState(TelephonyManager.CALL_STATE_IDLE)
setIsInCall(false)
}
- bouncerActionButtonInteractor =
- utils.bouncerActionButtonInteractor(
- mobileConnectionsRepository = mobileConnectionsRepository,
- telecomManager = telecomManager,
- emergencyAffordanceManager = emergencyAffordanceManager,
- )
- bouncerViewModel =
- utils.bouncerViewModel(
- bouncerInteractor = bouncerInteractor,
- authenticationInteractor = authenticationInteractor,
- actionButtonInteractor = bouncerActionButtonInteractor,
- )
+ bouncerActionButtonInteractor = kosmos.bouncerActionButtonInteractor
+ bouncerViewModel = kosmos.bouncerViewModel
shadeHeaderViewModel =
ShadeHeaderViewModel(
@@ -242,12 +234,11 @@
deviceEntryInteractor = deviceEntryInteractor,
shadeHeaderViewModel = shadeHeaderViewModel,
qsSceneAdapter = qsFlexiglassAdapter,
- notifications = utils.notificationsPlaceholderViewModel(),
+ notifications = kosmos.notificationsPlaceholderViewModel,
mediaDataManager = mediaDataManager,
- mediaHost = mediaHost,
)
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
val displayTracker = FakeDisplayTracker(context)
val sysUiState = SysUiState(displayTracker)
@@ -257,15 +248,15 @@
sceneInteractor = sceneInteractor,
deviceEntryInteractor = deviceEntryInteractor,
keyguardInteractor = keyguardInteractor,
- flags = utils.sceneContainerFlags,
+ flags = kosmos.fakeSceneContainerFlags,
sysUiState = sysUiState,
displayId = displayTracker.defaultDisplayId,
sceneLogger = mock(),
- falsingCollector = utils.falsingCollector(),
+ falsingCollector = kosmos.falsingCollector,
powerInteractor = powerInteractor,
bouncerInteractor = bouncerInteractor,
- simBouncerInteractor = dagger.Lazy { utils.simBouncerInteractor },
- authenticationInteractor = dagger.Lazy { utils.authenticationInteractor() },
+ simBouncerInteractor = dagger.Lazy { kosmos.simBouncerInteractor },
+ authenticationInteractor = dagger.Lazy { kosmos.authenticationInteractor },
windowController = mock(),
)
startable.start()
@@ -561,15 +552,15 @@
// Set the lockscreen enabled bit _before_ set the auth method as the code picks up on the
// lockscreen enabled bit _after_ the auth method is changed and the lockscreen enabled bit
// is not an observable that can trigger a new evaluation.
- utils.deviceEntryRepository.setLockscreenEnabled(enableLockscreen)
- utils.authenticationRepository.setAuthenticationMethod(authMethod)
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(enableLockscreen)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod)
runCurrent()
}
/** Emulates a phone call in progress. */
private fun TestScope.startPhoneCall() {
whenever(telecomManager.isInCall).thenReturn(true)
- utils.telephonyRepository.apply {
+ kosmos.fakeTelephonyRepository.apply {
setHasTelephonyRadio(true)
setIsInCall(true)
setCallState(TelephonyManager.CALL_STATE_OFFHOOK)
@@ -678,7 +669,7 @@
.that(authMethod.isSecure)
.isTrue()
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
runCurrent()
}
@@ -692,7 +683,7 @@
enterPin()
// This repository state is not changed by the AuthInteractor, it relies on
// KeyguardStateController.
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
emulateUiSceneTransition(
expectedVisible = false,
)
@@ -748,7 +739,7 @@
}
pinBouncerViewModel.onAuthenticateButtonClicked()
setAuthMethod(authMethodAfterSimUnlock)
- utils.mobileConnectionsRepository.isAnySimSecure.value = false
+ kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = false
runCurrent()
}
@@ -795,7 +786,7 @@
private fun TestScope.introduceLockedSim() {
setAuthMethod(AuthenticationMethodModel.Sim)
- utils.mobileConnectionsRepository.isAnySimSecure.value = true
+ kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = true
runCurrent()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
index ddeb05b..b267720 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
@@ -22,11 +22,14 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.sceneContainerConfig
import com.android.systemui.scene.sceneKeys
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -39,12 +42,12 @@
@RunWith(AndroidJUnit4::class)
class SceneContainerRepositoryTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
+ private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
+ private val testScope = kosmos.testScope
@Test
fun allSceneKeys() {
- val underTest = utils.fakeSceneContainerRepository()
+ val underTest = kosmos.sceneContainerRepository
assertThat(underTest.allSceneKeys())
.isEqualTo(
listOf(
@@ -61,7 +64,7 @@
@Test
fun desiredScene() =
testScope.runTest {
- val underTest = utils.fakeSceneContainerRepository()
+ val underTest = kosmos.sceneContainerRepository
val currentScene by collectLastValue(underTest.desiredScene)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
@@ -71,15 +74,15 @@
@Test(expected = IllegalStateException::class)
fun setDesiredScene_noSuchSceneInContainer_throws() {
- utils.kosmos.sceneKeys = listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
- val underTest = utils.fakeSceneContainerRepository(utils.fakeSceneContainerConfig())
+ kosmos.sceneKeys = listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
+ val underTest = kosmos.sceneContainerRepository
underTest.setDesiredScene(SceneModel(SceneKey.Shade))
}
@Test
fun isVisible() =
testScope.runTest {
- val underTest = utils.fakeSceneContainerRepository()
+ val underTest = kosmos.sceneContainerRepository
val isVisible by collectLastValue(underTest.isVisible)
assertThat(isVisible).isTrue()
@@ -93,19 +96,19 @@
@Test
fun transitionState_defaultsToIdle() =
testScope.runTest {
- val underTest = utils.fakeSceneContainerRepository()
+ val underTest = kosmos.sceneContainerRepository
val transitionState by collectLastValue(underTest.transitionState)
assertThat(transitionState)
.isEqualTo(
- ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey)
+ ObservableTransitionState.Idle(kosmos.sceneContainerConfig.initialSceneKey)
)
}
@Test
fun transitionState_reflectsUpdates() =
testScope.runTest {
- val underTest = utils.fakeSceneContainerRepository()
+ val underTest = kosmos.sceneContainerRepository
val transitionState =
MutableStateFlow<ObservableTransitionState>(
ObservableTransitionState.Idle(SceneKey.Lockscreen)
@@ -134,7 +137,7 @@
underTest.setTransitionState(null)
assertThat(reflectedTransitionState)
.isEqualTo(
- ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey)
+ ObservableTransitionState.Idle(kosmos.sceneContainerConfig.initialSceneKey)
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index 7f4bbbe..bf99a86 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -20,14 +20,23 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.scene.data.repository.sceneContainerRepository
+import com.android.systemui.scene.sceneContainerConfig
+import com.android.systemui.scene.sceneKeys
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -35,14 +44,20 @@
@RunWith(AndroidJUnit4::class)
class SceneInteractorTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val repository = utils.fakeSceneContainerRepository()
- private val underTest = utils.sceneInteractor(repository = repository)
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private lateinit var underTest: SceneInteractor
+
+ @Before
+ fun setUp() {
+ kosmos.fakeSceneContainerFlags.enabled = true
+ underTest = kosmos.sceneInteractor
+ }
@Test
fun allSceneKeys() {
- assertThat(underTest.allSceneKeys()).isEqualTo(utils.fakeSceneKeys())
+ assertThat(underTest.allSceneKeys()).isEqualTo(kosmos.sceneKeys)
}
@Test
@@ -56,6 +71,27 @@
}
@Test
+ fun changeScene_toGoneWhenUnl_doesNotThrow() =
+ testScope.runTest {
+ val desiredScene by collectLastValue(underTest.desiredScene)
+ assertThat(desiredScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
+
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ runCurrent()
+
+ underTest.changeScene(SceneModel(SceneKey.Gone), "reason")
+ assertThat(desiredScene).isEqualTo(SceneModel(SceneKey.Gone))
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun changeScene_toGoneWhenStillLocked_throws() =
+ testScope.runTest {
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
+
+ underTest.changeScene(SceneModel(SceneKey.Gone), "reason")
+ }
+
+ @Test
fun onSceneChanged() =
testScope.runTest {
val desiredScene by collectLastValue(underTest.desiredScene)
@@ -68,7 +104,7 @@
@Test
fun transitionState() =
testScope.runTest {
- val underTest = utils.fakeSceneContainerRepository()
+ val underTest = kosmos.sceneContainerRepository
val transitionState =
MutableStateFlow<ObservableTransitionState>(
ObservableTransitionState.Idle(SceneKey.Lockscreen)
@@ -97,7 +133,7 @@
underTest.setTransitionState(null)
assertThat(reflectedTransitionState)
.isEqualTo(
- ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey)
+ ObservableTransitionState.Idle(kosmos.sceneContainerConfig.initialSceneKey)
)
}
@@ -341,8 +377,8 @@
@Test
fun userInput() =
testScope.runTest {
- assertThat(utils.powerRepository.userTouchRegistered).isFalse()
+ assertThat(kosmos.fakePowerRepository.userTouchRegistered).isFalse()
underTest.onUserInput()
- assertThat(utils.powerRepository.userTouchRegistered).isTrue()
+ assertThat(kosmos.fakePowerRepository.userTouchRegistered).isTrue()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
index 8be4eeb..f23716c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.scene.domain.interactor
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.statusbar.IStatusBarService
@@ -28,7 +30,11 @@
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.init.NotificationsController
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
@@ -37,6 +43,7 @@
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -50,6 +57,7 @@
class WindowRootViewVisibilityInteractorTest : SysuiTestCase() {
private val testScope = TestScope()
+ private val testDispatcher = StandardTestDispatcher()
private val iStatusBarService = mock<IStatusBarService>()
private val executor = FakeExecutor(FakeSystemClock())
private val windowRootViewVisibilityRepository =
@@ -59,6 +67,9 @@
private val notificationPresenter = mock<NotificationPresenter>()
private val notificationsController = mock<NotificationsController>()
private val powerInteractor = PowerInteractorFactory.create().powerInteractor
+ private val activeNotificationsRepository = ActiveNotificationListRepository()
+ private val activeNotificationsInteractor =
+ ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher)
private val underTest =
WindowRootViewVisibilityInteractor(
@@ -67,6 +78,7 @@
keyguardRepository,
headsUpManager,
powerInteractor,
+ activeNotificationsInteractor,
)
.apply { setUp(notificationPresenter, notificationsController) }
@@ -257,7 +269,8 @@
}
@Test
- fun lockscreenShadeInteractive_hasHeadsUpAndNotifPresenterCollapsed_notifCountOne() =
+ @DisableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
+ fun lockscreenShadeInteractive_hasHeadsUpAndNotifPresenterCollapsed_flagOff_notifCountOne() =
testScope.runTest {
underTest.start()
@@ -273,6 +286,23 @@
}
@Test
+ @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
+ fun lockscreenShadeInteractive_hasHeadsUpAndNotifPresenterCollapsed_flagOn_notifCountOne() =
+ testScope.runTest {
+ underTest.start()
+
+ whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true)
+ whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true)
+ activeNotificationsRepository.setActiveNotifs(4)
+
+ makeLockscreenShadeVisible()
+
+ val notifCount = argumentCaptor<Int>()
+ verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture())
+ assertThat(notifCount.value).isEqualTo(1)
+ }
+
+ @Test
fun lockscreenShadeInteractive_hasHeadsUpAndNullPresenter_notifCountOne() =
testScope.runTest {
underTest.start()
@@ -288,7 +318,8 @@
}
@Test
- fun lockscreenShadeInteractive_noHeadsUp_notifCountMatchesNotifController() =
+ @DisableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
+ fun lockscreenShadeInteractive_noHeadsUp_flagOff_notifCountMatchesNotifController() =
testScope.runTest {
underTest.start()
whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true)
@@ -304,7 +335,25 @@
}
@Test
- fun lockscreenShadeInteractive_notifPresenterNotCollapsed_notifCountMatchesNotifController() =
+ @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
+ fun lockscreenShadeInteractive_noHeadsUp_flagOn_notifCountMatchesNotifController() =
+ testScope.runTest {
+ underTest.start()
+ whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true)
+
+ whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(false)
+ activeNotificationsRepository.setActiveNotifs(9)
+
+ makeLockscreenShadeVisible()
+
+ val notifCount = argumentCaptor<Int>()
+ verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture())
+ assertThat(notifCount.value).isEqualTo(9)
+ }
+
+ @Test
+ @DisableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
+ fun lockscreenShadeInteractive_notifPresenterNotCollapsed_flagOff_notifCountMatchesNotifController() =
testScope.runTest {
underTest.start()
whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true)
@@ -320,6 +369,23 @@
}
@Test
+ @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
+ fun lockscreenShadeInteractive_notifPresenterNotCollapsed_flagOn_notifCountMatchesNotifController() =
+ testScope.runTest {
+ underTest.start()
+ whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true)
+
+ whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(false)
+ activeNotificationsRepository.setActiveNotifs(8)
+
+ makeLockscreenShadeVisible()
+
+ val notifCount = argumentCaptor<Int>()
+ verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture())
+ assertThat(notifCount.value).isEqualTo(8)
+ }
+
+ @Test
fun lockscreenShadeInteractive_noHeadsUp_noNotifController_notifCountZero() =
testScope.runTest {
underTest.start()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index dd22976..fc0df12 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -25,19 +25,31 @@
import androidx.test.filters.SmallTest
import com.android.systemui.Flags as AconfigFlags
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.kosmos.testScope
import com.android.systemui.model.SysUiState
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -65,21 +77,15 @@
@Mock private lateinit var windowController: NotificationShadeWindowController
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val sceneInteractor = utils.sceneInteractor()
- private val sceneContainerFlags = utils.sceneContainerFlags
- private val authenticationInteractor = utils.authenticationInteractor()
- private val bouncerInteractor =
- utils.bouncerInteractor(authenticationInteractor = authenticationInteractor)
- private val faceAuthRepository = FakeDeviceEntryFaceAuthRepository()
- private val deviceEntryInteractor =
- utils.deviceEntryInteractor(
- faceAuthRepository = faceAuthRepository,
- authenticationInteractor = authenticationInteractor,
- sceneInteractor = sceneInteractor,
- )
- private val keyguardInteractor = utils.keyguardInteractor()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val sceneInteractor = kosmos.sceneInteractor
+ private val sceneContainerFlags = kosmos.fakeSceneContainerFlags
+ private val authenticationInteractor = kosmos.authenticationInteractor
+ private val bouncerInteractor = kosmos.bouncerInteractor
+ private val faceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository
+ private val deviceEntryInteractor = kosmos.deviceEntryInteractor
+ private val keyguardInteractor = kosmos.keyguardInteractor
private val sysUiState: SysUiState = mock()
private val falsingCollector: FalsingCollector = mock()
private val powerInteractor = PowerInteractorFactory.create().powerInteractor
@@ -103,7 +109,7 @@
falsingCollector = falsingCollector,
powerInteractor = powerInteractor,
bouncerInteractor = bouncerInteractor,
- simBouncerInteractor = dagger.Lazy { utils.simBouncerInteractor },
+ simBouncerInteractor = dagger.Lazy { kosmos.simBouncerInteractor },
authenticationInteractor = dagger.Lazy { authenticationInteractor },
windowController = windowController,
)
@@ -178,7 +184,7 @@
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
underTest.start()
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
}
@@ -194,7 +200,7 @@
assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
underTest.start()
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
}
@@ -210,7 +216,7 @@
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
underTest.start()
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
}
@@ -228,7 +234,7 @@
// Authenticate using a passive auth method like face auth while bypass is disabled.
faceAuthRepository.isAuthenticated.value = true
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
}
@@ -250,7 +256,7 @@
transitionStateFlowValue.value = ObservableTransitionState.Idle(SceneKey.Shade)
assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
runCurrent()
assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
@@ -269,7 +275,7 @@
// Authenticate using a passive auth method like face auth while bypass is disabled.
faceAuthRepository.isAuthenticated.value = true
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
}
@@ -305,6 +311,10 @@
SceneKey.QuickSettings,
)
.forEachIndexed { index, sceneKey ->
+ if (sceneKey == SceneKey.Gone) {
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ runCurrent()
+ }
sceneInteractor.changeScene(SceneModel(sceneKey), "reason")
runCurrent()
verify(sysUiState, times(index)).commitUpdate(Display.DEFAULT_DISPLAY)
@@ -379,7 +389,7 @@
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
underTest.start()
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
runCurrent()
powerInteractor.setAwakeForTest()
runCurrent()
@@ -414,6 +424,8 @@
}
// Changing to the Gone scene should report a successful unlock.
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ runCurrent()
sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason")
runCurrent()
verify(falsingCollector).onSuccessfulUnlock()
@@ -469,11 +481,11 @@
runCurrent()
verify(falsingCollector).setShowingAod(false)
- utils.keyguardRepository.setIsDozing(true)
+ kosmos.fakeKeyguardRepository.setIsDozing(true)
runCurrent()
verify(falsingCollector).setShowingAod(true)
- utils.keyguardRepository.setIsDozing(false)
+ kosmos.fakeKeyguardRepository.setIsDozing(false)
runCurrent()
verify(falsingCollector, times(2)).setShowingAod(false)
}
@@ -499,7 +511,7 @@
@Test
fun collectFalsingSignals_screenOnAndOff_aodUnavailable() =
testScope.runTest {
- utils.keyguardRepository.setAodAvailable(false)
+ kosmos.fakeKeyguardRepository.setAodAvailable(false)
runCurrent()
prepareState(
initialSceneKey = SceneKey.Lockscreen,
@@ -547,7 +559,7 @@
@Test
fun collectFalsingSignals_screenOnAndOff_aodAvailable() =
testScope.runTest {
- utils.keyguardRepository.setAodAvailable(true)
+ kosmos.fakeKeyguardRepository.setAodAvailable(true)
runCurrent()
prepareState(
initialSceneKey = SceneKey.Lockscreen,
@@ -607,6 +619,8 @@
runCurrent()
verify(falsingCollector).onBouncerShown()
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ runCurrent()
sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason")
runCurrent()
verify(falsingCollector, times(2)).onBouncerHidden()
@@ -625,7 +639,7 @@
underTest.start()
runCurrent()
- utils.mobileConnectionsRepository.isAnySimSecure.value = true
+ kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = true
runCurrent()
assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
@@ -634,7 +648,7 @@
@Test
fun switchesToLockscreen_whenSimBecomesUnlocked() =
testScope.runTest {
- utils.mobileConnectionsRepository.isAnySimSecure.value = true
+ kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = true
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
prepareState(
@@ -644,7 +658,7 @@
)
underTest.start()
runCurrent()
- utils.mobileConnectionsRepository.isAnySimSecure.value = false
+ kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = false
runCurrent()
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
@@ -653,7 +667,7 @@
@Test
fun switchesToGone_whenSimBecomesUnlocked_ifDeviceUnlockedAndLockscreenDisabled() =
testScope.runTest {
- utils.mobileConnectionsRepository.isAnySimSecure.value = true
+ kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = true
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
prepareState(
@@ -664,7 +678,7 @@
)
underTest.start()
runCurrent()
- utils.mobileConnectionsRepository.isAnySimSecure.value = false
+ kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = false
runCurrent()
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
@@ -735,9 +749,15 @@
"Lockscreen cannot be disabled while having a secure authentication method"
}
}
+
+ check(initialSceneKey != SceneKey.Gone || isDeviceUnlocked) {
+ "Cannot start on the Gone scene and have the device be locked at the same time."
+ }
+
sceneContainerFlags.enabled = true
- utils.deviceEntryRepository.setUnlocked(isDeviceUnlocked)
- utils.deviceEntryRepository.setBypassEnabled(isBypassEnabled)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(isDeviceUnlocked)
+ kosmos.fakeDeviceEntryRepository.setBypassEnabled(isBypassEnabled)
+ runCurrent()
val transitionStateFlow =
MutableStateFlow<ObservableTransitionState>(
ObservableTransitionState.Idle(SceneKey.Lockscreen)
@@ -749,8 +769,8 @@
sceneInteractor.onSceneChanged(SceneModel(it), "reason")
}
authenticationMethod?.let {
- utils.authenticationRepository.setAuthenticationMethod(authenticationMethod)
- utils.deviceEntryRepository.setLockscreenEnabled(
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authenticationMethod)
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(
isLockscreenEnabled = isLockscreenEnabled
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index c89cd9e..ede453d8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -21,13 +21,18 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.domain.interactor.falsingInteractor
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.sceneKeys
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -35,13 +40,19 @@
@RunWith(AndroidJUnit4::class)
class SceneContainerViewModelTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val interactor = utils.sceneInteractor()
- private val underTest =
- SceneContainerViewModel(
- sceneInteractor = interactor,
- falsingInteractor = utils.falsingInteractor(),
- )
+ private val kosmos = testKosmos()
+ private val interactor = kosmos.sceneInteractor
+ private lateinit var underTest: SceneContainerViewModel
+
+ @Before
+ fun setUp() {
+ kosmos.fakeSceneContainerFlags.enabled = true
+ underTest =
+ SceneContainerViewModel(
+ sceneInteractor = interactor,
+ falsingInteractor = kosmos.falsingInteractor,
+ )
+ }
@Test
fun isVisible() = runTest {
@@ -57,7 +68,7 @@
@Test
fun allSceneKeys() {
- assertThat(underTest.allSceneKeys).isEqualTo(utils.fakeSceneKeys())
+ assertThat(underTest.allSceneKeys).isEqualTo(kosmos.sceneKeys)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
index 77ddf15..5174502 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
@@ -7,7 +7,8 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
@@ -18,6 +19,7 @@
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.MutableStateFlow
@@ -31,9 +33,9 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class ShadeHeaderViewModelTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val sceneInteractor = utils.sceneInteractor()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val sceneInteractor = kosmos.sceneInteractor
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
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 1d2497d..251daff 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
@@ -19,16 +19,20 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
+import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.pipeline.MediaDataManager
-import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
@@ -36,6 +40,7 @@
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -53,15 +58,10 @@
@RunWith(AndroidJUnit4::class)
class ShadeSceneViewModelTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val sceneInteractor = utils.sceneInteractor()
- private val authenticationInteractor = utils.authenticationInteractor()
- private val deviceEntryInteractor =
- utils.deviceEntryInteractor(
- authenticationInteractor = authenticationInteractor,
- sceneInteractor = sceneInteractor,
- )
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val sceneInteractor = kosmos.sceneInteractor
+ private val deviceEntryInteractor = kosmos.deviceEntryInteractor
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
@@ -89,7 +89,6 @@
private lateinit var underTest: ShadeSceneViewModel
@Mock private lateinit var mediaDataManager: MediaDataManager
- @Mock private lateinit var mediaHost: MediaHost
@Before
fun setUp() {
@@ -110,9 +109,8 @@
deviceEntryInteractor = deviceEntryInteractor,
shadeHeaderViewModel = shadeHeaderViewModel,
qsSceneAdapter = qsFlexiglassAdapter,
- notifications = utils.notificationsPlaceholderViewModel(),
+ notifications = kosmos.notificationsPlaceholderViewModel,
mediaDataManager = mediaDataManager,
- mediaHost = mediaHost,
)
}
@@ -120,8 +118,10 @@
fun upTransitionSceneKey_deviceLocked_lockScreen() =
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Lockscreen)
}
@@ -130,8 +130,10 @@
fun upTransitionSceneKey_deviceUnlocked_gone() =
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
}
@@ -140,8 +142,10 @@
fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- utils.deviceEntryRepository.setLockscreenEnabled(true)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen), "reason")
@@ -152,8 +156,12 @@
fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() =
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- utils.deviceEntryRepository.setLockscreenEnabled(true)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
+ runCurrent()
sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason")
sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone), "reason")
@@ -164,8 +172,10 @@
fun onContentClicked_deviceUnlocked_switchesToGone() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.desiredScene)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
runCurrent()
underTest.onContentClicked()
@@ -177,8 +187,10 @@
fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.desiredScene)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
runCurrent()
underTest.onContentClicked()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryImplTest.kt
new file mode 100644
index 0000000..8a0400d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryImplTest.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.NotificationRemoteInputManager
+import com.android.systemui.util.mockito.withArgCaptor
+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
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class RemoteInputRepositoryImplTest : SysuiTestCase() {
+ @Mock private lateinit var remoteInputManager: NotificationRemoteInputManager
+
+ private lateinit var testScope: TestScope
+ private lateinit var underTest: RemoteInputRepositoryImpl
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ testScope = TestScope()
+ underTest = RemoteInputRepositoryImpl(remoteInputManager)
+ }
+
+ @Test
+ fun isRemoteInputActive_updatesOnChange() =
+ testScope.runTest {
+ val active by collectLastValue(underTest.isRemoteInputActive)
+ runCurrent()
+ assertThat(active).isFalse()
+
+ val callback = withArgCaptor {
+ verify(remoteInputManager).addControllerCallback(capture())
+ }
+
+ callback.onRemoteInputActive(true)
+ runCurrent()
+ assertThat(active).isTrue()
+
+ callback.onRemoteInputActive(false)
+ runCurrent()
+ assertThat(active).isFalse()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractorTest.kt
new file mode 100644
index 0000000..12469ddc
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractorTest.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.data.repository.fakeRemoteInputRepository
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class RemoteInputInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val fakeRemoteInputRepository = kosmos.fakeRemoteInputRepository
+ private val underTest = kosmos.remoteInputInteractor
+
+ @Test
+ fun isRemoteInputActive_true() =
+ testScope.runTest {
+ val active by collectLastValue(underTest.isRemoteInputActive)
+
+ fakeRemoteInputRepository.isRemoteInputActive.value = true
+ runCurrent()
+
+ assertThat(active).isTrue()
+ }
+
+ @Test
+ fun isRemoteInputActive_false() =
+ testScope.runTest {
+ val active by collectLastValue(underTest.isRemoteInputActive)
+
+ fakeRemoteInputRepository.isRemoteInputActive.value = false
+ runCurrent()
+
+ assertThat(active).isFalse()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
index 4cdb08a..607996d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
@@ -27,8 +27,7 @@
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
@@ -50,7 +49,7 @@
private val kosmos =
testKosmos().apply {
- sceneContainerFlags = FakeSceneContainerFlags(enabled = true)
+ fakeSceneContainerFlags.enabled = true
fakeFeatureFlagsClassic.apply {
set(Flags.FULL_SCREEN_USER_SWITCHER, false)
set(Flags.NSSL_DEBUG_LINES, false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/data/repository/UserSetupRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/data/repository/UserSetupRepositoryTest.kt
new file mode 100644
index 0000000..ebc81be
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/data/repository/UserSetupRepositoryTest.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.policy.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener
+import com.android.systemui.statusbar.policy.deviceProvisionedController
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UserSetupRepositoryTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private val testScope = kosmos.testScope
+ private val deviceProvisionedController : DeviceProvisionedController = mock()
+
+ private val underTest = UserSetupRepositoryImpl(
+ deviceProvisionedController,
+ kosmos.testDispatcher,
+ kosmos.applicationCoroutineScope,
+ )
+
+ @Test
+ fun userSetup_defaultFalse() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isUserSetUp)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun userSetup_updatesOnChange() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isUserSetUp)
+ runCurrent()
+
+ whenever(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
+ val callback = getDeviceProvisionedListener()
+ callback.onUserSetupChanged()
+
+ assertThat(latest).isTrue()
+ }
+
+ private fun getDeviceProvisionedListener(): DeviceProvisionedListener {
+ val captor = argumentCaptor<DeviceProvisionedListener>()
+ verify(deviceProvisionedController).addCallback(captor.capture())
+ return captor.value!!
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/UserSetupInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/UserSetupInteractorTest.kt
new file mode 100644
index 0000000..26c0f80
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/UserSetupInteractorTest.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.policy.data.repository.fakeUserSetupRepository
+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 UserSetupInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val fakeUserSetupRepository = kosmos.fakeUserSetupRepository
+ private val underTest = kosmos.userSetupInteractor
+
+ @Test
+ fun isUserSetup_false() =
+ testScope.runTest {
+ val setup by collectLastValue(underTest.isUserSetUp)
+
+ fakeUserSetupRepository.setUserSetUp(false)
+
+ assertThat(setup).isFalse()
+ }
+
+ @Test
+ fun isUserSetup_true() =
+ testScope.runTest {
+ val setup by collectLastValue(underTest.isUserSetUp)
+
+ fakeUserSetupRepository.setUserSetUp(true)
+
+ assertThat(setup).isTrue()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt
index 262795f..8e8e510 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt
@@ -24,12 +24,13 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.telephony.TelephonyListenerManager
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.kotlinArgumentCaptor
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -39,7 +40,6 @@
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class TelephonyRepositoryImplTest : SysuiTestCase() {
@@ -47,8 +47,8 @@
@Mock private lateinit var manager: TelephonyListenerManager
@Mock private lateinit var telecomManager: TelecomManager
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private lateinit var underTest: TelephonyRepositoryImpl
@@ -61,7 +61,7 @@
TelephonyRepositoryImpl(
applicationScope = testScope.backgroundScope,
applicationContext = context,
- backgroundDispatcher = utils.testDispatcher,
+ backgroundDispatcher = kosmos.testDispatcher,
manager = manager,
telecomManager = telecomManager,
)
diff --git a/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginLifecycleManager.java b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginLifecycleManager.java
index 3e5e8a0..f0ce460 100644
--- a/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginLifecycleManager.java
+++ b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginLifecycleManager.java
@@ -18,6 +18,8 @@
import android.content.ComponentName;
+import java.util.function.BiConsumer;
+
/**
* Provides the ability for consumers to control plugin lifecycle.
*
@@ -33,11 +35,8 @@
/** Returns the currently loaded plugin instance (if plugin is loaded) */
T getPlugin();
- /** Returns true if the lifecycle manager should log debug messages */
- boolean getIsDebug();
-
- /** Sets whether or not hte lifecycle manager should log debug messages */
- void setIsDebug(boolean debug);
+ /** Log tag and messages will be sent to the provided Consumer */
+ void setLogFunc(BiConsumer<String, String> logConsumer);
/** returns true if the plugin is currently loaded */
default boolean isLoaded() {
diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml
new file mode 100644
index 0000000..84b89ca
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_focused="true">
+ <shape android:shape="rectangle">
+ <corners android:radius="16dp" />
+ <!--By default this outline will not show hence 0 width.
+ width is set programmatically when needed and is gated by the flag:
+ com.android.systemui.Flags.pinInputFieldStyledFocusState-->
+ <stroke android:width="0dp"
+ android:color="@color/bouncer_password_focus_color" />
+ </shape>
+ </item>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
index 66c54f2..0b35559 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
@@ -23,7 +23,7 @@
android:layout_marginTop="@dimen/keyguard_lock_padding"
android:importantForAccessibility="no"
android:ellipsize="marquee"
- android:focusable="true"
+ android:focusable="false"
android:gravity="center"
android:singleLine="true" />
</merge>
diff --git a/packages/SystemUI/res-keyguard/values-hi/strings.xml b/packages/SystemUI/res-keyguard/values-hi/strings.xml
index 18d63ab..01b99ec 100644
--- a/packages/SystemUI/res-keyguard/values-hi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hi/strings.xml
@@ -104,7 +104,7 @@
<string name="kg_password_pin_failed" msgid="5136259126330604009">"SIM पिन की कार्यवाही विफल रही!"</string>
<string name="kg_password_puk_failed" msgid="6778867411556937118">"SIM PUK की कार्यवाही विफल रही!"</string>
<string name="accessibility_ime_switch_button" msgid="9082358310194861329">"इनपुट का तरीका बदलें"</string>
- <string name="airplane_mode" msgid="2528005343938497866">"हवाई जहाज़ मोड"</string>
+ <string name="airplane_mode" msgid="2528005343938497866">"फ़्लाइट मोड"</string>
<string name="kg_prompt_reason_restart_pattern" msgid="3321211830602827742">"डिवाइस रीस्टार्ट करने पर, पैटर्न ड्रॉ करना ज़रूरी है"</string>
<string name="kg_prompt_reason_restart_pin" msgid="2672166323886110512">"डिवाइस रीस्टार्ट करने पर, पिन डालना ज़रूरी है"</string>
<string name="kg_prompt_reason_restart_password" msgid="3967993994418885887">"डिवाइस रीस्टार्ट करने पर, पासवर्ड डालना ज़रूरी है"</string>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 0628c3e..ddad1e3 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -25,6 +25,12 @@
<!-- Maximum width of the sliding KeyguardSecurityContainer -->
<dimen name="keyguard_security_width">420dp</dimen>
+ <!-- Width for the keyguard pin input field -->
+ <dimen name="keyguard_pin_field_width">292dp</dimen>
+
+ <!-- Width for the keyguard pin input field -->
+ <dimen name="keyguard_pin_field_height">48dp</dimen>
+
<!-- Height of the sliding KeyguardSecurityContainer
(includes 2x keyguard_security_view_top_margin) -->
<dimen name="keyguard_security_height">420dp</dimen>
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index 565ed10..f51e109 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -62,10 +62,10 @@
<string name="keyguard_plugged_in_charging_slowly"><xliff:g id="percentage">%s</xliff:g> • Charging slowly</string>
<!-- When the lock screen is showing and the phone plugged in, and the defend mode is triggered, say that charging is temporarily limited. -->
- <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Charging optimized to protect battery</string>
+ <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Charging on hold to protect battery</string>
<!-- When the lock screen is showing and the phone plugged in with incompatible charger. -->
- <string name="keyguard_plugged_in_incompatible_charger"><xliff:g id="percentage">%s</xliff:g> • Issue with charging accessory</string>
+ <string name="keyguard_plugged_in_incompatible_charger"><xliff:g id="percentage">%s</xliff:g> • Check charging accessory</string>
<!-- SIM messages --><skip />
<!-- When the user inserts a sim card from an unsupported network, it becomes network locked -->
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 2cca951..4789a22 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -76,6 +76,7 @@
</style>
<style name="Widget.TextView.Password" parent="@android:style/Widget.TextView">
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+ <item name="android:background">@drawable/bouncer_password_text_view_focused_background</item>
<item name="android:gravity">center</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
diff --git a/packages/SystemUI/res/drawable/stat_sys_no_internet_branded_vpn.xml b/packages/SystemUI/res/drawable/stat_sys_no_internet_branded_vpn.xml
new file mode 100644
index 0000000..2161a62
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_no_internet_branded_vpn.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2023, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="17dp"
+ android:height="17dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M12.09,9C11.11,7.5 9.43,6.5 7.5,6.5C4.46,6.5 2,8.96 2,12c0,3.04 2.46,5.5 5.5,5.5c1.93,0 3.61,-1 4.59,-2.5H14v3h4V9H12.09zM18,13hv3h-2v-3h-5.16c-0.43,1.44 -1.76,2.5 -3.34,2.5C5.57,15.5 4,13.93 4,12c0,-1.93 1.57,-3.5 3.5,-3.5c1.58,0 2.9,1.06 3.34,2.5H18V13z"/>
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M7.5,12m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"/>
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M22,10h-2v8h2V10z"/>
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M22,20h-2v2h2V20z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_no_internet_vpn_ic.xml b/packages/SystemUI/res/drawable/stat_sys_no_internet_vpn_ic.xml
new file mode 100644
index 0000000..2161a62
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_no_internet_vpn_ic.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2023, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="17dp"
+ android:height="17dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M12.09,9C11.11,7.5 9.43,6.5 7.5,6.5C4.46,6.5 2,8.96 2,12c0,3.04 2.46,5.5 5.5,5.5c1.93,0 3.61,-1 4.59,-2.5H14v3h4V9H12.09zM18,13hv3h-2v-3h-5.16c-0.43,1.44 -1.76,2.5 -3.34,2.5C5.57,15.5 4,13.93 4,12c0,-1.93 1.57,-3.5 3.5,-3.5c1.58,0 2.9,1.06 3.34,2.5H18V13z"/>
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M7.5,12m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"/>
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M22,10h-2v8h2V10z"/>
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M22,20h-2v2h2V20z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/biometric_prompt_content_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_content_layout.xml
new file mode 100644
index 0000000..3908757
--- /dev/null
+++ b/packages/SystemUI/res/layout/biometric_prompt_content_layout.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/customized_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:orientation="vertical"
+ style="@style/AuthCredentialContentLayoutStyle">
+
+ <TextView
+ android:id="@+id/customized_view_title"
+ style="@style/TextAppearance.AuthCredential.ContentViewTitle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="marquee"
+ android:marqueeRepeatLimit="1"
+ android:singleLine="true" />
+</LinearLayout>
diff --git a/packages/CredentialManager/res/drawable/autofill_light_selectable_item_background.xml b/packages/SystemUI/res/layout/biometric_prompt_content_row_item_text_view.xml
similarity index 61%
rename from packages/CredentialManager/res/drawable/autofill_light_selectable_item_background.xml
rename to packages/SystemUI/res/layout/biometric_prompt_content_row_item_text_view.xml
index 9d16f32d..e39f60f 100644
--- a/packages/CredentialManager/res/drawable/autofill_light_selectable_item_background.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_content_row_item_text_view.xml
@@ -1,6 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (C) 2023 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,10 +14,8 @@
~ limitations under the License.
-->
-<!-- Copied from //frameworks/base/core/res/res/drawable/item_background_material.xml -->
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
- android:color="@color/autofill_light_colorControlHighlight">
- <item android:id="@android:id/mask">
- <color android:color="@android:color/white"/>
- </item>
-</ripple>
\ No newline at end of file
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/TextAppearance.AuthCredential.ContentViewListItem"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1.0" />
\ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/autofill_light_selectable_item_background.xml b/packages/SystemUI/res/layout/biometric_prompt_content_row_layout.xml
similarity index 61%
copy from packages/CredentialManager/res/drawable/autofill_light_selectable_item_background.xml
copy to packages/SystemUI/res/layout/biometric_prompt_content_row_layout.xml
index 9d16f32d..6c86736 100644
--- a/packages/CredentialManager/res/drawable/autofill_light_selectable_item_background.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_content_row_layout.xml
@@ -1,6 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (C) 2023 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,10 +14,8 @@
~ limitations under the License.
-->
-<!-- Copied from //frameworks/base/core/res/res/drawable/item_background_material.xml -->
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
- android:color="@color/autofill_light_colorControlHighlight">
- <item android:id="@android:id/mask">
- <color android:color="@android:color/white"/>
- </item>
-</ripple>
\ No newline at end of file
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/biometric_prompt_content_list_row_height"
+ android:gravity="center_vertical|start"
+ android:orientation="horizontal" />
diff --git a/packages/SystemUI/res/layout/biometric_prompt_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_layout.xml
index bea0e13..10f7113 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_layout.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_layout.xml
@@ -20,6 +20,13 @@
android:layout_height="wrap_content"
android:orientation="vertical">
+ <ImageView
+ android:id="@+id/logo"
+ android:layout_width="@dimen/biometric_auth_icon_size"
+ android:layout_height="@dimen/biometric_auth_icon_size"
+ android:layout_gravity="center"
+ android:scaleType="fitXY"/>
+
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
@@ -48,6 +55,23 @@
android:importantForAccessibility="no"
style="@style/TextAppearance.AuthCredential.Description"/>
+ <Space
+ android:id="@+id/space_above_content"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/biometric_prompt_space_above_content"
+ android:visibility="gone" />
+
+ <ScrollView
+ android:id="@+id/customized_view_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:fadeScrollbars="false"
+ android:gravity="center_vertical"
+ android:orientation="vertical"
+ android:paddingHorizontal="@dimen/biometric_prompt_content_container_padding_horizontal"
+ android:scrollbars="vertical"
+ android:visibility="gone" />
+
<Space android:id="@+id/space_above_icon"
android:layout_width="match_parent"
android:layout_height="48dp" />
diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
index 0534c6e..a0f916c 100644
--- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
@@ -17,6 +17,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:id="@+id/root"
style="@style/Widget.SliceView.Panel"
android:layout_width="wrap_content"
@@ -53,17 +54,40 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_title" />
+ <View
+ android:id="@+id/bluetooth_tile_dialog_progress_background"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ app:layout_constraintEnd_toEndOf="@id/bluetooth_tile_dialog_progress_animation"
+ app:layout_constraintStart_toStartOf="@id/bluetooth_tile_dialog_progress_animation"
+ app:layout_constraintTop_toTopOf="@id/bluetooth_tile_dialog_progress_animation"
+ app:layout_constraintBottom_toBottomOf="@id/bluetooth_tile_dialog_progress_animation"
+ android:background="?androidprv:attr/colorSurfaceVariant" />
+
+ <ProgressBar
+ android:id="@+id/bluetooth_tile_dialog_progress_animation"
+ android:layout_width="152dp"
+ android:layout_height="4dp"
+ android:layout_marginTop="16dp"
+ style="@style/TrimmedHorizontalProgressBar"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_subtitle"
+ android:visibility="invisible"
+ android:indeterminate="true" />
+
<androidx.core.widget.NestedScrollView
android:id="@+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="21dp"
+ android:minHeight="145dp"
android:fillViewport="true"
app:layout_constrainedHeight="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_subtitle">
+ app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_progress_animation">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/scroll_layout"
@@ -81,7 +105,7 @@
android:paddingStart="36dp"
android:text="@string/turn_on_bluetooth"
android:clickable="false"
- android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+ android:textAppearance="@style/TextAppearance.Dialog.Title"
android:textSize="16sp"
app:layout_constraintEnd_toStartOf="@+id/bluetooth_toggle"
app:layout_constraintStart_toStartOf="parent"
@@ -90,8 +114,7 @@
<Switch
android:id="@+id/bluetooth_toggle"
android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:paddingTop="10dp"
+ android:layout_height="64dp"
android:gravity="start|center_vertical"
android:paddingEnd="40dp"
android:contentDescription="@string/turn_on_bluetooth"
@@ -107,7 +130,6 @@
android:id="@+id/device_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="20dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bluetooth_toggle"
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index 85b6e8d..5db9eee 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -50,7 +50,10 @@
app:layout_constraintStart_toStartOf="@id/album_art"
app:layout_constraintEnd_toEndOf="@id/album_art"
app:layout_constraintTop_toTopOf="@id/album_art"
- app:layout_constraintBottom_toBottomOf="@id/album_art" />
+ app:layout_constraintBottom_toBottomOf="@id/album_art"
+ android:clipToOutline="true"
+ android:background="@drawable/qs_media_outline_layout_bg"
+ />
<com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView
android:id="@+id/turbulence_noise_view"
@@ -59,7 +62,10 @@
app:layout_constraintStart_toStartOf="@id/album_art"
app:layout_constraintEnd_toEndOf="@id/album_art"
app:layout_constraintTop_toTopOf="@id/album_art"
- app:layout_constraintBottom_toBottomOf="@id/album_art" />
+ app:layout_constraintBottom_toBottomOf="@id/album_art"
+ android:clipToOutline="true"
+ android:background="@drawable/qs_media_outline_layout_bg"
+ />
<!-- Guideline for output switcher -->
<androidx.constraintlayout.widget.Guideline
diff --git a/packages/SystemUI/res/layout/power_notification_controls_settings.xml b/packages/SystemUI/res/layout/power_notification_controls_settings.xml
deleted file mode 100644
index 83c8a51..0000000
--- a/packages/SystemUI/res/layout/power_notification_controls_settings.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
-
- <include layout="@layout/switch_bar" />
-
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:padding="16dp"
- android:text="@string/power_notification_controls_description"/>
-
-</LinearLayout>
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index 73874a0..b3f32a2 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -19,7 +19,7 @@
<com.android.systemui.qs.QSFooterView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/qs_footer"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
+ android:layout_height="@dimen/qs_footer_height"
android:layout_marginStart="@dimen/qs_footer_margin"
android:layout_marginEnd="@dimen/qs_footer_margin"
android:layout_marginBottom="@dimen/qs_footers_margin_bottom"
@@ -31,7 +31,7 @@
<LinearLayout
android:layout_width="match_parent"
- android:layout_height="@dimen/qs_footer_height"
+ android:layout_height="match_parent"
android:layout_gravity="center_vertical">
<TextView
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index db4cca3..aeb8311 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Kon nie Gesigslot opstel nie. Gaan na Instellings toe om weer te probeer."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Raak die vingerafdruksensor"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Druk die ontsluitikoon om voort te gaan"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Kan nie gesig herken nie. Gebruik eerder vingerafdruk."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Kan nie gesig herken nie"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Gebruik eerder vingerafdruk"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Gesigslot is onbeskikbaar"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth gekoppel."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laai tans • Vol oor <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Swiep links om die gemeenskaplike tutoriaal te begin"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Maak die legstukredigeerder oop"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Verwyder"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Voeg legstuk by"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Klaar"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Opstelling"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Berging"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Wenke"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Kitsprogramme"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> loop tans"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Program is oopgemaak sonder dat dit geïnstalleer is."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tik om toeganklikheidkenmerke oop te maak Pasmaak of vervang knoppie in Instellings.\n\n"<annotation id="link">"Bekyk instellings"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Skuif knoppie na kant om dit tydelik te versteek"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Ontdoen"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g>-kortpad is verwyder"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# kortpad is verwyder}other{# kortpaaie is verwyder}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Beweeg na links bo"</string>
@@ -1207,9 +1225,9 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Gebruikerteenwoordigheid is bespeur"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Stel versteknotasapp in Instellings"</string>
<string name="install_app" msgid="5066668100199613936">"Installeer app"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Swiep om voort te gaan"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Sinkroniseer wedersyds na eksterne skerm?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Jou binneste skerm sal weerspieël word. Jou boonste skerm sal afgeskakel word."</string>
<string name="mirror_display" msgid="2515262008898122928">"Sinkroniseer skerm wedersyds"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Maak toe"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Skerm is gekoppel"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 40fddc8..8bad367 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"በመልክ መክፈትን ማዋቀር አልተቻለም። እንደገና ለመሞከር ወደ ቅንብሮች ይሂዱ።"</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"የጣት አሻራ ዳሳሹን ይንኩ"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"ለመቀጠል የክፈት አዶውን ይጫኑ"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"መልክን መለየት አልተቻለም። በምትኩ የጣት አሻራ ይጠቀሙ።"</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"መልክን መለየት አልተቻለም"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"በምትኩ የጣት አሻራን ይጠቀሙ"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"በመልክ መክፈት አይገኝም"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ብሉቱዝ ተያይዟል።"</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ኃይል በመሙላት ላይ • በ<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ውስጥ ይሞላል"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"የጋራ አጋዥ ሥልጠናውን ለመጀመር ወደ ግራ ያንሸራትቱ።"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"የምግብር አርታዒውን ይክፈቱ"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"አስወግድ"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ምግብር አክል"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"ተከናውኗል"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"ውቅረት"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"ማከማቻ"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"ፍንጮች"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"የቅጽበት መተግበሪያዎች"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> አሂድ"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"መተግበሪያ ሳይጫን ተከፍቷል።"</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"የተደራሽነት ባህሪያትን ለመክፈት መታ ያድርጉ። ይህንን አዝራር በቅንብሮች ውስጥ ያብጁ ወይም ይተኩ።\n\n"<annotation id="link">"ቅንብሮችን አሳይ"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ለጊዜው ለመደበቅ አዝራሩን ወደ ጠርዝ ያንቀሳቅሱ"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"ቀልብስ"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> አቋራጭ ተወግዷል"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# አቋራጭ ተወግዷል}one{# አቋራጭ ተወግዷል}other{# አቋራጮች ተወግደዋል}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ወደ ላይኛው ግራ አንቀሳቅስ"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"የተጠቃሚ ተገኝነት ታውቋል"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"በቅንብሮች ውስጥ ነባሪ የማስታወሻዎች መተግበሪያን ያቀናብሩ"</string>
<string name="install_app" msgid="5066668100199613936">"መተግበሪያን ጫን"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"ለመቀጠል ያንሸራትቱ"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ወደ ውጫዊ ማሳያ ይንጸባረቅ?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"የውስጥ ማሳያዎ ይንጸባረቃል። የፊት ማሳያዎ ይጠፋል።"</string>
<string name="mirror_display" msgid="2515262008898122928">"ማሳያን አንጸባርቅ"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index d19c77b..2566df7 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"تعذّر إعداد ميزة \"فتح الجهاز بالتعرّف على الوجه\". انتقِل إلى \"الإعدادات\" لإعادة المحاولة."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"المس أداة استشعار بصمة الإصبع"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"للمتابعة، اضغط على رمز فتح القفل."</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"يتعذّر التعرّف على الوجه. استخدِم بصمة الإصبع بدلاً من ذلك."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"يتعذّر التعرّف على الوجه."</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"يمكنك استخدام بصمة إصبعك."</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"ميزة \"فتح الجهاز بالتعرف على الوجه\" غير متاحة."</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"تم توصيل البلوتوث."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • جارٍ الشحن • ستمتلئ البطارية خلال <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"مرِّر سريعًا لليمين لبدء الدليل التوجيهي العام."</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"فتح محرِّر التطبيقات المصغّرة"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"إزالة"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"إضافة تطبيق مصغّر"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"تم"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"عملية الإعداد"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"مساحة التخزين"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"تلميحات"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"التطبيقات الفورية"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"التطبيق <xliff:g id="APP">%1$s</xliff:g> قيد التشغيل"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"تمّ فتح التطبيق بدون تثبيته."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"انقر لفتح ميزات تسهيل الاستخدام. يمكنك تخصيص هذا الزر أو استبداله من الإعدادات.\n\n"<annotation id="link">"عرض الإعدادات"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"يمكنك نقل الزر إلى الحافة لإخفائه مؤقتًا."</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"تراجع"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"تمت إزالة اختصار <xliff:g id="FEATURE_NAME">%s</xliff:g>."</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{تمت إزالة اختصار واحد.}zero{تمت إزالة # اختصار.}two{تمت إزالة اختصارَين.}few{تمت إزالة # اختصارات.}many{تمت إزالة # اختصارًا.}other{تمت إزالة # اختصار.}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"النقل إلى أعلى يمين الشاشة"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"تم رصد تواجد المستخدم."</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"يمكنك ضبط تطبيق تدوين الملاحظات التلقائي في \"الإعدادات\"."</string>
<string name="install_app" msgid="5066668100199613936">"تثبيت التطبيق"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"مرِّر سريعًا للمتابعة."</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"هل تريد بث محتوى جهازك على الشاشة الخارجية؟"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"سيتم النسخ المطابق لمحتوى الشاشة الداخلية، وإيقاف الشاشة الأمامية."</string>
<string name="mirror_display" msgid="2515262008898122928">"بث المحتوى على الشاشة"</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 4e78f55..591c125 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"ফে’চ আনলক ছেট আপ কৰিব পৰা নগ’ল। পুনৰ চেষ্টা কৰিবলৈ ছেটিঙলৈ যাওক।"</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"ফিংগাৰপ্ৰিণ্ট ছেন্সৰটো স্পৰ্শ কৰক"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"অব্যাহত ৰাখিবলৈ আনলক কৰক চিহ্নটোত টিপক"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"মুখাৱয়ব চিনিব নোৱাৰি। ফিংগাৰপ্ৰিণ্ট ব্যৱহাৰ কৰক।"</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"মুখাৱয়ব চিনিব নোৱাৰি"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ইয়াৰ সলনি ফিংগাৰপ্ৰিণ্ট ব্যৱহাৰ কৰক"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"ফেচ আনলক সুবিধা উপলব্ধ নহয়"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ব্লুটুথ সংযোগ হ’ল।"</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • চাৰ্জ হৈ আছে • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ত সম্পূৰ্ণ হ’ব"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"সম্প্ৰদায় সম্পৰ্কীয় নিৰ্দেশনা আৰম্ভ কৰিবলৈ বাওঁফালে ছোৱাইপ কৰক"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"ৱিজেট সম্পাদকটো খোলক"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"আঁতৰাওক"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ৱিজেট যোগ দিয়ক"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"কৰা হ’ল"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"ছেটআপ"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"ষ্ট\'ৰেজ"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"ইংগিতবোৰ"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> চলি আছে"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"এপ্টো ইনষ্ট\'ল নকৰাকৈ খোলা হৈছে।"</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"সাধ্য সুবিধাসমূহ খুলিবলৈ টিপক। ছেটিঙত এই বুটামটো কাষ্টমাইজ অথবা সলনি কৰক।\n\n"<annotation id="link">"ছেটিং চাওক"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"বুটামটোক সাময়িকভাৱে লুকুৱাবলৈ ইয়াক একেবাৰে কাষলৈ লৈ যাওক"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"আনডু কৰক"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g>ৰ শ্বৰ্টকাট আঁতৰোৱা হ’ল"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# টা শ্বৰ্টকাট আঁতৰোৱা হ’ল}one{# টা শ্বৰ্টকাট আঁতৰোৱা হ’ল}other{# টা শ্বৰ্টকাট আঁতৰোৱা হ’ল}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"শীৰ্ষৰ বাওঁফালে নিয়ক"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"ব্যৱহাৰকাৰীৰ উপস্থিতি চিনাক্ত কৰা হৈছে"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ছেটিঙত টোকাৰ ডিফ’ল্ট এপ্ ছেট কৰক"</string>
<string name="install_app" msgid="5066668100199613936">"এপ্টো ইনষ্টল কৰক"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"অব্যাহত ৰাখিবলৈ ছোৱাইপ কৰক"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"বাহ্যিক ডিছপ্লে’লৈ মিৰ’ৰ কৰিবনে?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"আপোনাৰ ইনাৰ ডিছপ্লে’ প্ৰতিবিম্বিত কৰা হ’ব। আপোনাৰ ফ্ৰণ্ট ডিছপ্লে’ অফ কৰা হ’ব।"</string>
<string name="mirror_display" msgid="2515262008898122928">"ডিছপ্লে’ মিৰ’ৰ কৰক"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index bf32c5e..978c295 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Üz ilə Kiliddən Açma ayarlanmadı. Yenidən cəhd etmək üçün Ayarlara keçin."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Barmaq izi sensoruna klikləyin"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"\"Kiliddən çıxarın\" ikonasını basaraq davam edin"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Tanımaq olmur. Barmaq izini işlədin."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Üzü tanımaq olmur"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Barmaq izi istifadə edin"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Üz ilə kiliddən çıxarma əlçatan deyil"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth qoşulub."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Şarj edilir • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> sonra dolacaq"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"İcma təlimatını başlatmaq üçün sola sürüşdürün"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Vidcet redaktorunu açın"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Silin"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Vidcet əlavə edin"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Hazırdır"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Ayarlama"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Yaddaş"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Məsləhətlər"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Ani Tətbiqlər"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> işləyir"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Quraşdırılmadan açılan tətbiq."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Əlçatımlılıq funksiyalarını açmaq üçün toxunun. Ayarlarda bu düyməni fərdiləşdirin və ya dəyişdirin.\n\n"<annotation id="link">"Ayarlara baxın"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Düyməni müvəqqəti gizlətmək üçün kənara çəkin"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Geri qaytarın"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> qısayol silindi"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# qısayol silindi}other{# qısayol silindi}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Yuxarıya sola köçürün"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"İstifadəçi mövcudluğu aşkarlandı"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ayarlarda defolt qeydlər tətbiqi ayarlayın"</string>
<string name="install_app" msgid="5066668100199613936">"Tətbiqi quraşdırın"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Çəkərək davam edin"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Xarici displeyə əks etdirilsin?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"İç displey əks etdiriləcək. Ön ekran deaktiv ediləcək."</string>
<string name="mirror_display" msgid="2515262008898122928">"Displeyi əks etdirin"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 56c3e45..1668de0 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Podešavanje otključavanja licem nije uspelo. Idite u Podešavanja da biste probali ponovo."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Dodirnite senzor za otisak prsta"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Pritisnite ikonu otključavanja za nastavak"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Lice nije prepoznato. Koristite otisak prsta."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Lice nije prepoznato"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Koristite otisak prsta"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Otključavanje licem nije dostupno"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth je priključen."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Puni se • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do kraja punjenja"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Prevucite ulevo da biste započeli zajednički vodič"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Otvori uređivač vidžeta"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Ukloni"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodaj vidžet"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Gotovo"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Podešavanje"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Memorijski prostor"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Saveti"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Instant aplikacije"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"Aplikacija <xliff:g id="APP">%1$s</xliff:g> je pokrenuta"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Aplikacija se otvorila bez instaliranja."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Dodirnite za funkcije pristupačnosti. Prilagodite ili zamenite ovo dugme u Podešavanjima.\n\n"<annotation id="link">"Podešavanja"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Pomerite dugme do ivice da biste ga privremeno sakrili"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Opozovi"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Prečica funkcije <xliff:g id="FEATURE_NAME">%s</xliff:g> je uklonjena"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# prečica je uklonjena}one{# prečica je uklonjena}few{# prečice su uklonjene}other{# prečica je uklonjeno}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Premesti gore levo"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Prisustvo korisnika može da se otkrije"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Podesite podrazumevanu aplikaciju za beleške u Podešavanjima"</string>
<string name="install_app" msgid="5066668100199613936">"Instaliraj aplikaciju"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Prevucite da biste nastavili"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Želite li da preslikate na spoljnji ekran?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Unutrašnji ekran će se preslikati. Prednji ekran će se isključiti."</string>
<string name="mirror_display" msgid="2515262008898122928">"Preslikaj ekran"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 3bc6269..8d2adda 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Не ўдалося наладзіць функцыю распазнавання твару. Каб паўтарыць, перайдзіце ў Налады."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Дакраніцеся да сканера адбіткаў пальцаў"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Каб працягнуць, націсніце на значок разблакіроўкі"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Твар не распазнаны. Скарыстайце адбітак пальца."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Твар не распазнаны"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Скарыстайце адбітак пальца"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Распазнаванне твару не працуе"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth-сувязь."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ідзе зарадка • Поўны зарад праз <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Правядзіце пальцам па экране ўлева, каб азнаёміцца з дапаможнікам"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Адкрыць рэдактар віджэтаў"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Выдаліць"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Дадаць віджэт"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Гатова"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Наладжванне"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Захоўванне"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Падказкі"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Імгненныя праграмы"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"Праграма \"<xliff:g id="APP">%1$s</xliff:g>\" запушчана"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Праграма адкрыта без усталёўкі."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Націсніце, каб адкрыць спецыяльныя магчымасці. Рэгулюйце ці замяняйце кнопку ў Наладах.\n\n"<annotation id="link">"Прагляд налад"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Каб часова схаваць кнопку, перамясціце яе на край"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Адрабіць"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Выдалены ярлык <xliff:g id="FEATURE_NAME">%s</xliff:g>"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{Выдалены # ярлык}one{Выдалены # ярлык}few{Выдалена # ярлыкі}many{Выдалена # ярлыкоў}other{Выдалена # ярлыка}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Перамясціць лявей і вышэй"</string>
@@ -1207,9 +1225,9 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Выяўлена прысутнасць карыстальніка"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Задайце ў Наладах стандартную праграму для нататак"</string>
<string name="install_app" msgid="5066668100199613936">"Усталяваць праграму"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Правядзіце пальцам, каб працягнуць"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Адлюстраваць на знешнім дысплэі?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Будзе ўключана дубліраванне ўнутранага дысплэя. Пярэдні дысплэй будзе выключаны."</string>
<string name="mirror_display" msgid="2515262008898122928">"Адлюстраваць дысплэй"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Закрыць"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Дысплэй падключаны"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 22d167e..6f5890f 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Функцията „Отключване с лице“ не бе настроена. Отворете настройките, за да опитате отново."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Докоснете сензора за отпечатъци"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Натиснете иконата за отключване, за да продължите"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Лицето не е разпознато. Използвайте отпечатък."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Лицето не е разпознато"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Използвайте отпечатък"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"„Отключване с лице“ не е налице"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth е включен."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Зарежда се • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до пълно зареждане"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Прекарайте пръст наляво, за да стартирате общия урок"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Отваряне на редактора на приспособлението"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Премахване"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Добавяне на приспособление"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Готово"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Настройване"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Хранилище"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Съвети"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Мигновени приложения"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> работи"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Приложението се отвори, без да бъде инсталирано."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Докоснете, за да отворите функциите за достъпност. Персон./заменете бутона от настройките.\n\n"<annotation id="link">"Преглед на настройките"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Преместете бутона до края, за да го скриете временно"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Отмяна"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Прекият път за „<xliff:g id="FEATURE_NAME">%s</xliff:g>“ бе премахнат"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# пряк път бе премахнат}other{# преки пътя бяха премахнати}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Преместване горе вляво"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Установено е присъствие на потребител"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Задайте стандартно приложение за бележки от настройките"</string>
<string name="install_app" msgid="5066668100199613936">"Инсталиране на приложението"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Прекарайте пръст, за да продължите"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Да се дублира ли на външния екран?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Съдържанието на вътрешния ви дисплей ще бъде дублирано. Предният ви дисплей ще бъде изключен."</string>
<string name="mirror_display" msgid="2515262008898122928">"Дублиране на дисплея"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 045af93..601b29a 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"\'ফেস আনলক\' সেট-আপ করা যায়নি। আবার চেষ্টা করতে সেটিংসে যান।"</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"আঙ্গুলের ছাপের সেন্সর স্পর্শ করুন"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"চালিয়ে যেতে \'আনলক করুন\' আইকনে প্রেস করুন"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"মুখ শনাক্ত করতে পারছি না। পরিবর্তে আঙ্গুলের ছাপ ব্যবহার করুন।"</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"ফেস শনাক্ত করা যায়নি"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"পরিবর্তে ফিঙ্গারপ্রিন্ট ব্যবহার করুন"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"\'ফেস আনলক\' উপলভ্য নেই"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ব্লুটুথ সংযুক্ত হয়েছে৷"</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • চার্জ হচ্ছে • পুরো চার্জ হতে আরও <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> সময় লাগবে"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"কমিউনিটি টিউটোরিয়াল চালু করতে বাঁদিকে সোয়াইপ করুন"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"উইজেট এডিটর খুলুন"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"সরান"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"উইজেট যোগ করুন"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"হয়ে গেছে"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"সেট-আপ"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"স্টোরেজ"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"হিন্ট"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> চলছে"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"অ্যাপটি ইনস্টল না করে চালু করা হয়েছে।"</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"অ্যাক্সেসিবিলিটি ফিচার খুলতে ট্যাপ করুন। কাস্টমাইজ করুন বা সেটিংসে এই বোতামটি সরিয়ে দিন।\n\n"<annotation id="link">"সেটিংস দেখুন"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"এটি অস্থায়ীভাবে লুকাতে বোতামটি কোণে সরান"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"আগের অবস্থায় ফিরুন"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g>-এর শর্টকাট সরানো হয়েছে"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{#টি শর্টকাট সরানো হয়েছে}one{#টি শর্টকাট সরানো হয়েছে}other{#টি শর্টকাট সরানো হয়েছে}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"উপরে বাঁদিকে সরান"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"ব্যবহারকারীর উপস্থিতি শনাক্ত করা হয়েছে"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"\'সেটিংস\' থেকে ডিফল্ট নোট নেওয়ার অ্যাপ সেট করুন"</string>
<string name="install_app" msgid="5066668100199613936">"অ্যাপ ইনস্টল করুন"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"চালিয়ে যেতে সোয়াইপ করুন"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"এক্সটার্নাল ডিসপ্লেতে মিরর করবেন?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"আপনার ইনার ডিসপ্লে মিরর করা হবে। আপনার ফ্রন্ট ডিসপ্লে বন্ধ করা হবে।"</string>
<string name="mirror_display" msgid="2515262008898122928">"ডিসপ্লে দেখান"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index ae962ce..287eed7 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Postavljanje otključavanja licem nije uspjelo. Idite u Postavke da pokušate ponovo."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Dodirnite senzor za otisak prsta"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Nastavak pritiskanjem ikone za otključavanje"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Nije moguće prepoznati lice. Koristite otisak prsta."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Nije moguće prepoznati lice"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Koristite otisak prsta"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Otključavanje licem je nedostupno"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth je povezan."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Punjenje • Potpuna napunjenost za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Prevucite ulijevo da pokrenete zajednički vodič"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Otvaranje uređivača vidžeta"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Uklanjanje"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodajte vidžet"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Gotovo"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Postavljanje"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Pohrana"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Savjeti"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Instant aplikacije"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"Pokrenuta je aplikacija <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Aplikacija je otvorena bez prethodne instalacije."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Dodirnite da otvorite funkcije pristupačnosti. Prilagodite ili zamijenite dugme u Postavkama.\n\n"<annotation id="link">"Postavke"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Premjestite dugme do ivice da ga privremeno sakrijete"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Opozovi"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Prečica <xliff:g id="FEATURE_NAME">%s</xliff:g> je uklonjena"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# prečica je uklonjena}one{# prečica je uklonjena}few{# prečice su uklonjene}other{# prečica je uklonjeno}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Pomjeranje gore lijevo"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Otkriveno je prisustvo korisnika"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Postavite zadanu aplikaciju za bilješke u Postavkama"</string>
<string name="install_app" msgid="5066668100199613936">"Instaliraj aplikaciju"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Prevucite da nastavite"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Preslikati na vanjski ekran?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Unutrašnji ekran će se preslikavati. Prednji ekran će se isključiti."</string>
<string name="mirror_display" msgid="2515262008898122928">"Preslikaj ekran"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 8cf8828..d56e4f0 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"No s\'ha pogut configurar el desbloqueig facial. Ves a Configuració per tornar-ho a provar."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Toca el sensor d\'empremtes digitals"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Prem la icona de desbloqueig per continuar"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"No podem detectar la cara. Usa l\'empremta digital."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"No es reconeix la cara"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Utilitza l\'empremta digital"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Desbloqueig facial no està disponible"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connectat."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • S\'està carregant • Es completarà d\'aquí a <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Llisca cap a l\'esquerra per iniciar el tutorial de la comunitat"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Obre l\'editor de widgets"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Suprimeix"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Afegeix un widget"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Fet"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Configuració"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Emmagatzematge"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Suggeriments"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Aplicacions instantànies"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"S\'està executant <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"L\'aplicació s\'ha obert sense instal·lar-se."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Toca per obrir funcions d\'accessibilitat. Personalitza o substitueix el botó a Configuració.\n\n"<annotation id="link">"Mostra"</annotation>"."</string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mou el botó a l\'extrem per amagar-lo temporalment"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Desfés"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"S\'ha suprimit la drecera a <xliff:g id="FEATURE_NAME">%s</xliff:g>"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{S\'ha suprimit # drecera}many{S\'han suprimit # dreceres}other{S\'han suprimit # dreceres}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mou a dalt a l\'esquerra"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"S\'ha detectat la presència d\'usuaris"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Defineix l\'aplicació de notes predeterminada a Configuració"</string>
<string name="install_app" msgid="5066668100199613936">"Instal·la l\'aplicació"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Llisca per continuar"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Duplicar a la pantalla externa?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"La pantalla interior es duplicarà. La pantalla frontal es desactivarà."</string>
<string name="mirror_display" msgid="2515262008898122928">"Duplica la pantalla"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 17084dc..9c71436 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Odemknutí obličejem se nepodařilo nastavit. Pokud to chcete zkusit znovu, přejděte do Nastavení."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Dotkněte se snímače otisků prstů"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Klepněte na ikonu odemknutí"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Obličej se nepodařilo rozpoznat. Použijte místo něj otisk prstu."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Obličej nelze rozpoznat"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Použijte otisk prstu"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Odemknutí obličejem není k dispozici"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Rozhraní Bluetooth je připojeno."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Nabíjení • Plně nabito za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Přejetím doleva spustíte komunitní výukový program"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Otevřít editor widgetů"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Odstranit"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Přidat widget"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Hotovo"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Nastavit"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Úložiště"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Tipy"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Okamžité aplikace"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"Aplikace <xliff:g id="APP">%1$s</xliff:g> je spuštěna"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Aplikace byla otevřena bez instalace."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Klepnutím otevřete funkce přístupnosti. Tlačítko lze upravit nebo nahradit v Nastavení.\n\n"<annotation id="link">"Nastavení"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Přesunutím tlačítka k okraji ho dočasně skryjete"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Vrátit zpět"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Zkratka pro <xliff:g id="FEATURE_NAME">%s</xliff:g> byla odstraněna"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{Byla odstraněna # zkratka}few{Byly odstraněny # zkratky}many{Bylo odstraněno # zkratky}other{Bylo odstraněno # zkratek}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Přesunout vlevo nahoru"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Je zjištěna přítomnost uživatele"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Výchozí aplikaci pro poznámky nastavíte v Nastavení"</string>
<string name="install_app" msgid="5066668100199613936">"Nainstalovat aplikaci"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Pokračujte přejetím prstem"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Zrcadlit na externí displej?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Vnitřní displej bude zrcadlen. Přední displej bude vypnutý."</string>
<string name="mirror_display" msgid="2515262008898122928">"Zrcadlit displej"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 41abea3..06f9ff3 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Ansigtsoplåsning kunne ikke konfigureres. Gå til Indstillinger for at prøve igen."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Sæt fingeren på fingeraftrykssensoren"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Tryk på oplåsningsikonet for at fortsætte"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Ansigtet kan ikke genkendes. Brug fingeraftryk i stedet."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Ansigt kan ikke genkendes"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Brug fingeraftryk i stedet"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Ansigtsoplåsning er utilgængelig"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth tilsluttet."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Oplader • Fuldt opladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Stryg mod venstre for at starte den fælles vejledning"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Åbn redigeringsværktøjet til widgets"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Fjern"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Tilføj widget"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Udfør"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Konfiguration"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Lagerplads"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Tips"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> kører"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"En app blev åbnet uden at blive installeret."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tryk for at åbne hjælpefunktioner. Tilpas eller erstat denne knap i Indstillinger.\n\n"<annotation id="link">"Se indstillingerne"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Flyt knappen til kanten for at skjule den midlertidigt"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Fortryd"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Genvejen til <xliff:g id="FEATURE_NAME">%s</xliff:g> er fjernet"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# genvej er fjernet}one{# genvej er fjernet}other{# genveje er fjernet}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Flyt op til venstre"</string>
@@ -1033,7 +1051,7 @@
<string name="media_output_dialog_launch_app_text" msgid="1527413319632586259">"Åbn appen for at caste denne session."</string>
<string name="media_output_dialog_unknown_launch_app_name" msgid="1084899329829371336">"Ukendt app"</string>
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Stop med at caste"</string>
- <string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Enheder, der er tilgængelige for lydoutput."</string>
+ <string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Enheder, der er tilgængelige for lydudgang."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Lydstyrke"</string>
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Højttalere og skærme"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Brugertilstedeværelse er registreret"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Angiv standardapp til noter i Indstillinger"</string>
<string name="install_app" msgid="5066668100199613936">"Installer app"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Stryg for at fortsætte"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vil du spejle til ekstern skærm?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Din indre skærm spejles. Din skærm på forsiden slukkes."</string>
<string name="mirror_display" msgid="2515262008898122928">"Spejl skærm"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index d95e229..2acd2fe 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Die Entsperrung per Gesichtserkennung konnte nicht eingerichtet werden. Gehe zu den Einstellungen und versuche es noch einmal."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Berühre den Fingerabdrucksensor"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Tippe zum Fortfahren auf das Symbol „Entsperren“"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Gesicht wurde nicht erkannt. Verwende stattdessen den Fingerabdruck."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Gesicht nicht erkannt"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Fingerabdruck verwenden"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Entsperrung per Gesichtserkennung nicht verfügbar"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Mit Bluetooth verbunden"</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Wird geladen • Voll in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Wische nach links, um das gemeinsame Tutorial zu starten"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Widget-Editor öffnen"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Entfernen"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Widget hinzufügen"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Fertig"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Einrichtung"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Speicher"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Hinweise"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> wird ausgeführt"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"App wurde geöffnet, ohne vorher installiert zu werden."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tippe, um die Bedienungshilfen aufzurufen. Du kannst diese Schaltfläche in den Einstellungen anpassen oder ersetzen.\n\n"<annotation id="link">"Zu den Einstellungen"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Durch Ziehen an den Rand wird die Schaltfläche zeitweise ausgeblendet"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Rückgängig machen"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Verknüpfung für „<xliff:g id="FEATURE_NAME">%s</xliff:g>“ entfernt"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# Verknüpfung entfernt}other{# Verknüpfungen entfernt}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Nach oben links verschieben"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Anwesenheit des Nutzers wurde erkannt"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Standard-Notizen-App in den Einstellungen einrichten"</string>
<string name="install_app" msgid="5066668100199613936">"App installieren"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Zum Fortfahren wischen"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Auf externen Bildschirm spiegeln?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Dein inneres Display wird gespiegelt. Das Frontdisplay wird ausgeschaltet."</string>
<string name="mirror_display" msgid="2515262008898122928">"Bildschirm spiegeln"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 5848e4f..79d350b 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Δεν ήταν δυνατή η ρύθμιση για το Ξεκλείδωμα με το πρόσωπο. Μεταβείτε στις Ρυθμίσεις και δοκιμάστε ξανά."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Αγγίξτε τον αισθητήρα δακτυλικού αποτυπώματος"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Πατήστε το εικονίδιο ξεκλειδώματος για συνέχεια"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Το πρόσωπο δεν αναγνωρίζεται. Χρησιμ. δακτ. αποτ."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Αδύνατη η αναγν. προσώπου"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Χρησιμ. δακτυλ. αποτύπ."</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Ξεκλ. με πρόσωπο μη διαθ."</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Το Bluetooth είναι συνδεδεμένο."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Φόρτιση • Πλήρης φόρτιση σε <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Σύρετε προς τα αριστερά για να ξεκινήσετε τον κοινόχρηστο οδηγό"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Άνοιγμα προγράμ. επεξεργασίας γραφικών στοιχείων"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Κατάργηση"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Προσθήκη γραφικού στοιχείου"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Τέλος"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Ρύθμιση"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Αποθηκευτικός χώρος"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Συμβουλές"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Instant Εφαρμογές"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"Η εφαρμογή <xliff:g id="APP">%1$s</xliff:g> εκτελείται"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Η εφαρμογή άνοιξε χωρίς να έχει εγκατασταθεί."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Πατήστε για άνοιγμα των λειτουργιών προσβασιμότητας. Προσαρμόστε ή αντικαταστήστε το κουμπί στις Ρυθμίσεις.\n\n"<annotation id="link">"Προβολή ρυθμίσεων"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Μετακινήστε το κουμπί στο άκρο για προσωρινή απόκρυψη"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Αναίρεση"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Η συντόμευση <xliff:g id="FEATURE_NAME">%s</xliff:g> καταργήθηκε"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{Καταργήθηκε # συντόμευση}other{Καταργήθηκαν # συντομεύσεις}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Μετακίνηση επάνω αριστερά"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Εντοπίστηκε παρουσία χρήστη"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ορίστε την προεπιλεγμένη εφαρμογή σημειώσεων στις Ρυθμίσεις"</string>
<string name="install_app" msgid="5066668100199613936">"Εγκατάσταση εφαρμογής"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Σύρετε για συνέχεια"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Κατοπτρισμός σε εξωτερική οθόνη;"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Θα γίνει κατοπτρισμός της εσωτερικής προβολής. Η μπροστινή οθόνη θα απενεργοποιηθεί."</string>
<string name="mirror_display" msgid="2515262008898122928">"Κατοπτρισμός οθόνης"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 870e4dd..afd2e72 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Couldn\'t set up Face Unlock. Go to Settings to try again."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Touch the fingerprint sensor"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Press the unlock icon to continue"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Can’t recognise face. Use fingerprint instead."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Can’t recognise face"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Use fingerprint instead"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Face Unlock unavailable"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connected."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Swipe left to start the communal tutorial"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Open the widget editor"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Remove"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Add widget"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Done"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Setup"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Storage"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Hints"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> running"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"App opened without being installed."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tap to open accessibility features. Customise or replace this button in Settings.\n\n"<annotation id="link">"View settings"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Move button to the edge to hide it temporarily"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Undo"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> shortcut removed"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# shortcut removed}other{# shortcuts removed}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Move top left"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"User presence is detected"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Set default notes app in Settings"</string>
<string name="install_app" msgid="5066668100199613936">"Install app"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Swipe to continue"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Mirror to external display?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Your inner display will be mirrored. Your front display will be turned off."</string>
<string name="mirror_display" msgid="2515262008898122928">"Mirror display"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index f25baf2..a0000ef 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -188,10 +188,10 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Couldn’t set up face unlock. Go to Settings to try again."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Touch the fingerprint sensor"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Press the unlock icon to continue"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Can’t recognize face. Use fingerprint instead."</string>
+ <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Face not recognized. Use fingerprint instead."</string>
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Can’t recognize face"</string>
+ <string name="keyguard_face_failed" msgid="2346762871330729634">"Face not recognized"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Use fingerprint instead"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Face Unlock unavailable"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connected."</string>
@@ -413,6 +413,11 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Swipe left to start the communal tutorial"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Open the widget editor"</string>
+ <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Customize"</string>
+ <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Dismiss"</string>
+ <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Add, remove, and reorder your widgets in this space"</string>
+ <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Add more widgets"</string>
+ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Long press to customize widgets"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Remove"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Add widget"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Done"</string>
@@ -834,6 +839,7 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Setup"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Storage"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Hints"</string>
+ <string name="notification_channel_accessibility" msgid="8956203986976245820">"Accessibility"</string>
<string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> running"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"App opened without being installed."</string>
@@ -929,6 +935,8 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tap to open accessibility features. Customize or replace this button in Settings.\n\n"<annotation id="link">"View settings"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Move button to the edge to hide it temporarily"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Undo"</string>
+ <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Accessibility button hidden"</string>
+ <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Tap to show accessibility button"</string>
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> shortcut removed"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# shortcut removed}other{# shortcuts removed}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Move top left"</string>
@@ -1207,6 +1215,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"User presence is detected"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Set default notes app in Settings"</string>
<string name="install_app" msgid="5066668100199613936">"Install app"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Swipe to continue"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Mirror to external display?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Your inner display will be mirrored. Your front display will be turned off."</string>
<string name="mirror_display" msgid="2515262008898122928">"Mirror display"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 870e4dd..afd2e72 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Couldn\'t set up Face Unlock. Go to Settings to try again."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Touch the fingerprint sensor"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Press the unlock icon to continue"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Can’t recognise face. Use fingerprint instead."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Can’t recognise face"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Use fingerprint instead"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Face Unlock unavailable"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connected."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Swipe left to start the communal tutorial"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Open the widget editor"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Remove"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Add widget"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Done"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Setup"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Storage"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Hints"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> running"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"App opened without being installed."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tap to open accessibility features. Customise or replace this button in Settings.\n\n"<annotation id="link">"View settings"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Move button to the edge to hide it temporarily"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Undo"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> shortcut removed"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# shortcut removed}other{# shortcuts removed}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Move top left"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"User presence is detected"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Set default notes app in Settings"</string>
<string name="install_app" msgid="5066668100199613936">"Install app"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Swipe to continue"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Mirror to external display?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Your inner display will be mirrored. Your front display will be turned off."</string>
<string name="mirror_display" msgid="2515262008898122928">"Mirror display"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 870e4dd..afd2e72 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Couldn\'t set up Face Unlock. Go to Settings to try again."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Touch the fingerprint sensor"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Press the unlock icon to continue"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Can’t recognise face. Use fingerprint instead."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Can’t recognise face"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Use fingerprint instead"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Face Unlock unavailable"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connected."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Swipe left to start the communal tutorial"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Open the widget editor"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Remove"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Add widget"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Done"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Setup"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Storage"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Hints"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> running"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"App opened without being installed."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tap to open accessibility features. Customise or replace this button in Settings.\n\n"<annotation id="link">"View settings"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Move button to the edge to hide it temporarily"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Undo"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> shortcut removed"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# shortcut removed}other{# shortcuts removed}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Move top left"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"User presence is detected"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Set default notes app in Settings"</string>
<string name="install_app" msgid="5066668100199613936">"Install app"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Swipe to continue"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Mirror to external display?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Your inner display will be mirrored. Your front display will be turned off."</string>
<string name="mirror_display" msgid="2515262008898122928">"Mirror display"</string>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index b3ed714..835cf38 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -188,10 +188,10 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Couldn’t set up face unlock. Go to Settings to try again."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Touch the fingerprint sensor"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Press the unlock icon to continue"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Can’t recognize face. Use fingerprint instead."</string>
+ <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Face not recognized. Use fingerprint instead."</string>
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Can’t recognize face"</string>
+ <string name="keyguard_face_failed" msgid="2346762871330729634">"Face not recognized"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Use fingerprint instead"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Face Unlock unavailable"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connected."</string>
@@ -413,6 +413,11 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Swipe left to start the communal tutorial"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Open the widget editor"</string>
+ <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Customize"</string>
+ <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Dismiss"</string>
+ <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Add, remove, and reorder your widgets in this space"</string>
+ <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Add more widgets"</string>
+ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Long press to customize widgets"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Remove"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Add widget"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Done"</string>
@@ -834,6 +839,7 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Setup"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Storage"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Hints"</string>
+ <string name="notification_channel_accessibility" msgid="8956203986976245820">"Accessibility"</string>
<string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> running"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"App opened without being installed."</string>
@@ -929,6 +935,8 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tap to open accessibility features. Customize or replace this button in Settings.\n\n"<annotation id="link">"View settings"</annotation>""</string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Move button to the edge to hide it temporarily"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Undo"</string>
+ <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Accessibility button hidden"</string>
+ <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Tap to show accessibility button"</string>
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> shortcut removed"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# shortcut removed}other{# shortcuts removed}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Move top left"</string>
@@ -1207,6 +1215,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"User presence is detected"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Set default notes app in Settings"</string>
<string name="install_app" msgid="5066668100199613936">"Install app"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Swipe to continue"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Mirror to external display?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Your inner display will be mirrored. Your front display will be turned off."</string>
<string name="mirror_display" msgid="2515262008898122928">"Mirror display"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index c535560..a12c574 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"No se pudo configurar el desbloqueo facial. Ve a Configuración para volver a intentarlo."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Toca el sensor de huellas dactilares"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Presiona el ícono de desbloqueo para continuar"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"No se reconoce el rostro. Usa la huella dactilar."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"No se reconoce el rostro"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Usa la huella dactilar"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Desbloqueo facial no disponible"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado"</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando • Se completará en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Desliza el dedo a la izquierda para iniciar el instructivo comunal"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Abrir el editor de widget"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Quitar"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Agregar widget"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Listo"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Configuración"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Almacenamiento"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Sugerencias"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Apps instantáneas"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> en ejecución"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"La app se abrió sin instalarse."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Presiona para abrir las funciones de accesibilidad. Personaliza o cambia botón en Config.\n\n"<annotation id="link">"Ver config"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mueve el botón hacia el borde para ocultarlo temporalmente"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Deshacer"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Se quitó el acceso directo a <xliff:g id="FEATURE_NAME">%s</xliff:g>"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{Se quitó # acceso directo}many{Se quitaron # accesos directos}other{Se quitaron # accesos directos}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mover arriba a la izquierda"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Se detectó la presencia del usuario"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Configura la app de notas predeterminada en Configuración"</string>
<string name="install_app" msgid="5066668100199613936">"Instalar app"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Desliza el dedo para continuar"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"¿Quieres duplicar en la pantalla externa?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Se duplicará la pantalla interior. Se apagará la pantalla frontal."</string>
<string name="mirror_display" msgid="2515262008898122928">"Duplicar pantalla"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 1157ff1..765f2e3 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"No se ha podido configurar Desbloqueo facial. Ve a Ajustes e inténtalo de nuevo."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Toca el sensor de huellas digitales"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Pulsa el icono de desbloquear para continuar"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"No se reconoce la cara. Usa la huella digital."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"No se reconoce la cara"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Usa la huella digital"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Desbloqueo facial no disponible"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado"</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando • Carga completa en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Desliza hacia la izquierda para iniciar el tutorial de la comunidad"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Abrir editor de widgets"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Quitar"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Añadir widget"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Hecho"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Configuración"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Almacenamiento"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Sugerencias"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Aplicaciones Instantáneas"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> se está ejecutando"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"La aplicación se ha abierto sin instalarse."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Toca para abrir funciones de accesibilidad. Personaliza o sustituye este botón en Ajustes.\n\n"<annotation id="link">"Ver ajustes"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mueve el botón hacia el borde para ocultarlo temporalmente"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Deshacer"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Acceso directo a <xliff:g id="FEATURE_NAME">%s</xliff:g> quitado"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# acceso directo eliminado}many{# accesos directos eliminados}other{# accesos directos eliminados}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mover arriba a la izquierda"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Se ha detectado la presencia de usuarios"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Configura la aplicación de notas predeterminada en Ajustes"</string>
<string name="install_app" msgid="5066668100199613936">"Instalar aplicación"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Desliza para continuar"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"¿Proyectar a pantalla externa?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Se proyectará tu pantalla interior. Se apagará tu pantalla frontal."</string>
<string name="mirror_display" msgid="2515262008898122928">"Proyectar pantalla"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 36b3f1e..249e45f 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Näoga avamist ei õnnestunud seadistada. Avage seaded ja proovige uuesti."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Puudutage sõrmejäljeandurit"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Jätkamiseks vajutage avamise ikooni"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Nägu ei õnnestu tuvastada. Kasutage sõrmejälge."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Nägu ei õnnestu tuvastada"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Kasutage sõrmejälge"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Näoga avamine pole saadaval"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth on ühendatud."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laadimine • Täis <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> pärast"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Ühise õpetuse käivitamiseks pühkige vasakule"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Vidina redaktori avamine"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Eemalda"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Lisa vidin"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Valmis"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Seadistamine"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Salvestusruum"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Vihjed"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Installimata avatavad rakendused"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"Rakendus <xliff:g id="APP">%1$s</xliff:g> töötab"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Rakendus avati installimata."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Puudutage juurdepääsufunktsioonide avamiseks. Kohandage nuppu või asendage see seadetes.\n\n"<annotation id="link">"Kuva seaded"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Teisaldage nupp serva, et see ajutiselt peita"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Võta tagasi"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Funktsiooni <xliff:g id="FEATURE_NAME">%s</xliff:g> otsetee eemaldati"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# otsetee eemaldati}other{# otseteed eemaldati}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Teisalda üles vasakule"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Tuvastati kasutaja kohalolu"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Määrake seadetes märkmete vaikerakendus."</string>
<string name="install_app" msgid="5066668100199613936">"Installi rakendus"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Pühkige jätkamiseks"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Kas peegeldada välisekraanile?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Teie siseekraani peegeldatakse. Teie esiekraan lülitatakse välja."</string>
<string name="mirror_display" msgid="2515262008898122928">"Peegelda ekraani"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 5ea02d1..1a0d482 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Ezin izan da konfiguratu aurpegi bidez desblokeatzeko eginbidea. Berriro saiatzeko, joan ezarpenetara."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Sakatu hatz-marken sentsorea"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Aurrera egiteko, sakatu desblokeatzeko ikonoa"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Ezin da hauteman aurpegia. Erabili hatz-marka."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Ezin da ezagutu aurpegia"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Erabili hatz-marka"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Aurpegi bidez desblokeatzeko eginbidea ez dago erabilgarri"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetootha konektatuta."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Kargatzen • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> guztiz kargatu arte"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Tutorial komuna hasteko, pasatu hatza ezkerrera"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Ireki widget-editorea"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Kendu"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Gehitu widget bat"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Eginda"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Konfigurazioa"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Memoria"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Aholkuak"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Zuzeneko aplikazioak"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> abian da"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Ezer instalatu gabe ireki da aplikazioa."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Erabilerraztasun-eginbideak irekitzeko, sakatu hau. Ezarpenetan pertsonalizatu edo ordez dezakezu botoia.\n\n"<annotation id="link">"Ikusi ezarpenak"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Eraman botoia ertzera aldi baterako ezkutatzeko"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Desegin"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Kendu da lasterbidea (<xliff:g id="FEATURE_NAME">%s</xliff:g>)"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# lasterbide kendu da}other{# lasterbide kendu dira}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Eraman goialdera, ezkerretara"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Erabiltzailearen presentzia hauteman da"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ezarri oharren aplikazio lehenetsia ezarpenetan"</string>
<string name="install_app" msgid="5066668100199613936">"Instalatu aplikazioa"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Aurrera egiteko, pasatu hatza"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Kanpoko pantailan islatu nahi duzu?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Barneko pantaila islatuko da. Aurreko pantaila desaktibatu egingo da."</string>
<string name="mirror_display" msgid="2515262008898122928">"Islatu pantaila"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index aa77b4b..b77c949 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"«قفلگشایی با چهره» راهاندازی نشد. برای امتحان مجدد، به «تنظیمات» بروید."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"حسگر اثر انگشت را لمس کنید"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"برای ادامه، نماد قفلگشایی را فشار دهید"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"چهره شناسایی نشد. درعوض از اثر انگشت استفاده کنید."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"چهره شناسایی نشد"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"از اثر انگشت استفاده کنید"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"«قفلگشایی با چهره» دردسترس نیست"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"بلوتوث متصل است."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • درحال شارژ شدن • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> تا شارژ کامل"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"برای شروع آموزش گامبهگام عمومی، تند بهچپ بکشید"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"باز کردن ویرایشگر ابزارک"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"برداشتن"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"افزودن ابزارک"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"تمام"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"راهاندازی"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"فضای ذخیرهسازی"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"نکات"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"برنامههای فوری"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> درحال اجرا"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"برنامه بدون نصب شدن باز شد."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"برای باز کردن ویژگیهای دسترسپذیری ضربه بزنید. در تنظیمات این دکمه را سفارشی یا جایگزین کنید\n\n"<annotation id="link">"تنظیمات"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"برای پنهان کردن موقتی دکمه، آن را به لبه ببرید"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"واگرد"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"میانبر «<xliff:g id="FEATURE_NAME">%s</xliff:g>» برداشته شد"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{میانبر «#» برداشته شد}one{میانبر «#» برداشته شد}other{میانبر «#» برداشته شد}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"انتقال به بالا سمت راست"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"حضور کاربر شناسایی میشود"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"برنامه پیشفرض یادداشت را در «تنظیمات» تنظیم کنید"</string>
<string name="install_app" msgid="5066668100199613936">"نصب برنامه"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"برای ادامه، تند بکشید"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"روی نمایشگر خارجی قرینهسازی شود؟"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"نمایشگر داخلی شما قرینهسازی میشود. نمایشگر جلو خاموش میشود."</string>
<string name="mirror_display" msgid="2515262008898122928">"قرینهسازی نمایشگر"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 882c42c..b764e07 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Kasvojentunnistusavauksen käyttöönotto epäonnistui. Siirry asetuksiin ja yritä uudelleen."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Kosketa sormenjälkitunnistinta"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Jatka lukituksen avauskuvakkeella"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Kasvoja ei voi tunnistaa. Käytä sormenjälkeä."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Kasvoja ei voi tunnistaa"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Käytä sormenjälkeä"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Kasvojentunnistusavaus ei ole saatavilla"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth yhdistetty."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Latautuu • Täynnä <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> päästä"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Aloita yhteisöesittely pyyhkäisemällä vasemmalle"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Avaa widgetien muokkaaja"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Poista"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Lisää widget"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Valmis"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Määritys"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Tallennustila"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Vihjeet"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> on käynnissä"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Sovellus avattiin ilman asennusta."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Avaa esteettömyysominaisuudet napauttamalla. Yksilöi tai vaihda painike asetuksista.\n\n"<annotation id="link">"Avaa asetukset"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Piilota painike tilapäisesti siirtämällä se reunaan"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Kumoa"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Pikanäppäin (<xliff:g id="FEATURE_NAME">%s</xliff:g>) poistettu"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# pikanäppäin poistettu}other{# pikanäppäintä poistettu}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Siirrä vasempaan yläreunaan"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Käyttäjän läsnäolo havaittu"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Aseta oletusmuistiinpanosovellus Asetuksista"</string>
<string name="install_app" msgid="5066668100199613936">"Asenna sovellus"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Jatka pyyhkäisemällä"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Peilataanko ulkoiselle näytölle?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Sisänäyttö peilataan. Etunäyttö laitetaan pois päältä."</string>
<string name="mirror_display" msgid="2515262008898122928">"Peilaa näyttö"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 8370b01..68f69a6 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Impossible de configurer le Déverrouillage par reconnaissance faciale. Accédez au menu Paramètres pour réessayer."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Touchez le capteur d\'empreintes digitales"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Appuyez sur l\'icône Déverrouiller pour continuer"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Visage non reconnu. Utilisez plutôt l\'empreinte digitale."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Visage non reconnu"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Utiliser l\'empreinte digitale"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Déverrouillage par reconnaissance faciale inaccessible."</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connecté"</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Recharge en cours… • Se terminera dans <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Balayer l\'écran vers la gauche pour démarrer le tutoriel communautaire"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Ouvrir l\'éditeur de widget"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Retirer"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Ajouter un widget"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Terminé"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Configuration"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Stockage"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Conseils"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Applications instantanées"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> en cours d\'exécution"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Application ouverte sans avoir été installée."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Touchez pour ouvrir fonction. d\'access. Personnalisez ou remplacez bouton dans Param.\n\n"<annotation id="link">"Afficher param."</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Déplacez le bouton vers le bord pour le masquer temporairement"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Annuler"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Le raccourci <xliff:g id="FEATURE_NAME">%s</xliff:g> a été retiré"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# raccourci retiré}one{# raccourci retiré}many{# de raccourcis retirés}other{# raccourcis retirés}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Déplacer dans coin sup. gauche"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"La présence d\'un utilisateur est détectée"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Définir l\'application de prise de notes par défaut dans les Paramètres"</string>
<string name="install_app" msgid="5066668100199613936">"Installer l\'application"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Balayez l\'écran pour continuer"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Dupliquer l\'écran sur un moniteur externe?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Votre écran intérieur sera dupliqué. Votre écran frontal sera désactivé."</string>
<string name="mirror_display" msgid="2515262008898122928">"Dupliquer l\'écran"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index af81a31..0c8ef28 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Impossible de configurer le déverrouillage par reconnaissance faciale. Accédez aux paramètres pour réessayer."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Appuyez sur le lecteur d\'empreinte digitale"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Appuyez sur l\'icône de déverrouillage pour continuer"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Visage non reconnu. Utilisez votre empreinte."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Visage non reconnu"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Utilisez empreinte digit."</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Déverrouillage par reconnaissance faciale indisponible"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connecté"</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Recharge • Temps restant : <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Balayer vers la gauche pour démarrer le tutoriel collectif"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Ouvrir l\'éditeur de widgets"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Supprimer"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Ajouter un widget"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"OK"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Configurer"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Espace de stockage"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Astuces"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Applis instantanées"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> en cours d\'exécution"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Vous pouvez ouvrir cette application sans l\'installer."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Appuyez pour ouvrir fonctionnalités d\'accessibilité. Personnalisez ou remplacez bouton dans paramètres.\n\n"<annotation id="link">"Voir paramètres"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Déplacer le bouton vers le bord pour le masquer temporairement"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Annuler"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Raccourci vers <xliff:g id="FEATURE_NAME">%s</xliff:g> supprimé"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# raccourci supprimé}one{# raccourci supprimé}many{# raccourcis supprimés}other{# raccourcis supprimés}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Déplacer en haut à gauche"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"La présence de l\'utilisateur est détectée"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Définir une appli de notes par défaut dans les paramètres"</string>
<string name="install_app" msgid="5066668100199613936">"Installer l\'appli"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Balayer pour continuer"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Dupliquer sur l\'écran externe ?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Votre écran intérieur sera dupliqué. Votre écran frontal sera éteint."</string>
<string name="mirror_display" msgid="2515262008898122928">"Dupliquer l\'écran"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index ef0751b..a705f2ae 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Non se puido configurar o desbloqueo facial. Para tentalo de novo, vai a Configuración."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Toca o sensor de impresión dixital"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Preme a icona de desbloquear para continuar"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Non se recoñeceu a cara. Usa a impresión dixital."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Non se recoñeceu a cara"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Usa a impresión dixital"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"O desbloqueo facial non está dispoñible"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado"</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando • A carga completarase en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Pasa o dedo cara á esquerda para iniciar o titorial comunitario"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Abrir o editor de widgets"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Quitar"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Engadir widget"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Feito"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Configurar"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Almacenamento"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Consellos"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Aplicacións Instantáneas"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"Estase executando <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Abriuse a aplicación sen ter que instalala."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Toca para abrir as funcións de accesibilidade. Cambia este botón en Configuración.\n\n"<annotation id="link">"Ver configuración"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Para ocultar temporalmente o botón, móveo ata o bordo"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Desfacer"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Quitouse o atallo de <xliff:g id="FEATURE_NAME">%s</xliff:g>"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{Quitouse # atallo}other{Quitáronse # atallos}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mover á parte super. esquerda"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Detectouse a presenza de usuarios"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Establece a aplicación de notas predeterminada en Configuración"</string>
<string name="install_app" msgid="5066668100199613936">"Instalar aplicación"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Pasa o dedo para continuar"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Queres proxectar contido nunha pantalla externa?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Proxectarase a pantalla interior. Desactivarase a pantalla frontal."</string>
<string name="mirror_display" msgid="2515262008898122928">"Proxectar pantalla"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index d92231c..9965f45 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"ફેસ અનલૉક સુવિધાનું સેટઅપ કરી શક્યા નથી. ફરી પ્રયાસ કરવા માટે સેટિંગ પર જાઓ."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"ફિંગરપ્રિન્ટના સેન્સરને સ્પર્શ કરો"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"ચાલુ રાખવા \'અનલૉક કરો\' આઇકન દબાવો"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"ચહેરો ઓળખી શકતા નથી. તેને બદલે ફિંગરપ્રિન્ટ વાપરો."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"ચહેરો ઓળખાતો નથી"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"તો ફિંગરપ્રિન્ટ વાપરો"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"ફેસ અનલૉક સુવિધા ઉપલબ્ધ નથી"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"બ્લૂટૂથ કનેક્ટ થયું."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ચાર્જ થઈ રહ્યું છે • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>માં પૂરું ચાર્જ થઈ જશે"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"કૉમ્યુનલ ટ્યૂટૉરિઅલ શરૂ કરવા માટે ડાબે સ્વાઇપ કરો"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"વિજેટ એડિટર ખોલો"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"કાઢી નાખો"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"વિજેટ ઉમેરો"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"થઈ ગયું"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"સેટઅપ કરો"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"સ્ટોરેજ"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"હિન્ટ"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> ચાલી રહી છે"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"ઍપ ઇન્સ્ટૉલ કર્યા વિના ખુલી જાય છે."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ઍક્સેસિબિલિટી સુવિધાઓ ખોલવા માટે ટૅપ કરો. સેટિંગમાં આ બટનને કસ્ટમાઇઝ કરો અથવા બદલો.\n\n"<annotation id="link">"સેટિંગ જુઓ"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"તેને હંગામી રૂપે ખસેડવા માટે બટનને કિનારી પર ખસેડો"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"છેલ્લો ફેરફાર રદ કરો"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> શૉર્ટકટ કાઢી નાખ્યો"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# શૉર્ટકટ કાઢી નાખ્યો}one{# શૉર્ટકટ કાઢી નાખ્યો}other{# શૉર્ટકટ કાઢી નાખ્યા}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ઉપર ડાબે ખસેડો"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"વપરાશકર્તાની હાજરીની ભાળ મળી છે"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"સેટિંગમાં નોંધની ડિફૉલ્ટ ઍપ સેટ કરો"</string>
<string name="install_app" msgid="5066668100199613936">"ઍપ ઇન્સ્ટૉલ કરો"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"ચાલુ રાખવા સ્વાઇપ કરો"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"શું બાહ્ય ડિસ્પ્લે પર મિરર કરીએ?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"તમારું ઇનર ડિસ્પ્લે મિરર કરવામાં આવશે. તમારું ફ્રન્ટ ડિસ્પ્લે બંધ કરવામાં આવશે."</string>
<string name="mirror_display" msgid="2515262008898122928">"મિરર ડિસ્પ્લે"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index f8c78a9..e2ed3ab 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"फ़ेस अनलॉक की सुविधा सेट अप नहीं की जा सकी. सेटिंग पर जाकर दोबारा कोशिश करें."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"फ़िंगरप्रिंट सेंसर को छुएं"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"जारी रखने के लिए अनलॉक आइकॉन पर टैप करें"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"चेहरे की पहचान नहीं हुई. फ़िंगरप्रिंट इस्तेमाल करें."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"चेहरे की पहचान नहीं हुई"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"फ़िंगरप्रिंट इस्तेमाल करें"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"फ़ेस अनलॉक की सुविधा उपलब्ध नहीं है"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ब्लूटूथ कनेक्ट किया गया."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • चार्ज हो रहा है • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> में पूरा चार्ज हो जाएगा"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"कम्यूनिटी ट्यूटोरियल शुरू करने के लिए, बाईं ओर स्वाइप करें"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"विजेट एडिटर खोलें"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"हटाएं"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"विजेट जोड़ें"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"हो गया"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"सेट अप"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"स्टोरेज"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"संकेत"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> चल रहा है"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"ऐप्लिकेशन इंस्टॉल किए बिना ही खुल गया है."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"सुलभता सुविधाएं खोलने के लिए टैप करें. सेटिंग में, इस बटन को बदलें या अपने हिसाब से सेट करें.\n\n"<annotation id="link">"सेटिंग देखें"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"बटन को कुछ समय छिपाने के लिए, उसे किनारे पर ले जाएं"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"पहले जैसा करें"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> का शॉर्टकट हटाया गया"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# शॉर्टकट हटाया गया}one{# शॉर्टकट हटाया गया}other{# शॉर्टकट हटाए गए}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"सबसे ऊपर बाईं ओर ले जाएं"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"उपयोगकर्ता की मौजूदगी का पता लगाया गया"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"सेटिंग में जाकर, नोट लेने की सुविधा देने वाले ऐप्लिकेशन को डिफ़ॉल्ट के तौर पर सेट करें"</string>
<string name="install_app" msgid="5066668100199613936">"ऐप्लिकेशन इंस्टॉल करें"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"जारी रखने के लिए स्वाइप करें"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"क्या आपको किसी बाहरी डिवाइस पर डिसप्ले करना है?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"आपके फ़ोन के इनर डिसप्ले की स्क्रीन शेयर की जाएगी. फ़्रंट डिसप्ले को बंद कर दिया जाएगा."</string>
<string name="mirror_display" msgid="2515262008898122928">"डिसप्ले करें"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 0803aeb..f5d73ea 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Postavljanje otključavanja licem nije uspjelo. Pokušajte ponovo u postavkama."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Dodirnite senzor otiska prsta"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Pritisnite ikonu otključavanja da biste nastavili"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Prepoznavanje lica nije uspjelo. Upotrijebite otisak prsta."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Lice nije prepoznato"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Upotrijebite otisak prsta"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Otključavanje licem nije dostupno"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth povezan."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • punjenje • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do napunjenosti"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Prijeđite prstom ulijevo da biste pokrenuli zajednički vodič"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Otvaranje alata za uređivanje widgeta"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Ukloni"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodaj widget"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Gotovo"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Postavljanje"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Pohrana"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Savjeti"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Instant aplikacije"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"Izvodi se aplikacija <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Aplikacija je otvorena bez instaliranja."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Dodirnite za otvaranje značajki pristupačnosti. Prilagodite ili zamijenite taj gumb u postavkama.\n\n"<annotation id="link">"Pregledajte postavke"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Pomaknite gumb do ruba da biste ga privremeno sakrili"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Poništi"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Uklonjen je prečac za uslugu <xliff:g id="FEATURE_NAME">%s</xliff:g>"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{Uklonjen je # prečac}one{Uklonjen je # prečac}few{Uklonjena su # prečaca}other{Uklonjeno je # prečaca}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Premjesti u gornji lijevi kut"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Otkrivena je prisutnost korisnika"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Postavite zadanu aplikaciju za bilješke u postavkama"</string>
<string name="install_app" msgid="5066668100199613936">"Instalacija"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Prijeđite prstom da biste nastavili"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Želite li zrcaliti na vanjski zaslon?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Unutarnji zaslon bit će zrcaljen. Prednji zaslon bit će isključen."</string>
<string name="mirror_display" msgid="2515262008898122928">"Zrcaljenje zaslona"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index fd9d832..9ac44df 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Nem sikerült beállítani az arcalapú feloldást. Próbálkozzon újra a Beállításokban."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Érintse meg az ujjlenyomat-érzékelőt"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"A folytatáshoz koppintson a Feloldás ikonra"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Az arc nem felismerhető. Használjon ujjlenyomatot."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Az arc nem ismerhető fel"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Használjon ujjlenyomatot"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Nem áll rendelkezésre az Arcalapú feloldás"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth csatlakoztatva."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Töltés • A teljes töltöttségig: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Csúsztasson gyorsan balra a közösségi útmutató elindításához"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"A modulszerkesztő megnyitása"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<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>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Kész"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Beállítás"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Tárhely"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Tippek"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Azonnali alkalmazások"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"A(z) <xliff:g id="APP">%1$s</xliff:g> alkalmazás jelenleg fut"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Az alkalmazás telepítés nélkül lett megnyitva."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Koppintson a kisegítő lehetőségek megnyitásához. A gombot a Beállításokban módosíthatja.\n\n"<annotation id="link">"Beállítások"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"A gombot a szélre áthelyezve ideiglenesen elrejtheti"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Visszavonás"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> gyorsparancs eltávolítva"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# gyorsparancs eltávolítva}other{# gyorsparancs eltávolítva}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Áthelyezés fel és balra"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Felhasználói jelenlét észlelve"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Állítson be alapértelmezett jegyzetkészítő alkalmazást a Beállításokban"</string>
<string name="install_app" msgid="5066668100199613936">"Alkalmazás telepítése"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Csúsztasson a folytatáshoz"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Tükrözi a kijelzőt a külső képernyőre?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"A belső kijelző tükrözve lesz. Az elülső kijelző ki lesz kapcsolva."</string>
<string name="mirror_display" msgid="2515262008898122928">"Kijelző tükrözése"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index a9252e2..0d59017 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Չհաջողվեց կարգավորել դեմքով ապակողպումը։ Անցեք Կարգավորումներ և նորից փորձեք։"</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Հպեք մատնահետքի սկաներին"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Շարունակելու համար սեղմեք ապակողպման պատկերակը"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Դեմքը չի հաջողվում ճանաչել։ Օգտագործեք մատնահետքը։"</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Դեմքը չի ճանաչվել"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Օգտագործեք մատնահետք"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Դեմքով ապակողպումն անհասանելի է"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth-ը միացված է:"</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Լիցքավորում • Մնացել է <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Թերթեք ձախ՝ ուղեցույցը գործարկելու համար"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Բացել վիջեթների խմբագրիչը"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Հեռացնել"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Ավելացնել վիջեթ"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Պատրաստ է"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Կարգավորում"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Տարածք"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Հուշումներ"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Ակնթարթային հավելվածներ"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> հավելվածն աշխատում է"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Հավելվածը բացվել է առանց տեղադրման։"</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Հատուկ գործառույթները բացելու համար հպեք։ Անհատականացրեք այս կոճակը կարգավորումներում։\n\n"<annotation id="link">"Կարգավորումներ"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Կոճակը ժամանակավորապես թաքցնելու համար այն տեղափոխեք էկրանի եզր"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Հետարկել"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"«<xliff:g id="FEATURE_NAME">%s</xliff:g>» դյուրանցումը հեռացվեց"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# դյուրանցում հեռացվեց}one{# դյուրանցում հեռացվեց}other{# դյուրանցում հեռացվեց}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Տեղափոխել վերև՝ ձախ"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Հայտնաբերվել է օգտատեր"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Կարգավորեք նշումների կանխադրված հավելված Կարգավորումներում"</string>
<string name="install_app" msgid="5066668100199613936">"Տեղադրել հավելվածը"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Սահեցրեք շարունակելու համար"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Հայելապատճենե՞լ արտաքին էկրանին"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Ներքին էկրանը կհայելապատճենվի։ Առջևի էկրանը կանջատվի։"</string>
<string name="mirror_display" msgid="2515262008898122928">"Հայելապատճենել էկրանը"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 8ef809a..a0b5199 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Tidak dapat menyiapkan buka dengan wajah. Buka Setelan untuk mencoba lagi."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Sentuh sensor sidik jari"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Tekan ikon buka kunci untuk melanjutkan"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Tidak dapat mengenali wajah. Gunakan sidik jari."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Tidak mengenali wajah"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Gunakan sidik jari"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Buka dengan Wajah tidak tersedia"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth terhubung."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengisi daya • Penuh dalam waktu <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Geser ke kiri untuk memulai tutorial komunal"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Buka editor widget"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Hapus"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Tambahkan widget"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Selesai"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Penyiapan"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Penyimpanan"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Petunjuk"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Aplikasi Instan"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> sedang berjalan"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Aplikasi dapat dibuka tanpa perlu diinstal."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Ketuk untuk membuka fitur aksesibilitas. Sesuaikan atau ganti tombol ini di Setelan.\n\n"<annotation id="link">"Lihat setelan"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Pindahkan tombol ke tepi agar tersembunyi untuk sementara"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Urungkan"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Pintasan <xliff:g id="FEATURE_NAME">%s</xliff:g> dihapus"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# pintasan dihapus}other{# pintasan dihapus}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Pindahkan ke kiri atas"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Kehadiran pengguna terdeteksi"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Setel aplikasi catatan default di Setelan"</string>
<string name="install_app" msgid="5066668100199613936">"Instal aplikasi"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Geser untuk melanjutkan"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Cerminkan ke layar eksternal?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Layar dalam akan dicerminkan. Layar depan akan dinonaktifkan."</string>
<string name="mirror_display" msgid="2515262008898122928">"Cerminkan layar"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index cef3285..3a0843e 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Ekki var hægt að setja upp andlitskenni. Farðu í stillingar og reyndu aftur."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Snertu fingrafaralesarann"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Ýttu á táknið taka úr lás til að halda áfram"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Andlit þekkist ekki. Notaðu fingrafar í staðinn."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Andlit þekkist ekki"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Nota fingrafar í staðinn"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Andlitskenni ekki í boði"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth tengt."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Í hleðslu • Full hleðsla eftir <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Strjúktu til vinstri til að hefja samfélagsleiðsögnina"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Opna græjuritilinn"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<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>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Lokið"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Uppsetning"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Geymslurými"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Vísbendingar"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Skyndiforrit"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> er í gangi"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Forrit opnað án þess að vera uppsett."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Ýttu til að opna aðgengiseiginleika. Sérsníddu eða skiptu hnappinum út í stillingum.\n\n"<annotation id="link">"Skoða stillingar"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Færðu hnappinn að brúninni til að fela hann tímabundið"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Afturkalla"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Flýtileiðin <xliff:g id="FEATURE_NAME">%s</xliff:g> var fjarlægð"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# flýtileið var fjarlægð}one{# flýtileið var fjarlægð}other{# flýtileiðir voru fjarlægðar}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Færa efst til vinstri"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Viðvera notanda greindist"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Stilltu sjálfgefið glósuforrit í stillingunum"</string>
<string name="install_app" msgid="5066668100199613936">"Setja upp forrit"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Strjúktu til að halda áfram"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Spegla yfir á ytri skjá?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Innri skjárinn þinn verður speglaður. Slökkt verður á framskjánum þínum."</string>
<string name="mirror_display" msgid="2515262008898122928">"Spegla skjá"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index c5079fe..8ee96fc 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Impossibile configurare lo Sblocco con il Volto. Vai alle Impostazioni e riprova."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Tocca il sensore di impronte"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Premi l\'icona Sblocca per continuare"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Impossibile riconoscere il volto. Usa l\'impronta."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Volto non riconosciuto"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Usa l\'impronta"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Sblocco con il Volto non disponibile"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth collegato."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • In carica • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> alla ricarica completa"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Scorri a sinistra per iniziare il tutorial della community"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Apri l\'editor del widget"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Rimuovi"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Aggiungi widget"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Fine"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Configurazione"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Spazio di archiviazione"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Suggerimenti"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"App istantanee"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"App <xliff:g id="APP">%1$s</xliff:g> in esecuzione"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"App aperta senza essere stata installata."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tocca per aprire funzioni di accessibilità. Personalizza o sostituisci il pulsante in Impostazioni.\n\n"<annotation id="link">"Vedi impostazioni"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Sposta il pulsante fino al bordo per nasconderlo temporaneamente"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Elimina"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Scorciatoia <xliff:g id="FEATURE_NAME">%s</xliff:g> rimossa"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# scorciatoia rimossa}many{# scorciatoie rimosse}other{# scorciatoie rimosse}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Sposta in alto a sinistra"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Viene rilevata la presenza dell\'utente"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Imposta l\'app per le note predefinita nelle Impostazioni"</string>
<string name="install_app" msgid="5066668100199613936">"Installa app"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Scorri per continuare"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vuoi eseguire il mirroring al display esterno?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Verrà eseguito il mirroring del tuo display interno. Il tuo display frontale verrà spento."</string>
<string name="mirror_display" msgid="2515262008898122928">"Esegui il mirroring del display"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 486b22f..17236ea 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"לא ניתן להגדיר פתיחה ע\"י זיהוי הפנים. צריך לעבור להגדרות כדי לנסות שוב."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"יש לגעת בחיישן טביעות האצבע"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"להמשך יש ללחוץ על סמל ביטול הנעילה"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"לא ניתן לזהות את הפנים. יש להשתמש בטביעת אצבע במקום."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"לא ניתן לזהות את הפנים"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"שימוש בטביעת אצבע במקום זאת"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"אי אפשר לפתוח בזיהוי פנים"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth מחובר."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • בטעינה • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> עד לסיום"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"אפשר להחליק שמאלה כדי להפעיל את המדריך המשותף"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"פתיחה של הכלי לעריכת ווידג\'טים"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"הסרה"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"הוספת ווידג\'ט"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"סיום"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"הגדרה"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"אחסון"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"טיפים"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"אפליקציות ללא התקנה"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"האפליקציה <xliff:g id="APP">%1$s</xliff:g> פועלת"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"האפליקציה נפתחת בלי התקנה."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"מקישים כדי לפתוח את תכונות הנגישות. אפשר להחליף את הלחצן או להתאים אותו אישית בהגדרות.\n\n"<annotation id="link">"הצגת ההגדרות"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"כדי להסתיר זמנית את הלחצן, יש להזיז אותו לקצה"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"ביטול"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"קיצור הדרך אל <xliff:g id="FEATURE_NAME">%s</xliff:g> הוסר"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{קיצור הדרך הוסר}one{# קיצורי דרך הוסרו}two{# קיצורי דרך הוסרו}other{# קיצורי דרך הוסרו}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"העברה לפינה השמאלית העליונה"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"נוכחות המשתמש זוהתה"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"צריך להגדיר את אפליקציית ברירת המחדל לפתקים ב\'הגדרות\'"</string>
<string name="install_app" msgid="5066668100199613936">"התקנת האפליקציה"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"צריך להחליק כדי להמשיך"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"לשקף למסך חיצוני?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"המסך הפנימי שלך ישוכפל. המסך החיצוני שלך יכובה."</string>
<string name="mirror_display" msgid="2515262008898122928">"תצוגת מראה"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index c742f93..e193cb1 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"顔認証を設定できませんでした。[設定] に移動してもう一度お試しください。"</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"指紋認証センサーをタッチ"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"ロック解除アイコンを押して続行してください"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"顔を認識できません。指紋認証を使用してください。"</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"顔を認識できません"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"指紋認証をお使いください"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"顔認証を利用できません"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetoothに接続済み。"</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 充電中 • フル充電まで <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"左にスワイプすると、コミュニティ チュートリアルが開始します"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"ウィジェット エディタを開く"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"削除"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ウィジェットを追加"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"完了"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"セットアップ"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"ストレージ"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"ヒント"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> を実行中"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"アプリをインストールせずに開きました。"</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"タップしてユーザー補助機能を開きます。ボタンのカスタマイズや入れ替えを [設定] で行えます。\n\n"<annotation id="link">"設定を表示"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ボタンを一時的に非表示にするには、端に移動させてください"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"元に戻す"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> のショートカットを削除しました"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# 個のショートカットを削除}other{# 個のショートカットを削除}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"左上に移動"</string>
@@ -1207,8 +1225,9 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"会話を始められます"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"[設定] でデフォルトのメモアプリを設定してください"</string>
<string name="install_app" msgid="5066668100199613936">"アプリをインストール"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"スワイプして続行"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"外部ディスプレイにミラーリングしますか?"</string>
- <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"インナー ディスプレイがミラーリングされます。フロント ディスプレイは OFF になります。"</string>
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"インナー ディスプレイがミラーリングされます。フロント ディスプレイはオフになります。"</string>
<string name="mirror_display" msgid="2515262008898122928">"ディスプレイをミラーリングする"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"閉じる"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"ディスプレイに接続しました"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index 9d386ef..c841d1c 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"სახით განბლოკვის დაყენება ვერ მოხერხდა. გადადით პარამეტრებზე და ცადეთ ხელახლა."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"შეეხეთ თითის ანაბეჭდის სენსორს"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"გასაგრძელებლად დააჭირეთ განბლოკვის ხატულას"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"სახის ამოცნობა ვერ ხერხდება. სანაცვლოდ თითის ანაბეჭდი გამოიყენეთ."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"სახის ამოცნობა შეუძლებ."</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"გამოიყენეთ თითის ანაბეჭდი"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"სახით განბლოკვა მიუწვდომელია."</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth დაკავშირებულია."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • იტენება • სრულ დატენვამდე <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"გადაფურცლეთ მარცხნივ, რათა დაიწყოთ საერთო სახელმძღვანელო"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"გახსენით ვიჯეტის რედაქტორი"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"ამოშლა"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ვიჯეტის დამატება"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"მზადაა"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"დაყენება"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"მეხსიერება"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"მინიშნებები"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"მყისიერი აპები"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> გაშვებულია"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"აპი გაიხსნა ინსტალაციის გარეშე."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"შეეხეთ მარტივი წვდომის ფუნქციების გასახსნელად. მოარგეთ ან შეცვალეთ ეს ღილაკი პარამეტრებში.\n\n"<annotation id="link">"პარამეტრების ნახვა"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"გადაიტანეთ ღილაკი კიდეში, რათა დროებით დამალოთ ის"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"მოქმედების გაუქმება"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> მალსახმობი ამოშლილია"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# მალსახმობი ამოშლილია}other{# მალსახმობი ამოშლილია}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ზევით და მარცხნივ გადატანა"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"აღმოჩენილია მომხმარებლის ყოფნა"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"დააყენეთ ნაგულისხმევი შენიშვნების აპი პარამეტრებში"</string>
<string name="install_app" msgid="5066668100199613936">"აპის ინსტალაცია"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"გადაფურცლეთ გასაგრძელებლად"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"აირეკლოს გარე ეკრანზე?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"თქვენი შიდა ეკრანი აირეკლება. თქვენი წინა ეკრანი გამოირთვება."</string>
<string name="mirror_display" msgid="2515262008898122928">"ეკრანის არეკვლა"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index d2c60f1..f81ad05 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Бет тану функциясы реттелмеді. \"Параметрлер\" бөліміне өтіп, әрекетті қайталап көріңіз."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Саусақ ізін оқу сканерін түртіңіз"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Жалғастыру үшін құлыпты ашу белгішесін басыңыз."</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Бет танылмады. Орнына саусақ ізін пайдаланыңыз."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Бет танылмады."</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Орнына саусақ ізін пайдаланыңыз."</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Бет тану функциясы қолжетімсіз."</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth қосылған."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Зарядталып жатыр. • Толуына <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> қалды."</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Ортақ оқулықты ашу үшін солға қарай сырғытыңыз."</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Виджет редакторын ашу"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Өшіру"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Виджет қосу"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Дайын"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Реттеу"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Жад"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Кеңестер"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> іске қосулы"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Қолданба орнатылмай-ақ ашылды."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Арнайы мүмкіндікті ашу үшін түртіңіз. Түймені параметрден реттеңіз не ауыстырыңыз.\n\n"<annotation id="link">"Параметрді көру"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Түймені уақытша жасыру үшін оны шетке қарай жылжытыңыз."</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Қайтару"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> жылдам пәрмені өшірілді."</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# таңбаша өшірілді.}other{# таңбаша өшірілді.}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Жоғарғы сол жаққа жылжыту"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Пайдаланушы анықталды."</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Параметрлерден әдепкі жазба қолданбасын орнатыңыз."</string>
<string name="install_app" msgid="5066668100199613936">"Қолданбаны орнату"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Жалғастыру үшін сырғытыңыз."</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Сыртқы экран арқылы да көрсету керек пе?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Ішкі экран көшірмесі көрсетіледі. Алдыңғы экран өшіріледі."</string>
<string name="mirror_display" msgid="2515262008898122928">"Көрсету"</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index ec81523..09b9e15 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"មិនអាចរៀបចំការដោះសោតាមទម្រង់មុខបានទេ។ សូមចូលទៅកាន់ការកំណត់ ដើម្បីព្យាយាមម្ដងទៀត។"</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"ប៉ះឧបករណ៍ចាប់ស្នាមម្រាមដៃ"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"សូមចុចរូបដោះសោ ដើម្បីបន្ត"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"មិនអាចសម្គាល់មុខបានទេ។ សូមប្រើស្នាមម្រាមដៃជំនួសវិញ។"</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"មិនអាចសម្គាល់មុខបានទេ"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ប្រើស្នាមម្រាមដៃជំនួសវិញ"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"មិនអាចប្រើការដោះសោតាមទម្រង់មុខបានទេ"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"បានតភ្ជាប់ប៊្លូធូស។"</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • កំពុងសាកថ្ម • ពេញក្នុងរយៈពេល <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"អូសទៅឆ្វេង ដើម្បីចាប់ផ្ដើមមេរៀនសហគមន៍"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"បើកកម្មវិធីកែធាតុក្រាហ្វិក"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"ដកចេញ"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"បញ្ចូលធាតុក្រាហ្វិក"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"រួចរាល់"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"ការរៀបចំ"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"ទំហំផ្ទុក"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"ការសម្រួល"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"កម្មវិធីប្រើភ្លាមៗ"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> កំពុងដំណើរការ"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"កម្មវិធីត្រូវបានបើកដោយមិនចាំបាច់ដំឡើង។"</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ចុចដើម្បីបើកមុខងារភាពងាយស្រួល។ ប្ដូរ ឬប្ដូរប៊ូតុងនេះតាមបំណងនៅក្នុងការកំណត់។\n\n"<annotation id="link">"មើលការកំណត់"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ផ្លាស់ទីប៊ូតុងទៅគែម ដើម្បីលាក់វាជាបណ្ដោះអាសន្ន"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"ត្រឡប់វិញ"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"បានដកផ្លូវកាត់ <xliff:g id="FEATURE_NAME">%s</xliff:g> ចេញ"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{បានដកផ្លូវកាត់ # ចេញ}other{បានដកផ្លូវកាត់ # ចេញ}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ផ្លាស់ទីទៅខាងលើផ្នែកខាងឆ្វេង"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"វត្តមានអ្នកប្រើប្រាស់ត្រូវបានចាប់ដឹង"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"កំណត់កម្មវិធីកំណត់ចំណាំលំនាំដើមនៅក្នុងការកំណត់"</string>
<string name="install_app" msgid="5066668100199613936">"ដំឡើងកម្មវិធី"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"សូមអូសដើម្បីបន្ត"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"បញ្ចាំងទៅផ្ទាំងអេក្រង់ខាងក្រៅឬ?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"អេក្រង់ខាងក្នុងរបស់អ្នកនឹងត្រូវបានធ្វើសមកាលកម្មទៅវិញទៅមក។ អេក្រង់មុខរបស់អ្នកនឹងត្រូវបានបិទ។"</string>
<string name="mirror_display" msgid="2515262008898122928">"បញ្ចាំងទៅផ្ទាំងអេក្រង់"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 33f4528..e217154 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -74,7 +74,7 @@
<string name="remote_input_image_insertion_text" msgid="4850791636452521123">"ಚಿತ್ರವನ್ನು ಕಳುಹಿಸಲಾಗಿದೆ"</string>
<string name="screenshot_saving_title" msgid="2298349784913287333">"ಸ್ಕ್ರೀನ್ಶಾಟ್ ಉಳಿಸಲಾಗುತ್ತಿದೆ…"</string>
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್ಗೆ ಸ್ಕ್ರೀನ್ಶಾಟ್ ಉಳಿಸಲಾಗುತ್ತಿದೆ…"</string>
- <string name="screenshot_saved_title" msgid="8893267638659083153">"ಸ್ಕ್ರೀನ್ಶಾಟ್ ಅನ್ನು ಉಳಿಸಲಾಗಿದೆ"</string>
+ <string name="screenshot_saved_title" msgid="8893267638659083153">"ಸ್ಕ್ರೀನ್ಶಾಟ್ ಅನ್ನು ಸೇವ್ ಮಾಡಲಾಗಿದೆ"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"ಸ್ಕ್ರೀನ್ಶಾಟ್ ಅನ್ನು ಉಳಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
<string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"ಬಾಹ್ಯ ಡಿಸ್ಪ್ಲೇ"</string>
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ಸ್ಕ್ರೀನ್ಶಾಟ್ ಉಳಿಸುವ ಮೊದಲು ಸಾಧನವನ್ನು ಅನ್ಲಾಕ್ ಮಾಡಬೇಕು"</string>
@@ -116,7 +116,7 @@
<string name="screenrecord_taps_label" msgid="1595690528298857649">"ಸ್ಪರ್ಶಗಳನ್ನು ಸ್ಕ್ರೀನ್ ಮೇಲೆ ತೋರಿಸಿ"</string>
<string name="screenrecord_stop_label" msgid="72699670052087989">"ನಿಲ್ಲಿಸಿ"</string>
<string name="screenrecord_share_label" msgid="5025590804030086930">"ಹಂಚಿಕೊಳ್ಳಿ"</string>
- <string name="screenrecord_save_title" msgid="1886652605520893850">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಉಳಿಸಲಾಗಿದೆ"</string>
+ <string name="screenrecord_save_title" msgid="1886652605520893850">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಸೇವ್ ಮಾಡಲಾಗಿದೆ"</string>
<string name="screenrecord_save_text" msgid="3008973099800840163">"ವೀಕ್ಷಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಸೇವ್ ಮಾಡುವಾಗ ದೋಷ ಎದುರಾಗಿದೆ"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಪ್ರಾರಂಭಿಸುವಾಗ ದೋಷ ಕಂಡುಬಂದಿದೆ"</string>
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"ಫೇಸ್ ಅನ್ಲಾಕ್ ಅನ್ನು ಸೆಟಪ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ. ಮತ್ತೊಮ್ಮೆ ಪ್ರಯತ್ನಿಸಲು, ಸೆಟ್ಟಿಂಗ್ಗಳಿಗೆ ಹೋಗಿ."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"ಫಿಂಗರ್ಪ್ರಿಂಟ್ ಸೆನ್ಸರ್ ಅನ್ನು ಸ್ಪರ್ಶಿಸಿ"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"ಮುಂದುವರಿಯಲು ಅನ್ಲಾಕ್ ಐಕಾನ್ ಅನ್ನು ಒತ್ತಿರಿ"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"ಮುಖ ಗುರುತಿಸಲಾಗುತ್ತಿಲ್ಲ ಬದಲಿಗೆ ಫಿಂಗರ್ಪ್ರಿಂಟ್ ಬಳಸಿ."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"ಮುಖ ಗುರುತಿಸಲಾಗುತ್ತಿಲ್ಲ"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ಬದಲಿಗೆ ಫಿಂಗರ್ಪ್ರಿಂಟ್ ಬಳಸಿ"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"ಫೇಸ್ ಅನ್ಲಾಕ್ ಲಭ್ಯವಿಲ್ಲ"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ಬ್ಲೂಟೂತ್ ಸಂಪರ್ಕಗೊಂಡಿದೆ."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ದಲ್ಲಿ ಪೂರ್ಣಗೊಳ್ಳುತ್ತದೆ"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"ಸಮುದಾಯದ ಟ್ಯುಟೋರಿಯಲ್ ಅನ್ನು ಪ್ರಾರಂಭಿಸಲು ಎಡಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"ವಿಜೆಟ್ ಎಡಿಟರ್ ಅನ್ನು ತೆರೆಯಿರಿ"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"ತೆಗೆದುಹಾಕಿ"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ವಿಜೆಟ್ ಅನ್ನು ಸೇರಿಸಿ"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"ಮುಗಿದಿದೆ"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"ಸೆಟಪ್"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"ಸಂಗ್ರಹಣೆ"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"ಸುಳಿವುಗಳು"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> ರನ್ ಆಗುತ್ತಿದೆ"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"ಇನ್ಸ್ಟಾಲ್ ಮಾಡದೆ ಆ್ಯಪ್ ತೆರೆಯಲಾಗಿದೆ."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ಆ್ಯಕ್ಸೆಸಿಬಿಲಿಟಿ ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ತೆರೆಯಲು ಟ್ಯಾಪ್ ಮಾಡಿ. ಸೆಟ್ಟಿಂಗ್ಗಳಲ್ಲಿ ಈ ಬಟನ್ ಅನ್ನು ಕಸ್ಟಮೈಸ್ ಮಾಡಿ ಅಥವಾ ಬದಲಾಯಿಸಿ.\n\n"<annotation id="link">"ಸೆಟ್ಟಿಂಗ್ಗಳನ್ನು ವೀಕ್ಷಿಸಿ"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ಅದನ್ನು ತಾತ್ಕಾಲಿಕವಾಗಿ ಮರೆಮಾಡಲು ಅಂಚಿಗೆ ಬಟನ್ ಸರಿಸಿ"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"ರದ್ದುಗೊಳಿಸಿ"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> ಶಾರ್ಟ್ಕಟ್ ತೆಗೆದುಹಾಕಲಾಗಿದೆ"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# ಶಾರ್ಟ್ಕಟ್ ತೆಗೆದುಹಾಕಲಾಗಿದೆ}one{# ಶಾರ್ಟ್ಕಟ್ಗಳನ್ನು ತೆಗೆದುಹಾಕಲಾಗಿದೆ}other{# ಶಾರ್ಟ್ಕಟ್ಗಳನ್ನು ತೆಗೆದುಹಾಕಲಾಗಿದೆ}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ಎಡ ಮೇಲ್ಭಾಗಕ್ಕೆ ಸರಿಸಿ"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"ಬಳಕೆದಾರರ ಉಪಸ್ಥಿತಿಯನ್ನು ಪತ್ತೆಹಚ್ಚಲಾಗಿದೆ"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ಸೆಟ್ಟಿಂಗ್ಗಳಲ್ಲಿ ಡೀಫಾಲ್ಟ್ ಟಿಪ್ಪಣಿಗಳ ಆ್ಯಪ್ ಅನ್ನು ಸೆಟ್ ಮಾಡಿ"</string>
<string name="install_app" msgid="5066668100199613936">"ಆ್ಯಪ್ ಇನ್ಸ್ಟಾಲ್ ಮಾಡಿ"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"ಮುಂದುವರಿಯಲು ಸ್ವೈಪ್ ಮಾಡಿ"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ಬಾಹ್ಯ ಡಿಸ್ಪ್ಲೇಗೆ ಪ್ರತಿಬಿಂಬಿಸಬೇಕೆ?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"ನಿಮ್ಮ ಒಳಗಿನ ಡಿಸ್ಪ್ಲೇ ಅನ್ನು ಪ್ರತಿಬಿಂಬಿಸಲಾಗುತ್ತದೆ. ನಿಮ್ಮ ಫ್ರಂಟ್ ಡಿಸ್ಪ್ಲೇ ಅನ್ನು ಆಫ್ ಮಾಡಲಾಗುತ್ತದೆ."</string>
<string name="mirror_display" msgid="2515262008898122928">"ಮಿರರ್ ಡಿಸ್ಪ್ಲೇ"</string>
diff --git a/packages/SystemUI/res/values-kn/tiles_states_strings.xml b/packages/SystemUI/res/values-kn/tiles_states_strings.xml
index 876562d..250eb5a 100644
--- a/packages/SystemUI/res/values-kn/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-kn/tiles_states_strings.xml
@@ -34,147 +34,147 @@
<string-array name="tile_states_internet">
<item msgid="5499482407653291407">"ಲಭ್ಯವಿಲ್ಲ"</item>
<item msgid="3048856902433862868">"ಆಫ್"</item>
- <item msgid="6877982264300789870">"ಆನ್ ಮಾಡಿ"</item>
+ <item msgid="6877982264300789870">"ಆನ್"</item>
</string-array>
<string-array name="tile_states_wifi">
<item msgid="8054147400538405410">"ಲಭ್ಯವಿಲ್ಲ"</item>
<item msgid="4293012229142257455">"ಆಫ್"</item>
- <item msgid="6221288736127914861">"ಆನ್ ಮಾಡಿ"</item>
+ <item msgid="6221288736127914861">"ಆನ್"</item>
</string-array>
<string-array name="tile_states_cell">
<item msgid="1235899788959500719">"ಲಭ್ಯವಿಲ್ಲ"</item>
<item msgid="2074416252859094119">"ಆಫ್"</item>
- <item msgid="287997784730044767">"ಆನ್ ಮಾಡಿ"</item>
+ <item msgid="287997784730044767">"ಆನ್"</item>
</string-array>
<string-array name="tile_states_battery">
<item msgid="6311253873330062961">"ಲಭ್ಯವಿಲ್ಲ"</item>
<item msgid="7838121007534579872">"ಆಫ್"</item>
- <item msgid="1578872232501319194">"ಆನ್ ಮಾಡಿ"</item>
+ <item msgid="1578872232501319194">"ಆನ್"</item>
</string-array>
<string-array name="tile_states_dnd">
<item msgid="467587075903158357">"ಲಭ್ಯವಿಲ್ಲ"</item>
<item msgid="5376619709702103243">"ಆಫ್"</item>
- <item msgid="4875147066469902392">"ಆನ್ ಮಾಡಿ"</item>
+ <item msgid="4875147066469902392">"ಆನ್"</item>
</string-array>
<string-array name="tile_states_flashlight">
<item msgid="3465257127433353857">"ಲಭ್ಯವಿಲ್ಲ"</item>
<item msgid="5044688398303285224">"ಆಫ್"</item>
- <item msgid="8527389108867454098">"ಆನ್ ಮಾಡಿ"</item>
+ <item msgid="8527389108867454098">"ಆನ್"</item>
</string-array>
<string-array name="tile_states_rotation">
<item msgid="4578491772376121579">"ಲಭ್ಯವಿಲ್ಲ"</item>
<item msgid="5776427577477729185">"ಆಫ್"</item>
- <item msgid="7105052717007227415">"ಆನ್ ಮಾಡಿ"</item>
+ <item msgid="7105052717007227415">"ಆನ್"</item>
</string-array>
<string-array name="tile_states_bt">
<item msgid="5330252067413512277">"ಲಭ್ಯವಿಲ್ಲ"</item>
<item msgid="5315121904534729843">"ಆಫ್"</item>
- <item msgid="503679232285959074">"ಆನ್ ಮಾಡಿ"</item>
+ <item msgid="503679232285959074">"ಆನ್"</item>
</string-array>
<string-array name="tile_states_airplane">
<item msgid="1985366811411407764">"ಲಭ್ಯವಿಲ್ಲ"</item>
<item msgid="4801037224991420996">"ಆಫ್"</item>
- <item msgid="1982293347302546665">"ಆನ್ ಮಾಡಿ"</item>
+ <item msgid="1982293347302546665">"ಆನ್"</item>
</string-array>
<string-array name="tile_states_location">
<item msgid="3316542218706374405">"ಲಭ್ಯವಿಲ್ಲ"</item>
<item msgid="4813655083852587017">"ಆಫ್"</item>
- <item msgid="6744077414775180687">"ಆನ್ ಮಾಡಿ"</item>
+ <item msgid="6744077414775180687">"ಆನ್"</item>
</string-array>
<string-array name="tile_states_hotspot">
<item msgid="3145597331197351214">"ಲಭ್ಯವಿಲ್ಲ"</item>
<item msgid="5715725170633593906">"ಆಫ್"</item>
- <item msgid="2075645297847971154">"ಆನ್ ಮಾಡಿ"</item>
+ <item msgid="2075645297847971154">"ಆನ್"</item>
</string-array>
<string-array name="tile_states_color_correction">
<item msgid="2840507878437297682">"ಲಭ್ಯವಿಲ್ಲ"</item>
<item msgid="1909756493418256167">"ಆಫ್"</item>
- <item msgid="4531508423703413340">"ಆನ್ ಮಾಡಿ"</item>
+ <item msgid="4531508423703413340">"ಆನ್"</item>
</string-array>
<string-array name="tile_states_inversion">
<item msgid="3638187931191394628">"ಲಭ್ಯವಿಲ್ಲ"</item>
<item msgid="9103697205127645916">"ಆಫ್"</item>
- <item msgid="8067744885820618230">"ಆನ್ ಮಾಡಿ"</item>
+ <item msgid="8067744885820618230">"ಆನ್"</item>
</string-array>
<string-array name="tile_states_saver">
<item msgid="39714521631367660">"ಲಭ್ಯವಿಲ್ಲ"</item>
<item msgid="6983679487661600728">"ಆಫ್"</item>
- <item msgid="7520663805910678476">"ಆನ್ ಮಾಡಿ"</item>
+ <item msgid="7520663805910678476">"ಆನ್"</item>
</string-array>
<string-array name="tile_states_dark">
<item msgid="2762596907080603047">"ಲಭ್ಯವಿಲ್ಲ"</item>
<item msgid="400477985171353">"ಆಫ್"</item>
- <item msgid="630890598801118771">"ಆನ್ ಮಾಡಿ"</item>
+ <item msgid="630890598801118771">"ಆನ್"</item>
</string-array>
<string-array name="tile_states_work">
<item msgid="389523503690414094">"ಲಭ್ಯವಿಲ್ಲ"</item>
<item msgid="8045580926543311193">"ಆಫ್"</item>
- <item msgid="4913460972266982499">"ಆನ್ ಮಾಡಿ"</item>
+ <item msgid="4913460972266982499">"ಆನ್"</item>
</string-array>
<string-array name="tile_states_cast">
<item msgid="6032026038702435350">"ಲಭ್ಯವಿಲ್ಲ"</item>
<item msgid="1488620600954313499">"ಆಫ್"</item>
- <item msgid="588467578853244035">"ಆನ್ ಮಾಡಿ"</item>
+ <item msgid="588467578853244035">"ಆನ್"</item>
</string-array>
<string-array name="tile_states_night">
<item msgid="7857498964264855466">"ಲಭ್ಯವಿಲ್ಲ"</item>
<item msgid="2744885441164350155">"ಆಫ್"</item>
- <item msgid="151121227514952197">"ಆನ್ ಮಾಡಿ"</item>
+ <item msgid="151121227514952197">"ಆನ್"</item>
</string-array>
<string-array name="tile_states_screenrecord">
<item msgid="1085836626613341403">"ಲಭ್ಯವಿಲ್ಲ"</item>
<item msgid="8259411607272330225">"ಆಫ್"</item>
- <item msgid="578444932039713369">"ಆನ್ ಮಾಡಿ"</item>
+ <item msgid="578444932039713369">"ಆನ್"</item>
</string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"ಲಭ್ಯವಿಲ್ಲ"</item>
<item msgid="8707481475312432575">"ಆಫ್"</item>
- <item msgid="8031106212477483874">"ಆನ್ ಮಾಡಿ"</item>
+ <item msgid="8031106212477483874">"ಆನ್"</item>
</string-array>
<string-array name="tile_states_reduce_brightness">
<item msgid="1839836132729571766">"ಲಭ್ಯವಿಲ್ಲ"</item>
<item msgid="4572245614982283078">"ಆಫ್"</item>
- <item msgid="6536448410252185664">"ಆನ್ ಮಾಡಿ"</item>
+ <item msgid="6536448410252185664">"ಆನ್"</item>
</string-array>
<string-array name="tile_states_cameratoggle">
<item msgid="6680671247180519913">"ಲಭ್ಯವಿಲ್ಲ"</item>
<item msgid="4765607635752003190">"ಆಫ್"</item>
- <item msgid="1697460731949649844">"ಆನ್ ಮಾಡಿ"</item>
+ <item msgid="1697460731949649844">"ಆನ್"</item>
</string-array>
<string-array name="tile_states_mictoggle">
<item msgid="6895831614067195493">"ಲಭ್ಯವಿಲ್ಲ"</item>
<item msgid="3296179158646568218">"ಆಫ್"</item>
- <item msgid="8998632451221157987">"ಆನ್ ಮಾಡಿ"</item>
+ <item msgid="8998632451221157987">"ಆನ್"</item>
</string-array>
<string-array name="tile_states_controls">
<item msgid="8199009425335668294">"ಲಭ್ಯವಿಲ್ಲ"</item>
<item msgid="4544919905196727508">"ಆಫ್"</item>
- <item msgid="3422023746567004609">"ಆನ್ ಮಾಡಿ"</item>
+ <item msgid="3422023746567004609">"ಆನ್"</item>
</string-array>
<string-array name="tile_states_wallet">
<item msgid="4177615438710836341">"ಲಭ್ಯವಿಲ್ಲ"</item>
<item msgid="7571394439974244289">"ಆಫ್"</item>
- <item msgid="6866424167599381915">"ಆನ್ ಮಾಡಿ"</item>
+ <item msgid="6866424167599381915">"ಆನ್"</item>
</string-array>
<string-array name="tile_states_qr_code_scanner">
<item msgid="7435143266149257618">"ಲಭ್ಯವಿಲ್ಲ"</item>
<item msgid="3301403109049256043">"ಆಫ್"</item>
- <item msgid="8878684975184010135">"ಆನ್ ಮಾಡಿ"</item>
+ <item msgid="8878684975184010135">"ಆನ್"</item>
</string-array>
<string-array name="tile_states_alarm">
<item msgid="4936533380177298776">"ಲಭ್ಯವಿಲ್ಲ"</item>
<item msgid="2710157085538036590">"ಆಫ್"</item>
- <item msgid="7809470840976856149">"ಆನ್ ಮಾಡಿ"</item>
+ <item msgid="7809470840976856149">"ಆನ್"</item>
</string-array>
<string-array name="tile_states_onehanded">
<item msgid="8189342855739930015">"ಲಭ್ಯವಿಲ್ಲ"</item>
<item msgid="146088982397753810">"ಆಫ್"</item>
- <item msgid="460891964396502657">"ಆನ್ ಮಾಡಿ"</item>
+ <item msgid="460891964396502657">"ಆನ್"</item>
</string-array>
<string-array name="tile_states_dream">
<item msgid="6184819793571079513">"ಲಭ್ಯವಿಲ್ಲ"</item>
<item msgid="8014986104355098744">"ಆಫ್"</item>
- <item msgid="5966994759929723339">"ಆನ್ ಮಾಡಿ"</item>
+ <item msgid="5966994759929723339">"ಆನ್"</item>
</string-array>
<string-array name="tile_states_font_scaling">
<item msgid="3173069902082305985">"ಲಭ್ಯವಿಲ್ಲ"</item>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 897b266..940b66d 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"얼굴 인식 잠금 해제를 설정할 수 없습니다. 설정으로 이동하여 다시 시도해 보세요."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"지문 센서를 터치하세요."</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"계속하려면 잠금 해제 아이콘을 누르세요."</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"얼굴을 인식할 수 없습니다. 대신 지문을 사용하세요."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"얼굴을 인식할 수 없습니다."</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"대신 지문을 사용하세요."</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"얼굴 인식 잠금 해제를 사용할 수 없습니다."</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"블루투스가 연결되었습니다."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 충전 중 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> 후 충전 완료"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"공동 튜토리얼을 시작하려면 왼쪽으로 스와이프하세요"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"위젯 편집기 열기"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"삭제"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"위젯 추가"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"완료"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"설정"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"저장용량"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"힌트"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"인스턴트 앱"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> 실행 중"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"설치 없이 앱이 실행되었습니다."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"접근성 기능을 열려면 탭하세요. 설정에서 이 버튼을 맞춤설정하거나 교체할 수 있습니다.\n\n"<annotation id="link">"설정 보기"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"버튼을 가장자리로 옮겨서 일시적으로 숨기세요."</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"실행취소"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> 바로가기 삭제됨"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{바로가기 #개 삭제됨}other{바로가기 #개 삭제됨}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"왼쪽 상단으로 이동"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"사용자 정보가 감지되었습니다."</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"설정에서 기본 메모 앱 설정"</string>
<string name="install_app" msgid="5066668100199613936">"앱 설치"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"계속하려면 스와이프하세요"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"외부 디스플레이로 미러링하시겠습니까?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"내부 디스플레이가 미러링됩니다. 전면 디스플레이는 꺼집니다."</string>
<string name="mirror_display" msgid="2515262008898122928">"디스플레이 미러링"</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index 2f091ec..a6234c3 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Жүзүнөн таанып ачуу функциясы кошулган жок. Параметрлерге өтүп, кайталап көрүңүз."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Манжа изинин сенсорун басыңыз"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Улантуу үчүн кулпусун ачуу сүрөтчөсүн басыңыз"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Жүз таанылбай жатат. Манжа изин колдонуңуз."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Жүз таанылбай жатат"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Манжа изин колдонуңуз"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"\"Жүзүнөн таанып ачуу\" жеткиликсиз"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth байланышта"</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Кубатталууда • Толгонго чейин <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> калды"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Жалпы үйрөткүчтү иштетүү үчүн солго сүрүңүз"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Виджет түзөткүчтү ачуу"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Өчүрүү"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Виджет кошуу"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Бүттү"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Тууралоо"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Сактагыч"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Кеңештер"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Ыкчам ачылуучу колдонмолор"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> иштеп жатат"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Колдонмо орнотулбастан ачылды."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Атайын мүмкүнчүлүктөрдү ачуу үчүн басыңыз. Бул баскычты Параметрлерден өзгөртүңүз.\n\n"<annotation id="link">"Параметрлерди көрүү"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Баскычты убактылуу жашыра туруу үчүн экрандын четине жылдырыңыз"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Кайтаруу"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> ыкчам баскычы өчүрүлдү"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# ыкчам баскыч өчүрүлдү}other{# ыкчам баскыч өчүрүлдү}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Жогорку сол жакка жылдыруу"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Колдонуучу аныкталды"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Параметрлерден демейки кыска жазуулар колдонмосун тууралаңыз"</string>
<string name="install_app" msgid="5066668100199613936">"Колдонмону орнотуу"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Улантуу үчүн экранды сүрүп коюңуз"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Тышкы экранга чыгарасызбы?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Ички экраныңыз башка экранга чыгарылат. Алдыңкы экраныңыз өчүрүлөт."</string>
<string name="mirror_display" msgid="2515262008898122928">"Тышкы экран"</string>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index cfb4017..55606aa 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -86,8 +86,8 @@
<!-- Power Menu Lite -->
<!-- These values are for small screen landscape. For larger landscape screens, they are
overlaid -->
- <dimen name="global_actions_button_size">72dp</dimen>
- <dimen name="global_actions_button_padding">26dp</dimen>
+ <dimen name="global_actions_button_size">64dp</dimen>
+ <dimen name="global_actions_button_padding">20dp</dimen>
<!-- scroll view the size of 2 channel rows -->
<dimen name="notification_blocker_channel_list_height">128dp</dimen>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index c0c5d44..87ca722 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"ບໍ່ສາມາດຕັ້ງຄ່າການປົດລັອກດ້ວຍໜ້າໄດ້. ກະລຸນາເຂົ້າໄປການຕັ້ງຄ່າເພື່ອລອງໃໝ່."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"ແຕະໃສ່ເຊັນເຊີລາຍນິ້ວມື"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"ກົດໄອຄອນປົດລັອກເພື່ອສືບຕໍ່"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"ບໍ່ສາມາດຈຳແນກໜ້າໄດ້. ກະລຸນາໃຊ້ລາຍນິ້ວມືແທນ."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"ບໍ່ສາມາດຈຳແນກໃບໜ້າໄດ້"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ກະລຸນາໃຊ້ລາຍນິ້ວມືແທນ"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"ໃຊ້ການປົດລັອກດ້ວຍໜ້າບໍ່ໄດ້"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ເຊື່ອມຕໍ່ Bluetooth ແລ້ວ."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ກຳລັງສາກໄຟ • ຈະເຕັມໃນອີກ <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"ປັດຊ້າຍເພື່ອເລີ່ມບົດແນະນຳສ່ວນກາງ"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"ເປີດຕົວແກ້ໄຂວິດເຈັດ"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"ລຶບອອກ"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ເພີ່ມວິດເຈັດ"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"ແລ້ວໆ"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"ຕັ້ງຄ່າ"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"ບ່ອນເກັບຂໍ້ມູນ"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"ຄຳໃບ້"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"ອິນສະແຕນແອັບ"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> ກຳລັງເຮັດວຽກຢູ່"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"ເປີດແອັບໂດຍບໍ່ມີການຕິດຕັ້ງແລ້ວ."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ແຕະເພື່ອເປີດຄຸນສົມບັດການຊ່ວຍເຂົ້າເຖິງ. ປັບແຕ່ງ ຫຼື ປ່ຽນປຸ່ມນີ້ໃນການຕັ້ງຄ່າ.\n\n"<annotation id="link">"ເບິ່ງການຕັ້ງຄ່າ"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ຍ້າຍປຸ່ມໄປໃສ່ຂອບເພື່ອເຊື່ອງມັນຊົ່ວຄາວ"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"ຍົກເລີກ"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"ລຶບທາງລັດ <xliff:g id="FEATURE_NAME">%s</xliff:g> ອອກແລ້ວ"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{ລຶບ # ທາງລັດອອກແລ້ວ}other{ລຶບ # ທາງລັດອອກແລ້ວ}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ຍ້າຍຊ້າຍເທິງ"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"ກວດພົບຕົວຕົນຜູ້ໃຊ້"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ຕັ້ງຄ່າແອັບຈົດບັນທຶກເລີ່ມຕົ້ນໃນການຕັ້ງຄ່າ"</string>
<string name="install_app" msgid="5066668100199613936">"ຕິດຕັ້ງແອັບ"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"ປັດເພື່ອສືບຕໍ່"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ສາຍໃສ່ຈໍສະແດງຜົນພາຍນອກບໍ?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"ການສະແດງຜົນທາງໃນຂອງທ່ານຈະຖືກສະທ້ອນ. ການສະແດງຜົນທາງໜ້າຂອງທ່ານຈະຖືກປິດໄວ້."</string>
<string name="mirror_display" msgid="2515262008898122928">"ຈໍສະແດງຜົນແບບສະທ້ອນ"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 83d2724..eee0f7d 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Nepavyko nustatyti atrakinimo pagal veidą. Eikite į skiltį „Nustatymai“ ir bandykite dar kartą."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Palieskite piršto antspaudo jutiklį"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Tęskite paspaudę atrakinimo piktogramą"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Veidas neatpažintas. Naudokite kontrolinį kodą."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Veidas neatpažintas"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Naudoti piršto antspaudą"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Atrakinimo pagal veidą funkcija nepasiekiama"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"„Bluetooth“ prijungtas."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Įkraunama • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> iki visiško įkrovimo"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Perbraukite kairėn, paleistumėte bendruomenės mokomąją medžiagą"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Atidaryti valdiklio redagavimo programą"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<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>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Atlikta"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Sąranka"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Saugykla"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Užuominos"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Akimirksniu įkeliamos programos"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"Programa „<xliff:g id="APP">%1$s</xliff:g>“ paleista"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Programa atidaryta jos neįdiegus."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Palietę atidarykite pritaikymo neįgaliesiems funkcijas. Tinkinkite arba pakeiskite šį mygtuką nustatymuose.\n\n"<annotation id="link">"Žr. nustatymus"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Perkelkite mygtuką prie krašto, kad laikinai jį paslėptumėte"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Anuliuoti"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Pašalintas spart. klavišas „<xliff:g id="FEATURE_NAME">%s</xliff:g>“"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{Pašalintas # spartusis klavišas}one{Pašalintas # spartusis klavišas}few{Pašalinti # spartieji klavišai}many{Pašalinta # sparčiojo klavišo}other{Pašalinta # sparčiųjų klavišų}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Perkelti į viršų kairėje"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Aptikta naudotojo veikla"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Nustatykite numatytąją užrašų programą Nustatymuose"</string>
<string name="install_app" msgid="5066668100199613936">"Įdiegti programą"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Perbraukite, kad galėtumėte tęsti"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Bendrinti ekrano vaizdą išoriniame ekrane?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Bus bendrinamas vidinio rodinio ekrano vaizdas. Priekinis rodinys bus išjungtas."</string>
<string name="mirror_display" msgid="2515262008898122928">"Bendrinti ekrano vaizdą"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 4bc71c3..8631d20 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Nevarēja iestatīt autorizāciju pēc sejas. Atveriet iestatījumus, lai mēģinātu vēlreiz."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Pieskarieties pirksta nospieduma sensoram"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Lai turpinātu, nospiediet atbloķēšanas ikonu."</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Nevar atpazīt seju. Lietojiet pirksta nospiedumu."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Nevar atpazīt seju"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Lietot pirksta nospiedumu"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Autorizācija pēc sejas nav pieejama"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth savienojums ir izveidots."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Notiek uzlāde • Laiks līdz pilnai uzlādei: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Velciet pa kreisi, lai palaistu kopienas pamācību."</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Atvērt logrīku redaktoru"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<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>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Gatavs"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Iestatīšana"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Krātuve"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Padomi"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Tūlītējās lietotnes"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"Lietotne <xliff:g id="APP">%1$s</xliff:g> darbojas"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Lai atvērtu šo lietotni, tā nav jāinstalē."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Atveriet pieejamības funkcijas. Pielāgojiet vai aizstājiet šo pogu iestatījumos.\n\n"<annotation id="link">"Skatīt iestatījumus"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Lai īslaicīgi paslēptu pogu, pārvietojiet to uz malu"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Atsaukt"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Noņemts īsinājumtaustiņš <xliff:g id="FEATURE_NAME">%s</xliff:g>"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{Noņemts # īsinājumtaustiņš}zero{Noņemti # īsinājumtaustiņi}one{Noņemts # īsinājumtaustiņš}other{Noņemti # īsinājumtaustiņi}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Pārvietot augšpusē pa kreisi"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Konstatēta lietotāja klātbūtne"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Iestatījumos iestatiet noklusējuma piezīmju lietotni."</string>
<string name="install_app" msgid="5066668100199613936">"Instalēt lietotni"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Velciet, lai turpinātu"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vai spoguļot ārējā displejā?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Jūsu iekšējais displejs tiks spoguļots. Jūsu priekšējais displejs tiks izslēgts."</string>
<string name="mirror_display" msgid="2515262008898122928">"Spoguļot displeju"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 28a2a02..8f7c269 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Не можеше да се постави „Отклучување со лик“. Отворете „Поставки“ за да се обидете повторно."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Допрете го сензорот за отпечатоци"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Притиснете ја иконата за отклучување за да продолжите"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Не се препознава ликот. Користете отпечаток."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Не се препознава ликот"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Користи отпечаток"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"„Отклучувањето со лик“ е недостапно"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth е поврзан."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Се полни • Полна по <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Повлечете налево за да го започнете заедничкото упатство"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Го отвора уредникот на виџети"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Отстранува"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Додајте виџет"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Готово"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Поставување"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Капацитет"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Совети"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Инстант апликации"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"Се извршува <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Апликацијата беше отворена без да се инсталира."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Допрете за функциите за пристапност. Приспособете или заменете го копчево во „Поставки“.\n\n"<annotation id="link">"Прикажи поставки"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Преместете го копчето до работ за да го сокриете привремено"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Врати"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Кратенката за „<xliff:g id="FEATURE_NAME">%s</xliff:g>“ е отстранета"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{Отстранета е # кратенка}one{Отстранети се # кратенка}other{Отстранети се # кратенки}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Премести горе лево"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Откриено е присуство на корисник"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Поставете стандардна апликација за белешки во „Поставки“"</string>
<string name="install_app" msgid="5066668100199613936">"Инсталирајте ја апликацијата"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Повлечете нагоре за да продолжите"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Да се преслика на надворешниот екран?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Вашиот внатрешен екран ќе се отслика. Вашиот преден екран ќе се исклучи."</string>
<string name="mirror_display" msgid="2515262008898122928">"Пресликај екран"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 057f0b0..6f8ebc9 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"ഫെയ്സ് അൺലോക്ക് സജ്ജീകരിക്കാനായില്ല. വീണ്ടും ശ്രമിക്കാൻ ക്രമീകരണത്തിലേക്ക് പോകുക."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"ഫിംഗർപ്രിന്റ് സെൻസർ സ്പർശിക്കുക"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"തുടരാൻ അൺലോക്ക് ഐക്കൺ അമർത്തുക"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"മുഖം തിരിച്ചറിയാനായില്ല. പകരം ഫിംഗർപ്രിന്റ് ഉപയോഗിക്കൂ."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"മുഖം തിരിച്ചറിയാനാകുന്നില്ല"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"പകരം ഫിംഗർപ്രിന്റ് ഉപയോഗിക്കൂ"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"ഫെയ്സ് അൺലോക്ക് ലഭ്യമല്ല"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ബ്ലൂടൂത്ത് കണക്റ്റുചെയ്തു."</string>
@@ -413,6 +415,12 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ചാർജ് ചെയ്യുന്നു • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-ൽ പൂർത്തിയാകും"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"കമ്മ്യൂണൽ ട്യൂട്ടോറിയൽ ആരംഭിക്കാൻ ഇടത്തോട്ട് സ്വൈപ്പ് ചെയ്യുക"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"വിജറ്റ് എഡിറ്റർ തുറക്കുക"</string>
+ <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"ഇഷ്ടാനുസൃതമാക്കുക"</string>
+ <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"ഡിസ്മിസ് ചെയ്യുക"</string>
+ <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"ഈ സ്പെയ്സിൽ നിങ്ങളുടെ വിജറ്റുകൾ ചേർക്കുക, നീക്കം ചെയ്യുക, പുനഃക്രമീകരിക്കുക"</string>
+ <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"കൂടുതൽ വിജറ്റുകൾ ചേർക്കുക"</string>
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"നീക്കം ചെയ്യുക"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"വിജറ്റ് ചേർക്കുക"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"പൂർത്തിയായി"</string>
@@ -834,6 +842,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"സജ്ജീകരണം"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"സ്റ്റോറേജ്"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"സൂചനകൾ"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> റണ് ചെയ്യുന്നു"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"ഇൻസ്റ്റാൾ ചെയ്യാതെ ആപ്പ് തുറന്നു."</string>
@@ -929,6 +939,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ഉപയോഗസഹായി ഫീച്ചർ തുറക്കാൻ ടാപ്പ് ചെയ്യൂ. ക്രമീകരണത്തിൽ ഈ ബട്ടൺ ഇഷ്ടാനുസൃതമാക്കാം, മാറ്റാം.\n\n"<annotation id="link">"ക്രമീകരണം കാണൂ"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"തൽക്കാലം മറയ്ക്കുന്നതിന് ബട്ടൺ അരുകിലേക്ക് നീക്കുക"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"പഴയപടിയാക്കുക"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> കുറുക്കുവഴി നീക്കം ചെയ്തു"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# കുറുക്കുവഴി നീക്കം ചെയ്തു}other{# കുറുക്കുവഴികൾ നീക്കം ചെയ്തു}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"മുകളിൽ ഇടതുഭാഗത്തേക്ക് നീക്കുക"</string>
@@ -1207,6 +1221,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"ഉപയോക്താവിന്റെ സാന്നിധ്യം കണ്ടെത്തി"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ക്രമീകരണത്തിൽ കുറിപ്പുകൾക്കുള്ള ഡിഫോൾട്ട് ആപ്പ് സജ്ജീകരിക്കുക"</string>
<string name="install_app" msgid="5066668100199613936">"ആപ്പ് ഇൻസ്റ്റാൾ ചെയ്യൂ"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"തുടരാൻ സ്വൈപ്പ് ചെയ്യുക"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ബാഹ്യ ഡിസ്പ്ലേയിലേക്ക് മിറർ ചെയ്യണോ?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"നിങ്ങളുടെ ഇന്നർ ഡിസ്പ്ലേ മിറർ ചെയ്യും. നിങ്ങളുടെ ഫ്രണ്ട് ഡിസ്പ്ലേ ഓഫാകും."</string>
<string name="mirror_display" msgid="2515262008898122928">"മിറർ ഡിസ്പ്ലേ"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 933652b..cb0eff5 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Царайгаар түгжээ тайлахыг тохируулж чадсангүй. Дахин оролдохын тулд Тохиргоо руу очно уу."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Хурууны хээ мэдрэгчид хүрэх"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Үргэлжлүүлэхийн тулд түгжээг тайлах дүрс тэмдгийг дарна уу"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Царай таних боломжгүй. Оронд нь хурууны хээ ашигла"</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Царайг танихгүй байна"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Оронд нь хурууны хээ ашиглах"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Царайгаар түгжээ тайлах боломжгүй"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth холбогдсон."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Цэнэглэж байна • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-н дараа дүүрнэ"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Нийтийн практик хичээлийг эхлүүлэхийн тулд зүүн тийш шударна уу"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Виджет засварлагчийг нээх"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Хасах"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Виджет нэмэх"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Болсон"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Тохируулга"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Хадгалах сан"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Заавар"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Шуурхай апп"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g>-г ажиллуулж байна"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Аппыг суулгахгүйгээр нээсэн."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Хандалтын онцлогуудыг нээхийн тулд товшино уу. Энэ товчлуурыг Тохиргоо хэсэгт өөрчилж эсвэл солиорой.\n\n"<annotation id="link">"Тохиргоог харах"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Үүнийг түр нуухын тулд товчлуурыг зах руу зөөнө үү"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Болих"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g>-н товчлолыг хассан"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# товчлолыг хассан}other{# товчлолыг хассан}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Зүүн дээш зөөх"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Хэрэглэгч байгааг илрүүлсэн"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Тохиргоонд тэмдэглэлийн өгөгдмөл апп тохируулна уу"</string>
<string name="install_app" msgid="5066668100199613936">"Аппыг суулгах"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Үргэлжлүүлэхийн тулд шударна уу"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Гадны дэлгэцэд тусгал үүсгэх үү?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Таны дотоод дэлгэцийн тусгалыг үүсгэнэ. Таны урд талын дэлгэцийг унтраана."</string>
<string name="mirror_display" msgid="2515262008898122928">"Дэлгэцийн тусгал үүсгэх"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index aad269b..1a19c6b 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"फेस अनलॉक सेट करता आले नाही. सेटिंग्ज वर जा आणि पुन्हा प्रयत्न करा."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"फिंगरप्रिंट सेन्सरला स्पर्श करा"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"पुढे सुरू ठेवण्यासाठी, अनलॉक करा चा आयकन प्रेस करा"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"चेहरा ओळखू शकत नाही. त्याऐवजी फिंगरप्रिंट वापरा."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"चेहरा ओळखू शकत नाही"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"त्याऐवजी फिंगरप्रिंट वापरा"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"फेस अनलॉक उपलब्ध नाही"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ब्लूटूथ कनेक्ट केले."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • चार्ज होत आहे • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> मध्ये पूर्ण होईल"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"सामुदायिक ट्यूटोरियल सुरू करण्यासाठी डावीकडे स्वाइप करा"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"विजेट संपादक उघडा"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"काढून टाका"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"विजेट जोडा"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"पूर्ण झाले"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"सेटअप"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"स्टोरेज"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"सूचना"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> रन होत आहे"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"इंस्टॉल केल्याशिवाय अॅप उघडले."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"अॅक्सेसिबिलिटी वैशिष्ट्ये उघडण्यासाठी, टॅप करा. सेटिंग्जमध्ये हे बटण कस्टमाइझ करा किंवा बदला.\n\n"<annotation id="link">"सेटिंग्ज पहा"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"बटण तात्पुरते लपवण्यासाठी ते कोपर्यामध्ये हलवा"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"पहिल्यासारखे करा"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> शॉर्टकट काढून टाकला"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# शॉर्टकट काढून टाकला}other{# शॉर्टकट काढून टाकले}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"वर डावीकडे हलवा"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"वापरकर्त्याची उपस्थिती डिटेक्ट केली"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"सेटिंग्ज मध्ये डीफॉल्ट टिपा अॅप सेट करा"</string>
<string name="install_app" msgid="5066668100199613936">"अॅप इंस्टॉल करा"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"सुरू ठेवण्यासाठी स्वाइप करा"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"बाह्य डिस्प्लेवर मिरर करायचे आहे का?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"तुमचा अंतर्गत डिस्प्ले मिरर केला जाईल. तुमचा पुढील डिस्प्ले बंद केला जाईल."</string>
<string name="mirror_display" msgid="2515262008898122928">"डिस्प्ले मिरर करा"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 8287d2b..3bdb184 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Tidak dapat menyediakan buka kunci wajah. Akses Tetapan untuk mencuba lagi."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Sentuh penderia cap jari"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Tekan ikon buka kunci untuk meneruskan proses"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Tidak mengenali wajah. Gunakan cap jari."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Tak dapat mengecam wajah"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Gunakan cap jari"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Buka Kunci Wajah tidak tersedia"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth disambungkan."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengecas • Penuh dalam masa <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Leret ke kiri untuk memulakan tutorial umum"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Buka editor widget"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Alih keluar"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Tambahkan widget"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Selesai"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Persediaan"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Storan"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Pembayang"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Apl Segera"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> sedang berjalan"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Apl dibuka tanpa dipasang."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Ketik untuk membuka ciri kebolehaksesan. Sesuaikan/gantikan butang ini dalam Tetapan.\n\n"<annotation id="link">"Lihat tetapan"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Gerakkan butang ke tepi untuk disembunyikan buat sementara waktu"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Buat asal"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Pintasan <xliff:g id="FEATURE_NAME">%s</xliff:g> dialih keluar"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# pintasan dialih keluar}other{# pintasan dialih keluar}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Alihkan ke atas sebelah kiri"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Kehadiran pengguna dikesan"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Tetapkan apl nota lalai dalam Tetapan"</string>
<string name="install_app" msgid="5066668100199613936">"Pasang apl"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Leret untuk meneruskan proses"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Paparkan pada paparan luaran?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Paparan dalaman anda akan dicerminkan. Paparan depan anda akan dimatikan."</string>
<string name="mirror_display" msgid="2515262008898122928">"Segerakkan paparan"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 25afad4..af2d583 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"မျက်နှာပြ လော့ခ်ဖွင့်ခြင်းကို စနစ်ထည့်သွင်း၍မရပါ။ ဆက်တင်များသို့သွားပြီး ထပ်စမ်းကြည့်ပါ။"</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"လက်ဗွေအာရုံခံကိရိယာကို တို့ပါ"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"ရှေ့ဆက်ရန် လော့ခ်ဖွင့်သင်္ကေတကို နှိပ်ပါ"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"မျက်နှာကို မမှတ်မိပါ။ လက်ဗွေကို အစားထိုးသုံးပါ။"</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"မျက်နှာကို မမှတ်မိပါ"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"လက်ဗွေကို အစားထိုးသုံးပါ"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"မျက်နှာပြ လော့ခ်ဖွင့်ခြင်း မရနိုင်ပါ"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ဘလူးတုသ်ချိတ်ဆက်ထားမှု"</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • အားသွင်းနေသည် • အားပြည့်ရန် <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> လိုသည်"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"အများသုံးရှင်းလင်းပို့ချချက် စတင်ရန် ဘယ်သို့ပွတ်ဆွဲပါ"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"ဝိဂျက်တည်းဖြတ်စနစ် ဖွင့်ရန်"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"ဖယ်ရှားရန်"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ဝိဂျက်ထည့်ရန်"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"ပြီးပြီ"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"စနစ်ထည့်ရန်"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"သိုလှောင်ခန်း"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"အရိပ်အမြွက်များ"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> လုပ်ဆောင်နေသည်"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"အက်ပ်ကိုမထည့်သွင်းဘဲ ဖွင့်ထားသည်။"</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"အများသုံးစွဲနိုင်မှုဆိုင်ရာ ဝန်ဆောင်မှုများ ဖွင့်ရန် တို့ပါ။ ဆက်တင်များတွင် ဤခလုတ်ကို စိတ်ကြိုက်ပြင်ပါ (သို့) လဲပါ။\n\n"<annotation id="link">"ဆက်တင်များ ကြည့်ရန်"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ခလုတ်ကို ယာယီဝှက်ရန် အစွန်းသို့ရွှေ့ပါ"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"နောက်ပြန်ရန်"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> ဖြတ်လမ်းလင့်ခ် ဖယ်ရှားထားသည်"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{ဖြတ်လမ်းလင့်ခ် # ခု ဖယ်ရှားထားသည်}other{ဖြတ်လမ်းလင့်ခ် # ခု ဖယ်ရှားထားသည်}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ဘယ်ဘက်ထိပ်သို့ ရွှေ့ရန်"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"အသုံးပြုသူရှိကြောင်း တွေ့ရသည်"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ဆက်တင်များတွင် မူရင်းမှတ်စုများအက်ပ် သတ်မှတ်ပါ"</string>
<string name="install_app" msgid="5066668100199613936">"အက်ပ် ထည့်သွင်းရန်"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"ရှေ့ဆက်ရန် ပွတ်ဆွဲပါ"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ပြင်ပဖန်သားပြင်သို့ စကရင်ပွားမလား။"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"အတွင်းပြကွက်ကို စကရင်ပွားပါမည်။ ရှေ့မျက်နှာပြင်ပြကွက်ကို ပိတ်မည်။"</string>
<string name="mirror_display" msgid="2515262008898122928">"ဖန်သားပြင်ကို စကရင်ပွားရန်"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index d915743..89f58d7 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Kunne ikke konfigurere ansiktslåsen. Gå til innstillingene for å prøve på nytt."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Trykk på fingeravtrykkssensoren"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Trykk på lås opp-ikonet for å fortsette"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Ansiktet gjenkjennes ikke. Bruk fingeravtrykk."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Ansiktet gjenkjennes ikke"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Bruk fingeravtrykk"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Ansiktslås er utilgjengelig"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth er tilkoblet."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lader • Fulladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Sveip til venstre for å starte fellesveiledningen"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Åpne redigeringsverktøyet for moduler"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Fjern"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Legg til modul"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Ferdig"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Konfigurering"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Lagring"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Hint"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> kjører"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Appen ble åpnet uten at den ble installert."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Trykk for å åpne tilgj.funksjoner. Tilpass eller bytt knappen i Innstillinger.\n\n"<annotation id="link">"Se innstillingene"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Flytt knappen til kanten for å skjule den midlertidig"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Angre"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g>-snarveien er fjernet"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# snarvei er fjernet}other{# snarveier er fjernet}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Flytt til øverst til venstre"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Det er registrert at brukeren er til stede"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Du kan velge en standardapp for notater i Innstillinger"</string>
<string name="install_app" msgid="5066668100199613936">"Installer appen"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Sveip for å fortsette"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vil du speile til en ekstern skjerm?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Den indre skjermen speiles. Den ytre skjermen slås av."</string>
<string name="mirror_display" msgid="2515262008898122928">"Speil skjermen"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 4abb4a4..51d5044 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"फेस अनलक सेटअप गर्न सकिएन। फेरि प्रयास गर्न सेटिङमा जानुहोस्।"</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"फिंगरप्रिन्ट सेन्सरमा छुनुहोस्"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"जारी राख्न अनलक आइकनमा थिच्नुहोस्"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"अनुहार पहिचान गर्न सकिएन। बरु फिंगरप्रिन्ट प्रयोग गर्नुहोस्।"</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"अनुहार पहिचान गर्न सकिएन"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"बरु फिंगरप्रिन्ट प्रयोग गर्नुहोस्"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"फेस अनलक उपलब्ध छैन"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ब्लुटुथ जडान भयो।"</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • चार्ज हुँदै छ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> मा फुल चार्ज हुने छ"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"कम्युनल ट्युटोरियल सुरु गर्न बायाँतिर स्वाइप गर्नुहोस्"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"विजेट एडिटर खोल्नुहोस्"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"हटाउनुहोस्"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"विजेट हाल्नुहोस्"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"पूरा भयो"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"सेटअप गर्नुहोस्"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"भण्डारण"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"सङ्केतहरू"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"तात्कालिक एपहरू"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> चलिरहेको छ"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"स्थापना नगरिकनै एप खोलियो।"</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"सर्वसुलभता कायम गर्ने सुविधा खोल्न ट्याप गर्नुहोस्। सेटिङमा गई यो बटन कस्टमाइज गर्नुहोस् वा बदल्नुहोस्।\n\n"<annotation id="link">"सेटिङ हेर्नुहोस्"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"यो बटन केही बेर नदेखिने पार्न किनारातिर सार्नुहोस्"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"अन्डू गर्नुहोस्"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> सर्टकट हटाइएको छ"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# सर्टकट हटाइएको छ}other{# वटा सर्टकट हटाइएका छन्}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"सिरानको बायाँतिर सार्नुहोस्"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"प्रयोगकर्ता उपस्थित भएको कुरा पत्ता लागेको छ"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"सेटिङमा गई नोट बनाउने डिफल्ट एप तोक्नुहोस्"</string>
<string name="install_app" msgid="5066668100199613936">"एप इन्स्टल गर्नुहोस्"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"जारी राख्न स्वाइप गर्नुहोस्"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"बाह्य डिस्प्लेमा मिरर गर्ने हो?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"तपाईंको भित्री डिस्प्ले मिरर गरिने छ। तपाईंको अगाडिको डिस्प्ले अफ गरिने छ।"</string>
<string name="mirror_display" msgid="2515262008898122928">"डिस्प्ले मिरर गर्नुहोस्"</string>
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index bcc3c83..61a323d4 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -93,6 +93,8 @@
<color name="qs_user_switcher_selected_avatar_icon_color">#202124</color>
<!-- Color of background circle of user avatars in quick settings user switcher -->
<color name="qs_user_switcher_avatar_background">#3C4043</color>
+ <!-- Color of border for keyguard password input when focused -->
+ <color name="bouncer_password_focus_color">@*android:color/system_secondary_dark</color>
<!-- Accessibility floating menu -->
<color name="accessibility_floating_menu_background">#B3000000</color> <!-- 70% -->
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 0576730..9055195 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Kan ontgrendelen via gezichtsherkenning niet instellen. Ga naar Instellingen om het opnieuw te proberen."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Raak de vingerafdruksensor aan"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Druk op het ontgrendelicoon om door te gaan"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Gezicht niet herkend. Gebruik je vingerafdruk."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Gezicht niet herkend"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Vingerafdruk gebruiken"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Ontgrendelen via gezichtsherkenning niet beschikbaar"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth-verbinding ingesteld."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Opladen • Vol over <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Swipe naar links om de communitytutorial te starten"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"De widget-editor openen"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Verwijderen"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Widget toevoegen"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Klaar"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Instellen"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Opslag"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Hints"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Instant-apps"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> actief"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"App geopend zonder dat deze is geïnstalleerd."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tik voor toegankelijkheidsfuncties. Wijzig of vervang deze knop via Instellingen.\n\n"<annotation id="link">"Naar Instellingen"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Knop naar de rand verplaatsen om deze tijdelijk te verbergen"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Ongedaan maken"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Snelkoppeling voor <xliff:g id="FEATURE_NAME">%s</xliff:g> verwijderd"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# snelkoppeling verwijderd}other{# snelkoppelingen verwijderd}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Naar linksboven verplaatsen"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Gebruikersaanwezigheid is waargenomen"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Standaard notitie-app instellen in Instellingen"</string>
<string name="install_app" msgid="5066668100199613936">"App installeren"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Swipe om door te gaan"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Spiegelen naar extern scherm?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Het scherm aan de binnenkant wordt gemirrord. Het scherm aan de voorkant wordt uitgezet."</string>
<string name="mirror_display" msgid="2515262008898122928">"Scherm spiegelen"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index cb58e64..9a31fc0 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"ଫେସ ଅନଲକ ସେଟ ଅପ କରାଯାଇପାରିଲା ନାହିଁ। ପୁଣି ଚେଷ୍ଟା କରିବା ପାଇଁ ସେଟିଂସକୁ ଯାଆନ୍ତୁ।"</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"ଟିପଚିହ୍ନ ସେନସର୍କୁ ଛୁଅଁନ୍ତୁ"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"ଜାରି ରଖିବାକୁ ଅନଲକ ଆଇକନ ଦବାନ୍ତୁ"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"ଫେସ୍ ଚିହ୍ନଟ କରିହେବ ନାହିଁ। ଟିପଚିହ୍ନ ବ୍ୟବହାର କରନ୍ତୁ।"</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"ଫେସ ଚିହ୍ନଟ ହୋଇପାରିବ ନାହିଁ"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ଟିପଚିହ୍ନ ବ୍ୟବହାର କରନ୍ତୁ"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"ଫେସ ଅନଲକ ଉପଲବ୍ଧ ନାହିଁ"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ବ୍ଲୁଟୂଥ୍ ସଂଯୋଗ କରାଯାଇଛି।"</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ଚାର୍ଜ ହେଉଛି • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ରେ ସମ୍ପୂର୍ଣ୍ଣ ହେବ"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"କମ୍ୟୁନାଲ ଟ୍ୟୁଟୋରିଆଲ ଆରମ୍ଭ କରିବା ପାଇଁ ବାମକୁ ସ୍ୱାଇପ କରନ୍ତୁ"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"ୱିଜେଟ ଏଡିଟର ଖୋଲନ୍ତୁ"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"କାଢ଼ି ଦିଅନ୍ତୁ"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ୱିଜେଟ ଯୋଗ କରନ୍ତୁ"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"ହୋଇଗଲା"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"ସେଟଅପ"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"ଷ୍ଟୋରେଜ"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"ହିଣ୍ଟ"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> ଚାଲୁଛି"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"ଇନ୍ଷ୍ଟଲ୍ ନହୋଇ ଆପ୍ ଖୋଲିଛି।"</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ଆକ୍ସେସିବିଲିଟୀ ଫିଚର ଖୋଲିବାକୁ ଟାପ କରନ୍ତୁ। ସେଟିଂସରେ ଏହି ବଟନକୁ କଷ୍ଟମାଇଜ କର କିମ୍ବା ବଦଳାଅ।\n\n"<annotation id="link">"ସେଟିଂସ ଦେଖନ୍ତୁ"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ବଟନକୁ ଅସ୍ଥାୟୀ ଭାବେ ଲୁଚାଇବା ପାଇଁ ଏହାକୁ ଗୋଟିଏ ଧାରକୁ ମୁଭ୍ କରନ୍ତୁ"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"ପୂର୍ବବତ୍ କରନ୍ତୁ"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> ସର୍ଟକଟକୁ କାଢ଼ି ଦିଆଯାଇଛି"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{#ଟି ସର୍ଟକଟକୁ କାଢ଼ି ଦିଆଯାଇଛି}other{#ଟି ସର୍ଟକଟକୁ କାଢ଼ି ଦିଆଯାଇଛି}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ଶୀର୍ଷ ବାମକୁ ମୁଭ୍ କରନ୍ତୁ"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"ୟୁଜରଙ୍କ ଉପସ୍ଥିତି ଚିହ୍ନଟ କରାଯାଇଛି"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ସେଟିଂସରେ ଡିଫଲ୍ଟ ନୋଟ୍ସ ଆପ ସେଟ କରନ୍ତୁ"</string>
<string name="install_app" msgid="5066668100199613936">"ଆପ ଇନଷ୍ଟଲ କରନ୍ତୁ"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"ଜାରି ରଖିବାକୁ ସ୍ୱାଇପ କରନ୍ତୁ"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ଏକ୍ସଟର୍ନଲ ଡିସପ୍ଲେକୁ ମିରର କରିବେ?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"ଆପଣଙ୍କ ଇନର ଡିସପ୍ଲେକୁ ମିରର କରାଯିବ। ଆପଣଙ୍କ ଫ୍ରଣ୍ଟ ଡିସପ୍ଲେକୁ ବନ୍ଦ କରାଯିବ।"</string>
<string name="mirror_display" msgid="2515262008898122928">"ଡିସପ୍ଲେ ମିରର କରନ୍ତୁ"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 0340bd1..bd71d5f 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"ਫ਼ੇਸ ਅਣਲਾਕ ਦਾ ਸੈੱਟਅੱਪ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰਨ ਲਈ ਸੈਟਿੰਗਾਂ \'ਤੇ ਜਾਓ।"</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"ਫਿੰਗਰਪ੍ਰਿੰਟ ਸੈਂਸਰ ਨੂੰ ਸਪੱਰਸ਼ ਕਰੋ"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"ਜਾਰੀ ਰੱਖਣ ਲਈ \'ਅਣਲਾਕ ਕਰੋ\' ਪ੍ਰਤੀਕ ਨੂੰ ਦਬਾਓ"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"ਚਿਹਰਾ ਨਹੀਂ ਪਛਾਣ ਸਕਦੇ। ਇਸਦੀ ਬਜਾਏ ਫਿੰਗਰਪ੍ਰਿੰਟ ਵਰਤੋ।"</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"ਚਿਹਰੇ ਦੀ ਪਛਾਣ ਨਹੀਂ ਹੋਈ"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ਇਸਦੀ ਬਜਾਏ ਫਿੰਗਰਪ੍ਰਿੰਟ ਵਰਤੋ"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"ਫ਼ੇਸ ਅਣਲਾਕ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ਕਨੈਕਟ ਕੀਤੀ।"</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ਚਾਰਜ ਹੋ ਰਿਹਾ ਹੈ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ਵਿੱਚ ਪੂਰਾ ਚਾਰਜ ਹੋਵੇਗਾ"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"ਭਾਈਚਾਰਕ ਟਿਊਟੋਰੀਅਲ ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਖੱਬੇ ਪਾਸੇ ਵੱਲ ਸਵਾਈਪ ਕਰੋ"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"ਵਿਜੇਟ ਸੰਪਾਦਕ ਖੋਲ੍ਹੋ"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"ਹਟਾਓ"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ਵਿਜੇਟ ਸ਼ਾਮਲ ਕਰੋ"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"ਹੋ ਗਿਆ"</string>
@@ -827,13 +839,15 @@
<string name="tuner_right" msgid="8247571132790812149">"ਸੱਜਾ"</string>
<string name="tuner_menu" msgid="363690665924769420">"ਮੀਨੂ"</string>
<string name="tuner_app" msgid="6949280415826686972">"<xliff:g id="APP">%1$s</xliff:g> ਐਪ"</string>
- <string name="notification_channel_alerts" msgid="3385787053375150046">"ਸੁਚੇਤਨਾਵਾਂ"</string>
+ <string name="notification_channel_alerts" msgid="3385787053375150046">"ਅਲਰਟ"</string>
<string name="notification_channel_battery" msgid="9219995638046695106">"ਬੈਟਰੀ"</string>
<string name="notification_channel_screenshot" msgid="7665814998932211997">"ਸਕ੍ਰੀਨਸ਼ਾਟ"</string>
<string name="notification_channel_instant" msgid="7556135423486752680">"Instant Apps"</string>
<string name="notification_channel_setup" msgid="7660580986090760350">"ਸੈੱਟਅੱਪ ਕਰੋ"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"ਸਟੋਰੇਜ"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"ਸੰਕੇਤ"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> ਚੱਲ ਰਹੀ ਹੈ"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"ਸਥਾਪਤ ਕੀਤੇ ਬਿਨਾਂ ਐਪ ਖੋਲ੍ਹੀ ਗਈ।"</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ਪਹੁੰਚਯੋਗਤਾ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਖੋਲ੍ਹਣ ਲਈ ਟੈਪ ਕਰੋ। ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਇਹ ਬਟਨ ਵਿਉਂਤਬੱਧ ਕਰੋ ਜਾਂ ਬਦਲੋ।\n\n"<annotation id="link">"ਸੈਟਿੰਗਾਂ ਦੇਖੋ"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ਬਟਨ ਨੂੰ ਅਸਥਾਈ ਤੌਰ \'ਤੇ ਲੁਕਾਉਣ ਲਈ ਕਿਨਾਰੇ \'ਤੇ ਲਿਜਾਓ"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"ਅਣਕੀਤਾ ਕਰੋ"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> ਸ਼ਾਰਟਕੱਟ ਨੂੰ ਹਟਾਇਆ ਗਿਆ"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# ਸ਼ਾਰਟਕੱਟ ਨੂੰ ਹਟਾਇਆ ਗਿਆ}one{# ਸ਼ਾਰਟਕੱਟ ਨੂੰ ਹਟਾਇਆ ਗਿਆ}other{# ਸ਼ਾਰਟਕੱਟਾਂ ਨੂੰ ਹਟਾਇਆ ਗਿਆ}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ਉੱਪਰ ਵੱਲ ਖੱਬੇ ਲਿਜਾਓ"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"ਵਰਤੋਂਕਾਰ ਦੀ ਮੌਜੂਦਗੀ ਦਾ ਪਤਾ ਲਗਾਇਆ ਜਾਂਦਾ ਹੈ"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਜਾ ਕੇ ਪੂਰਵ-ਨਿਰਧਾਰਿਤ ਨੋਟ ਐਪ ਨੂੰ ਸੈੱਟ ਕਰੋ"</string>
<string name="install_app" msgid="5066668100199613936">"ਐਪ ਸਥਾਪਤ ਕਰੋ"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"ਜਾਰੀ ਰੱਖਣ ਲਈ ਸਵਾਈਪ ਕਰੋ"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ਕੀ ਬਾਹਰੀ ਡਿਸਪਲੇ \'ਤੇ ਪ੍ਰਤਿਬਿੰਬਿਤ ਕਰਨਾ ਹੈ?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"ਤੁਹਾਡੀ ਅੰਦਰੂਨੀ ਡਿਸਪਲੇ ਪ੍ਰਤੀਬਿੰਬਤ ਕੀਤੀ ਜਾਵੇਗੀ। ਤੁਹਾਡੀ ਅਗਲੀ ਡਿਸਪਲੇ ਬੰਦ ਕਰ ਦਿੱਤੀ ਜਾਵੇਗੀ।"</string>
<string name="mirror_display" msgid="2515262008898122928">"ਡਿਸਪਲੇ ਨੂੰ ਪ੍ਰਤਿਬਿੰਬਿਤ ਕਰੋ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index aa24818..c569f62 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Nie udało się skonfigurować rozpoznawania twarzy. Przejdź do ustawień, aby spróbować jeszcze raz."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Dotknij czytnika linii papilarnych"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Aby kontynuować, kliknij ikonę odblokowywania"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Nie rozpoznaję twarzy. Użyj odcisku palca."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Nie można rozpoznać twarzy"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Użyj odcisku palca"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Rozpoznawanie twarzy niedostępne"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth połączony."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ładowanie • Pełne naładowanie za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Aby uruchomić wspólny samouczek, przeciągnij palcem w lewo"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Otwórz edytor widżetów"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Usuń"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodaj widżet"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Gotowe"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Konfiguracja"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Pamięć wewnętrzna"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Wskazówki"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Aplikacje błyskawiczne"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"Aplikacja <xliff:g id="APP">%1$s</xliff:g> działa"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Aplikacja została otwarta bez zainstalowania."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Kliknij, aby otworzyć ułatwienia dostępu. Dostosuj lub zmień ten przycisk w Ustawieniach.\n\n"<annotation id="link">"Wyświetl ustawienia"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Przesuń przycisk do krawędzi, aby ukryć go tymczasowo"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Cofnij"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> – skrót został usunięty"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# skrót został usunięty}few{# skróty zostały usunięte}many{# skrótów zostało usuniętych}other{# skrótu zostało usunięte}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Przenieś w lewy górny róg"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Wykryto obecność użytkownika"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ustaw domyślną aplikację do obsługi notatek w Ustawieniach"</string>
<string name="install_app" msgid="5066668100199613936">"Zainstaluj aplikację"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Przesuń, aby kontynuować"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Powielić na wyświetlaczu zewnętrznym?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Powstanie odbicie lustrzane Twojego wewnętrznego ekranu. Przedni ekran zostanie wyłączony."</string>
<string name="mirror_display" msgid="2515262008898122928">"Powielaj obraz"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index c65c56e..001872a 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Não foi possível configurar o Desbloqueio facial. Acesse as Configurações e tente de novo."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Toque no sensor de impressão digital"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Pressione o ícone de desbloqueio para continuar"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Não foi possível reconhecer o rosto Use a impressão digital."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Rosto não reconhecido"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Use a impressão digital"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"O Desbloqueio facial não está disponível"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado."</string>
@@ -413,6 +415,12 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregando • Conclusão em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Deslize para a esquerda para iniciar o tutorial compartilhado"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Abrir o editor de widgets"</string>
+ <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Personalizar"</string>
+ <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Dispensar"</string>
+ <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Adicione, remova e reorganize seus widgets neste espaço"</string>
+ <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Adicione mais widgets"</string>
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Remover"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Adicionar widget"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Concluído"</string>
@@ -834,6 +842,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Configurar"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Armazenamento"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Dicas"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> em execução"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"O app é aberto sem precisar ser instalado."</string>
@@ -929,6 +939,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Toque para abrir os recursos de acessibilidade. Personalize ou substitua o botão nas Configurações.\n\n"<annotation id="link">"Ver configurações"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mova o botão para a borda para ocultá-lo temporariamente"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Desfazer"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Atalho de <xliff:g id="FEATURE_NAME">%s</xliff:g> removido"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# atalho removido}one{# atalho removido}many{# de atalhos removidos}other{# atalhos removidos}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mover para o canto superior esquerdo"</string>
@@ -1207,6 +1221,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Presença do usuário detectada"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Defina o app de notas padrão nas Configurações"</string>
<string name="install_app" msgid="5066668100199613936">"Instalar o app"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Deslize para continuar"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Espelhar para a tela externa?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Seu display interno será espelhado. O display frontal será desligado."</string>
<string name="mirror_display" msgid="2515262008898122928">"Espelhar tela"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 0b9580d..44317ae 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Não foi possível configurar o Desbloqueio facial. Aceda às Definições para tentar novamente."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Toque no sensor de impressões digitais."</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Prima o ícone de desbloqueio para continuar"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Impos. reconh. rosto. Utilize a impressão digital."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Imposs. reconhecer rosto"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Usar impressão digital"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Desbloqueio facial indisponível"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ligado."</string>
@@ -413,6 +415,12 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • A carregar • Carga completa em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Deslize rapidamente para a esquerda para iniciar o tutorial coletivo"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Abrir editor de widgets"</string>
+ <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Personalizar"</string>
+ <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Ignorar"</string>
+ <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Adicionar, remover e reordenar widgets neste espaço"</string>
+ <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Adicionar mais widgets"</string>
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Remover"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Adicionar widget"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Concluir"</string>
@@ -834,6 +842,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Configuração"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Armazenamento"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Sugestões"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Apps instantâneas"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> em execução"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"A app é aberta sem ser instalada."</string>
@@ -929,6 +939,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Toque para abrir funcionalidades de acessibilidade. Personal. ou substitua botão em Defin.\n\n"<annotation id="link">"Ver defin."</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mova o botão para a extremidade para o ocultar temporariamente"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Anular"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Atalho de <xliff:g id="FEATURE_NAME">%s</xliff:g> removido"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# atalho removido}many{# atalhos removidos}other{# atalhos removidos}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mover p/ parte sup. esquerda"</string>
@@ -1207,6 +1221,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Quando deteta a presença do utilizador"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Predefina a app de notas nas Definições"</string>
<string name="install_app" msgid="5066668100199613936">"Instalar app"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Deslize rapidamente para continuar"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Espelhar para o ecrã externo?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"O seu ecrã interior vai ser espelhado. O seu ecrã frontal vai ser desativado."</string>
<string name="mirror_display" msgid="2515262008898122928">"Espelhar ecrã"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index c65c56e..001872a 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Não foi possível configurar o Desbloqueio facial. Acesse as Configurações e tente de novo."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Toque no sensor de impressão digital"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Pressione o ícone de desbloqueio para continuar"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Não foi possível reconhecer o rosto Use a impressão digital."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Rosto não reconhecido"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Use a impressão digital"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"O Desbloqueio facial não está disponível"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado."</string>
@@ -413,6 +415,12 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregando • Conclusão em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Deslize para a esquerda para iniciar o tutorial compartilhado"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Abrir o editor de widgets"</string>
+ <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Personalizar"</string>
+ <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Dispensar"</string>
+ <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Adicione, remova e reorganize seus widgets neste espaço"</string>
+ <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Adicione mais widgets"</string>
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Remover"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Adicionar widget"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Concluído"</string>
@@ -834,6 +842,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Configurar"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Armazenamento"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Dicas"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> em execução"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"O app é aberto sem precisar ser instalado."</string>
@@ -929,6 +939,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Toque para abrir os recursos de acessibilidade. Personalize ou substitua o botão nas Configurações.\n\n"<annotation id="link">"Ver configurações"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mova o botão para a borda para ocultá-lo temporariamente"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Desfazer"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Atalho de <xliff:g id="FEATURE_NAME">%s</xliff:g> removido"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# atalho removido}one{# atalho removido}many{# de atalhos removidos}other{# atalhos removidos}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mover para o canto superior esquerdo"</string>
@@ -1207,6 +1221,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Presença do usuário detectada"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Defina o app de notas padrão nas Configurações"</string>
<string name="install_app" msgid="5066668100199613936">"Instalar o app"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Deslize para continuar"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Espelhar para a tela externa?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Seu display interno será espelhado. O display frontal será desligado."</string>
<string name="mirror_display" msgid="2515262008898122928">"Espelhar tela"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index b2fdef3..ab31599 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Nu s-a putut configura deblocarea facială. Accesează Setările pentru a încerca din nou."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Atinge senzorul de amprente"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Apasă pe pictograma de deblocare pentru a continua"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Chipul nu a fost recunoscut. Folosește amprenta."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Chip nerecunoscut"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Folosește amprenta"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Deblocarea facială nu este disponibilă"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Conectat prin Bluetooth."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Se încarcă • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> până la încărcarea completă"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Glisează spre stânga pentru a începe tutorialul pentru comunitate"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Deschide editorul de widgeturi"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Elimină"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Adaugă un widget"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Gata"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Configurarea"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Stocare"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Indicii"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Aplicații instantanee"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> rulează"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Aplicația a fost deschisă fără a fi instalată."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Atinge ca să deschizi funcțiile de accesibilitate. Personalizează sau înlocuiește butonul în setări.\n\n"<annotation id="link">"Vezi setările"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mută butonul spre margine pentru a-l ascunde temporar"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Anulează"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Comandă rapidă <xliff:g id="FEATURE_NAME">%s</xliff:g> eliminată"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# comandă rapidă eliminată}few{# comenzi rapide eliminate}other{# de comenzi rapide eliminate}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mută în stânga sus"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"S-a detectat prezența utilizatorului"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Setează aplicația prestabilită de note din Setări"</string>
<string name="install_app" msgid="5066668100199613936">"Instalează aplicația"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Glisează pentru a continua"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Oglindești pe ecranul extern?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Ecranul interior va fi oglindit. Ecranul frontal va fi dezactivat."</string>
<string name="mirror_display" msgid="2515262008898122928">"Afișare în oglindă"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index ff4bd45..b6ff0db 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Не удалось настроить фейсконтроль. Перейдите в настройки и повторите попытку."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Прикоснитесь к сканеру отпечатков пальцев."</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Нажмите на значок разблокировки."</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Не удалось распознать лицо. Используйте отпечаток."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Лицо не распознано."</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Используйте отпечаток."</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Фейсконтроль недоступен"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth-соединение установлено."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Зарядка • Осталось <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Чтобы ознакомиться с руководством, проведите по экрану влево"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Открыть редактор виджетов"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Удалить"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Добавить виджет"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Готово"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Настройка"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Хранилище"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Подсказки"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Приложения с мгновенным запуском"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> уже здесь!"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Приложение готово к работе, установка не требуется."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Нажмите, чтобы открыть спец. возможности. Настройте или замените эту кнопку в настройках.\n\n"<annotation id="link">"Настройки"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Чтобы временно скрыть кнопку, переместите ее к краю экрана"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Отменить"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g>: сочетание клавиш удалено."</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# сочетание клавиш удалено}one{# сочетание клавиш удалено}few{# сочетания клавиш удалено}many{# сочетаний клавиш удалено}other{# сочетания клавиш удалено}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Перенести в левый верхний угол"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Обнаружен пользователь"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Задайте стандартное приложение для заметок в настройках."</string>
<string name="install_app" msgid="5066668100199613936">"Установить приложение"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Проведите по экрану, чтобы продолжить."</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Дублировать на внешний дисплей?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Для внутреннего экрана включится дублирование. Передний экран будет отключен."</string>
<string name="mirror_display" msgid="2515262008898122928">"Дублировать"</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 4b4d08b..5c9faab 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"මුහුණෙන් අගුළු හැරීම පිහිටුවිය නොහැකි විය. නැවත උත්සාහ කිරීමට සැකසීම් වෙත යන්න."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"ඇඟිලි සලකුණු සංවේදකය ස්පර්ශ කරන්න"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"ඉදිරියට යාමට අගුළු ඇරීමේ නිරූපකය ඔබන්න"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"මුහුණ හැඳිනිය නොහැක. ඒ වෙනුවට ඇඟිලි සලකුණ භාවිත ක."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"මුහුණ හඳුනා ගත නොහැක"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ඒ වෙනුවට ඇඟිලි සලකුණ භාවිත කරන්න"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"මුහුණෙන් අගුළු ඇරීම නැත"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"බ්ලූටූත් සම්බන්ධිතයි."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ආරෝපණය වෙමින් • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>කින් සම්පූර්ණ වේ"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"පොදු නිබන්ධනය ආරම්භ කිරීමට වමට ස්වයිප් කරන්න"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"විජට් සංස්කාරකය විවෘත කරන්න"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"ඉවත් කරන්න"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"විජට්ටුව එක් කරන්න"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"නිමයි"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"පිහිටුවීම"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"ගබඩාව"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"ඉඟි"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"ක්ෂණික යෙදුම්"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> ධාවනය වෙමින්"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"ස්ථාපනය නොකර යෙදුම විවෘත කර ඇත."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ප්රවේශ්යතා විශේෂාංග විවෘත කිරීමට තට්ටු කරන්න. සැකසීම් තුළ මෙම බොත්තම අභිරුචිකරණය හෝ ප්රතිස්ථාපනය කරන්න.\n\n"<annotation id="link">"සැකසීම් බලන්න"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"එය තාවකාලිකව සැඟවීමට බොත්තම දාරයට ගෙන යන්න"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"අස් කරන්න"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> කෙටිමඟ ඉවත් කළා"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# කෙටිමඟක් ඉවත් කළා}one{කෙටිමං #ක් ඉවත් කළා}other{කෙටිමං #ක් ඉවත් කළා}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ඉහළ වමට ගෙන යන්න"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"පරිශීලක රූපාකාරය අනාවරණය වේ"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"සැකසීම් තුළ පෙරනිමි සටහන් යෙදුම සකසන්න"</string>
<string name="install_app" msgid="5066668100199613936">"යෙදුම ස්ථාපනය කරන්න"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"ඉදිරියට යාමට ස්වයිප් කරන්න"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"බාහිර සංදර්ශකයට දර්පණය කරන්න ද?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"ඔබේ අභ්යන්තර සංදර්ශකය පිළිබිඹු වනු ඇත. ඔබේ ඉදිරිපස සංදර්ශකය ක්රියාවිරහිත වනු ඇත."</string>
<string name="mirror_display" msgid="2515262008898122928">"සංදර්ශකය දර්පණය කරන්න"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 9e9507e..ae567c78 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Odomknutie tvárou sa nepodarilo nastaviť. Prejdite do Nastavení a skúste to znova."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Dotknite sa senzora odtlačkov prstov"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Pokračujte stlačením ikony odomknutia"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Tvár sa nedá rozpoznať. Použite odtlačok prsta."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Tvár sa nedá rozpoznať"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Používať radšej odtlačok"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Odomknutie tvárou nie je k dispozícii"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth pripojené."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Nabíja sa • Do úplného nabitia zostáva <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Potiahnutím doľava spustite komunitný návod"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Otvoriť editor miniaplikácií"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<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>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Hotovo"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Nastavenie"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Úložisko"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Tipy"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Okamžité aplikácie"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"Aplikácia <xliff:g id="APP">%1$s</xliff:g> je spustená"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Aplikácia bola otvorená bez inštalácie."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Funkcie dostupnosti otvoríte klepnutím. Tlačidlo prispôsobte alebo nahraďte v Nastav.\n\n"<annotation id="link">"Zobraz. nast."</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Ak chcete tlačidlo dočasne skryť, presuňte ho k okraju"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Späť"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Bola odstránená skratka <xliff:g id="FEATURE_NAME">%s</xliff:g>"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{Bola odstránená # skratka}few{Boli odstránené # skratky}many{# shortcuts removed}other{Bolo odstránených # skratiek}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Presunúť doľava nahor"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Bola rozpoznaná prítomnosť používateľa"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Nastavte predvolenú aplikáciu na poznámky v Nastaveniach"</string>
<string name="install_app" msgid="5066668100199613936">"Inštalovať aplikáciu"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Pokračujte potiahnutím"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Chcete zrkadliť na externú obrazovku?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Vnútorná obrazovka bude zrkadlená. Predná obrazovka bude vypnutá."</string>
<string name="mirror_display" msgid="2515262008898122928">"Zrkadliť obrazovku"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index cba5416..c2f2693 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Odklepanja z obrazom ni bilo mogoče nastaviti. Odprite nastavitve in poskusite znova."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Dotaknite se tipala prstnih odtisov"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Za nadaljevanje pritisnite ikono za odklepanje"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Obraza ni mogoče prepoznati. Uporabite prstni odtis."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Obraz ni bil prepoznan."</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Uporabite prstni odtis."</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Odklepanje z obrazom ni na voljo."</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Povezava Bluetooth vzpostavljena."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Polnjenje • Napolnjeno čez <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Povlecite levo, da zaženete vadnico za skupnost"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Odpiranje urejevalnika pripomočkov"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Odstrani"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodajanje pripomočka"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Končano"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Nastavitev"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Shramba"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Namigi"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Nenamestljive aplikacije"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"Aplikacija <xliff:g id="APP">%1$s</xliff:g> se izvaja"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Aplikacija je odprta brez namestitve."</string>
@@ -897,10 +911,10 @@
<string name="accessibility_control_move_down" msgid="5390922476900974512">"Premakni navzdol"</string>
<string name="accessibility_control_move_left" msgid="8156206978511401995">"Premakni levo"</string>
<string name="accessibility_control_move_right" msgid="8926821093629582888">"Premakni desno"</string>
- <string name="accessibility_control_increase_window_width" msgid="6992249470832493283">"Povečanje širine povečevalnika"</string>
- <string name="accessibility_control_decrease_window_width" msgid="5740401560105929681">"Zmanjšanje širine povečevalnika"</string>
- <string name="accessibility_control_increase_window_height" msgid="2200966116612324260">"Povečanje višine povečevalnika"</string>
- <string name="accessibility_control_decrease_window_height" msgid="2054479949445332761">"Zmanjšanje višine povečevalnika"</string>
+ <string name="accessibility_control_increase_window_width" msgid="6992249470832493283">"Povečanje širine lupe"</string>
+ <string name="accessibility_control_decrease_window_width" msgid="5740401560105929681">"Zmanjšanje širine lupe"</string>
+ <string name="accessibility_control_increase_window_height" msgid="2200966116612324260">"Povečanje višine lupe"</string>
+ <string name="accessibility_control_decrease_window_height" msgid="2054479949445332761">"Zmanjšanje višine lupe"</string>
<string name="magnification_mode_switch_description" msgid="2698364322069934733">"Stikalo za povečavo"</string>
<string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Povečanje celotnega zaslona"</string>
<string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Povečava dela zaslona"</string>
@@ -917,7 +931,7 @@
<string name="accessibility_magnification_right_handle" msgid="9055988237319397605">"Ročica desno"</string>
<string name="accessibility_magnification_bottom_handle" msgid="6531646968813821258">"Ročica spodaj"</string>
<string name="accessibility_magnification_settings_panel_description" msgid="8174187340747846953">"Nastavitve povečave"</string>
- <string name="accessibility_magnifier_size" msgid="3038755600030422334">"Velikost povečevalnika"</string>
+ <string name="accessibility_magnifier_size" msgid="3038755600030422334">"Velikost lupe"</string>
<string name="accessibility_magnification_zoom" msgid="4222088982642063979">"Povečava/pomanjšava"</string>
<string name="accessibility_magnification_medium" msgid="6994632616884562625">"Srednja"</string>
<string name="accessibility_magnification_small" msgid="8144502090651099970">"Majhna"</string>
@@ -925,10 +939,14 @@
<string name="accessibility_magnification_fullscreen" msgid="5043514702759201964">"Celozaslonski način"</string>
<string name="accessibility_magnification_done" msgid="263349129937348512">"Končano"</string>
<string name="accessibility_magnifier_edit" msgid="1522877239671820636">"Uredi"</string>
- <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Nastavitve okna povečevalnika"</string>
+ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Nastavitve okna lupe"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Dotaknite se za funkcije dostopnosti. Ta gumb lahko prilagodite ali zamenjate v nastavitvah.\n\n"<annotation id="link">"Ogled nastavitev"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Če želite gumb začasno skriti, ga premaknite ob rob."</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Razveljavi"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Odstranjena bližnjica za fun. <xliff:g id="FEATURE_NAME">%s</xliff:g>"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{Odstranjena # bližnjica}one{Odstranjena # bližnjica}two{Odstranjeni # bližnjici}few{Odstranjene # bližnjice}other{Odstranjenih # bližnjic}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Premakni zgoraj levo"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Zaznana je prisotnost uporabnika"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Nastavite privzeto aplikacijo za zapiske v nastavitvah."</string>
<string name="install_app" msgid="5066668100199613936">"Namesti aplikacijo"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Povlecite za nadaljevanje"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Želite zrcaliti na zunanji zaslon?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Notranji zaslon bo zrcaljen. Sprednji zaslon bo izklopljen."</string>
<string name="mirror_display" msgid="2515262008898122928">"Zrcali zaslon"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index b35668f..4443954 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Shkyçja me fytyrë nuk mund të konfigurohej. Shko te \"Cilësimet\" për të provuar përsëri."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Prek sensorin e gjurmës së gishtit"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Shtyp ikonën e shkyçjes për të vazhduar"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Nuk mund ta dallojë fytyrën. Përdor më mirë gjurmën e gishtit."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Fytyra nuk mund të njihet"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Përdor më mirë gjurmën e gishtit"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"\"Shkyçja me fytyrë\" nuk ofrohet"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Pajisja është lidhur me \"bluetooth\"."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Po karikohet • Plot për <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Rrëshqit shpejt majtas për të filluar udhëzuesin e përbashkët"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Hap modifikuesin e miniaplikacionit"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Hiq"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Shto miniaplikacionin"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"U krye"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Konfigurimi"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Hapësira ruajtëse"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Sugjerimet"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Aplikacionet e çastit"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"Po ekzekutohet <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Aplikacioni u hap pa u instaluar."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Trokit dhe hap veçoritë e qasshmërisë. Modifiko ose ndërro butonin te \"Cilësimet\".\n\n"<annotation id="link">"Shih cilësimet"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Zhvendose butonin në skaj për ta fshehur përkohësisht"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Zhbëj"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Shkurtorja për \"<xliff:g id="FEATURE_NAME">%s</xliff:g>\" u hoq"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# shkurtore u hoq}other{# shkurtore u hoqën}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Zhvendos lart majtas"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Është zbuluar prania e përdoruesit"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Cakto aplikacionin e parazgjedhur të shënimeve te \"Cilësimet\""</string>
<string name="install_app" msgid="5066668100199613936">"Instalo aplikacionin"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Rrëshqit shpejt për të vazhduar"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Të pasqyrohet në ekranin e jashtëm?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Ekrani i brendshëm do të pasqyrohet. Ekrani i parmë do të çaktivizohet."</string>
<string name="mirror_display" msgid="2515262008898122928">"Pasqyro ekranin"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 1d45fbd..ac1d7bf 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Подешавање откључавања лицем није успело. Идите у Подешавања да бисте пробали поново."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Додирните сензор за отисак прста"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Притисните икону откључавања за наставак"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Лице није препознато. Користите отисак прста."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Лице није препознато"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Користите отисак прста"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Откључавање лицем није доступно"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth је прикључен."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Пуни се • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до краја пуњења"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Превуците улево да бисте започели заједнички водич"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Отвори уређивач виџета"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Уклони"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Додај виџет"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Готово"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Подешавање"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Меморијски простор"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Савети"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Инстант апликације"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"Апликација <xliff:g id="APP">%1$s</xliff:g> је покренута"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Апликација се отворила без инсталирања."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Додирните за функције приступачности. Прилагодите или замените ово дугме у Подешавањима.\n\n"<annotation id="link">"Подешавања"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Померите дугме до ивице да бисте га привремено сакрили"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Опозови"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Пречица функције <xliff:g id="FEATURE_NAME">%s</xliff:g> је уклоњена"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# пречица је уклоњена}one{# пречица је уклоњена}few{# пречице су уклоњене}other{# пречица је уклоњено}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Премести горе лево"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Присуство корисника може да се открије"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Подесите подразумевану апликацију за белешке у Подешавањима"</string>
<string name="install_app" msgid="5066668100199613936">"Инсталирај апликацију"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Превуците да бисте наставили"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Желите ли да пресликате на спољњи екран?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Унутрашњи екран ће се пресликати. Предњи екран ће се искључити."</string>
<string name="mirror_display" msgid="2515262008898122928">"Пресликај екран"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 0d6272f..f8dbbc5 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Det gick inte att konfigurera ansiktslåset. Öppna inställningarna och försök igen."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Tryck på fingeravtryckssensorn"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Tryck på ikonen lås upp för att fortsätta"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Ansiktet kändes inte igen. Använd fingeravtryck."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Ansiktet kändes inte igen"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Använd fingeravtryck"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Ansiktslås är otillgängligt"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ansluten."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laddas • Fulladdat om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Svep åt vänster för att börja med gruppguiden"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Öppna widgetredigeraren"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<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>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Klar"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Konfigurering"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Lagring"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Tips"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Snabbappar"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> körs"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Appen öppnades utan installation."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tryck för att öppna tillgänglighetsfunktioner. Anpassa/ersätt knappen i Inställningar.\n\n"<annotation id="link">"Inställningar"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Flytta knappen till kanten för att dölja den tillfälligt"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Ångra"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Genväg till <xliff:g id="FEATURE_NAME">%s</xliff:g> har tagits bort"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# genväg har tagits bort}other{# genvägar har tagits bort}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Flytta högst upp till vänster"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Användarnärvaro har upptäckts"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ställ in en standardapp för anteckningar i inställningarna"</string>
<string name="install_app" msgid="5066668100199613936">"Installera appen"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Svep för att fortsätta"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vill du spegla till extern skärm?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Den inre skärmen speglas. Den främre skärmen stängs av."</string>
<string name="mirror_display" msgid="2515262008898122928">"Spegla skärm"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 1bd2ed7..dbc2eea 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Imeshindwa kuweka mipangilio ya kufungua kwa uso. Nenda kwenye Mipangilio ili ujaribu tena."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Gusa kitambua alama ya kidole"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Bonyeza aikoni ya kufungua ili uendelee"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Imeshindwa kutambua uso. Tumia alama ya kidole."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Imeshindwa kutambua uso"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Badala yake, tumia alama ya kidole"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Kipengele cha Kufungua kwa Uso hakipatikani"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth imeunganishwa."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Inachaji • Itajaa baada ya <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Telezesha kidole kushoto ili uanze mafunzo ya pamoja"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Fungua kihariri cha wijeti"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Ondoa"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Ongeza wijeti"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Nimemaliza"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Weka mipangilio"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Hifadhi"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Vidokezo"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Programu Zinazofunguka Papo Hapo"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"Programu ya <xliff:g id="APP">%1$s</xliff:g> inatumika"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Programu inafunguka bila kusakinishwa."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Gusa ili ufungue vipengele vya ufikivu. Weka mapendeleo au ubadilishe kitufe katika Mipangilio.\n\n"<annotation id="link">"Angalia mipangilio"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Sogeza kitufe kwenye ukingo ili ukifiche kwa muda"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Tendua"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Njia ya mkato ya <xliff:g id="FEATURE_NAME">%s</xliff:g> imeondolewa"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{Njia # ya mkato imeondolewa}other{Njia # za mkato zimeondolewa}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Sogeza juu kushoto"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Imetambua uwepo wa mtumiaji"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Teua programu chaguomsingi ya madokezo katika Mipangilio"</string>
<string name="install_app" msgid="5066668100199613936">"Sakinisha programu"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Telezesha kidole ili uendelee"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Ungependa kuonyesha kwenye skrini ya nje?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Mwonekano wa ndani wa kifaa chako utaakisiwa. Mwonekano wa mbele wa kifaa chako utazimwa."</string>
<string name="mirror_display" msgid="2515262008898122928">"Akisi skrini"</string>
diff --git a/packages/SystemUI/res/values-sw720dp-land/config.xml b/packages/SystemUI/res/values-sw720dp-land/config.xml
index be34a48..0955f67 100644
--- a/packages/SystemUI/res/values-sw720dp-land/config.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/config.xml
@@ -24,9 +24,6 @@
<!-- The maximum number of tiles in the QuickQSPanel -->
<integer name="quick_qs_panel_max_tiles">6</integer>
- <!-- Whether to use the split 2-column notification shade -->
- <bool name="config_use_split_notification_shade">true</bool>
-
<!-- The number of columns in the QuickSettings -->
<integer name="quick_settings_num_columns">3</integer>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 0cd076f..d70992e 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"\'முகம் காட்டித் திறத்தல்\' அம்சத்தை அமைக்க முடியவில்லை. அமைப்புகளுக்குச் சென்று மீண்டும் முயலவும்."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"கைரேகை சென்சாரைத் தொடவும்"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"தொடர, அன்லாக் ஐகானை அழுத்துங்கள்"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"முகத்தை அடையாளம் காண முடியவில்லை. கைரேகையைப் பயன்படுத்தவும்."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"முகத்தை கண்டறிய இயலவில்லை"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"கைரேகையை உபயோகிக்கவும்"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"முகம் காட்டித் திறத்தல் அம்சம் கிடைக்கவில்லை"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"புளூடூத் இணைக்கப்பட்டது."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • சார்ஜாகிறது • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> இல் முழுவதும் சார்ஜாகும்"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"சமூகப் பயிற்சியைத் தொடங்க இடதுபுறம் ஸ்வைப் செய்யுங்கள்"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"விட்ஜெட் எடிட்டரைத் திறக்கும்"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"அகற்றும்"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"விட்ஜெட்டைச் சேர்"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"முடிந்தது"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"அமைவு"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"சேமிப்பிடம்"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"குறிப்புகள்"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> இயங்குகிறது"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"நிறுவ வேண்டிய தேவையில்லாமல் ஆப்ஸ் திறக்கப்பட்டது."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"அணுகல்தன்மை அம்சத்தை திறக்க தட்டவும். அமைப்பில் பட்டனை பிரத்தியேகமாக்கலாம்/மாற்றலாம்.\n\n"<annotation id="link">"அமைப்பில் காண்க"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"பட்டனைத் தற்காலிகமாக மறைக்க ஓரத்திற்கு நகர்த்தும்"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"செயல்தவிர்"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> ஷார்ட்கட் அகற்றப்பட்டது"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# ஷார்ட்கட் அகற்றப்பட்டது}other{# ஷார்ட்கட்கள் அகற்றப்பட்டன}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"மேலே இடதுபுறத்திற்கு நகர்த்து"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"பயனர் கண்டறியப்பட்டுள்ளார்"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"குறிப்பு எடுப்பதற்கான இயல்புநிலை ஆப்ஸை அமைப்புகளில் அமையுங்கள்"</string>
<string name="install_app" msgid="5066668100199613936">"ஆப்ஸை நிறுவுங்கள்"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"ஸ்வைப் செய்து தொடரலாம்"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"வெளிப்புறக் காட்சிக்கு மிரர் செய்யவா?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"உங்கள் உட்புற டிஸ்பிளே பிரதிபலிக்கப்படும். உங்கள் முன்புற டிஸ்பிளே முடக்கப்படும்."</string>
<string name="mirror_display" msgid="2515262008898122928">"டிஸ்பிளேயை மிரர் செய்"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 6a59812..c87762e 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"ఫేస్ అన్లాక్ను సెటప్ చేయడం సాధ్యపడలేదు. సెట్టింగ్లకు వెళ్లి, ఆపై మళ్లీ ట్రై చేయండి."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"వేలిముద్ర సెన్సార్ను తాకండి"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"కొనసాగించడానికి అన్లాక్ చిహ్నాన్ని నొక్కండి"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"ముఖం గుర్తించలేము. బదులుగా వేలిముద్ర ఉపయోగించండి."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"ముఖం గుర్తించడం కుదరలేదు"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"బదులుగా వేలిముద్రను ఉపయోగించండి"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"ఫేస్ అన్లాక్ అందుబాటులో లేదు"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"బ్లూటూత్ కనెక్ట్ చేయబడింది."</string>
@@ -413,6 +415,12 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ఛార్జ్ అవుతోంది • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>లో పూర్తిగా ఛార్జ్ అవుతుంది"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"కమ్యూనల్ ట్యుటోరియల్ను ప్రారంభించడానికి ఎడమ వైపునకు స్వైప్ చేయండి"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"విడ్జెట్ ఎడిటర్ను తెరవండి"</string>
+ <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"అనుకూలంగా మార్చండి"</string>
+ <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"విస్మరించండి"</string>
+ <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"ఈ స్పేస్లో మీ విడ్జెట్లను జోడించండి, తీసివేయండి, క్రమపద్ధతిలో అమర్చండి"</string>
+ <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"మరిన్ని విడ్జెట్లను జోడించండి"</string>
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"తీసివేయండి"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"విడ్జెట్ను జోడించండి"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"పూర్తయింది"</string>
@@ -834,6 +842,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"సెటప్ చేయండి"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"స్టోరేజ్"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"సూచనలు"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"ఇన్స్టంట్ యాప్లు"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> అమలవుతోంది"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"ఇన్స్టాల్ చేయకుండా యాప్ తెరవబడింది."</string>
@@ -929,6 +939,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"యాక్సెసిబిలిటీ ఫీచర్లను తెరవడానికి ట్యాప్ చేయండి. సెట్టింగ్లలో ఈ బటన్ను అనుకూలంగా మార్చండి లేదా రీప్లేస్ చేయండి.\n\n"<annotation id="link">"వీక్షణ సెట్టింగ్లు"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"తాత్కాలికంగా దానిని దాచడానికి బటన్ను చివరకు తరలించండి"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"చర్య రద్దు చేయండి"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> షార్ట్కట్ తీసివేయబడింది"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# షార్ట్కట్ తీసివేయబడింది}other{# షార్ట్కట్లు తీసివేయబడ్డాయి}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ఎగువ ఎడమ వైపునకు తరలించు"</string>
@@ -1207,6 +1221,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"యూజర్ ఉనికి గుర్తించబడింది"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"సెట్టింగ్లలో ఆటోమేటిక్గా ఉండేలా ఒక నోట్స్ యాప్ను సెట్ చేసుకోండి"</string>
<string name="install_app" msgid="5066668100199613936">"యాప్ను ఇన్స్టాల్ చేయండి"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"కొనసాగించడానికి స్వైప్ చేయండి"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ఎక్స్టర్నల్ డిస్ప్లేకి మిర్రర్ చేయాలా?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"మీ లోపలి డిస్ప్లే మిర్రర్ చేయబడుతుంది. మీ ముందు వైపు డిస్ప్లే ఆఫ్ చేయబడుతుంది."</string>
<string name="mirror_display" msgid="2515262008898122928">"మిర్రర్ డిస్ప్లే"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index dc4c6cf..ab50427 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"ตั้งค่าการปลดล็อกด้วยใบหน้าไม่ได้ ไปที่การตั้งค่าเพื่อลองอีกครั้ง"</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"แตะเซ็นเซอร์ลายนิ้วมือ"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"กดไอคอนปลดล็อกเพื่อดำเนินการต่อ"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"ไม่รู้จักใบหน้า ใช้ลายนิ้วมือแทน"</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"ไม่รู้จักใบหน้า"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ใช้ลายนิ้วมือแทน"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"การปลดล็อกด้วยใบหน้าไม่พร้อมใช้งาน"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"เชื่อมต่อบลูทูธแล้ว"</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • กำลังชาร์จ • จะเต็มในอีก <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"ปัดไปทางซ้ายเพื่อเริ่มบทแนะนำส่วนกลาง"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"เปิดเครื่องมือแก้ไขวิดเจ็ต"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"นำออก"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"เพิ่มวิดเจ็ต"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"เสร็จสิ้น"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"ตั้งค่า"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"พื้นที่เก็บข้อมูล"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"คำแนะนำ"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Instant App"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> ทำงานอยู่"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"เปิดแอปได้โดยไม่ต้องติดตั้ง"</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"แตะเพื่อเปิดฟีเจอร์การช่วยเหลือพิเศษ ปรับแต่งหรือแทนที่ปุ่มนี้ในการตั้งค่า\n\n"<annotation id="link">"ดูการตั้งค่า"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ย้ายปุ่มไปที่ขอบเพื่อซ่อนชั่วคราว"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"เลิกทำ"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"นำทางลัด <xliff:g id="FEATURE_NAME">%s</xliff:g> ออกแล้ว"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{นำทางลัด # รายการออกแล้ว}other{นำทางลัด # รายการออกแล้ว}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ย้ายไปด้านซ้ายบน"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"ตรวจพบการแสดงข้อมูลของผู้ใช้"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"กำหนดแอปการจดบันทึกเริ่มต้นในการตั้งค่า"</string>
<string name="install_app" msgid="5066668100199613936">"ติดตั้งแอป"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"ปัดเพื่อทำต่อ"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"มิเรอร์ไปยังจอแสดงผลภายนอกไหม"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"ระบบจะมิเรอร์หน้าจอด้านใน และจะปิดหน้าจอด้านหน้า"</string>
<string name="mirror_display" msgid="2515262008898122928">"มิเรอร์จอแสดงผล"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index c09ac97..dcd687a 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Hindi na-set up ang pag-unlock gamit ang mukha. Pumunta sa Mga Setting para subukan ulit."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Pindutin ang fingerprint sensor"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Pindutin ang icon ng pag-unlock para magpatuloy"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Hindi makilala ang mukha. Gumamit ng fingerprint."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Hindi makilala ang mukha"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Gumamit ng fingerprint"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Hindi available ang Pag-unlock Gamit ang Mukha"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Nakakonekta ang Bluetooth."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Nagcha-charge • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> na lang para mapuno"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Mag-swipe pakaliwa para simulan ang communal na tutorial"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Buksan ang editor ng widget"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Alisin"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Magdagdag ng widget"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Tapos na"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Setup"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Storage"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Mga Hint"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"Tumatakbo ang <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Nabuksan ang app nang hindi ini-install."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"I-tap, buksan mga feature ng accessibility. I-customize o palitan button sa Mga Setting.\n\n"<annotation id="link">"Tingnan ang mga setting"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Ilipat ang button sa gilid para pansamantala itong itago"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"I-undo"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> shortcut ang naalis"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# shortcut ang naalis}one{# shortcut ang naalis}other{# na shortcut ang naalis}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Ilipat sa kaliwa sa itaas"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Na-detect ang presensya ng user"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Magtakda ng default na app sa pagtatala sa Mga Setting"</string>
<string name="install_app" msgid="5066668100199613936">"I-install ang app"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Mag-swipe para magpatuloy"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"I-mirror sa external na display?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Imi-mirror ang inner display mo. Io-off ang iyong front display."</string>
<string name="mirror_display" msgid="2515262008898122928">"I-mirror ang display"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index ee1909b..f043404 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Yüz tanıma kilidi kurulamadı. Tekrar denemek için Ayarlar\'a gidin."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Parmak izi sensörüne dokunun"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Devam etmek için kilit açma simgesine basın"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Yüz tanınamadı. Bunun yerine parmak izi kullanın."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Yüz tanınamadı"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Bunun yerine parmak izi kullanın"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Yüz Tanıma Kilidi kullanılamıyor"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth bağlandı."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Şarj oluyor • Dolmasına <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> kaldı"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Ortak eğitimi başlatmak için sola kaydırın"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Widget düzenleyiciyi açın"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Kaldır"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Widget ekle"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Bitti"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Kurulum"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Depolama alanı"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"İpuçları"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Hazır Uygulamalar"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> çalışıyor"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Uygulama yüklenmeden açıldı."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Erişilebilirlik özelliklerini açmak için dokunun. Bu düğmeyi Ayarlar\'dan özelleştirin veya değiştirin.\n\n"<annotation id="link">"Ayarları göster"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Düğmeyi geçici olarak gizlemek için kenara taşıyın"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Geri al"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> kısayol kaldırıldı"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# kısayol kaldırıldı}other{# kısayol kaldırıldı}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Sol üste taşı"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Kullanıcı varlığı algılandı"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ayarlar\'ı kullanarak varsayılan notlar ayarlayın"</string>
<string name="install_app" msgid="5066668100199613936">"Uygulamayı yükle"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Devam etmek için kaydırın"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Harici ekrana yansıtılsın mı?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"İç ekranınız yansıtılacak. Ön ekranınız kapatılacak."</string>
<string name="mirror_display" msgid="2515262008898122928">"Ekranı yansıt"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 04a97bc..fce90e5 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Не вдалося налаштувати фейс-контроль. Перейдіть у налаштування, щоб повторити спробу."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Торкніться сканера відбитків пальців"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Щоб продовжити, натисніть значок розблокування"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Обличчя не розпізнано. Скористайтеся відбитком пальця."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Обличчя не розпізнано"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Скористайтеся відбитком"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Фейс-контроль недоступний"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth під’єднано."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Заряджання • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до повного заряду"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Проведіть пальцем уліво, щоб відкрити спільний навчальний посібник"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Відкрити редактор віджетів"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Видалити"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Додати віджет"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Готово"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Налаштування"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Пам’ять"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Поради"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Додатки з миттєвим запуском"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> працює"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Додаток відкрито без встановлення."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Торкніться, щоб відкрити функції доступності. Змінити або замінити цю кнопку можна в Налаштуваннях.\n\n"<annotation id="link">"Налаштування"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Щоб тимчасово сховати кнопку, перемістіть її на край екрана"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Відмінити"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g>: швидкий запуск вилучено"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# ярлик вилучено}one{# ярлик вилучено}few{# ярлики вилучено}many{# ярликів вилучено}other{# ярлика вилучено}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Перемістити ліворуч угору"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Виявлено присутність користувача"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Призначте стандартний додаток для нотаток у налаштуваннях"</string>
<string name="install_app" msgid="5066668100199613936">"Установити додаток"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Щоб продовжити, проведіть пальцем по екрану"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Дублювати на зовнішньому екрані?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Ваш внутрішній екран буде продубльовано. Передній екран буде вимкнено."</string>
<string name="mirror_display" msgid="2515262008898122928">"Дублювати екран"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index fd984b9..1ae2118 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"فیس اَن لاک کو سیٹ اپ نہیں کیا جا سکا۔ دوبارہ کوشش کرنے کیلئے ترتیبات پر جائیں۔"</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"فنگر پرنٹ سینسر پر ٹچ کریں"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"جاری رکھنے کیلئے غیر مقفل کرنے کا آئیکن دبائیں"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"چہرے کی شناخت نہیں ہو سکی۔ اس کے بجائے فنگر پرنٹ استعمال کریں۔"</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"چہرے کی پہچان نہیں ہو سکی"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"اس کے بجائے فنگر پرنٹ استعمال کریں"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"فیس اَنلاک غیر دستیاب ہے"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"بلوٹوتھ مربوط ہے۔"</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • چارج ہو رہا ہے • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> میں مکمل"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"کمیونل ٹیوٹوریل شروع کرنے کے لیے بائیں سوائپ کریں"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"ویجیٹ ایڈیٹر کو کھولیں"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"ہٹائیں"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ویجیٹ شامل کریں"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"ہو گیا"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"سیٹ اپ"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"اسٹوریج"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"اشارات"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"فوری ایپس"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> چل رہی ہے"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"انسٹال کیے بغیر کھلنے والی ایپ۔"</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ایکسیسبیلٹی خصوصیات کھولنے کے لیے تھپتھپائیں۔ ترتیبات میں اس بٹن کو حسب ضرورت بنائیں یا تبدیل کریں۔\n\n"<annotation id="link">"ترتیبات ملاحظہ کریں"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"عارضی طور پر بٹن کو چھپانے کے لئے اسے کنارے پر لے جائیں"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"کالعدم کریں"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> شارٹ کٹ ہٹا دیا گیا"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# شارٹ کٹ ہٹا دیا گیا}other{# شارٹ کٹس ہٹا دیے گئے}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"اوپر بائیں جانب لے جائیں"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"صارف کی موجودگی کا پتہ چلا ہے"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ترتیبات میں ڈیفالٹ نوٹس ایپ سیٹ کریں"</string>
<string name="install_app" msgid="5066668100199613936">"ایپ انسٹال کریں"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"جاری رکھنے کے لیے سوائپ کریں"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"بیرونی ڈسپلے پر مرر کریں؟"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"آپ کے اندرونی ڈسپلے کو دو طرفہ مطابقت پذیر بنایا جائے گا۔ آپ کا فرنٹ ڈسپلے آف ہو جائے گا۔"</string>
<string name="mirror_display" msgid="2515262008898122928">"ڈسپلے کو مرر کریں"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index b9a9832..67d3db8 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Yuz bilan ochish sozlanmadimi. Sozlamalarni ochib, qaytadan urining."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Barmoq izi skaneriga tegining"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Davom etish uchun qulfni ochish belgisini bosing"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Bu yuz notanish. Barmoq izi orqali urining."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Yuz aniqlanmadi"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Barmoq izi orqali urining"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Yuz bilan ochilmaydi."</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ulandi."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Quvvat olmoqda • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> qoldi"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Qoʻllanma bilan tanishish uchun chapga suring"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Vidjet muharririni ochish"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Olib tashlash"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Vidjet kiritish"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Tayyor"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Sozlash"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Xotira"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Maslahatlar"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Darhol ochiladigan ilovalar"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> ishlayapti"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Ilova o‘rnatilmasdan ochildi."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Maxsus imkoniyatlarni ochish uchun bosing Sozlamalardan moslay yoki almashtira olasiz.\n\n"<annotation id="link">"Sozlamalar"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Vaqtinchalik berkitish uchun tugmani qirra tomon suring"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Bekor qilish"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> ta yorliq olindi"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# ta yorliq olindi}other{# ta yorliq olindi}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Yuqori chapga surish"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Foydalanuvchi aniqlandi"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Standart qaydlar ilovasini Sozlamalar orqali tanlang"</string>
<string name="install_app" msgid="5066668100199613936">"Ilovani oʻrnatish"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Davom etish uchun suring"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Tashqi displeyda aks ettirilsinmi?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Ichki ekran uchun aks ettirish yoqiladi. Old ekran oʻchiriladi."</string>
<string name="mirror_display" msgid="2515262008898122928">"Displeyni aks ettirish"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index b1ff9a8..e4ae7be 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Không thiết lập được tính năng Mở khoá bằng khuôn mặt. Hãy chuyển đến phần Cài đặt để thử lại."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Chạm vào cảm biến vân tay"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Nhấn vào biểu tượng mở khoá để tiếp tục"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Không thể nhận dạng khuôn mặt. Hãy dùng vân tay."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Không nhận ra khuôn mặt"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Hãy dùng vân tay"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Không dùng được tính năng Mở khoá bằng khuôn mặt"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Đã kết nối bluetooth."</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Đang sạc • Sẽ đầy sau <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Vuốt sang trái để bắt đầu xem hướng dẫn chung"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Mở trình chỉnh sửa tiện ích"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<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>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Xong"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Thiết lập"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Bộ nhớ"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Gợi ý"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Ứng dụng tức thì"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> đang chạy"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Ứng dụng được mở mà không cần cài đặt."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Nhấn để mở bộ tính năng hỗ trợ tiếp cận. Tuỳ chỉnh/thay thế nút này trong phần Cài đặt.\n\n"<annotation id="link">"Xem chế độ cài đặt"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Di chuyển nút sang cạnh để ẩn nút tạm thời"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Huỷ"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Đã xoá phím tắt dành cho <xliff:g id="FEATURE_NAME">%s</xliff:g>"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{Đã xoá # lối tắt}other{Đã xoá # lối tắt}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Chuyển lên trên cùng bên trái"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Phát hiện thấy người dùng đang hiện diện"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Đặt ứng dụng ghi chú mặc định trong phần Cài đặt"</string>
<string name="install_app" msgid="5066668100199613936">"Cài đặt ứng dụng"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Vuốt để tiếp tục"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Phản chiếu sang màn hình ngoài?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Màn hình trong của bạn sẽ được phản chiếu. Màn hình ngoài của bạn sẽ tắt."</string>
<string name="mirror_display" msgid="2515262008898122928">"Phản chiếu màn hình"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 237fd57..7fd84d9 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"无法设置“人脸解锁”功能。请前往“设置”重试。"</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"请触摸指纹传感器"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"按下解锁图标即可继续"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"无法识别人脸。请改用指纹。"</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"人脸识别失败"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"改用指纹"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"无法使用人脸解锁功能"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"蓝牙已连接。"</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 正在充电 • 将于 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>后充满"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"向左滑动即可启动公共教程"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"打开微件编辑器"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"移除"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"添加微件"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"完成"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"设置"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"存储空间"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"提示"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"免安装应用"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"正在运行<xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"已打开免安装应用。"</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"点按即可打开无障碍功能。您可在“设置”中自定义或更换此按钮。\n\n"<annotation id="link">"查看设置"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"将按钮移到边缘,即可暂时将其隐藏"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"撤消"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"已移除“<xliff:g id="FEATURE_NAME">%s</xliff:g>”快捷方式"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{已移除 # 个快捷方式}other{已移除 # 个快捷方式}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"移至左上角"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"检测到用户存在"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"在设置中设置默认记事应用"</string>
<string name="install_app" msgid="5066668100199613936">"安装应用"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"滑动可继续操作"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"镜像到外接显示屏?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"系统将镜像您的内屏,而关闭外屏。"</string>
<string name="mirror_display" msgid="2515262008898122928">"镜像到显示屏"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 313af30..568f823 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"無法設定「面孔解鎖」功能,請前往「設定」再試一次。"</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"請輕觸指紋感應器"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"按解鎖圖示即可繼續"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"無法辨識面孔,請改用指紋完成驗證。"</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"無法辨識面孔"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"請改用指紋"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"無法使用面孔解鎖"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"藍牙連線已建立。"</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 充電中 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充滿電"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"向左滑動即可開始共用教學課程"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"開啟小工具編輯器"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"移除"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"新增小工具"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"完成"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"設定"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"儲存空間"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"提示"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"免安裝應用程式"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> 運作中"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"已開啟免安裝應用程式。"</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"㩒一下就可以開無障礙功能。喺「設定」度自訂或者取代呢個按鈕。\n\n"<annotation id="link">"查看設定"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"將按鈕移到邊緣即可暫時隱藏"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"復原"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"已移除「<xliff:g id="FEATURE_NAME">%s</xliff:g>」捷徑"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{已移除 # 個捷徑}other{已移除 # 個捷徑}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"移去左上方"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"偵測到使用者動態"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"在「設定」中指定預設筆記應用程式"</string>
<string name="install_app" msgid="5066668100199613936">"安裝應用程式"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"輕掃即可繼續瀏覽"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"要鏡像投射至外部顯示屏嗎?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"鏡像畫面將顯示在內部螢幕,前方螢幕則會關閉。"</string>
<string name="mirror_display" msgid="2515262008898122928">"鏡像顯示"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 6a13d3d..86e6535 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"無法設定人臉解鎖功能,請前往「設定」再試一次。"</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"請輕觸指紋感應器"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"按下「解鎖」圖示即可繼續操作"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"無法辨識臉孔,請改用指紋完成驗證。"</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"無法辨識臉孔"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"請改用指紋"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"無法使用人臉解鎖功能"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"藍牙連線已建立。"</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 充電中 • 將於 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充飽"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"向左滑動即可啟動通用教學課程"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"開啟小工具編輯器"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"移除"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"新增小工具"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"完成"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"設定"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"儲存空間"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"提示"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"免安裝應用程式"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"正在執行「<xliff:g id="APP">%1$s</xliff:g>」"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"已開啟免安裝應用程式。"</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"輕觸即可開啟無障礙功能。你可以前往「設定」自訂或更換這個按鈕。\n\n"<annotation id="link">"查看設定"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"將按鈕移到邊緣處即可暫時隱藏"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"復原"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"已移除「<xliff:g id="FEATURE_NAME">%s</xliff:g>」捷徑"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{已移除 # 個捷徑}other{已移除 # 個捷徑}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"移到左上方"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"偵測到使用者"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"在「設定」中指定預設記事應用程式"</string>
<string name="install_app" msgid="5066668100199613936">"安裝應用程式"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"滑動畫面繼續瀏覽"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"要以鏡像方式投放至外部螢幕嗎?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"鏡像畫面將顯示在內螢幕,封面螢幕則會關閉。"</string>
<string name="mirror_display" msgid="2515262008898122928">"鏡像顯示"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 23862a7..564ba33 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -188,10 +188,12 @@
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Ayikwazanga ukusetha ukuvula ngobuso. Iya Kumasethingi ukuze uzame futhi."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Thinta inzwa yesigxivizo zeminwe"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Cindezela isithonjana sokuvula ukuze uqhubeke"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Ayibazi ubuso. Sebenzisa izigxivizo zeminwe kunalokho."</string>
+ <!-- no translation found for fingerprint_dialog_use_fingerprint_instead (5542430577183894219) -->
+ <skip />
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Ayikwazi ukubona ubuso"</string>
+ <!-- no translation found for keyguard_face_failed (2346762871330729634) -->
+ <skip />
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Kunalokho sebenzisa isigxivizo somunwe"</string>
<string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Ukuvula ngobuso akutholakali"</string>
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ixhunyiwe"</string>
@@ -413,6 +415,16 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Iyashaja • Izogcwala ngo-<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Swayiphela kwesokunxele ukuze uqale okokufundisa komphakathi"</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"Vula isihleli sewijethi"</string>
+ <!-- no translation found for cta_tile_button_to_open_widget_editor (3871562362382963878) -->
+ <skip />
+ <!-- no translation found for cta_tile_button_to_dismiss (3377597875997861754) -->
+ <skip />
+ <!-- no translation found for cta_label_to_edit_widget (6496885074209203756) -->
+ <skip />
+ <!-- no translation found for cta_label_to_open_widget_picker (3874946756976360699) -->
+ <skip />
+ <!-- no translation found for popup_on_dismiss_cta_tile_text (8292501780996070019) -->
+ <skip />
<string name="button_to_remove_widget" msgid="3948204829181214098">"Susa"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Engeza iwijethi"</string>
<string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Kwenziwe"</string>
@@ -834,6 +846,8 @@
<string name="notification_channel_setup" msgid="7660580986090760350">"Ukusetha"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"Isitoreji"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"Ukubonisa"</string>
+ <!-- no translation found for notification_channel_accessibility (8956203986976245820) -->
+ <skip />
<string name="instant_apps" msgid="8337185853050247304">"Izinhlelo zokusebenza ezisheshayo"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> esebenzayo"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Uhlelo lokusebenza luvulwe ngaphndle kokufakwa."</string>
@@ -929,6 +943,10 @@
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Thepha ukuze uvule izakhi zokufinyelela. Enza ngendlela oyifisayo noma shintsha le nkinobho Kumasethingi.\n\n"<annotation id="link">"Buka amasethingi"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Hambisa inkinobho onqenqemeni ukuze uyifihle okwesikhashana"</string>
<string name="accessibility_floating_button_undo" msgid="511112888715708241">"Hlehlisa"</string>
+ <!-- no translation found for accessibility_floating_button_hidden_notification_title (4115036997406994799) -->
+ <skip />
+ <!-- no translation found for accessibility_floating_button_hidden_notification_text (1457021647040915658) -->
+ <skip />
<string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Isinqamuleli se-<xliff:g id="FEATURE_NAME">%s</xliff:g> sisusiwe"</string>
<string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{Isinqamuleli esingu-# sisusiwe}one{Izinqamuleli ezingu-# zisusiwe}other{Izinqamuleli ezingu-# zisusiwe}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Hamba phezulu kwesokunxele"</string>
@@ -1207,6 +1225,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Ubukhona bomsebenzisi butholakele"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Setha i-app yamanothi azenzakalelayo Kumsethingi"</string>
<string name="install_app" msgid="5066668100199613936">"Faka i-app"</string>
+ <string name="dismissible_keyguard_swipe" msgid="2213369651289613196">"Swayipha ukuze uqhubeke"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Fanisa nesibonisi sangaphandle?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Isibonisi sakho sangaphakathi sizoboniswa. Isibonisi sakho sangaphambili sizovalwa."</string>
<string name="mirror_display" msgid="2515262008898122928">"Isibonisi sokufanisa"</string>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 5f6a39a..3839dd9 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -56,6 +56,8 @@
<color name="kg_user_switcher_restricted_avatar_icon_color">@color/GM2_grey_600</color>
<!-- Color of background circle of user avatars in keyguard user switcher -->
<color name="user_avatar_color_bg">?android:attr/colorBackgroundFloating</color>
+ <!-- Color of border for keyguard password input when focused -->
+ <color name="bouncer_password_focus_color">@*android:color/system_secondary_light</color>
<!-- Icon color for user avatars in user switcher quick settings -->
<color name="qs_user_switcher_avatar_icon_color">#3C4043</color>
@@ -135,6 +137,9 @@
<color name="biometric_dialog_gray">#ff757575</color>
<color name="biometric_dialog_accent">@color/material_dynamic_primary40</color>
<color name="biometric_dialog_error">#ffd93025</color> <!-- red 600 -->
+ <!-- Color for biometric prompt content view -->
+ <color name="biometric_prompt_content_background_color">#8AB4F8</color>
+ <color name="biometric_prompt_content_list_item_bullet_color">#1d873b</color>
<!-- SFPS colors -->
<color name="sfps_chevron_fill">@color/material_dynamic_primary90</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 90d8cdb..ee2a1ce 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -897,6 +897,10 @@
<dimen name="communal_tutorial_indicator_padding">24dp</dimen>
<dimen name="communal_tutorial_indicator_horizontal_offset">32dp</dimen>
+ <!-- Size of the maximum radius for the enforced rounded rectangles on communal hub.
+ Keep it the same as in Launcher-->
+ <dimen name="communal_enforced_rounded_corner_max_radius">16dp</dimen>
+
<!-- The width/height of the unlock icon view on keyguard. -->
<dimen name="keyguard_lock_height">42dp</dimen>
<dimen name="keyguard_lock_padding">20dp</dimen>
@@ -1092,6 +1096,16 @@
<dimen name="biometric_dialog_width">240dp</dimen>
<dimen name="biometric_dialog_height">240dp</dimen>
+ <!-- Dimensions for biometric prompt content view. -->
+ <dimen name="biometric_prompt_space_above_content">48dp</dimen>
+ <dimen name="biometric_prompt_content_container_padding_horizontal">24dp</dimen>
+ <dimen name="biometric_prompt_content_padding_horizontal">10dp</dimen>
+ <dimen name="biometric_prompt_content_list_row_height">24dp</dimen>
+ <dimen name="biometric_prompt_content_list_item_padding_horizontal">10dp</dimen>
+ <dimen name="biometric_prompt_content_list_item_text_size">14sp</dimen>
+ <dimen name="biometric_prompt_content_list_item_bullet_gap_width">10dp</dimen>
+ <dimen name="biometric_prompt_content_list_item_bullet_radius">5dp</dimen>
+
<!-- Biometric Auth Credential values -->
<dimen name="biometric_auth_icon_size">48dp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 6e035e8..ec4c7d5 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -249,6 +249,9 @@
-->
<item type="id" name="tag_smartspace_view" />
+ <!-- ID of the Scene Container root Composable view -->
+ <item type='id' name="scene_container_root_composable" />
+
<!-- Tag set on the Compose implementation of the QS footer actions. -->
<item type="id" name="tag_compose_qs_footer_actions" />
diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml
index c925b26..fad4d4f 100644
--- a/packages/SystemUI/res/values/integers.xml
+++ b/packages/SystemUI/res/values/integers.xml
@@ -16,6 +16,7 @@
-->
<resources>
<integer name="biometric_dialog_text_gravity">8388611</integer> <!-- gravity start -->
+ <integer name="biometric_prompt_content_list_item_max_lines_if_two_column">3</integer>
<integer name="qs_security_footer_maxLines">2</integer>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 5e10905..1989589 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -449,11 +449,11 @@
<!-- Content description after successful auth when confirmation required -->
<string name="fingerprint_dialog_authenticated_confirmation">Press the unlock icon to continue</string>
<!-- Message shown to inform the user a face cannot be recognized and fingerprint should instead be used.[CHAR LIMIT=50] -->
- <string name="fingerprint_dialog_use_fingerprint_instead">Can\u2019t recognize face. Use fingerprint instead.</string>
+ <string name="fingerprint_dialog_use_fingerprint_instead">Face not recognized. Use fingerprint instead.</string>
<!-- Message shown to inform the user a face cannot be recognized and fingerprint should instead be used.[CHAR LIMIT=50] -->
<string name="keyguard_face_failed_use_fp">@string/fingerprint_dialog_use_fingerprint_instead</string>
<!-- Message shown to inform the user a face cannot be recognized. [CHAR LIMIT=25] -->
- <string name="keyguard_face_failed">Can\u2019t recognize face</string>
+ <string name="keyguard_face_failed">Face not recognized</string>
<!-- Message shown to suggest using fingerprint sensor to authenticate after another biometric failed. [CHAR LIMIT=25] -->
<string name="keyguard_suggest_fingerprint">Use fingerprint instead</string>
<!-- Message shown to inform the user that face unlock is not available. [CHAR LIMIT=59] -->
@@ -1612,37 +1612,9 @@
<!-- Bluetooth enablement ok text [CHAR LIMIT=40] -->
<string name="enable_bluetooth_confirmation_ok">Turn on</string>
- <!-- [CHAR LIMIT=NONE] Importance Tuner setting title -->
- <string name="tuner_full_importance_settings">Power notification controls</string>
-
<!-- [CHAR LIMIT=NONE] Notification camera based rotation enabled description -->
<string name="rotation_lock_camera_rotation_on">On - Face-based</string>
- <string name="power_notification_controls_description">With power notification controls, you can set an importance level from 0 to 5 for an app\'s notifications.
- \n\n<b>Level 5</b>
- \n- Show at the top of the notification list
- \n- Allow full screen interruption
- \n- Always peek
- \n\n<b>Level 4</b>
- \n- Prevent full screen interruption
- \n- Always peek
- \n\n<b>Level 3</b>
- \n- Prevent full screen interruption
- \n- Never peek
- \n\n<b>Level 2</b>
- \n- Prevent full screen interruption
- \n- Never peek
- \n- Never make sound and vibration
- \n\n<b>Level 1</b>
- \n- Prevent full screen interruption
- \n- Never peek
- \n- Never make sound or vibrate
- \n- Hide from lock screen and status bar
- \n- Show at the bottom of the notification list
- \n\n<b>Level 0</b>
- \n- Block all notifications from the app
- </string>
-
<!-- Notification Inline controls: button to dismiss the blocking helper [CHAR_LIMIT=20] -->
<string name="inline_done_button">Done</string>
@@ -2284,6 +2256,8 @@
<string name="notification_channel_storage">Storage</string>
<!-- Title for the notification channel for hints and suggestions. [CHAR LIMIT=NONE] -->
<string name="notification_channel_hints">Hints</string>
+ <!-- Title for the notification channel for accessibility related (i.e. accessibility floating menu). [CHAR LIMIT=NONE] -->
+ <string name="notification_channel_accessibility">Accessibility</string>
<!-- App label of the instant apps notification [CHAR LIMIT=60] -->
<string name="instant_apps">Instant Apps</string>
@@ -2554,6 +2528,11 @@
<string name="accessibility_floating_button_docking_tooltip">Move button to the edge to hide it temporarily</string>
<!-- Text for the undo action button of the message view of the accessibility floating menu to perform undo operation. [CHAR LIMIT=30]-->
<string name="accessibility_floating_button_undo">Undo</string>
+ <!-- Notification title shown when accessibility floating button is in hidden state. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_floating_button_hidden_notification_title">Accessibility button hidden</string>
+ <!-- Notification content text to explain user can tap notification to bring back accessibility floating button. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_floating_button_hidden_notification_text">Tap to show accessibility button</string>
+
<!-- Text for the message view with undo action of the accessibility floating menu to show which feature shortcut was removed. [CHAR LIMIT=30]-->
<string name="accessibility_floating_button_undo_message_label_text"><xliff:g id="feature name" example="Magnification">%s</xliff:g> shortcut removed</string>
<!-- Text for the message view with undo action of the accessibility floating menu to show how many features shortcuts were removed. [CHAR LIMIT=30]-->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 7d7c050..ab3cacb 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -195,6 +195,24 @@
<item name="android:textSize">14sp</item>
</style>
+ <style name="TextAppearance.AuthCredential.ContentViewTitle">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:paddingTop">8dp</item>
+ <item name="android:paddingHorizontal">24dp</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:gravity">start</item>
+ </style>
+
+ <style name="TextAppearance.AuthCredential.ContentViewListItem">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:paddingTop">8dp</item>
+ <item name="android:paddingHorizontal">
+ @dimen/biometric_prompt_content_list_item_padding_horizontal
+ </item>
+ <item name="android:textSize">@dimen/biometric_prompt_content_list_item_text_size</item>
+ <item name="android:gravity">start</item>
+ </style>
+
<style name="TextAppearance.AuthCredential.Error">
<item name="android:paddingTop">6dp</item>
<item name="android:paddingHorizontal">24dp</item>
@@ -294,6 +312,11 @@
<item name="android:textSize">16sp</item>
</style>
+ <style name="AuthCredentialContentLayoutStyle">
+ <item name="android:background">@color/biometric_prompt_content_background_color</item>
+ <item name="android:paddingHorizontal">@dimen/biometric_prompt_content_padding_horizontal</item>
+ </style>
+
<style name="DeviceManagementDialogTitle">
<item name="android:gravity">center</item>
<item name="android:textAppearance">@style/TextAppearance.Dialog.Title</item>
diff --git a/packages/SystemUI/res/xml/other_settings.xml b/packages/SystemUI/res/xml/other_settings.xml
deleted file mode 100644
index 7719d5e..0000000
--- a/packages/SystemUI/res/xml/other_settings.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:sysui="http://schemas.android.com/apk/res-auto"
- android:title="@string/other">
-
- <!-- importance -->
- <Preference
- android:key="power_notification_controls"
- android:title="@string/tuner_full_importance_settings"
- android:fragment="com.android.systemui.tuner.PowerNotificationControlsFragment"/>
-
-</PreferenceScreen>
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 05106c9..326c7ef 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -34,9 +34,6 @@
srcs: [
":statslog-SystemUI-java-gen",
],
- lint: {
- baseline_filename: "lint-baseline.xml",
- },
}
android_library {
@@ -74,9 +71,6 @@
min_sdk_version: "current",
plugins: ["dagger2-compiler"],
kotlincflags: ["-Xjvm-default=all"],
- lint: {
- baseline_filename: "lint-baseline.xml",
- },
}
java_library {
@@ -88,9 +82,6 @@
static_kotlin_stdlib: false,
java_version: "1.8",
min_sdk_version: "current",
- lint: {
- baseline_filename: "lint-baseline.xml",
- },
}
java_library {
@@ -110,7 +101,4 @@
},
java_version: "1.8",
min_sdk_version: "current",
- lint: {
- baseline_filename: "lint-baseline.xml",
- },
}
diff --git a/packages/SystemUI/shared/lint-baseline.xml b/packages/SystemUI/shared/lint-baseline.xml
deleted file mode 100644
index 4bd6729..0000000
--- a/packages/SystemUI/shared/lint-baseline.xml
+++ /dev/null
@@ -1,708 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 7.1.0-dev" type="baseline" client="" name="" variant="all" version="7.1.0-dev">
-
- <issue
- id="NewApi"
- message="Call requires API level R (current min is 26): `android.os.RemoteException#rethrowFromSystemServer`"
- errorLine1=" throw e.rethrowFromSystemServer();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java"
- line="90"
- column="21"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 26): `android.graphics.Bitmap#getHardwareBuffer`"
- errorLine1=" mBuffer != null ? mBuffer.getHardwareBuffer() : null, mRect);"
- errorLine2=" ~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/AppTransitionAnimationSpecCompat.java"
- line="39"
- column="43"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 26): `new android.graphics.ParcelableColorSpace`"
- errorLine1=" ? new ParcelableColorSpace(ColorSpace.get(ColorSpace.Named.SRGB))"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/BitmapUtil.java"
- line="57"
- column="27"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 26): `new android.graphics.ParcelableColorSpace`"
- errorLine1=" : new ParcelableColorSpace(bitmap.getColorSpace());"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/BitmapUtil.java"
- line="58"
- column="27"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 26): `android.graphics.Bitmap#getHardwareBuffer`"
- errorLine1=" bundle.putParcelable(KEY_BUFFER, bitmap.getHardwareBuffer());"
- errorLine2=" ~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/BitmapUtil.java"
- line="61"
- column="49"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Cast from `ParcelableColorSpace` to `Parcelable` requires API level 31 (current min is 26)"
- errorLine1=" bundle.putParcelable(KEY_COLOR_SPACE, colorSpace);"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/BitmapUtil.java"
- line="62"
- column="47"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `android.graphics.Bitmap#wrapHardwareBuffer`"
- errorLine1=" return Bitmap.wrapHardwareBuffer(Objects.requireNonNull(buffer),"
- errorLine2=" ~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/BitmapUtil.java"
- line="84"
- column="23"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 26): `android.graphics.ParcelableColorSpace#getColorSpace`"
- errorLine1=" colorSpace.getColorSpace());"
- errorLine2=" ~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/BitmapUtil.java"
- line="85"
- column="28"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `new android.view.SurfaceControl.Transaction`"
- errorLine1=" final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java"
- line="122"
- column="47"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `new android.util.ArraySet`"
- errorLine1=" mPluginActions = new ArraySet<>(mSharedPrefs.getStringSet(PLUGIN_ACTIONS, null));"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginPrefs.java"
- line="41"
- column="26"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `new android.util.ArraySet`"
- errorLine1=" return new ArraySet<>(mPluginActions);"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginPrefs.java"
- line="45"
- column="16"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 28 (current min is 26): `android.graphics.Bitmap#createBitmap`"
- errorLine1=" return Bitmap.createBitmap(picture);"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/RecentsTransition.java"
- line="113"
- column="23"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `new android.view.SurfaceControl.Builder`"
- errorLine1=" mSurface = new SurfaceControl.Builder()"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
- line="116"
- column="24"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Builder#setName`"
- errorLine1=" .setName("Transition Unrotate")"
- errorLine2=" ~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
- line="117"
- column="22"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Builder#setParent`"
- errorLine1=" .setParent(parent)"
- errorLine2=" ~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
- line="119"
- column="22"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Builder#build`"
- errorLine1=" .build();"
- errorLine2=" ~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
- line="120"
- column="22"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#reparent`"
- errorLine1=" t.reparent(child, mSurface);"
- errorLine2=" ~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
- line="137"
- column="15"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `new android.view.SurfaceControl.Transaction`"
- errorLine1=" SurfaceControl.Transaction t = new SurfaceControl.Transaction();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
- line="143"
- column="44"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#reparent`"
- errorLine1=" t.reparent(mRotateChildren.get(i), rootLeash);"
- errorLine2=" ~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
- line="145"
- column="19"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#apply`"
- errorLine1=" t.apply();"
- errorLine2=" ~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
- line="148"
- column="15"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setLayer`"
- errorLine1=" t.setLayer(counterLauncher.mSurface, launcherLayer);"
- errorLine2=" ~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
- line="200"
- column="27"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setLayer`"
- errorLine1=" t.setLayer(counterLauncher.mSurface, info.getChanges().size() * 3);"
- errorLine2=" ~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
- line="206"
- column="27"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setLayer`"
- errorLine1=" t.setLayer(leash, info.getChanges().size() * 3 - i);"
- errorLine2=" ~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
- line="216"
- column="31"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setAlpha`"
- errorLine1=" t.setAlpha(wallpapersCompat[i].leash.getSurfaceControl(), 1.f);"
- errorLine2=" ~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
- line="223"
- column="27"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setLayer`"
- errorLine1=" t.setLayer(counterWallpaper.mSurface, -1);"
- errorLine2=" ~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
- line="233"
- column="31"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#apply`"
- errorLine1=" t.apply();"
- errorLine2=" ~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
- line="238"
- column="19"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level 29 (current min is 26): `android.app.ActivityManager.RunningTaskInfo#taskId`"
- errorLine1=" taskId = change.getTaskInfo() != null ? change.getTaskInfo().taskId : -1;"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java"
- line="101"
- column="49"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level 29 (current min is 26): `android.app.TaskInfo#taskId`"
- errorLine1=" taskId = change.getTaskInfo() != null ? change.getTaskInfo().taskId : -1;"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java"
- line="192"
- column="49"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level 29 (current min is 26): `android.app.ActivityManager.RunningTaskInfo#isRunning`"
- errorLine1=" isNotInRecents = !change.getTaskInfo().isRunning;"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java"
- line="116"
- column="31"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level 29 (current min is 26): `android.app.TaskInfo#isRunning`"
- errorLine1=" isNotInRecents = !change.getTaskInfo().isRunning;"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java"
- line="210"
- column="31"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl#release`"
- errorLine1=" leash.mSurfaceControl.release();"
- errorLine2=" ~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java"
- line="159"
- column="31"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl#release`"
- errorLine1=" mStartLeash.release();"
- errorLine2=" ~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java"
- line="161"
- column="25"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setLayer`"
- errorLine1=" t.setLayer(change.getLeash(), info.getChanges().size() * 3 - i);"
- errorLine2=" ~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java"
- line="97"
- column="27"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setAlpha`"
- errorLine1=" t.setAlpha(wallpapers[i].leash.mSurfaceControl, 1);"
- errorLine2=" ~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java"
- line="105"
- column="23"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#apply`"
- errorLine1=" t.apply();"
- errorLine2=" ~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java"
- line="107"
- column="19"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl#isValid`"
- errorLine1=" return mSurfaceControl != null && mSurfaceControl.isValid();"
- errorLine2=" ~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceControlCompat.java"
- line="41"
- column="59"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level R (current min is 26): `android.view.SurfaceControlViewHost#release`"
- errorLine1=" mSurfaceControlViewHost.release();"
- errorLine2=" ~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java"
- line="61"
- column="37"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level R (current min is 26): `android.view.SurfaceView#getHostToken`"
- errorLine1=" bundle.putBinder(KEY_HOST_TOKEN, surfaceView.getHostToken());"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestUtils.java"
- line="34"
- column="54"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `android.view.SurfaceView#getSurfaceControl`"
- errorLine1=" bundle.putParcelable(KEY_SURFACE_CONTROL, surfaceView.getSurfaceControl());"
- errorLine2=" ~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestUtils.java"
- line="35"
- column="63"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Cast from `SurfaceControl` to `Parcelable` requires API level 29 (current min is 26)"
- errorLine1=" bundle.putParcelable(KEY_SURFACE_CONTROL, surfaceView.getSurfaceControl());"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestUtils.java"
- line="35"
- column="51"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl#isValid`"
- errorLine1=" if (mBarrierSurfaceControl == null || !mBarrierSurfaceControl.isValid()) {"
- errorLine2=" ~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java"
- line="107"
- column="79"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `new android.view.SurfaceControl.Transaction`"
- errorLine1=" Transaction t = new Transaction();"
- errorLine2=" ~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java"
- line="113"
- column="33"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#apply`"
- errorLine1=" t.apply();"
- errorLine2=" ~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java"
- line="122"
- column="23"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setAlpha`"
- errorLine1=" t.setAlpha(surface, alpha);"
- errorLine2=" ~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java"
- line="361"
- column="19"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setLayer`"
- errorLine1=" t.setLayer(surface, layer);"
- errorLine2=" ~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java"
- line="364"
- column="19"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level 29 (current min is 26): `android.app.TaskInfo#origActivity`"
- errorLine1=" ComponentName sourceComponent = t.origActivity != null"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java"
- line="73"
- column="45"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level 29 (current min is 26): `android.app.TaskInfo#origActivity`"
- errorLine1=" ? t.origActivity"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java"
- line="75"
- column="23"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level 29 (current min is 26): `android.app.TaskInfo#taskId`"
- errorLine1=" this.id = t.taskId;"
- errorLine2=" ~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java"
- line="78"
- column="23"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level 29 (current min is 26): `android.app.TaskInfo#baseIntent`"
- errorLine1=" this.baseIntent = t.baseIntent;"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java"
- line="80"
- column="31"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level 29 (current min is 26): `android.app.TaskInfo#taskDescription`"
- errorLine1=" ActivityManager.TaskDescription td = taskInfo.taskDescription;"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java"
- line="242"
- column="46"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level 29 (current min is 26): `android.app.TaskInfo#topActivity`"
- errorLine1=" taskInfo.supportsSplitScreenMultiWindow, isLocked, td, taskInfo.topActivity);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java"
- line="246"
- column="72"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level 29 (current min is 26): `android.app.TaskInfo#topActivity`"
- errorLine1=" return info.topActivity;"
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskInfoCompat.java"
- line="49"
- column="16"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level 29 (current min is 26): `android.app.TaskInfo#taskDescription`"
- errorLine1=" return info.taskDescription;"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskInfoCompat.java"
- line="53"
- column="16"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level 29 (current min is 26): `android.app.ActivityManager.RunningTaskInfo#taskId`"
- errorLine1=" onTaskMovedToFront(taskInfo.taskId);"
- errorLine2=" ~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java"
- line="70"
- column="28"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level 29 (current min is 26): `android.app.TaskInfo#taskId`"
- errorLine1=" onTaskMovedToFront(taskInfo.taskId);"
- errorLine2=" ~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java"
- line="70"
- column="28"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `android.graphics.Bitmap#wrapHardwareBuffer`"
- errorLine1=" thumbnail = Bitmap.wrapHardwareBuffer(buffer, snapshot.getColorSpace());"
- errorLine2=" ~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java"
- line="69"
- column="36"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 26): `android.app.WallpaperColors#getColorHints`"
- errorLine1=" (colors.getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_THEME) != 0;"
- errorLine2=" ~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/TonalCompat.java"
- line="42"
- column="29"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `new android.view.SurfaceControl.Transaction`"
- errorLine1=" mTransaction = new Transaction();"
- errorLine2=" ~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java"
- line="31"
- column="24"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#apply`"
- errorLine1=" mTransaction.apply();"
- errorLine2=" ~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java"
- line="35"
- column="22"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setBufferSize`"
- errorLine1=" mTransaction.setBufferSize(surfaceControl.mSurfaceControl, w, h);"
- errorLine2=" ~~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java"
- line="54"
- column="22"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setLayer`"
- errorLine1=" mTransaction.setLayer(surfaceControl.mSurfaceControl, z);"
- errorLine2=" ~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java"
- line="59"
- column="22"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setAlpha`"
- errorLine1=" mTransaction.setAlpha(surfaceControl.mSurfaceControl, alpha);"
- errorLine2=" ~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java"
- line="64"
- column="22"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#apply`"
- errorLine1=" t.apply();"
- errorLine2=" ~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/ViewRootImplCompat.java"
- line="64"
- column="15"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 29 (current min is 26): `android.content.res.Resources#getFloat`"
- errorLine1=" .getFloat(Resources.getSystem().getIdentifier("
- errorLine2=" ~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/WallpaperManagerCompat.java"
- line="46"
- column="18"/>
- </issue>
-
-</issues>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
index 387f2e1..87cc86f 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
@@ -38,6 +38,7 @@
import java.io.File;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.BiConsumer;
import java.util.function.Supplier;
/**
@@ -57,7 +58,7 @@
private final PluginFactory<T> mPluginFactory;
private final String mTag;
- private boolean mIsDebug = false;
+ private BiConsumer<String, String> mLogConsumer = null;
private Context mPluginContext;
private T mPlugin;
@@ -86,17 +87,13 @@
return mTag;
}
- public boolean getIsDebug() {
- return mIsDebug;
+ public void setLogFunc(BiConsumer logConsumer) {
+ mLogConsumer = logConsumer;
}
- public void setIsDebug(boolean debug) {
- mIsDebug = debug;
- }
-
- private void logDebug(String message) {
- if (mIsDebug) {
- Log.i(mTag, message);
+ private void log(String message) {
+ if (mLogConsumer != null) {
+ mLogConsumer.accept(mTag, message);
}
}
@@ -105,19 +102,19 @@
boolean loadPlugin = mListener.onPluginAttached(this);
if (!loadPlugin) {
if (mPlugin != null) {
- logDebug("onCreate: auto-unload");
+ log("onCreate: auto-unload");
unloadPlugin();
}
return;
}
if (mPlugin == null) {
- logDebug("onCreate auto-load");
+ log("onCreate auto-load");
loadPlugin();
return;
}
- logDebug("onCreate: load callbacks");
+ log("onCreate: load callbacks");
mPluginFactory.checkVersion(mPlugin);
if (!(mPlugin instanceof PluginFragment)) {
// Only call onCreate for plugins that aren't fragments, as fragments
@@ -129,7 +126,7 @@
/** Alerts listener and plugin that the plugin is being shutdown. */
public synchronized void onDestroy() {
- logDebug("onDestroy");
+ log("onDestroy");
unloadPlugin();
mListener.onPluginDetached(this);
}
@@ -145,7 +142,7 @@
*/
public synchronized void loadPlugin() {
if (mPlugin != null) {
- logDebug("Load request when already loaded");
+ log("Load request when already loaded");
return;
}
@@ -157,7 +154,7 @@
return;
}
- logDebug("Loaded plugin; running callbacks");
+ log("Loaded plugin; running callbacks");
mPluginFactory.checkVersion(mPlugin);
if (!(mPlugin instanceof PluginFragment)) {
// Only call onCreate for plugins that aren't fragments, as fragments
@@ -174,11 +171,11 @@
*/
public synchronized void unloadPlugin() {
if (mPlugin == null) {
- logDebug("Unload request when already unloaded");
+ log("Unload request when already unloaded");
return;
}
- logDebug("Unloading plugin, running callbacks");
+ log("Unloading plugin, running callbacks");
mListener.onPluginUnloaded(mPlugin, this);
if (!(mPlugin instanceof PluginFragment)) {
// Only call onDestroy for plugins that aren't fragments, as fragments
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index 823bd2d..7088829 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -88,18 +88,23 @@
*/
void onNavButtonsDarkIntensityChanged(float darkIntensity) = 22;
- /**
- * Sent when split keyboard shortcut is triggered to enter stage split.
- */
- void enterStageSplitFromRunningApp(boolean leftOrTop) = 25;
+ /**
+ * Sent when when navigation bar luma sampling is enabled or disabled.
+ */
+ void onNavigationBarLumaSamplingEnabled(int displayId, boolean enable) = 23;
- /**
- * Sent when the surface for navigation bar is created or changed
- */
- void onNavigationBarSurface(in SurfaceControl surface) = 26;
+ /**
+ * Sent when split keyboard shortcut is triggered to enter stage split.
+ */
+ void enterStageSplitFromRunningApp(boolean leftOrTop) = 25;
- /**
- * Sent when the task bar stash state is toggled.
- */
- void onTaskbarToggled() = 27;
+ /**
+ * Sent when the surface for navigation bar is created or changed
+ */
+ void onNavigationBarSurface(in SurfaceControl surface) = 26;
+
+ /**
+ * Sent when the task bar stash state is toggled.
+ */
+ void onTaskbarToggled() = 27;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index a5a545a..033f93b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -3,6 +3,7 @@
import static com.android.keyguard.KeyguardStatusAreaView.TRANSLATE_X_CLOCK_DESIGN;
import static com.android.keyguard.KeyguardStatusAreaView.TRANSLATE_Y_CLOCK_DESIGN;
import static com.android.keyguard.KeyguardStatusAreaView.TRANSLATE_Y_CLOCK_SIZE;
+import static com.android.systemui.Flags.migrateClocksToBlueprint;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -191,11 +192,11 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
-
- mSmallClockFrame = findViewById(R.id.lockscreen_clock_view);
- mLargeClockFrame = findViewById(R.id.lockscreen_clock_view_large);
- mStatusArea = findViewById(R.id.keyguard_status_area);
-
+ if (!migrateClocksToBlueprint()) {
+ mSmallClockFrame = findViewById(R.id.lockscreen_clock_view);
+ mLargeClockFrame = findViewById(R.id.lockscreen_clock_view_large);
+ mStatusArea = findViewById(R.id.keyguard_status_area);
+ }
onConfigChanged();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 66f965a..efd8f7f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -37,6 +37,7 @@
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyboard.data.repository.KeyboardRepository;
import com.android.systemui.log.BouncerLogger;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.DevicePostureController;
@@ -210,6 +211,7 @@
private final FeatureFlags mFeatureFlags;
private final SelectedUserInteractor mSelectedUserInteractor;
private final UiEventLogger mUiEventLogger;
+ private final KeyboardRepository mKeyboardRepository;
@Inject
public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -223,7 +225,8 @@
DevicePostureController devicePostureController,
KeyguardViewController keyguardViewController,
FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor,
- UiEventLogger uiEventLogger) {
+ UiEventLogger uiEventLogger,
+ KeyboardRepository keyboardRepository) {
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
mLatencyTracker = latencyTracker;
@@ -240,6 +243,7 @@
mFeatureFlags = featureFlags;
mSelectedUserInteractor = selectedUserInteractor;
mUiEventLogger = uiEventLogger;
+ mKeyboardRepository = keyboardRepository;
}
/** Create a new {@link KeyguardInputViewController}. */
@@ -268,19 +272,22 @@
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mLiftToActivateListener, emergencyButtonController, mFalsingCollector,
mDevicePostureController, mFeatureFlags, mSelectedUserInteractor,
- mUiEventLogger);
+ mUiEventLogger, mKeyboardRepository
+ );
} else if (keyguardInputView instanceof KeyguardSimPinView) {
return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
- emergencyButtonController, mFeatureFlags, mSelectedUserInteractor);
+ emergencyButtonController, mFeatureFlags, mSelectedUserInteractor,
+ mKeyboardRepository);
} else if (keyguardInputView instanceof KeyguardSimPukView) {
return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
- emergencyButtonController, mFeatureFlags, mSelectedUserInteractor);
+ emergencyButtonController, mFeatureFlags, mSelectedUserInteractor,
+ mKeyboardRepository);
}
throw new RuntimeException("Unable to find controller for " + keyguardInputView);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index 36fe75f..fcff0db 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -25,6 +25,7 @@
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
+import static com.android.systemui.Flags.pinInputFieldStyledFocusState;
import android.animation.Animator;
import android.animation.AnimatorSet;
@@ -168,7 +169,9 @@
// Set selected property on so the view can send accessibility events.
mPasswordEntry.setSelected(true);
- mPasswordEntry.setDefaultFocusHighlightEnabled(false);
+ if (!pinInputFieldStyledFocusState()) {
+ mPasswordEntry.setDefaultFocusHighlightEnabled(false);
+ }
mOkButton = findViewById(R.id.key_enter);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index 376933d..60dd568 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -16,17 +16,25 @@
package com.android.keyguard;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
+import static com.android.systemui.Flags.pinInputFieldStyledFocusState;
+
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.StateListDrawable;
+import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnKeyListener;
import android.view.View.OnTouchListener;
+import android.view.ViewGroup;
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyboard.data.repository.KeyboardRepository;
import com.android.systemui.res.R;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
@@ -35,6 +43,7 @@
private final LiftToActivateListener mLiftToActivateListener;
private final FalsingCollector mFalsingCollector;
+ private final KeyboardRepository mKeyboardRepository;
protected PasswordTextView mPasswordEntry;
private final OnKeyListener mOnKeyListener = (v, keyCode, event) -> {
@@ -65,12 +74,14 @@
EmergencyButtonController emergencyButtonController,
FalsingCollector falsingCollector,
FeatureFlags featureFlags,
- SelectedUserInteractor selectedUserInteractor) {
+ SelectedUserInteractor selectedUserInteractor,
+ KeyboardRepository keyboardRepository) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, falsingCollector,
emergencyButtonController, featureFlags, selectedUserInteractor);
mLiftToActivateListener = liftToActivateListener;
mFalsingCollector = falsingCollector;
+ mKeyboardRepository = keyboardRepository;
mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
}
@@ -120,6 +131,35 @@
});
okButton.setOnHoverListener(mLiftToActivateListener);
}
+ if (pinInputFieldStyledFocusState()) {
+ collectFlow(mPasswordEntry, mKeyboardRepository.isAnyKeyboardConnected(),
+ this::setKeyboardBasedFocusOutline);
+
+ /**
+ * new UI Specs for PIN Input field have new dimensions go/pin-focus-states.
+ * However we want these changes behind a flag, and resource files cannot be flagged
+ * hence the dimension change in code. When the flags are removed these dimensions
+ * should be set in resources permanently and the code below removed.
+ */
+ ViewGroup.LayoutParams layoutParams = mPasswordEntry.getLayoutParams();
+ layoutParams.width = (int) getResources().getDimension(
+ R.dimen.keyguard_pin_field_width);
+ layoutParams.height = (int) getResources().getDimension(
+ R.dimen.keyguard_pin_field_height);
+ }
+ }
+
+ private void setKeyboardBasedFocusOutline(boolean isAnyKeyboardConnected) {
+ StateListDrawable background = (StateListDrawable) mPasswordEntry.getBackground();
+ GradientDrawable stateDrawable = (GradientDrawable) background.getStateDrawable(0);
+ int color = getResources().getColor(R.color.bouncer_password_focus_color);
+ if (!isAnyKeyboardConnected) {
+ stateDrawable.setStroke(0, color);
+ } else {
+ int strokeWidthInDP = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3,
+ getResources().getDisplayMetrics());
+ stateDrawable.setStroke(strokeWidthInDP, color);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index 2aab1f1..b958f55 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -28,6 +28,7 @@
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.keyboard.data.repository.KeyboardRepository;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
@@ -58,12 +59,13 @@
LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
EmergencyButtonController emergencyButtonController,
FalsingCollector falsingCollector,
- DevicePostureController postureController,
- FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor,
- UiEventLogger uiEventLogger) {
+ DevicePostureController postureController, FeatureFlags featureFlags,
+ SelectedUserInteractor selectedUserInteractor, UiEventLogger uiEventLogger,
+ KeyboardRepository keyboardRepository) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
- emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor);
+ emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
+ keyboardRepository);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mPostureController = postureController;
mLockPatternUtils = lockPatternUtils;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index e457ca1..8e5d0da 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -1111,7 +1111,7 @@
}
private boolean canDisplayUserSwitcher() {
- return mFeatureFlags.isEnabled(Flags.BOUNCER_USER_SWITCHER);
+ return getContext().getResources().getBoolean(R.bool.config_enableBouncerUserSwitcher);
}
private void configureMode() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index 5729119..1cdcbd0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -44,6 +44,7 @@
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyboard.data.repository.KeyboardRepository;
import com.android.systemui.res.R;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
@@ -93,10 +94,11 @@
LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
TelephonyManager telephonyManager, FalsingCollector falsingCollector,
EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags,
- SelectedUserInteractor selectedUserInteractor) {
+ SelectedUserInteractor selectedUserInteractor, KeyboardRepository keyboardRepository) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
- emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor);
+ emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
+ keyboardRepository);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mTelephonyManager = telephonyManager;
mSimImageView = mView.findViewById(R.id.keyguard_sim);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index 05fb5fa..f019d61 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -39,6 +39,7 @@
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyboard.data.repository.KeyboardRepository;
import com.android.systemui.res.R;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
@@ -90,10 +91,11 @@
LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
TelephonyManager telephonyManager, FalsingCollector falsingCollector,
EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags,
- SelectedUserInteractor selectedUserInteractor) {
+ SelectedUserInteractor selectedUserInteractor, KeyboardRepository keyboardRepository) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
- emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor);
+ emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
+ keyboardRepository);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mTelephonyManager = telephonyManager;
mSimImageView = mView.findViewById(R.id.keyguard_sim);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index d372f5a..1758831 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -494,7 +494,7 @@
boolean shouldBeCentered,
boolean animate) {
if (migrateClocksToBlueprint()) {
- mKeyguardInteractor.setClockShouldBeCentered(mSplitShadeEnabled && shouldBeCentered);
+ mKeyguardInteractor.setClockShouldBeCentered(shouldBeCentered);
} else {
mKeyguardClockSwitchController.setSplitShadeCentered(
splitShadeEnabled && shouldBeCentered);
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl b/packages/SystemUI/src/com/android/systemui/NoOpCoreStartable.kt
similarity index 79%
copy from core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
copy to packages/SystemUI/src/com/android/systemui/NoOpCoreStartable.kt
index ce92b6d..9f54c26 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
+++ b/packages/SystemUI/src/com/android/systemui/NoOpCoreStartable.kt
@@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.companion.virtual.camera;
-/**
- * The configuration of a single virtual camera stream.
- * @hide
- */
-parcelable VirtualCameraStreamConfig;
\ No newline at end of file
+package com.android.systemui
+
+/** A [CoreStartable] that does nothing. */
+class NoOpCoreStartable : CoreStartable {
+ override fun start() {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt
index b15aaaf..e88aaf01 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt
@@ -91,7 +91,7 @@
return app
}
- @UsesReflection(KeepTarget(extendsClassConstant = SysUIComponent::class, methodName = "inject"))
+ @UsesReflection(KeepTarget(instanceOfClassConstant = SysUIComponent::class, methodName = "inject"))
override fun instantiateProviderCompat(cl: ClassLoader, className: String): ContentProvider {
val contentProvider = super.instantiateProviderCompat(cl, className)
if (contentProvider is ContextInitializer) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt
index 6483ae4..c7e5b64 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt
@@ -19,16 +19,20 @@
import android.os.UserHandle
import android.provider.Settings.Secure
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.withContext
/** Provides data related to color correction. */
@@ -45,22 +49,24 @@
@Inject
constructor(
@Background private val bgCoroutineContext: CoroutineContext,
+ @Application private val scope: CoroutineScope,
private val secureSettings: SecureSettings,
) : ColorCorrectionRepository {
- companion object {
- const val SETTING_NAME = Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED
- const val DISABLED = 0
- const val ENABLED = 1
- }
+ private val userMap = mutableMapOf<Int, Flow<Boolean>>()
override fun isEnabled(userHandle: UserHandle): Flow<Boolean> =
- secureSettings
- .observerFlow(userHandle.identifier, SETTING_NAME)
- .onStart { emit(Unit) }
- .map { secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED }
- .distinctUntilChanged()
- .flowOn(bgCoroutineContext)
+ userMap.getOrPut(userHandle.identifier) {
+ secureSettings
+ .observerFlow(userHandle.identifier, SETTING_NAME)
+ .onStart { emit(Unit) }
+ .map {
+ secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED
+ }
+ .distinctUntilChanged()
+ .flowOn(bgCoroutineContext)
+ .shareIn(scope, SharingStarted.WhileSubscribed(), replay = 1)
+ }
override suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean =
withContext(bgCoroutineContext) {
@@ -70,4 +76,10 @@
userHandle.identifier
)
}
+
+ companion object {
+ private const val SETTING_NAME = Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED
+ private const val DISABLED = 0
+ private const val ENABLED = 1
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt
index bbf10c5..419eada 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt
@@ -19,16 +19,20 @@
import android.os.UserHandle
import android.provider.Settings.Secure
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.withContext
/** Provides data related to color inversion. */
@@ -45,16 +49,24 @@
@Inject
constructor(
@Background private val bgCoroutineContext: CoroutineContext,
+ @Application private val scope: CoroutineScope,
private val secureSettings: SecureSettings,
) : ColorInversionRepository {
+ private val userMap = mutableMapOf<Int, Flow<Boolean>>()
+
override fun isEnabled(userHandle: UserHandle): Flow<Boolean> =
- secureSettings
- .observerFlow(userHandle.identifier, SETTING_NAME)
- .onStart { emit(Unit) }
- .map { secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED }
- .distinctUntilChanged()
- .flowOn(bgCoroutineContext)
+ userMap.getOrPut(userHandle.identifier) {
+ secureSettings
+ .observerFlow(userHandle.identifier, SETTING_NAME)
+ .onStart { emit(Unit) }
+ .map {
+ secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED
+ }
+ .distinctUntilChanged()
+ .flowOn(bgCoroutineContext)
+ .shareIn(scope, SharingStarted.WhileSubscribed(), replay = 1)
+ }
override suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean =
withContext(bgCoroutineContext) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
index 49e0df6..568b24d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
@@ -24,6 +24,7 @@
import androidx.annotation.NonNull;
import androidx.dynamicanimation.animation.DynamicAnimation;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.common.bubbles.DismissView;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
@@ -116,6 +117,11 @@
mMagnetizedObject.setMagnetListener(magnetListener);
}
+ @VisibleForTesting
+ MagnetizedObject.MagnetListener getMagnetListener() {
+ return mMagnetizedObject.getMagnetListener();
+ }
+
void maybeConsumeDownMotionEvent(MotionEvent event) {
mMagnetizedObject.maybeConsumeMotionEvent(event);
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuNotificationFactory.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuNotificationFactory.java
new file mode 100644
index 0000000..b5eeaa1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuNotificationFactory.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.floatingmenu;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.UserHandle;
+
+import com.android.systemui.res.R;
+import com.android.systemui.util.NotificationChannels;
+
+class MenuNotificationFactory {
+ public static final String ACTION_UNDO =
+ "com.android.systemui.accessibility.floatingmenu.action.UNDO";
+ public static final String ACTION_DELETE =
+ "com.android.systemui.accessibility.floatingmenu.action.DELETE";
+
+ private final Context mContext;
+
+ MenuNotificationFactory(Context context) {
+ mContext = context;
+ }
+
+ public Notification createHiddenNotification() {
+ final CharSequence title = mContext.getText(
+ R.string.accessibility_floating_button_hidden_notification_title);
+ final CharSequence content = mContext.getText(
+ R.string.accessibility_floating_button_hidden_notification_text);
+
+ return new Notification.Builder(mContext, NotificationChannels.ALERTS)
+ .setContentTitle(title)
+ .setContentText(content)
+ .setSmallIcon(R.drawable.ic_settings_24dp)
+ .setContentIntent(buildUndoIntent())
+ .setDeleteIntent(buildDeleteIntent())
+ .setColor(mContext.getResources().getColor(
+ com.android.internal.R.color.system_notification_accent_color))
+ .setLocalOnly(true)
+ .setCategory(Notification.CATEGORY_SYSTEM)
+ .build();
+ }
+
+ private PendingIntent buildUndoIntent() {
+ final Intent intent = new Intent(ACTION_UNDO);
+
+ return PendingIntent.getBroadcast(mContext, /* requestCode= */ 0, intent,
+ PendingIntent.FLAG_IMMUTABLE);
+
+ }
+
+ private PendingIntent buildDeleteIntent() {
+ final Intent intent = new Intent(ACTION_DELETE);
+
+ return PendingIntent.getBroadcastAsUser(mContext, /* requestCode= */ 0, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT
+ | PendingIntent.FLAG_IMMUTABLE, UserHandle.CURRENT);
+
+ }
+}
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 62d5feb..6869bba 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -26,16 +26,21 @@
import static com.android.internal.accessibility.util.AccessibilityUtils.getAccessibilityServiceFragmentType;
import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
import static com.android.systemui.accessibility.floatingmenu.MenuMessageView.Index;
+import static com.android.systemui.accessibility.floatingmenu.MenuNotificationFactory.ACTION_DELETE;
+import static com.android.systemui.accessibility.floatingmenu.MenuNotificationFactory.ACTION_UNDO;
import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.IntDef;
import android.annotation.StringDef;
import android.annotation.SuppressLint;
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
@@ -58,6 +63,7 @@
import com.android.internal.accessibility.dialog.AccessibilityTarget;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.util.Preconditions;
import com.android.systemui.Flags;
import com.android.systemui.res.R;
@@ -91,6 +97,8 @@
private final MenuViewAppearance mMenuViewAppearance;
private final MenuAnimationController mMenuAnimationController;
private final AccessibilityManager mAccessibilityManager;
+ private final NotificationManager mNotificationManager;
+ private final MenuNotificationFactory mNotificationFactory;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final IAccessibilityFloatingMenu mFloatingMenu;
private final SecureSettings mSecureSettings;
@@ -103,7 +111,9 @@
private final Rect mImeInsetsRect = new Rect();
private boolean mIsMigrationTooltipShowing;
private boolean mShouldShowDockTooltip;
+ private boolean mIsNotificationShown;
private Optional<MenuEduTooltipView> mEduTooltipView = Optional.empty();
+ private BroadcastReceiver mNotificationActionReceiver;
@IntDef({
LayerIndex.MENU_VIEW,
@@ -184,10 +194,16 @@
mMenuViewAppearance = new MenuViewAppearance(context, windowManager);
mMenuView = new MenuView(context, mMenuViewModel, mMenuViewAppearance);
mMenuAnimationController = mMenuView.getMenuAnimationController();
- mMenuAnimationController.setDismissCallback(this::hideMenuAndShowMessage);
+ if (Flags.floatingMenuDragToHide()) {
+ mMenuAnimationController.setDismissCallback(this::hideMenuAndShowNotification);
+ } else {
+ mMenuAnimationController.setDismissCallback(this::hideMenuAndShowMessage);
+ }
mMenuAnimationController.setSpringAnimationsEndAction(this::onSpringAnimationsEndAction);
mDismissView = new DismissView(context);
DismissViewUtils.setup(mDismissView);
+ mNotificationFactory = new MenuNotificationFactory(context);
+ mNotificationManager = context.getSystemService(NotificationManager.class);
mDragToInteractAnimationController = new DragToInteractAnimationController(
mDismissView, mMenuView);
mDragToInteractAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
@@ -204,7 +220,11 @@
@Override
public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
- hideMenuAndShowMessage();
+ if (Flags.floatingMenuDragToHide()) {
+ hideMenuAndShowNotification();
+ } else {
+ hideMenuAndShowMessage();
+ }
mDismissView.hide();
mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ false);
}
@@ -218,18 +238,25 @@
mMessageView = new MenuMessageView(context);
mMenuView.setOnTargetFeaturesChangeListener(newTargetFeatures -> {
- if (newTargetFeatures.size() < 1) {
- return;
- }
-
- // During the undo action period, the pending action will be canceled and undo back
- // to the previous state if users did any action related to the accessibility features.
- if (mMessageView.getVisibility() == VISIBLE) {
+ if (Flags.floatingMenuDragToHide()) {
+ dismissNotification();
undo();
- }
+ } else {
+ if (newTargetFeatures.size() < 1) {
+ return;
+ }
- final TextView messageText = (TextView) mMessageView.getChildAt(Index.TEXT_VIEW);
- messageText.setText(getMessageText(newTargetFeatures));
+ // During the undo action period, the pending action will be canceled and undo back
+ // to the previous state if users did any action related to the accessibility
+ // features.
+ if (mMessageView.getVisibility() == VISIBLE) {
+ undo();
+ }
+
+
+ final TextView messageText = (TextView) mMessageView.getChildAt(Index.TEXT_VIEW);
+ messageText.setText(getMessageText(newTargetFeatures));
+ }
});
addView(mMenuView, LayerIndex.MENU_VIEW);
@@ -456,6 +483,50 @@
mMenuAnimationController.startShrinkAnimation(() -> mMenuView.setVisibility(GONE));
}
+ private void hideMenuAndShowNotification() {
+ mMenuAnimationController.startShrinkAnimation(() -> mMenuView.setVisibility(GONE));
+ showNotification();
+ }
+
+ private void showNotification() {
+ registerReceiverIfNeeded();
+ if (!mIsNotificationShown) {
+ mNotificationManager.notify(
+ SystemMessageProto.SystemMessage.NOTE_A11Y_FLOATING_MENU_HIDDEN,
+ mNotificationFactory.createHiddenNotification());
+ mIsNotificationShown = true;
+ }
+ }
+
+ private void dismissNotification() {
+ unregisterReceiverIfNeeded();
+ if (mIsNotificationShown) {
+ mNotificationManager.cancel(
+ SystemMessageProto.SystemMessage.NOTE_A11Y_FLOATING_MENU_HIDDEN);
+ mIsNotificationShown = false;
+ }
+ }
+
+ private void registerReceiverIfNeeded() {
+ if (mNotificationActionReceiver != null) {
+ return;
+ }
+ mNotificationActionReceiver = new MenuNotificationActionReceiver();
+ final IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(ACTION_UNDO);
+ intentFilter.addAction(ACTION_DELETE);
+ getContext().registerReceiver(mNotificationActionReceiver, intentFilter,
+ Context.RECEIVER_EXPORTED);
+ }
+
+ private void unregisterReceiverIfNeeded() {
+ if (mNotificationActionReceiver == null) {
+ return;
+ }
+ getContext().unregisterReceiver(mNotificationActionReceiver);
+ mNotificationActionReceiver = null;
+ }
+
private void undo() {
mHandler.removeCallbacksAndMessages(/* token= */ null);
mMessageView.setVisibility(GONE);
@@ -464,4 +535,23 @@
mMenuView.setVisibility(VISIBLE);
mMenuAnimationController.startGrowAnimation();
}
+
+ @VisibleForTesting
+ DragToInteractAnimationController getDragToInteractAnimationController() {
+ return mDragToInteractAnimationController;
+ }
+
+ private class MenuNotificationActionReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (ACTION_UNDO.equals(action)) {
+ dismissNotification();
+ undo();
+ } else if (ACTION_DELETE.equals(action)) {
+ dismissNotification();
+ mDismissMenuAction.run();
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index ab23564..57e308f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -399,7 +399,8 @@
config.mPromptInfo,
config.mUserId,
config.mOperationId,
- new BiometricModalities(fpProps, faceProps));
+ new BiometricModalities(fpProps, faceProps),
+ config.mOpPackageName);
final BiometricPromptLayout view = (BiometricPromptLayout) layoutInflater.inflate(
R.layout.biometric_prompt_layout, null, false);
@@ -470,7 +471,8 @@
mBackgroundView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
mPromptSelectorInteractorProvider.get().useCredentialsForAuthentication(
- mConfig.mPromptInfo, credentialType, mConfig.mUserId, mConfig.mOperationId);
+ mConfig.mPromptInfo, credentialType, mConfig.mUserId, mConfig.mOperationId,
+ mConfig.mOpPackageName);
final CredentialViewModel vm = mCredentialViewModelProvider.get();
vm.setAnimateContents(animateContents);
((CredentialView) mCredentialView).init(vm, this, mPanelController, animatePanel);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 093a1ff..a40b4d7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -47,6 +47,7 @@
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
@@ -1127,6 +1128,9 @@
}
mCurrentDialog.dismissFromSystemServer();
+ for (Callback cb : mCallbacks) {
+ cb.onBiometricPromptDismissed();
+ }
// BiometricService will have already sent the callback to the client in this case.
// This avoids a round trip to SystemUI. So, just dismiss the dialog and we're done.
@@ -1156,6 +1160,15 @@
}
/**
+ * Does the provided user have at least one optical udfps fingerprint enrolled?
+ */
+ public boolean isOpticalUdfpsEnrolled(int userId) {
+ return isUdfpsEnrolled(userId)
+ && mUdfpsProps != null
+ && mUdfpsProps.get(0).sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
+ }
+
+ /**
* Whether the passed userId has enrolled UDFPS.
*/
public boolean isUdfpsEnrolled(int userId) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
index 7d9ec08..f5603ed 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
@@ -23,6 +23,7 @@
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.animation.Interpolators
import com.android.systemui.Dumpable
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -49,7 +50,8 @@
protected val statusBarStateController: StatusBarStateController,
protected val shadeInteractor: ShadeInteractor,
protected val dialogManager: SystemUIDialogManager,
- private val dumpManager: DumpManager
+ private val dumpManager: DumpManager,
+ private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
) : ViewController<T>(view), Dumpable {
protected abstract val tag: String
@@ -130,11 +132,13 @@
override fun onViewAttached() {
dialogManager.registerListener(dialogListener)
dumpManager.registerDumpable(dumpTag, this)
+ udfpsOverlayInteractor.setHandleTouches(shouldHandle = true)
}
override fun onViewDetached() {
dialogManager.unregisterListener(dialogListener)
dumpManager.unregisterDumpable(dumpTag)
+ udfpsOverlayInteractor.setHandleTouches(shouldHandle = true)
}
/**
@@ -165,6 +169,7 @@
fun updatePauseAuth() {
if (view.setPauseAuth(shouldPauseAuth())) {
view.postInvalidate()
+ udfpsOverlayInteractor.setHandleTouches(shouldHandle = !shouldPauseAuth())
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
index e7b0d9f..e0455b58 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui.biometrics
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -28,13 +29,15 @@
statusBarStateController: StatusBarStateController,
shadeInteractor: ShadeInteractor,
systemUIDialogManager: SystemUIDialogManager,
- dumpManager: DumpManager
+ dumpManager: DumpManager,
+ udfpsOverlayInteractor: UdfpsOverlayInteractor,
) : UdfpsAnimationViewController<UdfpsBpView>(
view,
statusBarStateController,
shadeInteractor,
systemUIDialogManager,
- dumpManager
+ dumpManager,
+ udfpsOverlayInteractor,
) {
override val tag = "UdfpsBpViewController"
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index d664637..66fe4b3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -68,6 +68,7 @@
import com.android.systemui.Dumpable;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.biometrics.dagger.BiometricsBackground;
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
import com.android.systemui.biometrics.udfps.InteractionEvent;
import com.android.systemui.biometrics.udfps.NormalizedTouchData;
@@ -171,6 +172,7 @@
@NonNull private final Lazy<DefaultUdfpsTouchOverlayViewModel>
mDefaultUdfpsTouchOverlayViewModel;
@NonNull private final AlternateBouncerInteractor mAlternateBouncerInteractor;
+ @NonNull private final UdfpsOverlayInteractor mUdfpsOverlayInteractor;
@NonNull private final InputManager mInputManager;
@NonNull private final UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
@NonNull private final SelectedUserInteractor mSelectedUserInteractor;
@@ -187,7 +189,7 @@
@Nullable private UdfpsDisplayModeProvider mUdfpsDisplayMode;
// The ID of the pointer for which ACTION_DOWN has occurred. -1 means no pointer is active.
- private int mActivePointerId = -1;
+ private int mActivePointerId = MotionEvent.INVALID_POINTER_ID;
// Whether a pointer has been pilfered for current gesture
private boolean mPointerPilfered = false;
// The timestamp of the most recent touch log.
@@ -293,7 +295,8 @@
mSelectedUserInteractor,
mDeviceEntryUdfpsTouchOverlayViewModel,
mDefaultUdfpsTouchOverlayViewModel,
- mShadeInteractor
+ mShadeInteractor,
+ mUdfpsOverlayInteractor
)));
}
@@ -510,7 +513,16 @@
+ mOverlay.getRequestId());
return false;
}
- if (!DeviceEntryUdfpsRefactor.isEnabled()) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN
+ || event.getAction() == MotionEvent.ACTION_HOVER_ENTER) {
+ // Reset on ACTION_DOWN, start of new gesture
+ mPointerPilfered = false;
+ if (mActivePointerId != MotionEvent.INVALID_POINTER_ID) {
+ Log.w(TAG, "onTouch down received without a preceding up");
+ }
+ mActivePointerId = MotionEvent.INVALID_POINTER_ID;
+ mOnFingerDown = false;
+ } else if (!DeviceEntryUdfpsRefactor.isEnabled()) {
if ((mLockscreenShadeTransitionController.getQSDragProgress() != 0f
&& !mAlternateBouncerInteractor.isVisibleState())
|| mPrimaryBouncerInteractor.isInTransit()) {
@@ -518,11 +530,6 @@
return false;
}
}
- if (event.getAction() == MotionEvent.ACTION_DOWN
- || event.getAction() == MotionEvent.ACTION_HOVER_ENTER) {
- // Reset on ACTION_DOWN, start of new gesture
- mPointerPilfered = false;
- }
final TouchProcessorResult result = mTouchProcessor.processTouch(event, mActivePointerId,
mOverlayParams);
@@ -670,7 +677,8 @@
@NonNull FpsUnlockTracker fpsUnlockTracker,
@NonNull KeyguardTransitionInteractor keyguardTransitionInteractor,
Lazy<DeviceEntryUdfpsTouchOverlayViewModel> deviceEntryUdfpsTouchOverlayViewModel,
- Lazy<DefaultUdfpsTouchOverlayViewModel> defaultUdfpsTouchOverlayViewModel) {
+ Lazy<DefaultUdfpsTouchOverlayViewModel> defaultUdfpsTouchOverlayViewModel,
+ @NonNull UdfpsOverlayInteractor udfpsOverlayInteractor) {
mContext = context;
mExecution = execution;
mVibrator = vibrator;
@@ -711,6 +719,7 @@
mPrimaryBouncerInteractor = primaryBouncerInteractor;
mShadeInteractor = shadeInteractor;
mAlternateBouncerInteractor = alternateBouncerInteractor;
+ mUdfpsOverlayInteractor = udfpsOverlayInteractor;
mInputManager = inputManager;
mUdfpsKeyguardAccessibilityDelegate = udfpsKeyguardAccessibilityDelegate;
mSelectedUserInteractor = selectedUserInteractor;
@@ -1080,7 +1089,7 @@
long gestureStart,
boolean isAod) {
mExecution.assertIsMainThread();
- mActivePointerId = -1;
+ mActivePointerId = MotionEvent.INVALID_POINTER_ID;
mAcquiredReceived = false;
if (mOnFingerDown) {
mFingerprintManager.onPointerUp(requestId, mSensorProps.sensorId, pointerId, x,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index b94a177..4176083 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -45,6 +45,7 @@
import androidx.annotation.VisibleForTesting
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
import com.android.systemui.biometrics.ui.binder.UdfpsTouchOverlayBinder
import com.android.systemui.biometrics.ui.view.UdfpsTouchOverlay
@@ -109,6 +110,7 @@
private val deviceEntryUdfpsTouchOverlayViewModel: Lazy<DeviceEntryUdfpsTouchOverlayViewModel>,
private val defaultUdfpsTouchOverlayViewModel: Lazy<DefaultUdfpsTouchOverlayViewModel>,
private val shadeInteractor: ShadeInteractor,
+ private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
) {
private var overlayViewLegacy: UdfpsView? = null
private set
@@ -281,7 +283,8 @@
statusBarStateController,
shadeInteractor,
dialogManager,
- dumpManager
+ dumpManager,
+ udfpsOverlayInteractor,
)
}
REASON_AUTH_KEYGUARD -> {
@@ -306,6 +309,7 @@
selectedUserInteractor,
transitionInteractor,
shadeInteractor,
+ udfpsOverlayInteractor,
)
}
REASON_AUTH_BP -> {
@@ -315,7 +319,8 @@
statusBarStateController,
shadeInteractor,
dialogManager,
- dumpManager
+ dumpManager,
+ udfpsOverlayInteractor,
)
}
REASON_AUTH_OTHER,
@@ -325,7 +330,8 @@
statusBarStateController,
shadeInteractor,
dialogManager,
- dumpManager
+ dumpManager,
+ udfpsOverlayInteractor,
)
}
else -> {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
index abbfa01..02eae9ced 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
@@ -131,25 +131,29 @@
icon.measure(
MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST));
- } else if (child.getId() == R.id.space_above_icon) {
+ } else if (child.getId() == R.id.space_above_icon
+ || child.getId() == R.id.space_above_content
+ || child.getId() == R.id.button_bar) {
child.measure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(
child.getLayoutParams().height, MeasureSpec.EXACTLY));
- } else if (child.getId() == R.id.button_bar) {
- child.measure(
- MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(child.getLayoutParams().height,
- MeasureSpec.EXACTLY));
} else if (child.getId() == R.id.space_below_icon) {
// Set the spacer height so the fingerprint icon is on the physical sensor area
final int clampedSpacerHeight = Math.max(mBottomSpacerHeight, 0);
child.measure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(clampedSpacerHeight, MeasureSpec.EXACTLY));
- } else if (child.getId() == R.id.description) {
+ } else if (child.getId() == R.id.description
+ || child.getId() == R.id.customized_view_container) {
//skip description view and compute later
continue;
+ } else if (child.getId() == R.id.logo) {
+ child.measure(
+ MeasureSpec.makeMeasureSpec(child.getLayoutParams().width,
+ MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(child.getLayoutParams().height,
+ MeasureSpec.EXACTLY));
} else {
child.measure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
@@ -161,27 +165,28 @@
}
}
- //re-calculate the height of description
+ //re-calculate the height of body content
View description = mView.findViewById(R.id.description);
+ View contentView = mView.findViewById(R.id.customized_view_container);
if (description != null && description.getVisibility() != View.GONE) {
totalHeight += measureDescription(description, displayHeight, width, totalHeight);
+ } else if (contentView != null && contentView.getVisibility() != View.GONE) {
+ totalHeight += measureDescription(contentView, displayHeight, width, totalHeight);
}
return new AuthDialog.LayoutParams(width, totalHeight);
}
- private int measureDescription(View description, int displayHeight, int currWidth,
+ private int measureDescription(View bodyContent, int displayHeight, int currWidth,
int currHeight) {
- //description view should be measured in AuthBiometricFingerprintView#onMeasureInternal
- //so we could getMeasuredHeight in onMeasureInternalPortrait directly.
- int newHeight = description.getMeasuredHeight() + currHeight;
+ int newHeight = bodyContent.getMeasuredHeight() + currHeight;
int limit = (int) (displayHeight * 0.75);
if (newHeight > limit) {
- description.measure(
+ bodyContent.measure(
MeasureSpec.makeMeasureSpec(currWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(limit - currHeight, MeasureSpec.EXACTLY));
}
- return description.getMeasuredHeight();
+ return bodyContent.getMeasuredHeight();
}
@NonNull
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt
index ab3fbb1..cfbbc26 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui.biometrics
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -30,13 +31,15 @@
statusBarStateController: StatusBarStateController,
shadeInteractor: ShadeInteractor,
systemUIDialogManager: SystemUIDialogManager,
- dumpManager: DumpManager
+ dumpManager: DumpManager,
+ udfpsOverlayInteractor: UdfpsOverlayInteractor,
) : UdfpsAnimationViewController<UdfpsFpmEmptyView>(
view,
statusBarStateController,
shadeInteractor,
systemUIDialogManager,
- dumpManager
+ dumpManager,
+ udfpsOverlayInteractor,
) {
override val tag = "UdfpsFpmOtherViewController"
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index 9f17024..7020d05 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -27,6 +27,7 @@
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATE_APPEAR_ON_SCREEN_OFF
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dump.DumpManager
@@ -75,6 +76,7 @@
private val selectedUserInteractor: SelectedUserInteractor,
private val transitionInteractor: KeyguardTransitionInteractor,
shadeInteractor: ShadeInteractor,
+ udfpsOverlayInteractor: UdfpsOverlayInteractor,
) :
UdfpsAnimationViewController<UdfpsKeyguardViewLegacy>(
view,
@@ -82,6 +84,7 @@
shadeInteractor,
systemUIDialogManager,
dumpManager,
+ udfpsOverlayInteractor,
) {
private val uniqueIdentifier = this.toString()
private var showingUdfpsBouncer = false
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 b35fbbc..ad7bb0e 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
@@ -55,6 +55,9 @@
/** The kind of credential to use (biometric, pin, pattern, etc.). */
val kind: StateFlow<PromptKind>
+ /** The package name that the prompt is called from. */
+ val opPackageName: StateFlow<String?>
+
/**
* If explicit confirmation is required.
*
@@ -68,6 +71,7 @@
userId: Int,
gatekeeperChallenge: Long?,
kind: PromptKind,
+ opPackageName: String,
)
/** Unset the prompt info. */
@@ -108,6 +112,9 @@
private val _kind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.Biometric())
override val kind = _kind.asStateFlow()
+ private val _opPackageName: MutableStateFlow<String?> = MutableStateFlow(null)
+ override val opPackageName = _opPackageName.asStateFlow()
+
private val _faceSettings =
_userId.map { id -> faceSettings.forUser(id) }.distinctUntilChanged()
private val _faceSettingAlwaysRequireConfirmation =
@@ -127,11 +134,13 @@
userId: Int,
gatekeeperChallenge: Long?,
kind: PromptKind,
+ opPackageName: String,
) {
_kind.value = kind
_userId.value = userId
_challenge.value = gatekeeperChallenge
_promptInfo.value = promptInfo
+ _opPackageName.value = opPackageName
}
override fun unsetPrompt() {
@@ -139,6 +148,7 @@
_userId.value = null
_challenge.value = null
_kind.value = PromptKind.Biometric()
+ _opPackageName.value = null
}
companion object {
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
new file mode 100644
index 0000000..e6939f0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.biometrics.domain.interactor
+
+import android.content.Context
+import android.hardware.biometrics.SensorLocationInternal
+import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.SensorLocation
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+
+@SysUISingleton
+class FingerprintPropertyInteractor
+@Inject
+constructor(
+ @Application private val context: Context,
+ repository: FingerprintPropertyRepository,
+ configurationInteractor: ConfigurationInteractor,
+ displayStateInteractor: DisplayStateInteractor,
+) {
+ /**
+ * Devices with multiple physical displays use unique display ids to determine which sensor is
+ * on the active physical display. This value represents a unique physical display id.
+ */
+ private val uniqueDisplayId: Flow<String> =
+ displayStateInteractor.displayChanges
+ .map { context.display?.uniqueId }
+ .filterNotNull()
+ .distinctUntilChanged()
+
+ /**
+ * Sensor location for the:
+ * - current physical display
+ * - device's natural screen resolution
+ * - device's natural orientation
+ */
+ private val unscaledSensorLocation: Flow<SensorLocationInternal> =
+ combine(
+ repository.sensorLocations,
+ uniqueDisplayId,
+ ) { locations, displayId ->
+ // Devices without multiple physical displays do not use the display id as the key;
+ // instead, the key is an empty string.
+ locations.getOrDefault(
+ displayId,
+ locations.getOrDefault("", SensorLocationInternal.DEFAULT)
+ )
+ }
+
+ /**
+ * Sensor location for the:
+ * - current physical display
+ * - current screen resolution
+ * - device's natural orientation
+ */
+ val sensorLocation: Flow<SensorLocation> =
+ combine(
+ unscaledSensorLocation,
+ configurationInteractor.scaleForResolution,
+ ) { unscaledSensorLocation, scale ->
+ val sensorLocation =
+ SensorLocation(
+ unscaledSensorLocation.sensorLocationX,
+ unscaledSensorLocation.sensorLocationY,
+ unscaledSensorLocation.sensorRadius,
+ )
+ sensorLocation.scale = scale
+ sensorLocation
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt
index 863ba8d..70be0f2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt
@@ -63,6 +63,9 @@
/** Current display state, defined as [AuthenticateOptions.DisplayState] */
val displayState: Flow<Int>
+ /** If touches on the fingerprint sensor should be ignored by the HAL. */
+ val isHardwareIgnoringTouches: Flow<Boolean>
+
/**
* Add a permanent context listener.
*
@@ -79,12 +82,11 @@
@Application private val applicationScope: CoroutineScope,
private val foldProvider: FoldStateProvider,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ udfpsOverlayInteractor: UdfpsOverlayInteractor,
) : LogContextInteractor {
init {
- applicationScope.launch {
- foldProvider.start()
- }
+ applicationScope.launch { foldProvider.start() }
}
override val displayState =
@@ -102,6 +104,9 @@
}
}
+ override val isHardwareIgnoringTouches: Flow<Boolean> =
+ udfpsOverlayInteractor.shouldHandleTouches.map { shouldHandle -> !shouldHandle }
+
override val isAod =
displayState.map { it == AuthenticateOptions.DISPLAY_STATE_AOD }.distinctUntilChanged()
@@ -159,6 +164,12 @@
.catch { t -> Log.w(TAG, "failed to notify new display state", t) }
.launchIn(this)
+ isHardwareIgnoringTouches
+ .distinctUntilChanged()
+ .onEach { state -> listener.onHardwareIgnoreTouchesChanged(state) }
+ .catch { t -> Log.w(TAG, "failed to notify new set ignore state", t) }
+ .launchIn(this)
+
listener.asBinder().linkToDeath({ cancel() }, 0)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
index ac4b717..359e2e7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
@@ -115,12 +115,14 @@
@Utils.CredentialType kind: Int,
userId: Int,
challenge: Long,
+ opPackageName: String,
) {
biometricPromptRepository.setPrompt(
promptInfo,
userId,
challenge,
- kind.asBiometricPromptCredential()
+ kind.asBiometricPromptCredential(),
+ opPackageName,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
index 65a2c0a..b3f9574 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
@@ -76,6 +76,7 @@
userId: Int,
challenge: Long,
modalities: BiometricModalities,
+ opPackageName: String,
)
/** Use credential-based authentication instead of biometrics. */
@@ -84,6 +85,7 @@
@Utils.CredentialType kind: Int,
userId: Int,
challenge: Long,
+ opPackageName: String,
)
/** Unset the current authentication request. */
@@ -104,9 +106,12 @@
promptRepository.promptInfo,
promptRepository.challenge,
promptRepository.userId,
- promptRepository.kind
- ) { promptInfo, challenge, userId, kind ->
- if (promptInfo == null || userId == null || challenge == null) {
+ promptRepository.kind,
+ promptRepository.opPackageName,
+ ) { promptInfo, challenge, userId, kind, opPackageName ->
+ if (
+ promptInfo == null || userId == null || challenge == null || opPackageName == null
+ ) {
return@combine null
}
@@ -117,6 +122,7 @@
userInfo = BiometricUserInfo(userId = userId),
operationInfo = BiometricOperationInfo(gatekeeperChallenge = challenge),
modalities = kind.activeModalities,
+ opPackageName = opPackageName,
)
else -> null
}
@@ -152,13 +158,15 @@
promptInfo: PromptInfo,
userId: Int,
challenge: Long,
- modalities: BiometricModalities
+ modalities: BiometricModalities,
+ opPackageName: String,
) {
promptRepository.setPrompt(
promptInfo = promptInfo,
userId = userId,
gatekeeperChallenge = challenge,
kind = PromptKind.Biometric(modalities),
+ opPackageName = opPackageName,
)
}
@@ -167,12 +175,14 @@
@Utils.CredentialType kind: Int,
userId: Int,
challenge: Long,
+ opPackageName: String,
) {
promptRepository.setPrompt(
promptInfo = promptInfo,
userId = userId,
gatekeeperChallenge = challenge,
kind = kind.asBiometricPromptCredential(),
+ opPackageName = opPackageName,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
index f4a2811..4fc1b58 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
@@ -30,8 +30,10 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -56,6 +58,16 @@
return isUdfpsEnrolled && isWithinOverlayBounds
}
+ /** Sets whether Udfps overlay should handle touches */
+ fun setHandleTouches(shouldHandle: Boolean = true) {
+ _shouldHandleTouches.value = shouldHandle
+ }
+
+ private var _shouldHandleTouches = MutableStateFlow(true)
+
+ /** Whether Udfps overlay should handle touches */
+ val shouldHandleTouches: StateFlow<Boolean> = _shouldHandleTouches.asStateFlow()
+
/** Returns the current udfpsOverlayParams */
val udfpsOverlayParams: StateFlow<UdfpsOverlayParams> =
ConflatedCallbackFlow.conflatedCallbackFlow {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
index 8fbb250..c17c8dc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
@@ -1,5 +1,7 @@
package com.android.systemui.biometrics.domain.model
+import android.graphics.Bitmap
+import android.hardware.biometrics.PromptContentView
import android.hardware.biometrics.PromptInfo
import com.android.systemui.biometrics.shared.model.BiometricModalities
import com.android.systemui.biometrics.shared.model.BiometricUserInfo
@@ -25,6 +27,7 @@
userInfo: BiometricUserInfo,
operationInfo: BiometricOperationInfo,
val modalities: BiometricModalities,
+ val opPackageName: String,
) :
BiometricPromptRequest(
title = info.title?.toString() ?: "",
@@ -34,6 +37,9 @@
operationInfo = operationInfo,
showEmergencyCallButton = info.isShowEmergencyCallButton
) {
+ val contentView: PromptContentView? = info.contentView
+ val logoRes: Int = info.logoRes
+ val logoBitmap: Bitmap? = info.logoBitmap
val negativeButtonText: String = info.negativeButtonText?.toString() ?: ""
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorLocation.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorLocation.kt
new file mode 100644
index 0000000..dddadbd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorLocation.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.biometrics.shared.model
+
+/** Provides current sensor location information in the current screen resolution [scale]. */
+data class SensorLocation(
+ private val naturalCenterX: Int,
+ private val naturalCenterY: Int,
+ private val naturalRadius: Int
+) {
+ /**
+ * Scale to apply to the sensor location's natural parameters to support different screen
+ * resolutions.
+ */
+ var scale: Float = 1f
+
+ val centerX: Float
+ get() {
+ return naturalCenterX * scale
+ }
+ val centerY: Float
+ get() {
+ return naturalCenterY * scale
+ }
+ val radius: Float
+ get() {
+ return naturalRadius * scale
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
index 0d72b9c..b450896 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
@@ -101,6 +101,7 @@
final View child = getChildAt(i);
if (child.getId() == R.id.space_above_icon
+ || child.getId() == R.id.space_above_content
|| child.getId() == R.id.space_below_icon
|| child.getId() == R.id.button_bar) {
child.measure(
@@ -114,6 +115,12 @@
MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(iconView.getLayoutParams().height,
MeasureSpec.EXACTLY));
+ } else if (child.getId() == R.id.logo) {
+ child.measure(
+ MeasureSpec.makeMeasureSpec(child.getLayoutParams().width,
+ MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(child.getLayoutParams().height,
+ MeasureSpec.EXACTLY));
} else if (child.getId() == R.id.biometric_icon) {
child.measure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt
new file mode 100644
index 0000000..16e7f05
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.ui.binder
+
+import android.content.Context
+import android.content.res.Resources
+import android.content.res.Resources.Theme
+import android.graphics.Paint
+import android.hardware.biometrics.PromptContentItem
+import android.hardware.biometrics.PromptContentItemBulletedText
+import android.hardware.biometrics.PromptContentItemPlainText
+import android.hardware.biometrics.PromptContentView
+import android.hardware.biometrics.PromptVerticalListContentView
+import android.text.SpannableString
+import android.text.Spanned
+import android.text.style.BulletSpan
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.LinearLayout
+import android.widget.ScrollView
+import android.widget.Space
+import android.widget.TextView
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.biometrics.ui.BiometricPromptLayout
+import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
+import kotlin.math.ceil
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+
+/** Sub-binder for [BiometricPromptLayout.customized_view_container]. */
+object BiometricCustomizedViewBinder {
+ fun bind(customizedViewContainer: ScrollView, spaceAbove: Space, viewModel: PromptViewModel) {
+ customizedViewContainer.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ launch {
+ val contentView: PromptContentView? = viewModel.contentView.first()
+
+ if (contentView != null) {
+ val context = customizedViewContainer.context
+ customizedViewContainer.addView(contentView.toView(context))
+ customizedViewContainer.visibility = View.VISIBLE
+ spaceAbove.visibility = View.VISIBLE
+ } else {
+ customizedViewContainer.visibility = View.GONE
+ spaceAbove.visibility = View.GONE
+ }
+ }
+ }
+ }
+ }
+}
+
+private fun PromptContentView.toView(context: Context): View {
+ val resources = context.resources
+ val inflater = LayoutInflater.from(context)
+ when (this) {
+ is PromptVerticalListContentView -> {
+ val contentView =
+ inflater.inflate(R.layout.biometric_prompt_content_layout, null) as LinearLayout
+
+ val descriptionView = contentView.requireViewById<TextView>(R.id.customized_view_title)
+ if (!description.isNullOrEmpty()) {
+ descriptionView.text = description
+ } else {
+ descriptionView.visibility = View.GONE
+ }
+
+ // Show two column by default, once there is an item exceeding max lines, show single
+ // item instead.
+ val showTwoColumn = listItems.all { !it.doesExceedMaxLinesIfTwoColumn(resources) }
+ var currRowView = createNewRowLayout(inflater)
+ for (item in listItems) {
+ val itemView = item.toView(resources, inflater, context.theme)
+ currRowView.addView(itemView)
+
+ if (!showTwoColumn || currRowView.childCount == 2) {
+ contentView.addView(currRowView)
+ currRowView = createNewRowLayout(inflater)
+ }
+ }
+ if (currRowView.childCount > 0) {
+ contentView.addView(currRowView)
+ }
+
+ return contentView
+ }
+ else -> {
+ throw IllegalStateException("No such PromptContentView: $this")
+ }
+ }
+}
+
+private fun createNewRowLayout(inflater: LayoutInflater): LinearLayout {
+ return inflater.inflate(R.layout.biometric_prompt_content_row_layout, null) as LinearLayout
+}
+
+private fun PromptContentItem.doesExceedMaxLinesIfTwoColumn(
+ resources: Resources,
+): Boolean {
+ val passedInText: CharSequence =
+ when (this) {
+ is PromptContentItemPlainText -> text
+ is PromptContentItemBulletedText -> text
+ else -> {
+ throw IllegalStateException("No such PromptContentItem: $this")
+ }
+ }
+
+ when (this) {
+ is PromptContentItemPlainText,
+ is PromptContentItemBulletedText -> {
+ val dialogMargin =
+ resources.getDimensionPixelSize(R.dimen.biometric_dialog_border_padding)
+ val halfDialogWidth =
+ Resources.getSystem().displayMetrics.widthPixels / 2 - dialogMargin
+ val containerPadding =
+ resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_content_container_padding_horizontal
+ )
+ val contentPadding =
+ resources.getDimensionPixelSize(R.dimen.biometric_prompt_content_padding_horizontal)
+ val listItemPadding = getListItemPadding(resources)
+ val maxWidth = halfDialogWidth - containerPadding - contentPadding - listItemPadding
+
+ val text = "$passedInText"
+ val textSize =
+ resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_content_list_item_text_size
+ )
+ val paint = Paint()
+ paint.textSize = textSize.toFloat()
+
+ val maxLines =
+ resources.getInteger(
+ R.integer.biometric_prompt_content_list_item_max_lines_if_two_column
+ )
+ val numLines = ceil(paint.measureText(text).toDouble() / maxWidth).toInt()
+ return numLines > maxLines
+ }
+ else -> {
+ throw IllegalStateException("No such PromptContentItem: $this")
+ }
+ }
+}
+
+private fun PromptContentItem.toView(
+ resources: Resources,
+ inflater: LayoutInflater,
+ theme: Theme,
+): TextView {
+ val textView =
+ inflater.inflate(R.layout.biometric_prompt_content_row_item_text_view, null) as TextView
+ val lp = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1f)
+ textView.layoutParams = lp
+
+ when (this) {
+ is PromptContentItemPlainText -> {
+ textView.text = text
+ }
+ is PromptContentItemBulletedText -> {
+ val bulletedText = SpannableString(text)
+ val span =
+ BulletSpan(
+ getListItemBulletGapWidth(resources),
+ getListItemBulletColor(resources, theme),
+ getListItemBulletRadius(resources)
+ )
+ bulletedText.setSpan(span, 0 /* start */, text.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
+ textView.text = bulletedText
+ }
+ else -> {
+ throw IllegalStateException("No such PromptContentItem: $this")
+ }
+ }
+ return textView
+}
+
+private fun PromptContentItem.getListItemPadding(resources: Resources): Int {
+ var listItemPadding =
+ resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_content_list_item_padding_horizontal
+ ) * 2
+ when (this) {
+ is PromptContentItemPlainText -> {}
+ is PromptContentItemBulletedText -> {
+ listItemPadding +=
+ getListItemBulletRadius(resources) * 2 + getListItemBulletGapWidth(resources)
+ }
+ else -> {
+ throw IllegalStateException("No such PromptContentItem: $this")
+ }
+ }
+ return listItemPadding
+}
+
+private fun getListItemBulletRadius(resources: Resources): Int =
+ resources.getDimensionPixelSize(R.dimen.biometric_prompt_content_list_item_bullet_radius)
+
+private fun getListItemBulletGapWidth(resources: Resources): Int =
+ resources.getDimensionPixelSize(R.dimen.biometric_prompt_content_list_item_bullet_gap_width)
+
+private fun getListItemBulletColor(resources: Resources, theme: Theme): Int =
+ resources.getColor(R.color.biometric_prompt_content_list_item_bullet_color, theme)
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 7b8cb82..285ab4a 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
@@ -31,6 +31,8 @@
import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO
import android.view.accessibility.AccessibilityManager
import android.widget.Button
+import android.widget.ImageView
+import android.widget.ScrollView
import android.widget.TextView
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
@@ -91,11 +93,16 @@
val textColorHint =
view.resources.getColor(R.color.biometric_dialog_gray, view.context.theme)
+ val logoView = view.requireViewById<ImageView>(R.id.logo)
val titleView = view.requireViewById<TextView>(R.id.title)
val subtitleView = view.requireViewById<TextView>(R.id.subtitle)
val descriptionView = view.requireViewById<TextView>(R.id.description)
+ val customizedViewContainer =
+ view.requireViewById<ScrollView>(R.id.customized_view_container)
// set selected to enable marquee unless a screen reader is enabled
+ logoView.isSelected =
+ !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
titleView.isSelected =
!accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
subtitleView.isSelected =
@@ -149,9 +156,15 @@
}
}
+ logoView.setImageDrawable(viewModel.logo.first())
titleView.text = viewModel.title.first()
- descriptionView.text = viewModel.description.first()
subtitleView.text = viewModel.subtitle.first()
+ descriptionView.text = viewModel.description.first()
+ BiometricCustomizedViewBinder.bind(
+ customizedViewContainer,
+ view.requireViewById(R.id.space_above_content),
+ viewModel
+ )
// set button listeners
negativeButton.setOnClickListener { legacyCallback.onButtonNegative() }
@@ -175,15 +188,19 @@
viewModel = viewModel,
viewsToHideWhenSmall =
listOf(
+ logoView,
titleView,
subtitleView,
descriptionView,
+ customizedViewContainer,
),
viewsToFadeInOnSizeChange =
listOf(
+ logoView,
titleView,
subtitleView,
descriptionView,
+ customizedViewContainer,
indicatorMessageView,
negativeButton,
cancelButton,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index 1a7b6c9..d5695f3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -25,6 +25,7 @@
import android.view.WindowInsets
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
+import android.widget.ImageView
import android.widget.TextView
import androidx.core.animation.addListener
import androidx.core.view.doOnLayout
@@ -55,7 +56,7 @@
fun bind(
view: BiometricPromptLayout,
viewModel: PromptViewModel,
- viewsToHideWhenSmall: List<TextView>,
+ viewsToHideWhenSmall: List<View>,
viewsToFadeInOnSizeChange: List<View>,
panelViewController: AuthPanelController,
jankListener: BiometricJankListener,
@@ -110,7 +111,7 @@
// prepare for animated size transitions
for (v in viewsToHideWhenSmall) {
- v.showTextOrHide(forceHide = size.isSmall)
+ v.showContentOrHide(forceHide = size.isSmall)
}
if (currentSize == null && size.isSmall) {
iconHolderView.alpha = 0f
@@ -119,6 +120,10 @@
viewsToFadeInOnSizeChange.forEach { it.alpha = 0f }
}
+ // TODO(b/302735104): Fix wrong height due to the delay of
+ // PromptContentView. addOnLayoutChangeListener() will cause crash when
+ // showing credential view, since |PromptIconViewModel| won't release the
+ // flow.
// propagate size changes to legacy panel controller and animate transitions
view.doOnLayout {
val width = view.measuredWidth
@@ -228,8 +233,15 @@
return r == Surface.ROTATION_90 || r == Surface.ROTATION_270
}
-private fun TextView.showTextOrHide(forceHide: Boolean = false) {
- visibility = if (forceHide || text.isBlank()) View.GONE else View.VISIBLE
+private fun View.showContentOrHide(forceHide: Boolean = false) {
+ val isTextViewWithBlankText = this is TextView && this.text.isBlank()
+ val isImageViewWithoutImage = this is ImageView && this.drawable == null
+ visibility =
+ if (forceHide || isTextViewWithBlankText || isImageViewWithoutImage) {
+ View.GONE
+ } else {
+ View.VISIBLE
+ }
}
private fun View.asVerticalAnimator(
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 d899827e..dca0338 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
@@ -13,11 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package com.android.systemui.biometrics.ui.viewmodel
import android.content.Context
import android.graphics.Rect
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
import android.hardware.biometrics.BiometricPrompt
+import android.hardware.biometrics.PromptContentView
import android.util.Log
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
@@ -231,6 +235,19 @@
}
}
+ /** Logo for the prompt. */
+ val logo: Flow<Drawable?> =
+ promptSelectorInteractor.prompt
+ .map {
+ when {
+ it == null -> null
+ it.logoRes != -1 -> context.resources.getDrawable(it.logoRes, context.theme)
+ it.logoBitmap != null -> BitmapDrawable(context.resources, it.logoBitmap)
+ else -> context.packageManager.getApplicationIcon(it.opPackageName)
+ }
+ }
+ .distinctUntilChanged()
+
/** Title for the prompt. */
val title: Flow<String> =
promptSelectorInteractor.prompt.map { it?.title ?: "" }.distinctUntilChanged()
@@ -239,9 +256,20 @@
val subtitle: Flow<String> =
promptSelectorInteractor.prompt.map { it?.subtitle ?: "" }.distinctUntilChanged()
- /** Description for the prompt. */
- val description: Flow<String> =
+ /** Custom content view for the prompt. */
+ val contentView: Flow<PromptContentView?> =
+ promptSelectorInteractor.prompt.map { it?.contentView }.distinctUntilChanged()
+
+ private val originalDescription =
promptSelectorInteractor.prompt.map { it?.description ?: "" }.distinctUntilChanged()
+ /**
+ * Description for the prompt. Description view and contentView is mutually exclusive. Pass
+ * description down only when contentView is null.
+ */
+ val description: Flow<String> =
+ combine(contentView, originalDescription) { contentView, description ->
+ if (contentView == null) description else ""
+ }
/** If the indicator (help, error) message should be shown. */
val isIndicatorMessageVisible: Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
index c2a1d8f..d0ff185 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
@@ -20,6 +20,7 @@
import android.util.Log
import com.android.systemui.biometrics.shared.SideFpsControllerRefactor
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
+import com.android.systemui.bouncer.shared.model.BouncerDismissActionModel
import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -90,6 +91,9 @@
val alternateBouncerUIAvailable: StateFlow<Boolean>
val sideFpsShowing: StateFlow<Boolean>
+ /** Action that should be run right after the bouncer is dismissed. */
+ var bouncerDismissActionModel: BouncerDismissActionModel?
+
var lastAlternateBouncerVisibleTime: Long
fun setPrimaryScrimmed(isScrimmed: Boolean)
@@ -134,6 +138,8 @@
@Application private val applicationScope: CoroutineScope,
@BouncerTableLog private val buffer: TableLogBuffer,
) : KeyguardBouncerRepository {
+ override var bouncerDismissActionModel: BouncerDismissActionModel? = null
+
/** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
private val _primaryBouncerShow = MutableStateFlow(false)
override val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
index 654fa22..8c87b0c 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
@@ -28,10 +28,12 @@
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.DejankUtils
+import com.android.systemui.Flags
import com.android.systemui.biometrics.shared.SideFpsControllerRefactor
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
+import com.android.systemui.bouncer.shared.model.BouncerDismissActionModel
import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel
import com.android.systemui.bouncer.ui.BouncerView
import com.android.systemui.classifier.FalsingCollector
@@ -154,12 +156,12 @@
/** Show the bouncer if necessary and set the relevant states. */
@JvmOverloads
fun show(isScrimmed: Boolean) {
- if (primaryBouncerView.delegate == null) {
+ if (primaryBouncerView.delegate == null && !Flags.composeBouncer()) {
Log.d(
TAG,
"PrimaryBouncerInteractor#show is being called before the " +
- "primaryBouncerDelegate is set. Let's exit early so we don't set the wrong " +
- "primaryBouncer state."
+ "primaryBouncerDelegate is set. Let's exit early so we don't " +
+ "set the wrong primaryBouncer state."
)
return
}
@@ -272,15 +274,24 @@
repository.setShowMessage(BouncerShowMessageModel(message, colorStateList))
}
+ val bouncerDismissAction: BouncerDismissActionModel?
+ get() = repository.bouncerDismissActionModel
+
/**
* Sets actions to the bouncer based on how the bouncer is dismissed. If the bouncer is
- * unlocked, we will run the onDismissAction. If the bouncer is existed before unlocking, we
- * call cancelAction.
+ * unlocked, we will run the onDismissAction. If the bouncer is exited before unlocking, we call
+ * cancelAction.
*/
fun setDismissAction(
onDismissAction: ActivityStarter.OnDismissAction?,
cancelAction: Runnable?
) {
+ repository.bouncerDismissActionModel =
+ if (onDismissAction != null && cancelAction != null) {
+ BouncerDismissActionModel(onDismissAction, cancelAction)
+ } else {
+ null
+ }
primaryBouncerView.delegate?.setDismissAction(onDismissAction, cancelAction)
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerDismissActionModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerDismissActionModel.kt
new file mode 100644
index 0000000..02b444f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerDismissActionModel.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.shared.model
+
+import com.android.systemui.plugins.ActivityStarter
+
+/** Represents the action that needs to be performed after bouncer is dismissed. */
+data class BouncerDismissActionModel(
+ /** If the bouncer is unlocked, [onDismissAction] will be run. */
+ val onDismissAction: ActivityStarter.OnDismissAction?,
+ /** If the bouncer is exited before unlocking, [onCancel] will be invoked. */
+ val onCancel: Runnable?
+)
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerDialogFactory.kt
similarity index 67%
copy from core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
copy to packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerDialogFactory.kt
index ce92b6d..5defe475 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerDialogFactory.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.
@@ -13,10 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.companion.virtual.camera;
-/**
- * The configuration of a single virtual camera stream.
- * @hide
- */
-parcelable VirtualCameraStreamConfig;
\ No newline at end of file
+package com.android.systemui.bouncer.ui
+
+import android.app.AlertDialog
+
+/** Factory to create alert dialogs for use in bouncer component. */
+interface BouncerDialogFactory {
+ operator fun invoke(): AlertDialog
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt
index 7f3b794..f3903de 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt
@@ -16,9 +16,15 @@
package com.android.systemui.bouncer.ui
+import android.app.AlertDialog
+import android.content.Context
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModelModule
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.phone.SystemUIDialog
import dagger.Binds
import dagger.Module
+import dagger.Provides
@Module(
includes =
@@ -29,4 +35,17 @@
interface BouncerViewModule {
/** Binds BouncerView to BouncerViewImpl and makes it injectable. */
@Binds fun bindBouncerView(bouncerViewImpl: BouncerViewImpl): BouncerView
+
+ companion object {
+
+ @Provides
+ @SysUISingleton
+ fun bouncerDialogFactory(@Application context: Context): BouncerDialogFactory {
+ return object : BouncerDialogFactory {
+ override fun invoke(): AlertDialog {
+ return SystemUIDialog(context)
+ }
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
new file mode 100644
index 0000000..dd253a8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
@@ -0,0 +1,89 @@
+package com.android.systemui.bouncer.ui.binder
+
+import android.view.ViewGroup
+import com.android.keyguard.KeyguardMessageAreaController
+import com.android.keyguard.ViewMediatorCallback
+import com.android.keyguard.dagger.KeyguardBouncerComponent
+import com.android.systemui.Flags
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.bouncer.ui.BouncerDialogFactory
+import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.Flags.COMPOSE_BOUNCER_ENABLED
+import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
+import com.android.systemui.log.BouncerLogger
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import dagger.Lazy
+import javax.inject.Inject
+
+/** Helper data class that allows to lazy load all the dependencies of the legacy bouncer. */
+@SysUISingleton
+data class LegacyBouncerDependencies
+@Inject
+constructor(
+ val viewModel: KeyguardBouncerViewModel,
+ val primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
+ val componentFactory: KeyguardBouncerComponent.Factory,
+ val messageAreaControllerFactory: KeyguardMessageAreaController.Factory,
+ val bouncerMessageInteractor: BouncerMessageInteractor,
+ val bouncerLogger: BouncerLogger,
+ val selectedUserInteractor: SelectedUserInteractor,
+)
+
+/** Helper data class that allows to lazy load all the dependencies of the compose based bouncer. */
+@SysUISingleton
+data class ComposeBouncerDependencies
+@Inject
+constructor(
+ val legacyInteractor: PrimaryBouncerInteractor,
+ val viewModel: BouncerViewModel,
+ val dialogFactory: BouncerDialogFactory,
+ val authenticationInteractor: AuthenticationInteractor,
+ val viewMediatorCallback: ViewMediatorCallback?,
+ val selectedUserInteractor: SelectedUserInteractor,
+)
+
+/**
+ * Toggles between the compose and non compose version of the bouncer, instantiating only the
+ * dependencies required for each.
+ */
+@SysUISingleton
+class BouncerViewBinder
+@Inject
+constructor(
+ private val legacyBouncerDependencies: Lazy<LegacyBouncerDependencies>,
+ private val composeBouncerDependencies: Lazy<ComposeBouncerDependencies>,
+) {
+ fun bind(view: ViewGroup) {
+ if (
+ ComposeFacade.isComposeAvailable() && Flags.composeBouncer() && COMPOSE_BOUNCER_ENABLED
+ ) {
+ val deps = composeBouncerDependencies.get()
+ ComposeBouncerViewBinder.bind(
+ view,
+ deps.legacyInteractor,
+ deps.viewModel,
+ deps.dialogFactory,
+ deps.authenticationInteractor,
+ deps.selectedUserInteractor,
+ deps.viewMediatorCallback,
+ )
+ } else {
+ val deps = legacyBouncerDependencies.get()
+ KeyguardBouncerViewBinder.bind(
+ view,
+ deps.viewModel,
+ deps.primaryBouncerToGoneTransitionViewModel,
+ deps.componentFactory,
+ deps.messageAreaControllerFactory,
+ deps.bouncerMessageInteractor,
+ deps.bouncerLogger,
+ deps.selectedUserInteractor,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
new file mode 100644
index 0000000..7b05395
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
@@ -0,0 +1,75 @@
+package com.android.systemui.bouncer.ui.binder
+
+import android.view.ViewGroup
+import androidx.core.view.isVisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.keyguard.ViewMediatorCallback
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.bouncer.ui.BouncerDialogFactory
+import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+/** View binder responsible for binding the compose version of the bouncer. */
+object ComposeBouncerViewBinder {
+ fun bind(
+ view: ViewGroup,
+ legacyInteractor: PrimaryBouncerInteractor,
+ viewModel: BouncerViewModel,
+ dialogFactory: BouncerDialogFactory,
+ authenticationInteractor: AuthenticationInteractor,
+ selectedUserInteractor: SelectedUserInteractor,
+ viewMediatorCallback: ViewMediatorCallback?,
+ ) {
+ view.addView(
+ ComposeFacade.createBouncer(
+ view.context,
+ viewModel,
+ dialogFactory,
+ )
+ )
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ launch {
+ legacyInteractor.isShowing.collectLatest { bouncerShowing ->
+ view.isVisible = bouncerShowing
+ }
+ }
+
+ launch {
+ authenticationInteractor.onAuthenticationResult.collectLatest {
+ authenticationSucceeded ->
+ if (authenticationSucceeded) {
+ // Some dismiss actions require that keyguard be dismissed right away or
+ // deferred until something else later on dismisses keyguard (eg. end of
+ // a hide animation).
+ val deferKeyguardDone =
+ legacyInteractor.bouncerDismissAction?.onDismissAction?.onDismiss()
+ legacyInteractor.setDismissAction(null, null)
+
+ viewMediatorCallback?.let {
+ val selectedUserId = selectedUserInteractor.getSelectedUserId()
+ if (deferKeyguardDone == true) {
+ it.keyguardDonePending(selectedUserId)
+ } else {
+ it.keyguardDone(selectedUserId)
+ }
+ }
+ }
+ }
+ }
+ launch {
+ legacyInteractor.startingDisappearAnimation.collectLatest {
+ it.run()
+ legacyInteractor.hide()
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageUpdateLogger.kt b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageUpdateLogger.kt
index adbb37c..4184ef7 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageUpdateLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageUpdateLogger.kt
@@ -31,6 +31,7 @@
is PackageChangeModel.UpdateStarted -> "started updating"
is PackageChangeModel.UpdateFinished -> "finished updating"
is PackageChangeModel.Changed -> "changed"
+ is PackageChangeModel.Empty -> throw IllegalStateException("Unexpected empty value: $model")
}
/** A debug logger for [PackageChangeRepository]. */
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/shared/model/PackageChangeModel.kt b/packages/SystemUI/src/com/android/systemui/common/data/shared/model/PackageChangeModel.kt
index 853eff7..3ae87c3 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/shared/model/PackageChangeModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/data/shared/model/PackageChangeModel.kt
@@ -23,6 +23,14 @@
val packageName: String
val packageUid: Int
+ /** Empty change, provided for convenience when a sensible default value is needed. */
+ data object Empty : PackageChangeModel {
+ override val packageName: String
+ get() = ""
+ override val packageUid: Int
+ get() = 0
+ }
+
/**
* An existing application package was uninstalled.
*
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index 10768ea..dc07c1b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -18,6 +18,7 @@
import com.android.systemui.communal.data.db.CommunalDatabaseModule
import com.android.systemui.communal.data.repository.CommunalMediaRepositoryModule
+import com.android.systemui.communal.data.repository.CommunalPrefsRepositoryModule
import com.android.systemui.communal.data.repository.CommunalRepositoryModule
import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule
import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule
@@ -34,6 +35,7 @@
CommunalTutorialRepositoryModule::class,
CommunalWidgetRepositoryModule::class,
CommunalDatabaseModule::class,
+ CommunalPrefsRepositoryModule::class,
]
)
interface CommunalModule {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
new file mode 100644
index 0000000..c2ea2e9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.data.repository
+
+import android.content.Context
+import android.content.SharedPreferences
+import android.content.pm.UserInfo
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserFileManagerExt.observeSharedPreferences
+import com.android.systemui.user.data.repository.UserRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+/**
+ * Stores simple preferences for the current user in communal hub. For use cases like "has the CTA
+ * tile been dismissed?"
+ */
+interface CommunalPrefsRepository {
+
+ /** Whether the CTA tile has been dismissed. */
+ val isCtaDismissed: Flow<Boolean>
+
+ /** Save the CTA tile dismissed state for the current user. */
+ suspend fun setCtaDismissedForCurrentUser()
+}
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class CommunalPrefsRepositoryImpl
+@Inject
+constructor(
+ @Background private val backgroundScope: CoroutineScope,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ private val userRepository: UserRepository,
+ private val userFileManager: UserFileManager,
+) : CommunalPrefsRepository {
+
+ override val isCtaDismissed: Flow<Boolean> =
+ userRepository.selectedUserInfo
+ .flatMapLatest(::observeCtaDismissState)
+ .stateIn(
+ scope = backgroundScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
+
+ override suspend fun setCtaDismissedForCurrentUser() =
+ withContext(bgDispatcher) {
+ getSharedPrefsForUser(userRepository.getSelectedUserInfo())
+ .edit()
+ .putBoolean(CTA_DISMISSED_STATE, true)
+ .apply()
+ }
+
+ private fun observeCtaDismissState(user: UserInfo): Flow<Boolean> =
+ userFileManager
+ .observeSharedPreferences(FILE_NAME, Context.MODE_PRIVATE, user.id)
+ // Emit at the start of collection to ensure we get an initial value
+ .onStart { emit(Unit) }
+ .map { getCtaDismissedState() }
+ .flowOn(bgDispatcher)
+
+ private suspend fun getCtaDismissedState(): Boolean =
+ withContext(bgDispatcher) {
+ getSharedPrefsForUser(userRepository.getSelectedUserInfo())
+ .getBoolean(CTA_DISMISSED_STATE, false)
+ }
+
+ private fun getSharedPrefsForUser(user: UserInfo): SharedPreferences {
+ return userFileManager.getSharedPreferences(
+ FILE_NAME,
+ Context.MODE_PRIVATE,
+ user.id,
+ )
+ }
+
+ companion object {
+ const val TAG = "CommunalRepository"
+ const val FILE_NAME = "communal_hub_prefs"
+ const val CTA_DISMISSED_STATE = "cta_dismissed"
+ }
+}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryModule.kt
similarity index 64%
copy from core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
copy to packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryModule.kt
index ce92b6d..a4ff6d3 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryModule.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.
@@ -12,11 +12,15 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
+ *
*/
-package android.companion.virtual.camera;
-/**
- * The configuration of a single virtual camera stream.
- * @hide
- */
-parcelable VirtualCameraStreamConfig;
\ No newline at end of file
+package com.android.systemui.communal.data.repository
+
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface CommunalPrefsRepositoryModule {
+ @Binds fun communalPrefsRepository(impl: CommunalPrefsRepositoryImpl): CommunalPrefsRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
index 553b3eb..1f4be40 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
@@ -56,9 +56,6 @@
/** Exposes the transition state of the communal [SceneTransitionLayout]. */
val transitionState: StateFlow<ObservableCommunalTransitionState>
- /** Whether the CTA tile is visible in the hub under view mode. */
- val isCtaTileInViewModeVisible: Flow<Boolean>
-
/** Updates the requested scene. */
fun setDesiredScene(desiredScene: CommunalSceneKey)
@@ -68,9 +65,6 @@
* Note that you must call is with `null` when the UI is done or risk a memory leak.
*/
fun setTransitionState(transitionState: Flow<ObservableCommunalTransitionState>?)
-
- /** Updates whether to display the CTA tile in the hub under view mode. */
- fun setCtaTileInViewModeVisibility(isVisible: Boolean)
}
@OptIn(ExperimentalCoroutinesApi::class)
@@ -102,16 +96,6 @@
initialValue = defaultTransitionState,
)
- // TODO(b/313462210) - persist the value in local storage, so the tile won't show up again
- // once dismissed.
- private val _isCtaTileInViewModeVisible: MutableStateFlow<Boolean> = MutableStateFlow(true)
- override val isCtaTileInViewModeVisible: Flow<Boolean> =
- _isCtaTileInViewModeVisible.asStateFlow()
-
- override fun setCtaTileInViewModeVisibility(isVisible: Boolean) {
- _isCtaTileInViewModeVisible.value = isVisible
- }
-
override fun setDesiredScene(desiredScene: CommunalSceneKey) {
_desiredScene.value = desiredScene
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt
index 9a9b0e2..046aaaa 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal.data.repository
import android.provider.Settings
+import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED
import android.provider.Settings.Secure.HubModeTutorialState
import com.android.systemui.dagger.SysUISingleton
@@ -24,7 +25,6 @@
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
-import com.android.systemui.settings.UserTracker
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
@@ -35,6 +35,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
@@ -62,14 +63,19 @@
constructor(
@Application private val applicationScope: CoroutineScope,
@Background private val backgroundDispatcher: CoroutineDispatcher,
- userRepository: UserRepository,
+ private val userRepository: UserRepository,
private val secureSettings: SecureSettings,
- private val userTracker: UserTracker,
@CommunalLog logBuffer: LogBuffer,
) : CommunalTutorialRepository {
companion object {
private const val TAG = "CommunalTutorialRepository"
+
+ const val MIN_TUTORIAL_VERSION = HUB_MODE_TUTORIAL_COMPLETED
+
+ // A version number which ensures that users, regardless of their completion of previous
+ // versions, see the updated tutorial when this number is bumped.
+ const val CURRENT_TUTORIAL_VERSION = MIN_TUTORIAL_VERSION + 1
}
private data class SettingsState(
@@ -80,7 +86,7 @@
private val settingsState: Flow<SettingsState> =
userRepository.selectedUserInfo
- .flatMapLatest { observeSettings() }
+ .flatMapLatest { userInfo -> observeSettings(userInfo.id) }
.shareIn(scope = applicationScope, started = SharingStarted.WhileSubscribed())
/** Emits the state of tutorial state in settings */
@@ -91,31 +97,37 @@
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = HUB_MODE_TUTORIAL_NOT_STARTED
+ initialValue = HUB_MODE_TUTORIAL_NOT_STARTED,
)
- private fun observeSettings(): Flow<SettingsState> =
+ private fun observeSettings(userId: Int): Flow<SettingsState> =
secureSettings
.observerFlow(
- userId = userTracker.userId,
- names =
- arrayOf(
- Settings.Secure.HUB_MODE_TUTORIAL_STATE,
- )
+ userId = userId,
+ names = arrayOf(Settings.Secure.HUB_MODE_TUTORIAL_STATE),
)
// Force an update
.onStart { emit(Unit) }
- .map { readFromSettings() }
+ .map { readFromSettings(userId) }
- private suspend fun readFromSettings(): SettingsState =
+ private suspend fun readFromSettings(userId: Int): SettingsState =
withContext(backgroundDispatcher) {
- val userId = userTracker.userId
- val hubModeTutorialState =
+ var hubModeTutorialState =
secureSettings.getIntForUser(
Settings.Secure.HUB_MODE_TUTORIAL_STATE,
HUB_MODE_TUTORIAL_NOT_STARTED,
userId,
)
+
+ if (hubModeTutorialState >= CURRENT_TUTORIAL_VERSION) {
+ // Tutorial is considered "completed" if the user has completed the current or a
+ // newer version.
+ hubModeTutorialState = HUB_MODE_TUTORIAL_COMPLETED
+ } else if (hubModeTutorialState >= MIN_TUTORIAL_VERSION) {
+ // Tutorial is considered "not started" if the user completed a version older than
+ // the current.
+ hubModeTutorialState = HUB_MODE_TUTORIAL_NOT_STARTED
+ }
val settingsState = SettingsState(hubModeTutorialState)
logger.d({ "Communal tutorial state for user $int1 in settings: $str1" }) {
int1 = userId
@@ -127,18 +139,40 @@
override suspend fun setTutorialState(state: Int): Unit =
withContext(backgroundDispatcher) {
- val userId = userTracker.userId
+ val userId = userRepository.getSelectedUserInfo().id
if (tutorialSettingState.value == state) {
return@withContext
}
+ val newState =
+ if (state == HUB_MODE_TUTORIAL_COMPLETED) CURRENT_TUTORIAL_VERSION else state
logger.d({ "Update communal tutorial state to $int1 for user $int2" }) {
- int1 = state
+ int1 = newState
int2 = userId
}
secureSettings.putIntForUser(
Settings.Secure.HUB_MODE_TUTORIAL_STATE,
- state,
+ newState,
userId,
)
}
}
+
+// TODO(b/320769333): delete me and use the real repo above when tutorial is ready.
+@SysUISingleton
+class CommunalTutorialDisabledRepositoryImpl
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+) : CommunalTutorialRepository {
+ override val tutorialSettingState: StateFlow<Int> =
+ emptyFlow<Int>()
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = HUB_MODE_TUTORIAL_COMPLETED,
+ )
+
+ override suspend fun setTutorialState(state: Int) {
+ // Do nothing
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryModule.kt
index 69b0a27..b4f838a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryModule.kt
@@ -22,6 +22,9 @@
@Module
interface CommunalTutorialRepositoryModule {
+ // TODO(b/320769333): use [CommunalTutorialRepositoryImpl] when tutorial is ready.
@Binds
- fun communalTutorialRepository(impl: CommunalTutorialRepositoryImpl): CommunalTutorialRepository
+ fun communalTutorialRepository(
+ impl: CommunalTutorialDisabledRepositoryImpl
+ ): CommunalTutorialRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index e6816e9..1c362e9 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -16,26 +16,21 @@
package com.android.systemui.communal.data.repository
-import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetManager
import android.content.ComponentName
-import android.content.Intent
-import android.content.IntentFilter
-import android.os.UserManager
import androidx.annotation.WorkerThread
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.communal.data.db.CommunalItemRank
import com.android.systemui.communal.data.db.CommunalWidgetDao
import com.android.systemui.communal.data.db.CommunalWidgetItem
import com.android.systemui.communal.shared.CommunalWidgetHost
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
-import com.android.systemui.settings.UserTracker
import java.util.Optional
import javax.inject.Inject
import kotlin.coroutines.cancellation.CancellationException
@@ -43,16 +38,12 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
/** Encapsulates the state of widgets for communal mode. */
interface CommunalWidgetRepository {
@@ -75,23 +66,21 @@
* @param widgetIdToPriorityMap mapping of the widget ids to the priority of the widget.
*/
fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {}
+
+ /** Update whether the app widget host should be active. */
+ fun updateAppWidgetHostActive(active: Boolean)
}
-@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class CommunalWidgetRepositoryImpl
@Inject
constructor(
private val appWidgetManager: Optional<AppWidgetManager>,
- private val appWidgetHost: AppWidgetHost,
+ private val appWidgetHost: CommunalAppWidgetHost,
@Application private val applicationScope: CoroutineScope,
@Background private val bgDispatcher: CoroutineDispatcher,
- broadcastDispatcher: BroadcastDispatcher,
- communalRepository: CommunalRepository,
private val communalWidgetHost: CommunalWidgetHost,
private val communalWidgetDao: CommunalWidgetDao,
- private val userManager: UserManager,
- private val userTracker: UserTracker,
@CommunalLog logBuffer: LogBuffer,
) : CommunalWidgetRepository {
companion object {
@@ -100,40 +89,22 @@
private val logger = Logger(logBuffer, TAG)
- // Whether the [AppWidgetHost] is listening for updates.
- private var isHostListening = false
-
- private suspend fun isUserUnlockingOrUnlocked(): Boolean =
- withContext(bgDispatcher) { userManager.isUserUnlockingOrUnlocked(userTracker.userHandle) }
-
- private val isUserUnlocked: Flow<Boolean> =
- flowOf(communalRepository.isCommunalEnabled)
- .flatMapLatest { enabled ->
- if (enabled) {
- broadcastDispatcher
- .broadcastFlow(
- filter = IntentFilter(Intent.ACTION_USER_UNLOCKED),
- user = userTracker.userHandle
- )
- .onStart { emit(Unit) }
- .mapLatest { isUserUnlockingOrUnlocked() }
- } else {
- emptyFlow()
- }
- }
- .distinctUntilChanged()
-
- private val isHostActive: Flow<Boolean> =
- isUserUnlocked.map {
- if (it) {
- startListening()
- true
- } else {
- stopListening()
- false
- }
+ override fun updateAppWidgetHostActive(active: Boolean) {
+ if (active == isHostActive.value) {
+ return
}
+ if (active) {
+ appWidgetHost.startListening()
+ } else {
+ appWidgetHost.stopListening()
+ }
+ isHostActive.value = active
+ }
+
+ private val isHostActive = MutableStateFlow(false)
+
+ @OptIn(ExperimentalCoroutinesApi::class)
override val communalWidgets: Flow<List<CommunalWidgetContentModel>> =
isHostActive.flatMapLatest { isHostActive ->
if (!isHostActive || !appWidgetManager.isPresent) {
@@ -218,22 +189,4 @@
priority = entry.key.rank,
)
}
-
- private fun startListening() {
- if (isHostListening) {
- return
- }
-
- appWidgetHost.startListening()
- isHostListening = true
- }
-
- private fun stopListening() {
- if (!isHostListening) {
- return
- }
-
- appWidgetHost.stopListening()
- isHostListening = false
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
index d0d9e3f..52f42c1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
@@ -17,11 +17,11 @@
package com.android.systemui.communal.data.repository
-import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetManager
import android.content.Context
import android.content.res.Resources
import com.android.systemui.communal.shared.CommunalWidgetHost
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
@@ -48,15 +48,15 @@
@SysUISingleton
@Provides
- fun provideAppWidgetHost(@Application context: Context): AppWidgetHost {
- return AppWidgetHost(context, APP_WIDGET_HOST_ID)
+ fun provideCommunalAppWidgetHost(@Application context: Context): CommunalAppWidgetHost {
+ return CommunalAppWidgetHost(context, APP_WIDGET_HOST_ID)
}
@SysUISingleton
@Provides
fun provideCommunalWidgetHost(
appWidgetManager: Optional<AppWidgetManager>,
- appWidgetHost: AppWidgetHost,
+ appWidgetHost: CommunalAppWidgetHost,
@CommunalLog logBuffer: LogBuffer,
): CommunalWidgetHost {
return CommunalWidgetHost(appWidgetManager, appWidgetHost, logBuffer)
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 24d4c6c..71523b9 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
@@ -17,9 +17,9 @@
package com.android.systemui.communal.domain.interactor
import android.app.smartspace.SmartspaceTarget
-import android.appwidget.AppWidgetHost
import android.content.ComponentName
import com.android.systemui.communal.data.repository.CommunalMediaRepository
+import com.android.systemui.communal.data.repository.CommunalPrefsRepository
import com.android.systemui.communal.data.repository.CommunalRepository
import com.android.systemui.communal.data.repository.CommunalWidgetRepository
import com.android.systemui.communal.domain.model.CommunalContentModel
@@ -29,28 +29,40 @@
import com.android.systemui.communal.shared.model.CommunalContentSize.THIRD
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.smartspace.data.repository.SmartspaceRepository
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
/** Encapsulates business-logic related to communal mode. */
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class CommunalInteractor
@Inject
constructor(
+ @Application private val applicationScope: CoroutineScope,
private val communalRepository: CommunalRepository,
private val widgetRepository: CommunalWidgetRepository,
+ private val communalPrefsRepository: CommunalPrefsRepository,
mediaRepository: CommunalMediaRepository,
smartspaceRepository: SmartspaceRepository,
keyguardInteractor: KeyguardInteractor,
- private val appWidgetHost: AppWidgetHost,
+ private val appWidgetHost: CommunalAppWidgetHost,
private val editWidgetsActivityStarter: EditWidgetsActivityStarter
) {
@@ -58,6 +70,20 @@
val isCommunalEnabled: Boolean
get() = communalRepository.isCommunalEnabled
+ /** Whether communal features are enabled and available. */
+ val isCommunalAvailable: StateFlow<Boolean> =
+ flowOf(isCommunalEnabled)
+ .flatMapLatest { enabled ->
+ if (enabled) keyguardInteractor.isEncryptedOrLockdown.map { !it } else flowOf(false)
+ }
+ .distinctUntilChanged()
+ .onEach { available -> widgetRepository.updateAppWidgetHostActive(available) }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
+
/**
* Target scene as requested by the underlying [SceneTransitionLayout] or through
* [onSceneChanged].
@@ -77,6 +103,29 @@
communalRepository.setTransitionState(transitionState)
}
+ /** Returns a flow that tracks the progress of transitions to the given scene from 0-1. */
+ fun transitionProgressToScene(targetScene: CommunalSceneKey) =
+ transitionState
+ .flatMapLatest { state ->
+ when (state) {
+ is ObservableCommunalTransitionState.Idle ->
+ flowOf(CommunalTransitionProgress.Idle(state.scene))
+ is ObservableCommunalTransitionState.Transition ->
+ if (state.toScene == targetScene) {
+ state.progress.map {
+ CommunalTransitionProgress.Transition(
+ // Clamp the progress values between 0 and 1 as actual progress
+ // values can be higher than 0 or lower than 1 due to a fling.
+ progress = it.coerceIn(0.0f, 1.0f)
+ )
+ }
+ } else {
+ flowOf(CommunalTransitionProgress.OtherTransition)
+ }
+ }
+ }
+ .distinctUntilChanged()
+
/**
* Flow that emits a boolean if the communal UI is showing, ie. the [desiredScene] is the
* [CommunalSceneKey.Communal].
@@ -97,7 +146,7 @@
}
/** Dismiss the CTA tile from the hub in view mode. */
- fun dismissCtaTile() = communalRepository.setCtaTileInViewModeVisibility(isVisible = false)
+ suspend fun dismissCtaTile() = communalPrefsRepository.setCtaDismissedForCurrentUser()
/**
* Add a widget at the specified position.
@@ -149,8 +198,8 @@
/** CTA tile to be displayed in the glanceable hub (view mode). */
val ctaTileContent: Flow<List<CommunalContentModel.CtaTileInViewMode>> =
- communalRepository.isCtaTileInViewModeVisible.map { visible ->
- if (visible) listOf(CommunalContentModel.CtaTileInViewMode()) else emptyList()
+ communalPrefsRepository.isCtaDismissed.map { isDismissed ->
+ if (isDismissed) emptyList() else listOf(CommunalContentModel.CtaTileInViewMode())
}
/** A list of tutorial content to be displayed in the communal hub in tutorial mode. */
@@ -232,3 +281,17 @@
}
}
}
+
+/** Simplified transition progress data class for tracking a single transition between scenes. */
+sealed class CommunalTransitionProgress {
+ /** No transition/animation is currently running. */
+ data class Idle(val scene: CommunalSceneKey) : CommunalTransitionProgress()
+
+ /** There is a transition animating to the expected scene. */
+ data class Transition(
+ val progress: Float,
+ ) : CommunalTransitionProgress()
+
+ /** There is a transition animating to a scene other than the expected scene. */
+ data object OtherTransition : CommunalTransitionProgress()
+}
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 46f957f..0d52afd 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
@@ -16,10 +16,10 @@
package com.android.systemui.communal.domain.model
-import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetProviderInfo
import android.widget.RemoteViews
import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import java.util.UUID
/** Encapsulates data for a communal content. */
@@ -44,7 +44,7 @@
class Widget(
val appWidgetId: Int,
val providerInfo: AppWidgetProviderInfo,
- val appWidgetHost: AppWidgetHost,
+ val appWidgetHost: CommunalAppWidgetHost,
) : CommunalContentModel {
override val key = KEY.widget(appWidgetId)
// Widget size is always half.
diff --git a/packages/SystemUI/src/com/android/systemui/communal/log/CommunalLoggerStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/log/CommunalLoggerStartable.kt
new file mode 100644
index 0000000..889023e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/log/CommunalLoggerStartable.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.
+ */
+
+package com.android.systemui.communal.log
+
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.CoreStartable
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.shared.log.CommunalUiEvent
+import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.kotlin.pairwise
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.drop
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+
+/** A [CoreStartable] responsible for logging metrics for the communal hub. */
+@SysUISingleton
+class CommunalLoggerStartable
+@Inject
+constructor(
+ @Background private val backgroundScope: CoroutineScope,
+ private val communalInteractor: CommunalInteractor,
+ private val uiEventLogger: UiEventLogger,
+) : CoreStartable {
+
+ override fun start() {
+ communalInteractor.transitionState
+ .map { state ->
+ when {
+ state.isOnCommunal() -> CommunalUiEvent.COMMUNAL_HUB_SHOWN
+ state.isNotOnCommunal() -> CommunalUiEvent.COMMUNAL_HUB_GONE
+ else -> null
+ }
+ }
+ .filterNotNull()
+ .distinctUntilChanged()
+ // Drop the default value.
+ .drop(1)
+ .onEach { uiEvent -> uiEventLogger.log(uiEvent) }
+ .launchIn(backgroundScope)
+
+ communalInteractor.transitionState
+ .pairwise()
+ .map { (old, new) ->
+ when {
+ new.isOnCommunal() && old.isSwipingToCommunal() ->
+ CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_FINISH
+ new.isOnCommunal() && old.isSwipingFromCommunal() ->
+ CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_CANCEL
+ new.isNotOnCommunal() && old.isSwipingFromCommunal() ->
+ CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_FINISH
+ new.isNotOnCommunal() && old.isSwipingToCommunal() ->
+ CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_CANCEL
+ new.isSwipingToCommunal() && old.isNotOnCommunal() ->
+ CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_START
+ new.isSwipingFromCommunal() && old.isOnCommunal() ->
+ CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_START
+ else -> null
+ }
+ }
+ .filterNotNull()
+ .distinctUntilChanged()
+ .onEach { uiEvent -> uiEventLogger.log(uiEvent) }
+ .launchIn(backgroundScope)
+ }
+}
+
+/** Whether currently in communal scene. */
+private fun ObservableCommunalTransitionState.isOnCommunal(): Boolean {
+ return this is ObservableCommunalTransitionState.Idle && scene == CommunalSceneKey.Communal
+}
+
+/** Whether currently in a scene other than communal. */
+private fun ObservableCommunalTransitionState.isNotOnCommunal(): Boolean {
+ return this is ObservableCommunalTransitionState.Idle && scene != CommunalSceneKey.Communal
+}
+
+/** Whether currently transitioning from another scene to communal. */
+private fun ObservableCommunalTransitionState.isSwipingToCommunal(): Boolean {
+ return this is ObservableCommunalTransitionState.Transition &&
+ toScene == CommunalSceneKey.Communal &&
+ isInitiatedByUserInput
+}
+
+/** Whether currently transitioning from communal to another scene. */
+private fun ObservableCommunalTransitionState.isSwipingFromCommunal(): Boolean {
+ return this is ObservableCommunalTransitionState.Transition &&
+ fromScene == CommunalSceneKey.Communal &&
+ isInitiatedByUserInput
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt
index 41f9cb4..7fe37cc 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt
@@ -16,11 +16,11 @@
package com.android.systemui.communal.shared
-import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_CONFIGURATION_OPTIONAL
import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE
import android.content.ComponentName
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
@@ -35,7 +35,7 @@
@Inject
constructor(
private val appWidgetManager: Optional<AppWidgetManager>,
- private val appWidgetHost: AppWidgetHost,
+ private val appWidgetHost: CommunalAppWidgetHost,
@CommunalLog logBuffer: LogBuffer,
) {
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt
index e167f3e..b64c195 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt
@@ -22,8 +22,6 @@
/** UI events for the Communal Hub. */
enum class CommunalUiEvent(private val id: Int) : UiEventEnum {
@UiEvent(doc = "Communal Hub is fully shown") COMMUNAL_HUB_SHOWN(1566),
- @UiEvent(doc = "Communal Hub starts entering") COMMUNAL_HUB_ENTERING(1575),
- @UiEvent(doc = "Communal Hub starts exiting") COMMUNAL_HUB_EXITING(1576),
@UiEvent(doc = "Communal Hub is fully gone") COMMUNAL_HUB_GONE(1577),
@UiEvent(doc = "Communal Hub times out") COMMUNAL_HUB_TIMEOUT(1578),
@UiEvent(doc = "The visible content in the Communal Hub is fully loaded and rendered")
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 28f48ce..73bb0b0 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
@@ -17,32 +17,37 @@
package com.android.systemui.communal.ui.viewmodel
import android.content.ComponentName
-import android.os.PowerManager
-import android.os.SystemClock
-import android.view.MotionEvent
import android.widget.RemoteViews
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
import com.android.systemui.media.controls.ui.MediaHost
-import com.android.systemui.shade.ShadeViewController
-import javax.inject.Provider
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flowOf
/** The base view model for the communal hub. */
abstract class BaseCommunalViewModel(
private val communalInteractor: CommunalInteractor,
- private val shadeViewController: Provider<ShadeViewController>,
- private val powerManager: PowerManager,
val mediaHost: MediaHost,
) {
+ val isCommunalAvailable: StateFlow<Boolean> = communalInteractor.isCommunalAvailable
+
val isKeyguardVisible: Flow<Boolean> = communalInteractor.isKeyguardVisible
val currentScene: StateFlow<CommunalSceneKey> = communalInteractor.desiredScene
+ /** Whether widgets are currently being re-ordered. */
+ open val reorderingWidgets: StateFlow<Boolean> = MutableStateFlow(false)
+
+ private val _selectedIndex: MutableStateFlow<Int?> = MutableStateFlow(null)
+
+ /** The index of the currently selected item, or null if no item selected. */
+ val selectedIndex: StateFlow<Int?>
+ get() = _selectedIndex
+
fun onSceneChanged(scene: CommunalSceneKey) {
communalInteractor.onSceneChanged(scene)
}
@@ -71,26 +76,6 @@
return true
}
- // TODO(b/308813166): remove once CommunalContainer is moved lower in z-order and doesn't block
- // touches anymore.
- /** Called when a touch is received outside the edge swipe area when hub mode is closed. */
- fun onOuterTouch(motionEvent: MotionEvent) {
- // Forward the touch to the shade so that basic gestures like swipe up/down for
- // shade/bouncer work.
- shadeViewController.get().handleExternalTouch(motionEvent)
- }
-
- // TODO(b/308813166): remove once CommunalContainer is moved lower in z-order and doesn't block
- // touches anymore.
- /** Called to refresh the screen timeout when a user touch is received. */
- fun onUserActivity() {
- powerManager.userActivity(
- SystemClock.uptimeMillis(),
- PowerManager.USER_ACTIVITY_EVENT_TOUCH,
- 0
- )
- }
-
/** A list of all the communal content to be displayed in the communal hub. */
abstract val communalContent: Flow<List<CommunalContentModel>>
@@ -132,4 +117,9 @@
/** Called as the user cancels dragging a widget to reorder. */
open fun onReorderWidgetCancel() {}
+
+ /** Set the index of the currently selected item */
+ fun setSelectedIndex(index: Int?) {
+ _selectedIndex.value = index
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 0cbf3f13..317dd40 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -20,27 +20,27 @@
import android.app.Activity.RESULT_CANCELED
import android.app.Activity.RESULT_OK
import android.app.ActivityOptions
-import android.appwidget.AppWidgetHost
import android.content.ActivityNotFoundException
import android.content.ComponentName
-import android.os.PowerManager
import android.widget.RemoteViews
import com.android.internal.logging.UiEventLogger
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.log.CommunalUiEvent
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.media.dagger.MediaModule
-import com.android.systemui.shade.ShadeViewController
import com.android.systemui.util.nullableAtomicReference
import javax.inject.Inject
import javax.inject.Named
-import javax.inject.Provider
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
/** The view model for communal hub in edit mode. */
@SysUISingleton
@@ -48,12 +48,10 @@
@Inject
constructor(
private val communalInteractor: CommunalInteractor,
- private val appWidgetHost: AppWidgetHost,
- shadeViewController: Provider<ShadeViewController>,
- powerManager: PowerManager,
+ private val appWidgetHost: CommunalAppWidgetHost,
@Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
private val uiEventLogger: UiEventLogger,
-) : BaseCommunalViewModel(communalInteractor, shadeViewController, powerManager, mediaHost) {
+) : BaseCommunalViewModel(communalInteractor, mediaHost) {
private companion object {
private const val KEY_SPLASH_SCREEN_STYLE = "android.activity.splashScreenStyle"
@@ -74,9 +72,15 @@
// Only widgets are editable. The CTA tile comes last in the list and remains visible.
override val communalContent: Flow<List<CommunalContentModel>> =
- communalInteractor.widgetContent.map { widgets ->
- widgets + listOf(CommunalContentModel.CtaTileInEditMode())
- }
+ communalInteractor.widgetContent
+ // Clear the selected index when the list is updated.
+ .onEach { setSelectedIndex(null) }
+ .map { widgets -> widgets + listOf(CommunalContentModel.CtaTileInEditMode()) }
+
+ private val _reorderingWidgets = MutableStateFlow(false)
+
+ override val reorderingWidgets: StateFlow<Boolean>
+ get() = _reorderingWidgets
override fun onDeleteWidget(id: Int) = communalInteractor.deleteWidget(id)
@@ -140,14 +144,19 @@
}
override fun onReorderWidgetStart() {
+ // Clear selection status
+ setSelectedIndex(null)
+ _reorderingWidgets.value = true
uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_START)
}
override fun onReorderWidgetEnd() {
+ _reorderingWidgets.value = false
uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_FINISH)
}
override fun onReorderWidgetCancel() {
+ _reorderingWidgets.value = false
uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_CANCEL)
}
}
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 1c69685..d619362 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
@@ -16,7 +16,6 @@
package com.android.systemui.communal.ui.viewmodel
-import android.os.PowerManager
import android.widget.RemoteViews
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
@@ -24,12 +23,12 @@
import com.android.systemui.communal.widgets.WidgetInteractionHandler
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.media.controls.ui.MediaHierarchyManager
import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.media.controls.ui.MediaHostState
import com.android.systemui.media.dagger.MediaModule
-import com.android.systemui.shade.ShadeViewController
import javax.inject.Inject
import javax.inject.Named
-import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
@@ -51,10 +50,8 @@
private val communalInteractor: CommunalInteractor,
private val interactionHandler: WidgetInteractionHandler,
tutorialInteractor: CommunalTutorialInteractor,
- shadeViewController: Provider<ShadeViewController>,
- powerManager: PowerManager,
@Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
-) : BaseCommunalViewModel(communalInteractor, shadeViewController, powerManager, mediaHost) {
+) : BaseCommunalViewModel(communalInteractor, mediaHost) {
@OptIn(ExperimentalCoroutinesApi::class)
override val communalContent: Flow<List<CommunalContentModel>> =
tutorialInteractor.isTutorialAvailable.flatMapLatest { isTutorialMode ->
@@ -75,12 +72,25 @@
override val isPopupOnDismissCtaShowing: Flow<Boolean> =
_isPopupOnDismissCtaShowing.asStateFlow()
+ 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.
+ with(mediaHost) {
+ expansion = MediaHostState.EXPANDED
+ showsOnlyActiveMedia = false
+ falsingProtectionNeeded = false
+ init(MediaHierarchyManager.LOCATION_COMMUNAL_HUB)
+ }
+ }
+
override fun onOpenWidgetEditor() = communalInteractor.showWidgetEditor()
override fun onDismissCtaTile() {
- communalInteractor.dismissCtaTile()
- setPopupOnDismissCtaVisibility(true)
- schedulePopupHiding()
+ scope.launch {
+ communalInteractor.dismissCtaTile()
+ setPopupOnDismissCtaVisibility(true)
+ schedulePopupHiding()
+ }
}
override fun getInteractionHandler(): RemoteViews.InteractionHandler = interactionHandler
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
new file mode 100644
index 0000000..003c9d5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.widgets
+
+import android.appwidget.AppWidgetHost
+import android.appwidget.AppWidgetHostView
+import android.appwidget.AppWidgetProviderInfo
+import android.content.Context
+
+/** Communal app widget host that creates a [CommunalAppWidgetHostView]. */
+class CommunalAppWidgetHost(context: Context, hostId: Int) : AppWidgetHost(context, hostId) {
+ override fun onCreateView(
+ context: Context,
+ appWidgetId: Int,
+ appWidget: AppWidgetProviderInfo?
+ ): AppWidgetHostView {
+ return CommunalAppWidgetHostView(context)
+ }
+
+ /**
+ * Creates and returns a [CommunalAppWidgetHostView]. This method does the same thing as
+ * `createView`. The only difference is that the returned value will be casted to
+ * [CommunalAppWidgetHostView].
+ */
+ fun createViewForCommunal(
+ context: Context?,
+ appWidgetId: Int,
+ appWidget: AppWidgetProviderInfo?
+ ): CommunalAppWidgetHostView {
+ // `createView` internally calls `onCreateView` to create the view. We cannot override
+ // `createView`, but we are sure that the hostView is `CommunalAppWidgetHostView`
+ return createView(context, appWidgetId, appWidget) as CommunalAppWidgetHostView
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
new file mode 100644
index 0000000..2b7d823
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.widgets
+
+import android.appwidget.AppWidgetHostView
+import android.content.Context
+import android.graphics.Outline
+import android.graphics.Rect
+import android.view.View
+import android.view.ViewOutlineProvider
+
+/** AppWidgetHostView that displays in communal hub with support for rounded corners. */
+class CommunalAppWidgetHostView(context: Context) : AppWidgetHostView(context) {
+ // Mutable corner radius.
+ var enforcedCornerRadius: Float
+
+ // Mutable `Rect`. The size will be mutated when the widget is reapplied.
+ var enforcedRectangle: Rect
+
+ init {
+ enforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(context)
+ enforcedRectangle = Rect()
+ }
+
+ override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
+ super.onLayout(changed, l, t, r, b)
+
+ enforceRoundedCorners()
+ }
+
+ private val cornerRadiusEnforcementOutline: ViewOutlineProvider =
+ object : ViewOutlineProvider() {
+ override fun getOutline(view: View?, outline: Outline) {
+ if (enforcedRectangle.isEmpty || enforcedCornerRadius <= 0) {
+ outline.setEmpty()
+ } else {
+ outline.setRoundRect(enforcedRectangle, enforcedCornerRadius)
+ }
+ }
+ }
+
+ private fun enforceRoundedCorners() {
+ if (enforcedCornerRadius <= 0) {
+ resetRoundedCorners()
+ return
+ }
+ val background: View? = RoundedCornerEnforcement.findBackground(this)
+ if (background == null || RoundedCornerEnforcement.hasAppWidgetOptedOut(this, background)) {
+ resetRoundedCorners()
+ return
+ }
+ RoundedCornerEnforcement.computeRoundedRectangle(this, background, enforcedRectangle)
+ outlineProvider = cornerRadiusEnforcementOutline
+ clipToOutline = true
+ invalidateOutline()
+ }
+
+ private fun resetRoundedCorners() {
+ outlineProvider = ViewOutlineProvider.BACKGROUND
+ clipToOutline = false
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index bfc6f2b..380ed61 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -30,6 +30,8 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.compose.ComposeFacade.setCommunalEditWidgetActivityContent
import javax.inject.Inject
@@ -41,6 +43,7 @@
constructor(
private val communalViewModel: CommunalEditModeViewModel,
private var windowManagerService: IWindowManager? = null,
+ private val uiEventLogger: UiEventLogger,
) : ComponentActivity() {
companion object {
private const val EXTRA_IS_PENDING_WIDGET_DRAG = "is_pending_widget_drag"
@@ -54,6 +57,8 @@
registerForActivityResult(StartActivityForResult()) { result ->
when (result.resultCode) {
RESULT_OK -> {
+ uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_WIDGET_PICKER_SHOWN)
+
result.data?.let { intent ->
val isPendingWidgetDrag =
intent.getBooleanExtra(EXTRA_IS_PENDING_WIDGET_DRAG, false)
@@ -144,4 +149,16 @@
communalViewModel.setConfigurationResult(resultCode)
}
}
+
+ override fun onStart() {
+ super.onStart()
+
+ uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_EDIT_MODE_SHOWN)
+ }
+
+ override fun onStop() {
+ super.onStop()
+
+ uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_EDIT_MODE_GONE)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/RoundedCornerEnforcement.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/RoundedCornerEnforcement.kt
new file mode 100644
index 0000000..abda44b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/RoundedCornerEnforcement.kt
@@ -0,0 +1,154 @@
+/*
+ * 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.widgets
+
+import android.annotation.IdRes
+import android.annotation.Nullable
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Rect
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.os.BuildCompat.isAtLeastS
+import com.android.systemui.res.R
+import kotlin.math.min
+
+/**
+ * Utilities to compute the enforced use of rounded corners on App Widgets. This is a fork of the
+ * Launcher3 source code to enforce the same visual treatment on communal hub.
+ */
+internal object RoundedCornerEnforcement {
+ /**
+ * Find the background view for a widget.
+ *
+ * @param appWidget the view containing the App Widget (typically the instance of
+ * [CommunalAppWidgetHostView]).
+ */
+ fun findBackground(appWidget: View): View? {
+ val backgrounds = findViewsWithId(appWidget, R.id.background)
+ if (backgrounds.size == 1) {
+ return backgrounds[0]
+ }
+ // Really, the argument should contain the widget, so it cannot be the background.
+ if (appWidget is ViewGroup) {
+ val vg = appWidget
+ if (vg.childCount > 0) {
+ return findUndefinedBackground(vg.getChildAt(0))
+ }
+ }
+ return appWidget
+ }
+
+ /** Check whether the app widget has opted out of the enforcement. */
+ fun hasAppWidgetOptedOut(appWidget: View?, background: View): Boolean {
+ return background.id == R.id.background && background.clipToOutline
+ }
+
+ /**
+ * Computes the rounded rectangle needed for this app widget.
+ *
+ * @param appWidget View onto which the rounded rectangle will be applied.
+ * @param background Background view. This must be either `appWidget` or a descendant of
+ * `appWidget`.
+ * @param outRect Rectangle set to the rounded rectangle coordinates, in the reference frame of
+ * `appWidget`.
+ */
+ fun computeRoundedRectangle(appWidget: View, background: View, outRect: Rect) {
+ var background = background
+ outRect.left = 0
+ outRect.right = background.width
+ outRect.top = 0
+ outRect.bottom = background.height
+ while (background !== appWidget) {
+ outRect.offset(background.left, background.top)
+ background = background.parent as View
+ }
+ }
+
+ /** Get the radius of the rounded rectangle defined in the host's resource. */
+ private fun getOwnedEnforcedRadius(context: Context): Float {
+ val res: Resources = context.resources
+ return res.getDimension(R.dimen.communal_enforced_rounded_corner_max_radius)
+ }
+
+ /**
+ * Computes the radius of the rounded rectangle that should be applied to a widget expanded in
+ * the given context.
+ */
+ fun computeEnforcedRadius(context: Context): Float {
+ if (!isAtLeastS()) {
+ return 0f
+ }
+ val res: Resources = context.resources
+ val systemRadius: Float =
+ res.getDimension(android.R.dimen.system_app_widget_background_radius)
+ val defaultRadius = getOwnedEnforcedRadius(context)
+ return min(defaultRadius, systemRadius)
+ }
+
+ private fun findViewsWithId(view: View, @IdRes viewId: Int): List<View> {
+ val output: MutableList<View> = ArrayList()
+ accumulateViewsWithId(view, viewId, output)
+ return output
+ }
+
+ // Traverse views. If the predicate returns true, continue on the children, otherwise, don't.
+ private fun accumulateViewsWithId(view: View, @IdRes viewId: Int, output: MutableList<View>) {
+ if (view.id == viewId) {
+ output.add(view)
+ return
+ }
+ if (view is ViewGroup) {
+ val vg = view
+ for (i in 0 until vg.childCount) {
+ accumulateViewsWithId(vg.getChildAt(i), viewId, output)
+ }
+ }
+ }
+
+ private fun isViewVisible(view: View): Boolean {
+ return if (view.visibility != View.VISIBLE) {
+ false
+ } else !view.willNotDraw() || view.foreground != null || view.background != null
+ }
+
+ @Nullable
+ private fun findUndefinedBackground(current: View): View? {
+ if (current.visibility != View.VISIBLE) {
+ return null
+ }
+ if (isViewVisible(current)) {
+ return current
+ }
+ var lastVisibleView: View? = null
+ // Find the first view that is either not a ViewGroup, or a ViewGroup which will draw
+ // something, or a ViewGroup that contains more than one view.
+ if (current is ViewGroup) {
+ val vg = current
+ for (i in 0 until vg.childCount) {
+ val visibleView = findUndefinedBackground(vg.getChildAt(i))
+ if (visibleView != null) {
+ if (lastVisibleView != null) {
+ return current // At least two visible children
+ }
+ lastVisibleView = visibleView
+ }
+ }
+ }
+ return lastVisibleView
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
index 3a92739..acbdecc 100644
--- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
+++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
@@ -22,6 +22,8 @@
import android.view.WindowInsets
import androidx.activity.ComponentActivity
import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.bouncer.ui.BouncerDialogFactory
+import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
@@ -88,6 +90,13 @@
viewModel: BaseCommunalViewModel,
): View
+ /** Create a [View] to represent the [BouncerViewModel]. */
+ fun createBouncer(
+ context: Context,
+ viewModel: BouncerViewModel,
+ dialogFactory: BouncerDialogFactory,
+ ): View
+
/** Creates a container that hosts the communal UI and handles gesture transitions. */
fun createCommunalContainer(context: Context, viewModel: BaseCommunalViewModel): View
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepository.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepository.kt
index 5bb6eec..074b9ff3 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepository.kt
@@ -17,23 +17,36 @@
package com.android.systemui.controls.panels
import android.content.ComponentName
+import android.os.UserHandle
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.controls.ui.SelectedItem
import com.android.systemui.flags.Flags
+import kotlinx.coroutines.flow.Flow
/** Stores user-selected preferred component. */
interface SelectedComponentRepository {
- /**
- * Returns currently set preferred component, or null when nothing is set. Consider using
- * [ControlsUiController.getPreferredSelectedItem] to get domain specific data
- */
- fun getSelectedComponent(): SelectedComponent?
+ /** Returns current set preferred component for the specified user. */
+ fun selectedComponentFlow(userHandle: UserHandle): Flow<SelectedComponent?>
- /** Sets preferred component. Use [getSelectedComponent] to get current one */
+ /**
+ * Returns the current set preferred component for the specified user, or null when nothing is
+ * set. If no user is specified, the current user's preference is used. This method by default
+ * operates in the context of the current user unless another user is explicitly specified.
+ * Consider using [ControlsUiController.getPreferredSelectedItem] to get domain specific data.
+ */
+ fun getSelectedComponent(userHandle: UserHandle = UserHandle.CURRENT): SelectedComponent?
+
+ /**
+ * Sets the preferred component for the current user. Use [getSelectedComponent] to retrieve the
+ * currently set preferred component. This method applies to the current user's settings.
+ */
fun setSelectedComponent(selectedComponent: SelectedComponent)
- /** Clears current preferred component. [getSelectedComponent] will return null afterwards */
+ /**
+ * Clears the current user's preferred component. After this operation, [getSelectedComponent]
+ * will return null for the current user.
+ */
fun removeSelectedComponent()
/**
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
index c9edd4a..0baa81a 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
@@ -19,13 +19,24 @@
import android.content.ComponentName
import android.content.Context
import android.content.SharedPreferences
+import android.os.UserHandle
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.launch
+@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
@SysUISingleton
class SelectedComponentRepositoryImpl
@Inject
@@ -33,6 +44,8 @@
private val userFileManager: UserFileManager,
private val userTracker: UserTracker,
private val featureFlags: FeatureFlags,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ @Application private val applicationScope: CoroutineScope
) : SelectedComponentRepository {
private companion object {
@@ -42,16 +55,42 @@
const val SHOULD_ADD_DEFAULT_PANEL = "should_add_default_panel"
}
- private val sharedPreferences: SharedPreferences
- get() =
- userFileManager.getSharedPreferences(
- fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
- mode = Context.MODE_PRIVATE,
- userId = userTracker.userId
- )
+ private fun getSharedPreferencesForUser(userId: Int): SharedPreferences {
+ return userFileManager.getSharedPreferences(
+ fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
+ mode = Context.MODE_PRIVATE,
+ userId = userId
+ )
+ }
- override fun getSelectedComponent(): SelectedComponentRepository.SelectedComponent? {
- with(sharedPreferences) {
+ override fun selectedComponentFlow(
+ userHandle: UserHandle
+ ): Flow<SelectedComponentRepository.SelectedComponent?> {
+ return conflatedCallbackFlow {
+ val sharedPreferencesByUserId = getSharedPreferencesForUser(userHandle.identifier)
+ val listener =
+ SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
+ applicationScope.launch(bgDispatcher) {
+ if (key == PREF_COMPONENT) {
+ trySend(getSelectedComponent(userHandle))
+ }
+ }
+ }
+ sharedPreferencesByUserId.registerOnSharedPreferenceChangeListener(listener)
+ send(getSelectedComponent(userHandle))
+ awaitClose {
+ sharedPreferencesByUserId.unregisterOnSharedPreferenceChangeListener(listener)
+ }
+ }
+ .flowOn(bgDispatcher)
+ }
+
+ override fun getSelectedComponent(
+ userHandle: UserHandle
+ ): SelectedComponentRepository.SelectedComponent? {
+ val userId =
+ if (userHandle == UserHandle.CURRENT) userTracker.userId else userHandle.identifier
+ with(getSharedPreferencesForUser(userId)) {
val componentString = getString(PREF_COMPONENT, null) ?: return null
return SelectedComponentRepository.SelectedComponent(
name = getString(PREF_STRUCTURE_OR_APP_NAME, "")!!,
@@ -64,7 +103,7 @@
override fun setSelectedComponent(
selectedComponent: SelectedComponentRepository.SelectedComponent
) {
- sharedPreferences
+ getSharedPreferencesForUser(userTracker.userId)
.edit()
.putString(PREF_COMPONENT, selectedComponent.componentName?.flattenToString())
.putString(PREF_STRUCTURE_OR_APP_NAME, selectedComponent.name)
@@ -73,7 +112,7 @@
}
override fun removeSelectedComponent() {
- sharedPreferences
+ getSharedPreferencesForUser(userTracker.userId)
.edit()
.remove(PREF_COMPONENT)
.remove(PREF_STRUCTURE_OR_APP_NAME)
@@ -82,9 +121,12 @@
}
override fun shouldAddDefaultComponent(): Boolean =
- sharedPreferences.getBoolean(SHOULD_ADD_DEFAULT_PANEL, true)
+ getSharedPreferencesForUser(userTracker.userId).getBoolean(SHOULD_ADD_DEFAULT_PANEL, true)
override fun setShouldAddDefaultComponent(shouldAdd: Boolean) {
- sharedPreferences.edit().putBoolean(SHOULD_ADD_DEFAULT_PANEL, shouldAdd).apply()
+ getSharedPreferencesForUser(userTracker.userId)
+ .edit()
+ .putBoolean(SHOULD_ADD_DEFAULT_PANEL, shouldAdd)
+ .apply()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index b2d7052..6d9994f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -556,7 +556,7 @@
@Provides
@Singleton
static SubscriptionManager provideSubscriptionManager(Context context) {
- return context.getSystemService(SubscriptionManager.class);
+ return context.getSystemService(SubscriptionManager.class).createForAllUserProfiles();
}
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 87a736d..8d82b55 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.log.CommunalLoggerStartable
import com.android.systemui.controls.dagger.StartControlsStartableModule
import com.android.systemui.dagger.qualifiers.PerUser
import com.android.systemui.dreams.AssistantAttentionMonitor
@@ -318,4 +319,9 @@
@IntoMap
@ClassKey(KeyguardDismissBinder::class)
abstract fun bindKeyguardDismissBinder(impl: KeyguardDismissBinder): CoreStartable
+
+ @Binds
+ @IntoMap
+ @ClassKey(CommunalLoggerStartable::class)
+ abstract fun bindCommunalLoggerStartable(impl: CommunalLoggerStartable): CoreStartable
}
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 2680328..0985357 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
@@ -61,6 +61,7 @@
deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository,
trustRepository: TrustRepository,
flags: SceneContainerFlags,
+ deviceUnlockedInteractor: DeviceUnlockedInteractor,
) {
val enteringDeviceFromBiometricUnlock: Flow<BiometricUnlockSource> =
repository.enteringDeviceFromBiometricUnlock
@@ -74,19 +75,7 @@
* of this flow will always be `true`, even if the lockscreen is showing and still needs to be
* dismissed by the user to proceed.
*/
- val isUnlocked: StateFlow<Boolean> =
- combine(
- repository.isUnlocked,
- authenticationInteractor.authenticationMethod,
- ) { isUnlocked, authenticationMethod ->
- (!authenticationMethod.isSecure || isUnlocked) &&
- authenticationMethod != AuthenticationMethodModel.Sim
- }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.Eagerly,
- initialValue = false,
- )
+ val isUnlocked: StateFlow<Boolean> = deviceUnlockedInteractor.isDeviceUnlocked
/**
* Whether the device has been entered (i.e. the lockscreen has been dismissed, by any method).
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
new file mode 100644
index 0000000..b0495fb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.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.deviceentry.domain.interactor
+
+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.DeviceEntryRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.stateIn
+
+@SysUISingleton
+class DeviceUnlockedInteractor
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ authenticationInteractor: AuthenticationInteractor,
+ deviceEntryRepository: DeviceEntryRepository,
+) {
+
+ /**
+ * Whether the device is unlocked.
+ *
+ * A device that is not yet unlocked requires unlocking by completing an authentication
+ * challenge according to the current authentication method, unless in cases when the current
+ * authentication method is not "secure" (for example, None and Swipe); in such cases, the value
+ * of this flow will always be `true`, even if the lockscreen is showing and still needs to be
+ * dismissed by the user to proceed.
+ */
+ val isDeviceUnlocked: StateFlow<Boolean> =
+ combine(
+ deviceEntryRepository.isUnlocked,
+ authenticationInteractor.authenticationMethod,
+ ) { isUnlocked, authenticationMethod ->
+ (!authenticationMethod.isSecure || isUnlocked) &&
+ authenticationMethod != AuthenticationMethodModel.Sim
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = false,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 699532c..846736c 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -122,11 +122,6 @@
val NEW_UNLOCK_SWIPE_ANIMATION = releasedFlag("new_unlock_swipe_animation")
val CHARGING_RIPPLE = resourceBooleanFlag(R.bool.flag_charging_ripple, "charging_ripple")
- // TODO(b/254512281): Tracking Bug
- @JvmField
- val BOUNCER_USER_SWITCHER =
- resourceBooleanFlag(R.bool.config_enableBouncerUserSwitcher, "bouncer_user_switcher")
-
// TODO(b/254512676): Tracking Bug
@JvmField
val LOCKSCREEN_CUSTOM_CLOCKS =
@@ -353,12 +348,6 @@
// TODO(b/254512673): Tracking Bug
@JvmField val DREAM_MEDIA_TAP_TO_OPEN = unreleasedFlag("dream_media_tap_to_open")
- // TODO(b/254513168): Tracking Bug
- @JvmField val UMO_SURFACE_RIPPLE = releasedFlag("umo_surface_ripple")
-
- // TODO(b/261734857): Tracking Bug
- @JvmField val UMO_TURBULENCE_NOISE = releasedFlag("umo_turbulence_noise")
-
// TODO(b/263272731): Tracking Bug
val MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE = releasedFlag("media_ttt_receiver_success_ripple")
@@ -377,9 +366,6 @@
// 1000 - dock
val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag("simulate_dock_through_charging")
- // TODO(b/254512758): Tracking Bug
- @JvmField val ROUNDED_BOX_RIPPLE = releasedFlag("rounded_box_ripple")
-
// TODO(b/273509374): Tracking Bug
@JvmField
val ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS =
@@ -490,6 +476,13 @@
// TODO(b/283300105): Tracking Bug
@JvmField val SCENE_CONTAINER_ENABLED = false
+ /**
+ * Whether the compose bouncer is enabled. This ensures ProGuard can
+ * remove unused code from our APK at compile time.
+ */
+ // TODO(b/280877228): Tracking Bug
+ @JvmField val COMPOSE_BOUNCER_ENABLED = false
+
// 1900
@JvmField val NOTE_TASKS = releasedFlag("keycode_flag")
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt
index 496c64e..c6fb4f9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt
@@ -19,6 +19,8 @@
import com.android.systemui.keyboard.data.repository.KeyboardRepository
import com.android.systemui.keyboard.data.repository.KeyboardRepositoryImpl
+import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepository
+import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepositoryImpl
import dagger.Binds
import dagger.Module
@@ -27,4 +29,9 @@
@Binds
abstract fun bindKeyboardRepository(repository: KeyboardRepositoryImpl): KeyboardRepository
+
+ @Binds
+ abstract fun bindStickyKeysRepository(
+ repository: StickyKeysRepositoryImpl
+ ): StickyKeysRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt
new file mode 100644
index 0000000..37034f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.keyboard.stickykeys
+
+import com.android.systemui.keyboard.stickykeys.shared.model.Locked
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.KeyboardLog
+import javax.inject.Inject
+
+private const val TAG = "stickyKeys"
+
+class StickyKeysLogger @Inject constructor(@KeyboardLog private val buffer: LogBuffer) {
+ fun logNewStickyKeysReceived(linkedHashMap: Map<ModifierKey, Locked>) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ { str1 = linkedHashMap.toString() },
+ { "new sticky keys state received: $str1" }
+ )
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
new file mode 100644
index 0000000..34d2888
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.keyboard.stickykeys.data.repository
+
+import android.hardware.input.InputManager
+import android.hardware.input.InputManager.StickyModifierStateListener
+import android.hardware.input.StickyModifierState
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyboard.stickykeys.StickyKeysLogger
+import com.android.systemui.keyboard.stickykeys.shared.model.Locked
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT_GR
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.CTRL
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import javax.inject.Inject
+
+interface StickyKeysRepository {
+ val stickyKeys: Flow<LinkedHashMap<ModifierKey, Locked>>
+ val settingEnabled: Flow<Boolean>
+}
+
+class StickyKeysRepositoryImpl
+@Inject
+constructor(
+ private val inputManager: InputManager,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val stickyKeysLogger: StickyKeysLogger,
+) : StickyKeysRepository {
+
+ override val stickyKeys: Flow<LinkedHashMap<ModifierKey, Locked>> =
+ conflatedCallbackFlow {
+ val listener = StickyModifierStateListener { stickyModifierState ->
+ trySendWithFailureLogging(stickyModifierState, TAG)
+ }
+ // after registering, InputManager calls listener with the current value
+ inputManager.registerStickyModifierStateListener(Runnable::run, listener)
+ awaitClose { inputManager.unregisterStickyModifierStateListener(listener) }
+ }
+ .map { toStickyKeysMap(it) }
+ .onEach { stickyKeysLogger.logNewStickyKeysReceived(it) }
+ .flowOn(backgroundDispatcher)
+
+ // TODO(b/319837892): Implement reading actual setting
+ override val settingEnabled: StateFlow<Boolean> = MutableStateFlow(true)
+
+ private fun toStickyKeysMap(state: StickyModifierState): LinkedHashMap<ModifierKey, Locked> {
+ val keys = linkedMapOf<ModifierKey, Locked>()
+ state.apply {
+ if (isAltGrModifierOn) keys[ALT_GR] = Locked(false)
+ if (isAltGrModifierLocked) keys[ALT_GR] = Locked(true)
+ if (isAltModifierOn) keys[ALT] = Locked(false)
+ if (isAltModifierLocked) keys[ALT] = Locked(true)
+ if (isCtrlModifierOn) keys[CTRL] = Locked(false)
+ if (isCtrlModifierLocked) keys[CTRL] = Locked(true)
+ if (isMetaModifierOn) keys[META] = Locked(false)
+ if (isMetaModifierLocked) keys[META] = Locked(true)
+ if (isShiftModifierOn) keys[SHIFT] = Locked(false)
+ if (isShiftModifierLocked) keys[SHIFT] = Locked(true)
+ }
+ return keys
+ }
+
+ companion object {
+ const val TAG = "StickyKeysRepositoryImpl"
+ }
+}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt
similarity index 63%
copy from core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
copy to packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt
index ce92b6d..d5f082a 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.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.
@@ -13,10 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.companion.virtual.camera;
-/**
- * The configuration of a single virtual camera stream.
- * @hide
- */
-parcelable VirtualCameraStreamConfig;
\ No newline at end of file
+package com.android.systemui.keyboard.stickykeys.shared.model
+
+@JvmInline
+value class Locked(val locked: Boolean)
+
+enum class ModifierKey(val text: String) {
+ ALT("ALT LEFT"),
+ ALT_GR("ALT RIGHT"),
+ CTRL("CTRL"),
+ META("META"),
+ SHIFT("SHIFT"),
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModel.kt
new file mode 100644
index 0000000..26eb706
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModel.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.keyboard.stickykeys.ui.viewmodel
+
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyboard.data.repository.KeyboardRepository
+import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepository
+import com.android.systemui.keyboard.stickykeys.shared.model.Locked
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.stateIn
+import javax.inject.Inject
+
+class StickyKeysIndicatorViewModel
+@Inject
+constructor(
+ stickyKeysRepository: StickyKeysRepository,
+ keyboardRepository: KeyboardRepository,
+ @Application applicationScope: CoroutineScope,
+) {
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ val indicatorContent: Flow<Map<ModifierKey, Locked>> =
+ keyboardRepository.isAnyKeyboardConnected
+ .flatMapLatest { keyboardPresent ->
+ if (keyboardPresent) stickyKeysRepository.settingEnabled else flowOf(false)
+ }
+ .flatMapLatest { enabled ->
+ if (enabled) stickyKeysRepository.stickyKeys else flowOf(emptyMap())
+ }
+ .stateIn(applicationScope, SharingStarted.Lazily, emptyMap())
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
index b373f85..fede479 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
@@ -53,14 +53,14 @@
@SysUISingleton
@FaceAuthTableLog
fun provideFaceAuthTableLog(factory: TableLogBufferFactory): TableLogBuffer {
- return factory.create("FaceAuthTableLog", 100)
+ return factory.create("FaceAuthTableLog", 400)
}
@Provides
@SysUISingleton
@FaceDetectTableLog
fun provideFaceDetectTableLog(factory: TableLogBufferFactory): TableLogBuffer {
- return factory.create("FaceDetectTableLog", 100)
+ return factory.create("FaceDetectTableLog", 400)
}
}
}
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 704ebdd..d012d24 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
@@ -40,11 +40,13 @@
import com.android.systemui.keyguard.shared.model.KeyguardDone
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
@@ -54,7 +56,10 @@
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
/** Defines interface for classes that encapsulate application state for the keyguard. */
@@ -208,6 +213,12 @@
val clockShouldBeCentered: Flow<Boolean>
/**
+ * Whether the primary authentication is required for the given user due to lockdown or
+ * encryption after reboot.
+ */
+ val isEncryptedOrLockdown: Flow<Boolean>
+
+ /**
* Returns `true` if the keyguard is showing; `false` otherwise.
*
* Note: this is also `true` when the lock-screen is occluded with an `Activity` "above" it in
@@ -279,6 +290,7 @@
@Application private val scope: CoroutineScope,
private val systemClock: SystemClock,
facePropertyRepository: FacePropertyRepository,
+ private val userTracker: UserTracker,
) : KeyguardRepository {
private val _dismissAction: MutableStateFlow<DismissAction> =
MutableStateFlow(DismissAction.None)
@@ -544,6 +556,24 @@
awaitClose { dozeTransitionListener.removeCallback(callback) }
}
+ @OptIn(ExperimentalCoroutinesApi::class)
+ override val isEncryptedOrLockdown: Flow<Boolean> =
+ conflatedCallbackFlow {
+ val callback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onStrongAuthStateChanged(userId: Int) {
+ trySend(userId)
+ }
+ }
+
+ keyguardUpdateMonitor.registerCallback(callback)
+
+ awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
+ }
+ .filter { userId -> userId == userTracker.userId }
+ .onStart { emit(userTracker.userId) }
+ .mapLatest { userId -> keyguardUpdateMonitor.isEncryptedOrLockdown(userId) }
+
override fun isKeyguardShowing(): Boolean {
return keyguardStateController.isShowing
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt
new file mode 100644
index 0000000..afe9151
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.view.View
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+@SysUISingleton
+class KeyguardSmartspaceRepository @Inject constructor() {
+ private val _bcSmartspaceVisibility: MutableStateFlow<Int> = MutableStateFlow(View.GONE)
+ val bcSmartspaceVisibility: StateFlow<Int> = _bcSmartspaceVisibility.asStateFlow()
+
+ fun setBcSmartspaceVisibility(visibility: Int) {
+ _bcSmartspaceVisibility.value = visibility
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
index ecf78d5..b1a2297 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
@@ -30,6 +30,7 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
@@ -82,10 +83,11 @@
*/
val showIndicatorForDeviceEntry: Flow<Boolean> =
combine(showIndicatorForPrimaryBouncer, showIndicatorForAlternateBouncer) {
- showForPrimaryBouncer,
- showForAlternateBouncer ->
- showForPrimaryBouncer || showForAlternateBouncer
- }
+ showForPrimaryBouncer,
+ showForAlternateBouncer ->
+ showForPrimaryBouncer || showForAlternateBouncer
+ }
+ .distinctUntilChanged()
private fun shouldShowIndicatorForPrimaryBouncer(): Boolean {
val sfpsEnabled: Boolean =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index 52f2759..7b1466c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -18,7 +18,8 @@
import android.animation.ValueAnimator
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.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -28,6 +29,7 @@
import com.android.wm.shell.animation.Interpolators
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.combine
@@ -39,13 +41,18 @@
@Inject
constructor(
override val transitionRepository: KeyguardTransitionRepository,
- override val transitionInteractor: KeyguardTransitionInteractor,
- @Application private val scope: CoroutineScope,
+ transitionInteractor: KeyguardTransitionInteractor,
+ @Background private val scope: CoroutineScope,
+ @Background bgDispatcher: CoroutineDispatcher,
+ @Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
private val powerInteractor: PowerInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.ALTERNATE_BOUNCER,
+ transitionInteractor = transitionInteractor,
+ mainDispatcher = mainDispatcher,
+ bgDispatcher = bgDispatcher,
) {
override fun start() {
@@ -65,7 +72,7 @@
.sample(
combine(
keyguardInteractor.primaryBouncerShowing,
- transitionInteractor.startedKeyguardTransitionStep,
+ startedKeyguardTransitionStep,
powerInteractor.isAwake,
keyguardInteractor.isAodAvailable,
::toQuad
@@ -102,20 +109,19 @@
private fun listenForAlternateBouncerToGone() {
scope.launch {
- keyguardInteractor.isKeyguardGoingAway
- .sample(transitionInteractor.finishedKeyguardState, ::Pair)
- .collect { (isKeyguardGoingAway, keyguardState) ->
- if (isKeyguardGoingAway && keyguardState == KeyguardState.ALTERNATE_BOUNCER) {
- startTransitionTo(KeyguardState.GONE)
- }
+ keyguardInteractor.isKeyguardGoingAway.sample(finishedKeyguardState, ::Pair).collect {
+ (isKeyguardGoingAway, keyguardState) ->
+ if (isKeyguardGoingAway && keyguardState == KeyguardState.ALTERNATE_BOUNCER) {
+ startTransitionTo(KeyguardState.GONE)
}
+ }
}
}
private fun listenForAlternateBouncerToPrimaryBouncer() {
scope.launch {
keyguardInteractor.primaryBouncerShowing
- .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .sample(startedKeyguardTransitionStep, ::Pair)
.collect { (isPrimaryBouncerShowing, startedKeyguardState) ->
if (
isPrimaryBouncerShowing &&
@@ -139,8 +145,10 @@
}
companion object {
+ const val TAG = "FromAlternateBouncerTransitionInteractor"
val TRANSITION_DURATION_MS = 300.milliseconds
val TO_GONE_DURATION = 500.milliseconds
val TO_AOD_DURATION = TRANSITION_DURATION_MS
+ val TO_PRIMARY_BOUNCER_DURATION = TRANSITION_DURATION_MS
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 8584401..8fa33ee7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -19,7 +19,8 @@
import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
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.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
import com.android.systemui.keyguard.shared.model.DozeStateModel
@@ -29,6 +30,7 @@
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
@@ -38,48 +40,68 @@
@Inject
constructor(
override val transitionRepository: KeyguardTransitionRepository,
- override val transitionInteractor: KeyguardTransitionInteractor,
- @Application private val scope: CoroutineScope,
+ transitionInteractor: KeyguardTransitionInteractor,
+ @Background private val scope: CoroutineScope,
+ @Background bgDispatcher: CoroutineDispatcher,
+ @Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.AOD,
+ transitionInteractor = transitionInteractor,
+ mainDispatcher = mainDispatcher,
+ bgDispatcher = bgDispatcher,
) {
override fun start() {
- listenForAodToLockscreenOrOccluded()
+ listenForAodToLockscreen()
+ listenForAodToPrimaryBouncer()
listenForAodToGone()
+ listenForAodToOccluded()
listenForTransitionToCamera(scope, keyguardInteractor)
}
- private fun listenForAodToLockscreenOrOccluded() {
+ /**
+ * There are cases where the transition to AOD begins but never completes, such as tapping power
+ * during an incoming phone call when unlocked. In this case, GONE->AOD should be interrupted to
+ * run AOD->OCCLUDED.
+ */
+ private fun listenForAodToOccluded() {
+ scope.launch {
+ keyguardInteractor.isKeyguardOccluded.sample(startedKeyguardState, ::Pair).collect {
+ (isOccluded, startedKeyguardState) ->
+ if (isOccluded && startedKeyguardState == KeyguardState.AOD) {
+ startTransitionTo(
+ toState = KeyguardState.OCCLUDED,
+ modeOnCanceled = TransitionModeOnCanceled.RESET
+ )
+ }
+ }
+ }
+ }
+
+ private fun listenForAodToLockscreen() {
scope.launch {
keyguardInteractor
.dozeTransitionTo(DozeStateModel.FINISH)
.sample(
combine(
- transitionInteractor.startedKeyguardTransitionStep,
+ startedKeyguardTransitionStep,
keyguardInteractor.isKeyguardOccluded,
::Pair
),
::toTriple
)
.collect { (_, lastStartedStep, occluded) ->
- if (lastStartedStep.to == KeyguardState.AOD) {
- val toState =
- if (occluded) KeyguardState.OCCLUDED else KeyguardState.LOCKSCREEN
+ if (lastStartedStep.to == KeyguardState.AOD && !occluded) {
val modeOnCanceled =
- if (
- toState == KeyguardState.LOCKSCREEN &&
- lastStartedStep.from == KeyguardState.LOCKSCREEN
- ) {
+ if (lastStartedStep.from == KeyguardState.LOCKSCREEN) {
TransitionModeOnCanceled.REVERSE
} else {
TransitionModeOnCanceled.LAST_VALUE
}
-
startTransitionTo(
- toState = toState,
+ toState = KeyguardState.LOCKSCREEN,
modeOnCanceled = modeOnCanceled,
)
}
@@ -87,20 +109,32 @@
}
}
- private fun listenForAodToGone() {
+ /**
+ * If there is a biometric lockout and FPS is tapped while on AOD, it should go directly to the
+ * PRIMARY_BOUNCER.
+ */
+ private fun listenForAodToPrimaryBouncer() {
scope.launch {
- keyguardInteractor.biometricUnlockState
- .sample(transitionInteractor.finishedKeyguardState, ::Pair)
- .collect { pair ->
- val (biometricUnlockState, keyguardState) = pair
- if (
- keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState)
- ) {
- startTransitionTo(KeyguardState.GONE)
+ keyguardInteractor.primaryBouncerShowing
+ .sample(startedKeyguardTransitionStep, ::Pair)
+ .collect { (isBouncerShowing, lastStartedTransitionStep) ->
+ if (isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.AOD) {
+ startTransitionTo(KeyguardState.PRIMARY_BOUNCER)
}
}
}
}
+
+ private fun listenForAodToGone() {
+ scope.launch {
+ keyguardInteractor.biometricUnlockState.sample(finishedKeyguardState, ::Pair).collect {
+ (biometricUnlockState, keyguardState) ->
+ if (keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState)) {
+ startTransitionTo(KeyguardState.GONE)
+ }
+ }
+ }
+ }
override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
return ValueAnimator().apply {
interpolator = Interpolators.LINEAR
@@ -113,6 +147,7 @@
}
companion object {
+ const val TAG = "FromAodTransitionInteractor"
private val DEFAULT_DURATION = 500.milliseconds
val TO_LOCKSCREEN_DURATION = 500.milliseconds
val TO_GONE_DURATION = DEFAULT_DURATION
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index eca7088..fcb7698 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -19,7 +19,8 @@
import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
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.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -28,6 +29,7 @@
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
@@ -37,13 +39,18 @@
@Inject
constructor(
override val transitionRepository: KeyguardTransitionRepository,
- override val transitionInteractor: KeyguardTransitionInteractor,
- @Application private val scope: CoroutineScope,
+ transitionInteractor: KeyguardTransitionInteractor,
+ @Background private val scope: CoroutineScope,
+ @Background bgDispatcher: CoroutineDispatcher,
+ @Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
private val powerInteractor: PowerInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.DOZING,
+ transitionInteractor = transitionInteractor,
+ mainDispatcher = mainDispatcher,
+ bgDispatcher = bgDispatcher,
) {
override fun start() {
@@ -57,7 +64,7 @@
powerInteractor.isAwake
.sample(
combine(
- transitionInteractor.startedKeyguardTransitionStep,
+ startedKeyguardTransitionStep,
keyguardInteractor.isKeyguardOccluded,
::Pair
),
@@ -76,7 +83,7 @@
private fun listenForDozingToGone() {
scope.launch {
keyguardInteractor.biometricUnlockState
- .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .sample(startedKeyguardTransitionStep, ::Pair)
.collect { (biometricUnlockState, lastStartedTransition) ->
if (
lastStartedTransition.to == KeyguardState.DOZING &&
@@ -96,6 +103,7 @@
}
companion object {
+ const val TAG = "FromDozingTransitionInteractor"
private val DEFAULT_DURATION = 500.milliseconds
val TO_LOCKSCREEN_DURATION = DEFAULT_DURATION
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
index dac6ef5..a6cdaa8c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
@@ -19,7 +19,8 @@
import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
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.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.DozeStateModel
@@ -28,6 +29,7 @@
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.combine
@@ -39,12 +41,17 @@
@Inject
constructor(
override val transitionRepository: KeyguardTransitionRepository,
- override val transitionInteractor: KeyguardTransitionInteractor,
- @Application private val scope: CoroutineScope,
+ transitionInteractor: KeyguardTransitionInteractor,
+ @Background private val scope: CoroutineScope,
+ @Background bgDispatcher: CoroutineDispatcher,
+ @Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+ transitionInteractor = transitionInteractor,
+ mainDispatcher = mainDispatcher,
+ bgDispatcher = bgDispatcher,
) {
override fun start() {
@@ -64,7 +71,7 @@
.sample(
combine(
keyguardInteractor.dozeTransitionModel,
- transitionInteractor.startedKeyguardTransitionStep,
+ startedKeyguardTransitionStep,
::Pair
),
::toTriple
@@ -88,7 +95,7 @@
.sample(
combine(
keyguardInteractor.isKeyguardOccluded,
- transitionInteractor.startedKeyguardTransitionStep,
+ startedKeyguardTransitionStep,
::Pair,
),
::toTriple
@@ -108,7 +115,7 @@
private fun listenForDreamingLockscreenHostedToPrimaryBouncer() {
scope.launch {
keyguardInteractor.primaryBouncerShowing
- .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .sample(startedKeyguardTransitionStep, ::Pair)
.collect { (isBouncerShowing, lastStartedTransitionStep) ->
if (
isBouncerShowing &&
@@ -123,7 +130,7 @@
private fun listenForDreamingLockscreenHostedToGone() {
scope.launch {
keyguardInteractor.biometricUnlockState
- .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .sample(startedKeyguardTransitionStep, ::Pair)
.collect { (biometricUnlockState, lastStartedTransitionStep) ->
if (
lastStartedTransitionStep.to == KeyguardState.DREAMING_LOCKSCREEN_HOSTED &&
@@ -137,11 +144,7 @@
private fun listenForDreamingLockscreenHostedToDozing() {
scope.launch {
- combine(
- keyguardInteractor.dozeTransitionModel,
- transitionInteractor.startedKeyguardTransitionStep,
- ::Pair
- )
+ combine(keyguardInteractor.dozeTransitionModel, startedKeyguardTransitionStep, ::Pair)
.collect { (dozeTransitionModel, lastStartedTransitionStep) ->
if (
dozeTransitionModel.to == DozeStateModel.DOZE &&
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 7fdcf2f..13ffd63 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
@@ -19,7 +19,8 @@
import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
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.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.DozeStateModel
@@ -28,6 +29,7 @@
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
@@ -37,12 +39,17 @@
@Inject
constructor(
override val transitionRepository: KeyguardTransitionRepository,
- override val transitionInteractor: KeyguardTransitionInteractor,
- @Application private val scope: CoroutineScope,
+ transitionInteractor: KeyguardTransitionInteractor,
+ @Background private val scope: CoroutineScope,
+ @Background bgDispatcher: CoroutineDispatcher,
+ @Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.DREAMING,
+ transitionInteractor = transitionInteractor,
+ mainDispatcher = mainDispatcher,
+ bgDispatcher = bgDispatcher,
) {
override fun start() {
@@ -66,7 +73,7 @@
private fun listenForDreamingToOccluded() {
scope.launch {
combine(keyguardInteractor.isKeyguardOccluded, keyguardInteractor.isDreaming, ::Pair)
- .sample(transitionInteractor.startedKeyguardTransitionStep, ::toTriple)
+ .sample(startedKeyguardTransitionStep, ::toTriple)
.collect { (isOccluded, isDreaming, lastStartedTransition) ->
if (
isOccluded &&
@@ -82,7 +89,7 @@
private fun listenForDreamingToGone() {
scope.launch {
keyguardInteractor.biometricUnlockState
- .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .sample(startedKeyguardTransitionStep, ::Pair)
.collect { (biometricUnlockState, lastStartedTransitionStep) ->
if (
lastStartedTransitionStep.to == KeyguardState.DREAMING &&
@@ -96,11 +103,7 @@
private fun listenForDreamingToAodOrDozing() {
scope.launch {
- combine(
- keyguardInteractor.dozeTransitionModel,
- transitionInteractor.finishedKeyguardState,
- ::Pair
- )
+ combine(keyguardInteractor.dozeTransitionModel, finishedKeyguardState, ::Pair)
.collect { (dozeTransitionModel, keyguardState) ->
if (keyguardState == KeyguardState.DREAMING) {
if (dozeTransitionModel.to == DozeStateModel.DOZE) {
@@ -123,6 +126,7 @@
}
companion object {
+ const val TAG = "FromDreamingTransitionInteractor"
private val DEFAULT_DURATION = 500.milliseconds
val TO_LOCKSCREEN_DURATION = 1167.milliseconds
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index 70c2e6d..48b3d9a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -19,23 +19,37 @@
import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
import com.android.systemui.Flags
+import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineDispatcher
@SysUISingleton
class FromGlanceableHubTransitionInteractor
@Inject
constructor(
+ private val glanceableHubTransitions: GlanceableHubTransitions,
override val transitionRepository: KeyguardTransitionRepository,
- override val transitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(fromState = KeyguardState.GLANCEABLE_HUB) {
+ transitionInteractor: KeyguardTransitionInteractor,
+ @Main mainDispatcher: CoroutineDispatcher,
+ @Background bgDispatcher: CoroutineDispatcher,
+) :
+ TransitionInteractor(
+ fromState = KeyguardState.GLANCEABLE_HUB,
+ transitionInteractor = transitionInteractor,
+ mainDispatcher = mainDispatcher,
+ bgDispatcher = bgDispatcher,
+ ) {
override fun start() {
if (!Flags.communalHub()) {
return
}
+ listenForHubToLockscreen()
}
override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
@@ -45,6 +59,18 @@
}
}
+ /**
+ * Listens for the glanceable hub transition to lock screen and directly drives the keyguard
+ * transition.
+ */
+ private fun listenForHubToLockscreen() {
+ glanceableHubTransitions.listenForLockscreenAndHubTransition(
+ transitionName = "listenForHubToLockscreen",
+ transitionOwnerName = TAG,
+ toScene = CommunalSceneKey.Blank
+ )
+ }
+
companion object {
const val TAG = "FromGlanceableHubTransitionInteractor"
val DEFAULT_DURATION = 500.milliseconds
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index 62a0b0e..742790e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -19,7 +19,8 @@
import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
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.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
@@ -28,6 +29,7 @@
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
@@ -37,13 +39,18 @@
@Inject
constructor(
override val transitionRepository: KeyguardTransitionRepository,
- override val transitionInteractor: KeyguardTransitionInteractor,
- @Application private val scope: CoroutineScope,
+ transitionInteractor: KeyguardTransitionInteractor,
+ @Background private val scope: CoroutineScope,
+ @Background bgDispatcher: CoroutineDispatcher,
+ @Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
private val powerInteractor: PowerInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.GONE,
+ transitionInteractor = transitionInteractor,
+ mainDispatcher = mainDispatcher,
+ bgDispatcher = bgDispatcher,
) {
override fun start() {
@@ -57,7 +64,7 @@
private fun listenForGoneToLockscreen() {
scope.launch {
keyguardInteractor.isKeyguardShowing
- .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .sample(startedKeyguardTransitionStep, ::Pair)
.collect { (isKeyguardShowing, lastStartedStep) ->
if (isKeyguardShowing && lastStartedStep.to == KeyguardState.GONE) {
startTransitionTo(KeyguardState.LOCKSCREEN)
@@ -69,7 +76,7 @@
private fun listenForGoneToDreamingLockscreenHosted() {
scope.launch {
keyguardInteractor.isActiveDreamLockscreenHosted
- .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .sample(startedKeyguardTransitionStep, ::Pair)
.collect { (isActiveDreamLockscreenHosted, lastStartedStep) ->
if (isActiveDreamLockscreenHosted && lastStartedStep.to == KeyguardState.GONE) {
startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
@@ -83,7 +90,7 @@
keyguardInteractor.isAbleToDream
.sample(
combine(
- transitionInteractor.startedKeyguardTransitionStep,
+ startedKeyguardTransitionStep,
keyguardInteractor.isActiveDreamLockscreenHosted,
::Pair
),
@@ -106,7 +113,7 @@
powerInteractor.isAsleep
.sample(
combine(
- transitionInteractor.startedKeyguardTransitionStep,
+ startedKeyguardTransitionStep,
keyguardInteractor.isAodAvailable,
::Pair
),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index cecc653..8b2b45f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -18,9 +18,10 @@
import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
-import com.android.app.tracing.coroutines.launch
+import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
@@ -39,28 +40,37 @@
import java.util.UUID
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
@SysUISingleton
class FromLockscreenTransitionInteractor
@Inject
constructor(
override val transitionRepository: KeyguardTransitionRepository,
- override val transitionInteractor: KeyguardTransitionInteractor,
- @Application private val scope: CoroutineScope,
+ transitionInteractor: KeyguardTransitionInteractor,
+ @Background private val scope: CoroutineScope,
+ @Background bgDispatcher: CoroutineDispatcher,
+ @Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
private val flags: FeatureFlags,
private val shadeRepository: ShadeRepository,
private val powerInteractor: PowerInteractor,
+ private val glanceableHubTransitions: GlanceableHubTransitions,
inWindowLauncherUnlockAnimationInteractor: Lazy<InWindowLauncherUnlockAnimationInteractor>,
) :
TransitionInteractor(
fromState = KeyguardState.LOCKSCREEN,
+ transitionInteractor = transitionInteractor,
+ mainDispatcher = mainDispatcher,
+ bgDispatcher = bgDispatcher,
) {
override fun start() {
@@ -73,6 +83,7 @@
listenForLockscreenToPrimaryBouncerDragging()
listenForLockscreenToAlternateBouncer()
listenForLockscreenTransitionToCamera()
+ listenForLockscreenToGlanceableHub()
}
/**
@@ -147,12 +158,12 @@
private fun listenForLockscreenToDreaming() {
val invalidFromStates = setOf(KeyguardState.AOD, KeyguardState.DOZING)
- scope.launch("$TAG#listenForLockscreenToDreaming") {
+ scope.launch {
keyguardInteractor.isAbleToDream
.sample(
combine(
- transitionInteractor.startedKeyguardTransitionStep,
- transitionInteractor.finishedKeyguardState,
+ startedKeyguardTransitionStep,
+ finishedKeyguardState,
keyguardInteractor.isActiveDreamLockscreenHosted,
::Triple
),
@@ -180,9 +191,9 @@
}
private fun listenForLockscreenToPrimaryBouncer() {
- scope.launch("$TAG#listenForLockscreenToPrimaryBouncer") {
+ scope.launch {
keyguardInteractor.primaryBouncerShowing
- .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .sample(startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
val (isBouncerShowing, lastStartedTransitionStep) = pair
if (
@@ -195,9 +206,9 @@
}
private fun listenForLockscreenToAlternateBouncer() {
- scope.launch("$TAG#listenForLockscreenToAlternateBouncer") {
+ scope.launch {
keyguardInteractor.alternateBouncerShowing
- .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .sample(startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
val (isAlternateBouncerShowing, lastStartedTransitionStep) = pair
if (
@@ -213,11 +224,11 @@
/* Starts transitions when manually dragging up the bouncer from the lockscreen. */
private fun listenForLockscreenToPrimaryBouncerDragging() {
var transitionId: UUID? = null
- scope.launch("$TAG#listenForLockscreenToPrimaryBouncerDragging") {
+ scope.launch {
shadeRepository.legacyShadeExpansion
.sample(
combine(
- transitionInteractor.startedKeyguardTransitionStep,
+ startedKeyguardTransitionStep,
keyguardInteractor.statusBarState,
keyguardInteractor.isKeyguardUnlocked,
::Triple
@@ -225,64 +236,66 @@
::toQuad
)
.collect { (shadeExpansion, keyguardState, statusBarState, isKeyguardUnlocked) ->
- val id = transitionId
- if (id != null) {
- if (keyguardState.to == KeyguardState.PRIMARY_BOUNCER) {
- // An existing `id` means a transition is started, and calls to
- // `updateTransition` will control it until FINISHED or CANCELED
- var nextState =
- if (shadeExpansion == 0f) {
- TransitionState.FINISHED
- } else if (shadeExpansion == 1f) {
- TransitionState.CANCELED
- } else {
- TransitionState.RUNNING
+ withContext(mainDispatcher) {
+ val id = transitionId
+ if (id != null) {
+ if (keyguardState.to == KeyguardState.PRIMARY_BOUNCER) {
+ // An existing `id` means a transition is started, and calls to
+ // `updateTransition` will control it until FINISHED or CANCELED
+ var nextState =
+ if (shadeExpansion == 0f) {
+ TransitionState.FINISHED
+ } else if (shadeExpansion == 1f) {
+ TransitionState.CANCELED
+ } else {
+ TransitionState.RUNNING
+ }
+ transitionRepository.updateTransition(
+ id,
+ 1f - shadeExpansion,
+ nextState,
+ )
+
+ if (
+ nextState == TransitionState.CANCELED ||
+ nextState == TransitionState.FINISHED
+ ) {
+ transitionId = null
}
- transitionRepository.updateTransition(
- id,
- 1f - shadeExpansion,
- nextState,
- )
- if (
- nextState == TransitionState.CANCELED ||
- nextState == TransitionState.FINISHED
- ) {
- transitionId = null
- }
-
- // If canceled, just put the state back
- // TODO(b/278086361): This logic should happen in
- // FromPrimaryBouncerInteractor.
- if (nextState == TransitionState.CANCELED) {
- transitionRepository.startTransition(
- TransitionInfo(
- ownerName = name,
- from = KeyguardState.PRIMARY_BOUNCER,
- to = KeyguardState.LOCKSCREEN,
- animator =
- getDefaultAnimatorForTransitionsToState(
- KeyguardState.LOCKSCREEN
- )
- .apply { duration = 0 }
+ // If canceled, just put the state back
+ // TODO(b/278086361): This logic should happen in
+ // FromPrimaryBouncerInteractor.
+ if (nextState == TransitionState.CANCELED) {
+ transitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = name,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.LOCKSCREEN,
+ animator =
+ getDefaultAnimatorForTransitionsToState(
+ KeyguardState.LOCKSCREEN
+ )
+ .apply { duration = 0 }
+ )
)
- )
+ }
}
- }
- } else {
- // TODO (b/251849525): Remove statusbarstate check when that state is
- // integrated into KeyguardTransitionRepository
- if (
- keyguardState.to == KeyguardState.LOCKSCREEN &&
- shadeRepository.legacyShadeTracking.value &&
- !isKeyguardUnlocked &&
- statusBarState == KEYGUARD
- ) {
- transitionId =
- startTransitionTo(
- toState = KeyguardState.PRIMARY_BOUNCER,
- animator = null, // transition will be manually controlled
- )
+ } else {
+ // TODO (b/251849525): Remove statusbarstate check when that state is
+ // integrated into KeyguardTransitionRepository
+ if (
+ keyguardState.to == KeyguardState.LOCKSCREEN &&
+ shadeRepository.legacyShadeTracking.value &&
+ !isKeyguardUnlocked &&
+ statusBarState == KEYGUARD
+ ) {
+ transitionId =
+ startTransitionTo(
+ toState = KeyguardState.PRIMARY_BOUNCER,
+ animator = null, // transition will be manually controlled
+ )
+ }
}
}
}
@@ -290,7 +303,7 @@
}
fun dismissKeyguard() {
- startTransitionTo(KeyguardState.GONE)
+ scope.launch { startTransitionTo(KeyguardState.GONE) }
}
private fun listenForLockscreenToGone() {
@@ -298,9 +311,9 @@
return
}
- scope.launch("$TAG#listenForLockscreenToGone") {
+ scope.launch {
keyguardInteractor.isKeyguardGoingAway
- .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .sample(startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
val (isKeyguardGoingAway, lastStartedStep) = pair
if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
@@ -315,9 +328,9 @@
return
}
- scope.launch("$TAG#listenForLockscreenToGoneDragging") {
+ scope.launch {
keyguardInteractor.isKeyguardGoingAway
- .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .sample(startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
val (isKeyguardGoingAway, lastStartedStep) = pair
if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
@@ -328,23 +341,22 @@
}
private fun listenForLockscreenToOccluded() {
- scope.launch("$TAG#listenForLockscreenToOccluded") {
- keyguardInteractor.isKeyguardOccluded
- .sample(transitionInteractor.startedKeyguardState, ::Pair)
- .collect { (isOccluded, keyguardState) ->
- if (isOccluded && keyguardState == KeyguardState.LOCKSCREEN) {
- startTransitionTo(KeyguardState.OCCLUDED)
- }
+ scope.launch {
+ keyguardInteractor.isKeyguardOccluded.sample(startedKeyguardState, ::Pair).collect {
+ (isOccluded, keyguardState) ->
+ if (isOccluded && keyguardState == KeyguardState.LOCKSCREEN) {
+ startTransitionTo(KeyguardState.OCCLUDED)
}
+ }
}
}
private fun listenForLockscreenToAodOrDozing() {
- scope.launch("$TAG#listenForLockscreenToAodOrDozing") {
+ scope.launch {
powerInteractor.isAsleep
.sample(
combine(
- transitionInteractor.startedKeyguardTransitionStep,
+ startedKeyguardTransitionStep,
keyguardInteractor.isAodAvailable,
::Pair
),
@@ -372,6 +384,22 @@
}
}
+ /**
+ * Listens for transition from glanceable hub back to lock screen and directly drives the
+ * keyguard transition.
+ */
+ private fun listenForLockscreenToGlanceableHub() {
+ if (!com.android.systemui.Flags.communalHub()) {
+ return
+ }
+
+ glanceableHubTransitions.listenForLockscreenAndHubTransition(
+ transitionName = "listenForLockscreenToGlanceableHub",
+ transitionOwnerName = TAG,
+ toScene = CommunalSceneKey.Communal
+ )
+ }
+
override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
return ValueAnimator().apply {
interpolator = Interpolators.LINEAR
@@ -397,5 +425,6 @@
val TO_AOD_DURATION = 500.milliseconds
val TO_PRIMARY_BOUNCER_DURATION = DEFAULT_DURATION
val TO_GONE_DURATION = DEFAULT_DURATION
+ val TO_GLANCEABLE_HUB_DURATION = DEFAULT_DURATION
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index 6a8555c..40061f4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -19,7 +19,8 @@
import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
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.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -27,6 +28,7 @@
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
@@ -36,13 +38,18 @@
@Inject
constructor(
override val transitionRepository: KeyguardTransitionRepository,
- override val transitionInteractor: KeyguardTransitionInteractor,
- @Application private val scope: CoroutineScope,
+ transitionInteractor: KeyguardTransitionInteractor,
+ @Background private val scope: CoroutineScope,
+ @Background bgDispatcher: CoroutineDispatcher,
+ @Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
private val powerInteractor: PowerInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.OCCLUDED,
+ transitionInteractor = transitionInteractor,
+ mainDispatcher = mainDispatcher,
+ bgDispatcher = bgDispatcher,
) {
override fun start() {
@@ -57,7 +64,7 @@
private fun listenForOccludedToPrimaryBouncer() {
scope.launch {
keyguardInteractor.primaryBouncerShowing
- .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .sample(startedKeyguardTransitionStep, ::Pair)
.collect { (isBouncerShowing, lastStartedTransitionStep) ->
if (
isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.OCCLUDED
@@ -70,13 +77,12 @@
private fun listenForOccludedToDreaming() {
scope.launch {
- keyguardInteractor.isAbleToDream
- .sample(transitionInteractor.finishedKeyguardState, ::Pair)
- .collect { (isAbleToDream, keyguardState) ->
- if (isAbleToDream && keyguardState == KeyguardState.OCCLUDED) {
- startTransitionTo(KeyguardState.DREAMING)
- }
+ keyguardInteractor.isAbleToDream.sample(finishedKeyguardState, ::Pair).collect {
+ (isAbleToDream, keyguardState) ->
+ if (isAbleToDream && keyguardState == KeyguardState.OCCLUDED) {
+ startTransitionTo(KeyguardState.DREAMING)
}
+ }
}
}
@@ -86,7 +92,7 @@
.sample(
combine(
keyguardInteractor.isKeyguardShowing,
- transitionInteractor.startedKeyguardTransitionStep,
+ startedKeyguardTransitionStep,
::Pair
),
::toTriple
@@ -111,7 +117,7 @@
.sample(
combine(
keyguardInteractor.isKeyguardShowing,
- transitionInteractor.startedKeyguardTransitionStep,
+ startedKeyguardTransitionStep,
::Pair
),
::toTriple
@@ -135,7 +141,7 @@
powerInteractor.isAsleep
.sample(
combine(
- transitionInteractor.startedKeyguardTransitionStep,
+ startedKeyguardTransitionStep,
keyguardInteractor.isAodAvailable,
::Pair
),
@@ -154,7 +160,7 @@
private fun listenForOccludedToAlternateBouncer() {
scope.launch {
keyguardInteractor.alternateBouncerShowing
- .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .sample(startedKeyguardTransitionStep, ::Pair)
.collect { (isAlternateBouncerShowing, lastStartedTransitionStep) ->
if (
isAlternateBouncerShowing &&
@@ -183,6 +189,7 @@
}
companion object {
+ const val TAG = "FromOccludedTransitionInteractor"
private val DEFAULT_DURATION = 500.milliseconds
val TO_LOCKSCREEN_DURATION = 933.milliseconds
val TO_AOD_DURATION = DEFAULT_DURATION
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index 5f246e1..c62055f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -19,7 +19,8 @@
import android.animation.ValueAnimator
import com.android.keyguard.KeyguardSecurityModel
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
@@ -35,6 +36,7 @@
import com.android.wm.shell.animation.Interpolators
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -47,8 +49,10 @@
@Inject
constructor(
override val transitionRepository: KeyguardTransitionRepository,
- override val transitionInteractor: KeyguardTransitionInteractor,
- @Application private val scope: CoroutineScope,
+ transitionInteractor: KeyguardTransitionInteractor,
+ @Background private val scope: CoroutineScope,
+ @Background bgDispatcher: CoroutineDispatcher,
+ @Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
private val flags: FeatureFlags,
private val keyguardSecurityModel: KeyguardSecurityModel,
@@ -57,6 +61,9 @@
) :
TransitionInteractor(
fromState = KeyguardState.PRIMARY_BOUNCER,
+ transitionInteractor = transitionInteractor,
+ mainDispatcher = mainDispatcher,
+ bgDispatcher = bgDispatcher,
) {
override fun start() {
@@ -115,7 +122,7 @@
.distinctUntilChanged()
fun dismissPrimaryBouncer() {
- startTransitionTo(KeyguardState.GONE)
+ scope.launch { startTransitionTo(KeyguardState.GONE) }
}
private fun listenForPrimaryBouncerToLockscreenOrOccluded() {
@@ -124,7 +131,7 @@
.sample(
combine(
powerInteractor.isAwake,
- transitionInteractor.startedKeyguardTransitionStep,
+ startedKeyguardTransitionStep,
keyguardInteractor.isKeyguardOccluded,
keyguardInteractor.isActiveDreamLockscreenHosted,
::toQuad
@@ -158,7 +165,7 @@
.sample(
combine(
powerInteractor.isAsleep,
- transitionInteractor.startedKeyguardTransitionStep,
+ startedKeyguardTransitionStep,
keyguardInteractor.isAodAvailable,
::Triple
),
@@ -185,7 +192,7 @@
.sample(
combine(
keyguardInteractor.isActiveDreamLockscreenHosted,
- transitionInteractor.startedKeyguardTransitionStep,
+ startedKeyguardTransitionStep,
::Pair
),
::toTriple
@@ -213,7 +220,7 @@
scope.launch {
keyguardInteractor.isKeyguardGoingAway
- .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .sample(startedKeyguardTransitionStep, ::Pair)
.collect { (isKeyguardGoingAway, lastStartedTransitionStep) ->
if (
isKeyguardGoingAway &&
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
new file mode 100644
index 0000000..cb50839
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
@@ -0,0 +1,151 @@
+/*
+ * 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 android.animation.ValueAnimator
+import com.android.app.animation.Interpolators
+import com.android.app.tracing.coroutines.launch
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalTransitionProgress
+import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.util.kotlin.sample
+import java.util.UUID
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+
+class GlanceableHubTransitions
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ private val transitionInteractor: KeyguardTransitionInteractor,
+ private val transitionRepository: KeyguardTransitionRepository,
+ private val communalInteractor: CommunalInteractor,
+) {
+ /**
+ * Listens for the glanceable hub transition to the specified scene and directly drives the
+ * keyguard transition between the lockscreen and the hub.
+ *
+ * The glanceable hub transition progress is used as the source of truth as it cannot be driven
+ * externally. The progress is used for both transitions caused by user touch input or by
+ * programmatic changes.
+ */
+ fun listenForLockscreenAndHubTransition(
+ transitionName: String,
+ transitionOwnerName: String,
+ toScene: CommunalSceneKey
+ ) {
+ val fromState: KeyguardState
+ val toState: KeyguardState
+ if (toScene == CommunalSceneKey.Blank) {
+ fromState = KeyguardState.GLANCEABLE_HUB
+ toState = KeyguardState.LOCKSCREEN
+ } else {
+ fromState = KeyguardState.LOCKSCREEN
+ toState = KeyguardState.GLANCEABLE_HUB
+ }
+ var transitionId: UUID? = null
+ scope.launch("$transitionOwnerName#$transitionName") {
+ communalInteractor
+ .transitionProgressToScene(toScene)
+ .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .collect { pair ->
+ val (transitionProgress, lastStartedStep) = pair
+
+ val id = transitionId
+ if (id == null) {
+ // No transition started.
+ if (
+ transitionProgress is CommunalTransitionProgress.Transition &&
+ lastStartedStep.to == fromState
+ ) {
+ transitionId =
+ transitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = transitionOwnerName,
+ from = fromState,
+ to = toState,
+ animator = null, // transition will be manually controlled
+ )
+ )
+ }
+ } else {
+ if (lastStartedStep.to != toState) {
+ return@collect
+ }
+ // An existing `id` means a transition is started, and calls to
+ // `updateTransition` will control it until FINISHED or CANCELED
+ val nextState: TransitionState
+ val progressFraction: Float
+ when (transitionProgress) {
+ is CommunalTransitionProgress.Idle -> {
+ if (transitionProgress.scene == toScene) {
+ nextState = TransitionState.FINISHED
+ progressFraction = 1f
+ } else {
+ nextState = TransitionState.CANCELED
+ progressFraction = 0f
+ }
+ }
+ is CommunalTransitionProgress.Transition -> {
+ nextState = TransitionState.RUNNING
+ progressFraction = transitionProgress.progress
+ }
+ is CommunalTransitionProgress.OtherTransition -> {
+ // Shouldn't happen but if another transition starts during the
+ // current one, mark the current one as canceled.
+ nextState = TransitionState.CANCELED
+ progressFraction = 0f
+ }
+ }
+ transitionRepository.updateTransition(
+ id,
+ progressFraction,
+ nextState,
+ )
+
+ if (
+ nextState == TransitionState.CANCELED ||
+ nextState == TransitionState.FINISHED
+ ) {
+ transitionId = null
+ }
+
+ // If canceled, just put the state back.
+ if (nextState == TransitionState.CANCELED) {
+ transitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = transitionOwnerName,
+ from = toState,
+ to = fromState,
+ animator =
+ ValueAnimator().apply {
+ interpolator = Interpolators.LINEAR
+ duration = 0
+ }
+ )
+ )
+ }
+ }
+ }
+ }
+ }
+}
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 6eb3b64..6170356 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
@@ -264,6 +264,12 @@
}
}
+ /**
+ * Whether the primary authentication is required for the given user due to lockdown or
+ * encryption after reboot.
+ */
+ val isEncryptedOrLockdown: Flow<Boolean> = repository.isEncryptedOrLockdown
+
fun dozeTransitionTo(vararg states: DozeStateModel): Flow<DozeTransitionModel> {
return dozeTransitionModel.filter { states.contains(it.to) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSmartspaceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSmartspaceInteractor.kt
new file mode 100644
index 0000000..67b5745
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSmartspaceInteractor.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardSmartspaceRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
+
+@SysUISingleton
+class KeyguardSmartspaceInteractor
+@Inject
+constructor(private val keyguardSmartspaceRepository: KeyguardSmartspaceRepository) {
+ var bcSmartspaceVisibility: StateFlow<Int> = keyguardSmartspaceRepository.bcSmartspaceVisibility
+
+ fun setBcSmartspaceVisibility(visibility: Int) {
+ keyguardSmartspaceRepository.setBcSmartspaceVisibility(visibility)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index f7d1543..b43ab5e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -27,6 +27,7 @@
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.DREAMING_LOCKSCREEN_HOSTED
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
@@ -36,17 +37,19 @@
import com.android.systemui.keyguard.shared.model.TransitionStep
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.shareIn
/** Encapsulates business-logic related to the keyguard transitions. */
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class KeyguardTransitionInteractor
@Inject
@@ -135,10 +138,18 @@
val lockscreenToDreamingLockscreenHostedTransition: Flow<TransitionStep> =
repository.transition(LOCKSCREEN, DREAMING_LOCKSCREEN_HOSTED)
+ /** LOCKSCREEN->GLANCEABLE_HUB transition information. */
+ val lockscreenToGlanceableHubTransition: Flow<TransitionStep> =
+ repository.transition(LOCKSCREEN, GLANCEABLE_HUB)
+
/** LOCKSCREEN->OCCLUDED transition information. */
val lockscreenToOccludedTransition: Flow<TransitionStep> =
repository.transition(LOCKSCREEN, OCCLUDED)
+ /** GLANCEABLE_HUB->LOCKSCREEN transition information. */
+ val glanceableHubToLockscreenTransition: Flow<TransitionStep> =
+ repository.transition(GLANCEABLE_HUB, LOCKSCREEN)
+
/** OCCLUDED->LOCKSCREEN transition information. */
val occludedToLockscreenTransition: Flow<TransitionStep> =
repository.transition(OCCLUDED, LOCKSCREEN)
@@ -154,6 +165,8 @@
val dozingToLockscreenTransition: Flow<TransitionStep> =
repository.transition(DOZING, LOCKSCREEN)
+ val transitions = repository.transitions
+
/** Receive all [TransitionStep] matching a filter of [from]->[to] */
fun transition(from: KeyguardState, to: KeyguardState): Flow<TransitionStep> {
return repository.transition(from, to)
@@ -181,29 +194,121 @@
val finishedKeyguardTransitionStep: Flow<TransitionStep> =
repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED }
- /** The destination state of the last started transition. */
+ /** The destination state of the last [TransitionState.STARTED] transition. */
val startedKeyguardState: SharedFlow<KeyguardState> =
startedKeyguardTransitionStep
.map { step -> step.to }
.shareIn(scope, SharingStarted.Eagerly, replay = 1)
- /** The last completed [KeyguardState] transition */
+ /**
+ * The last [KeyguardState] to which we [TransitionState.FINISHED] a transition.
+ *
+ * WARNING: This will NOT emit a value if a transition is CANCELED, and will also not emit a
+ * value when a subsequent transition is STARTED. It will *only* emit once we have finally
+ * FINISHED in a state. This can have unintuitive implications.
+ *
+ * For example, if we're transitioning from GONE -> DOZING, and that transition is CANCELED in
+ * favor of a DOZING -> LOCKSCREEN transition, the FINISHED state is still GONE, and will remain
+ * GONE throughout the DOZING -> LOCKSCREEN transition until the DOZING -> LOCKSCREEN transition
+ * finishes (at which point we'll be FINISHED in LOCKSCREEN).
+ *
+ * Since there's no real limit to how many consecutive transitions can be canceled, it's even
+ * possible for the FINISHED state to be the same as the STARTED state while still
+ * transitioning.
+ *
+ * For example:
+ * 1. We're finished in GONE.
+ * 2. The user presses the power button, starting a GONE -> DOZING transition. We're still
+ * FINISHED in GONE.
+ * 3. The user changes their mind, pressing the power button to wake up; this starts a DOZING ->
+ * LOCKSCREEN transition. We're still FINISHED in GONE.
+ * 4. The user quickly swipes away the lockscreen prior to DOZING -> LOCKSCREEN finishing; this
+ * starts a LOCKSCREEN -> GONE transition. We're still FINISHED in GONE, but we've also
+ * STARTED a transition *to* GONE.
+ * 5. We'll emit KeyguardState.GONE again once the transition finishes.
+ *
+ * If you just need to know when we eventually settle into a state, this flow is likely
+ * sufficient. However, if you're having issues with state *during* transitions started after
+ * one or more canceled transitions, you probably need to use [currentKeyguardState].
+ */
val finishedKeyguardState: SharedFlow<KeyguardState> =
finishedKeyguardTransitionStep
.map { step -> step.to }
.shareIn(scope, SharingStarted.Eagerly, replay = 1)
/**
- * Whether we're currently in a transition to a new [KeyguardState] and haven't yet completed
- * it.
+ * The [KeyguardState] we're currently in.
+ *
+ * If we're not in transition, this is simply the [finishedKeyguardState]. If we're in
+ * transition, this is the state we're transitioning *from*.
+ *
+ * Absent CANCELED transitions, [currentKeyguardState] and [finishedKeyguardState] are always
+ * identical - if a transition FINISHES in a given state, the subsequent state we START a
+ * transition *from* would always be that same previously FINISHED state.
+ *
+ * However, if a transition is CANCELED, the next transition will START from a state we never
+ * FINISHED in. For example, if we transition from GONE -> DOZING, but CANCEL that transition in
+ * favor of DOZING -> LOCKSCREEN, we've STARTED a transition *from* DOZING despite never
+ * FINISHING in DOZING. Thus, the current state will be DOZING but the FINISHED state will still
+ * be GONE.
+ *
+ * In this example, if there was DOZING-related state that needs to be set up in order to
+ * properly render a DOZING -> LOCKSCREEN transition, it would never be set up if we were
+ * listening for [finishedKeyguardState] to emit DOZING. However, [currentKeyguardState] would
+ * emit DOZING immediately upon STARTING DOZING -> LOCKSCREEN, allowing us to set up the state.
+ *
+ * Whether you want to use [currentKeyguardState] or [finishedKeyguardState] depends on your
+ * specific use case and how you want to handle cancellations. In general, if you're dealing
+ * with state/UI present across multiple [KeyguardState]s, you probably want
+ * [currentKeyguardState]. If you're dealing with state/UI encapsulated within a single state,
+ * you likely want [finishedKeyguardState].
+ *
+ * As an example, let's say you want to animate in a message on the lockscreen UI after waking
+ * up, and that TextView is not involved in animations between states. You'd want to collect
+ * [finishedKeyguardState], so you'll only animate it in once we're settled on the lockscreen.
+ * If you use [currentKeyguardState] in this case, a DOZING -> LOCKSCREEN transition that is
+ * interrupted by a LOCKSCREEN -> GONE transition would cause the message to become visible
+ * immediately upon LOCKSCREEN -> GONE STARTING, as the current state would become LOCKSCREEN in
+ * that case. That's likely not what you want.
+ *
+ * On the other hand, let's say you're animating the smartspace from alpha 0f to 1f during
+ * DOZING -> LOCKSCREEN, but the transition is interrupted by LOCKSCREEN -> GONE. LS -> GONE
+ * needs the smartspace to be alpha=1f so that it can play the shared-element unlock animation.
+ * In this case, we'd want to collect [currentKeyguardState] and ensure the smartspace is
+ * visible when the current state is LOCKSCREEN. If you use [finishedKeyguardState] in this
+ * case, the smartspace will never be set to alpha = 1f and you'll have a half-faded smartspace
+ * during the LS -> GONE transition.
+ *
+ * If you need special-case handling for cancellations (such as conditional handling depending
+ * on which [KeyguardState] was canceled) you can collect [canceledKeyguardTransitionStep]
+ * directly.
+ *
+ * As a helpful footnote, here's the values of [finishedKeyguardState] and
+ * [currentKeyguardState] during a sequence with two cancellations:
+ * 1. We're FINISHED in GONE. currentKeyguardState=GONE; finishedKeyguardState=GONE.
+ * 2. We START a transition from GONE -> DOZING. currentKeyguardState=GONE;
+ * finishedKeyguardState=GONE.
+ * 3. We CANCEL this transition and START a transition from DOZING -> LOCKSCREEN.
+ * currentKeyguardState=DOZING; finishedKeyguardState=GONE.
+ * 4. We subsequently also CANCEL DOZING -> LOCKSCREEN and START LOCKSCREEN -> GONE.
+ * currentKeyguardState=LOCKSCREEN finishedKeyguardState=GONE.
+ * 5. LOCKSCREEN -> GONE is allowed to FINISH. currentKeyguardState=GONE;
+ * finishedKeyguardState=GONE.
*/
- val isInTransitionToAnyState =
- combine(
- startedKeyguardTransitionStep,
- finishedKeyguardState,
- ) { startedStep, finishedState ->
- startedStep.to != finishedState
- }
+ val currentKeyguardState: SharedFlow<KeyguardState> =
+ repository.transitions
+ .mapLatest {
+ if (it.transitionState == TransitionState.FINISHED) {
+ it.to
+ } else {
+ it.from
+ }
+ }
+ .distinctUntilChanged()
+ .shareIn(scope, SharingStarted.Eagerly, replay = 1)
+
+ /** Whether we've currently STARTED a transition and haven't yet FINISHED it. */
+ val isInTransitionToAnyState = isInTransitionWhere({ true }, { true })
/**
* The amount of transition into or out of the given [KeyguardState].
@@ -293,13 +398,12 @@
fromStatePredicate: (KeyguardState) -> Boolean,
toStatePredicate: (KeyguardState) -> Boolean,
): Flow<Boolean> {
- return combine(
- startedKeyguardTransitionStep,
- finishedKeyguardState,
- ) { startedStep, finishedState ->
- fromStatePredicate(startedStep.from) &&
- toStatePredicate(startedStep.to) &&
- finishedState != startedStep.to
+ return repository.transitions
+ .filter { it.transitionState != TransitionState.CANCELED }
+ .mapLatest {
+ it.transitionState != TransitionState.FINISHED &&
+ fromStatePredicate(it.from) &&
+ toStatePredicate(it.to)
}
.distinctUntilChanged()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index d5ac283..5c2df45 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -24,8 +24,11 @@
import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.util.kotlin.sample
import java.util.UUID
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
/**
* Each TransitionInteractor is responsible for determining under which conditions to notify
@@ -40,14 +43,25 @@
*/
sealed class TransitionInteractor(
val fromState: KeyguardState,
+ val transitionInteractor: KeyguardTransitionInteractor,
+ val mainDispatcher: CoroutineDispatcher,
+ val bgDispatcher: CoroutineDispatcher,
) {
val name = this::class.simpleName ?: "UnknownTransitionInteractor"
-
abstract val transitionRepository: KeyguardTransitionRepository
- abstract val transitionInteractor: KeyguardTransitionInteractor
abstract fun start()
- fun startTransitionTo(
+ /* Use background dispatcher for all [KeyguardTransitionInteractor] flows. Necessary because
+ * the [sample] utility internally runs a collect on the Unconfined dispatcher, resulting
+ * in continuations on the main thread. We don't want that for classes that inherit from this.
+ */
+ val startedKeyguardTransitionStep =
+ transitionInteractor.startedKeyguardTransitionStep.flowOn(bgDispatcher)
+ // The following are MutableSharedFlows, and do not require flowOn
+ val startedKeyguardState = transitionInteractor.startedKeyguardState
+ val finishedKeyguardState = transitionInteractor.finishedKeyguardState
+
+ suspend fun startTransitionTo(
toState: KeyguardState,
animator: ValueAnimator? = getDefaultAnimatorForTransitionsToState(toState),
modeOnCanceled: TransitionModeOnCanceled = TransitionModeOnCanceled.LAST_VALUE
@@ -67,16 +81,17 @@
)
return null
}
-
- return transitionRepository.startTransition(
- TransitionInfo(
- name,
- fromState,
- toState,
- animator,
- modeOnCanceled,
+ return withContext(mainDispatcher) {
+ transitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ fromState,
+ toState,
+ animator,
+ modeOnCanceled,
+ )
)
- )
+ }
}
/** This signal may come in before the occlusion signal, and can provide a custom transition */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
index cf1d247..4abda74 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -17,9 +17,13 @@
import android.view.animation.Interpolator
import com.android.app.animation.Interpolators.LINEAR
+import com.android.app.tracing.coroutines.launch
import com.android.keyguard.logging.KeyguardTransitionAnimationLogger
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+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
@@ -31,10 +35,12 @@
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapNotNull
/**
* Assists in creating sub-flows for a KeyguardTransition. Call [setup] once for a transition, and
@@ -45,21 +51,49 @@
@Inject
constructor(
@Application private val scope: CoroutineScope,
+ private val transitionInteractor: KeyguardTransitionInteractor,
private val logger: KeyguardTransitionAnimationLogger,
) {
+ private val transitionMap = mutableMapOf<Edge, MutableSharedFlow<TransitionStep>>()
- /**
- * Invoke once per transition between FROM->TO states to get access to
- * [SharedFlowBuilder#sharedFlow].
- */
+ init {
+ scope.launch("KeyguardTransitionAnimationFlow") {
+ transitionInteractor.transitions.collect {
+ // FROM->TO
+ transitionMap[Edge(it.from, it.to)]?.emit(it)
+ // FROM->(ANY)
+ transitionMap[Edge(it.from, null)]?.emit(it)
+ // (ANY)->TO
+ transitionMap[Edge(null, it.to)]?.emit(it)
+ }
+ }
+ }
+
+ private fun getOrCreateFlow(edge: Edge): MutableSharedFlow<TransitionStep> {
+ return transitionMap.getOrPut(edge) {
+ MutableSharedFlow<TransitionStep>(
+ extraBufferCapacity = 10,
+ onBufferOverflow = BufferOverflow.DROP_OLDEST
+ )
+ }
+ }
+
+ /** Invoke once per transition between FROM->TO states to get access to a shared flow. */
fun setup(
duration: Duration,
- stepFlow: Flow<TransitionStep>,
- ) = SharedFlowBuilder(duration, stepFlow)
+ from: KeyguardState?,
+ to: KeyguardState?,
+ ): FlowBuilder {
+ if (from == null && to == null) {
+ throw IllegalArgumentException("from and to are both null")
+ }
- inner class SharedFlowBuilder(
+ return FlowBuilder(duration, Edge(from, to))
+ }
+
+ inner class FlowBuilder(
private val transitionDuration: Duration,
- private val stepFlow: Flow<TransitionStep>,
+ private val edge: Edge,
) {
/**
* Transitions will occur over a [transitionDuration] with [TransitionStep]s being emitted
@@ -115,20 +149,21 @@
}?.let { onStep(interpolator.getInterpolation(it)) }
}
- return stepFlow
+ return getOrCreateFlow(edge)
.map { step ->
- val value =
- when (step.transitionState) {
- STARTED -> stepToValue(step)
- RUNNING -> stepToValue(step)
- CANCELED -> onCancel?.invoke()
- FINISHED -> onFinish?.invoke()
- }
- logger.logTransitionStep(name, step, value)
- value
+ StateToValue(
+ step.transitionState,
+ when (step.transitionState) {
+ STARTED -> stepToValue(step)
+ RUNNING -> stepToValue(step)
+ CANCELED -> onCancel?.invoke()
+ FINISHED -> onFinish?.invoke()
+ }
+ )
+ .also { logger.logTransitionStep(name, step, it.value) }
}
- .filterNotNull()
.distinctUntilChanged()
+ .mapNotNull { stateToValue -> stateToValue.value }
}
/**
@@ -138,4 +173,14 @@
return sharedFlow(duration = 1.milliseconds, onStep = { value }, onFinish = { value })
}
}
+
+ data class Edge(
+ val from: KeyguardState?,
+ val to: KeyguardState?,
+ )
+
+ data class StateToValue(
+ val transitionState: TransitionState,
+ val value: Float?,
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
index bf763b4..400b8bf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
@@ -30,7 +30,10 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.animation.Interpolators
+import com.android.keyguard.KeyguardClockSwitch.LARGE
+import com.android.keyguard.KeyguardClockSwitch.SMALL
import com.android.systemui.Flags.migrateClocksToBlueprint
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
@@ -48,6 +51,7 @@
keyguardRootView: ConstraintLayout,
viewModel: KeyguardClockViewModel,
keyguardClockInteractor: KeyguardClockInteractor,
+ blueprintInteractor: KeyguardBlueprintInteractor,
) {
keyguardRootView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
@@ -61,18 +65,16 @@
viewModel.currentClock.collect { currentClock ->
cleanupClockViews(viewModel.clock, keyguardRootView, viewModel.burnInLayer)
viewModel.clock = currentClock
- addClockViews(currentClock, keyguardRootView, viewModel.burnInLayer)
- viewModel.burnInLayer?.updatePostLayout(keyguardRootView)
- applyConstraints(clockSection, keyguardRootView, true)
+ addClockViews(currentClock, keyguardRootView)
+ updateBurnInLayer(keyguardRootView, viewModel)
+ blueprintInteractor.refreshBlueprint()
}
}
- // TODO: Weather clock dozing animation
- // will trigger both shouldBeCentered and clockSize change
- // we should avoid this
launch {
if (!migrateClocksToBlueprint()) return@launch
viewModel.clockSize.collect {
- applyConstraints(clockSection, keyguardRootView, true)
+ updateBurnInLayer(keyguardRootView, viewModel)
+ blueprintInteractor.refreshBlueprint()
}
}
launch {
@@ -82,7 +84,7 @@
if (it.largeClock.config.hasCustomPositionUpdatedAnimation) {
playClockCenteringAnimation(clockSection, keyguardRootView, it)
} else {
- applyConstraints(clockSection, keyguardRootView, true)
+ blueprintInteractor.refreshBlueprint()
}
}
}
@@ -90,6 +92,29 @@
}
}
}
+ @VisibleForTesting
+ fun updateBurnInLayer(
+ keyguardRootView: ConstraintLayout,
+ viewModel: KeyguardClockViewModel,
+ ) {
+ val burnInLayer = viewModel.burnInLayer
+ val clockController = viewModel.currentClock.value
+ clockController?.let { clock ->
+ when (viewModel.clockSize.value) {
+ LARGE -> {
+ clock.smallClock.layout.views.forEach { burnInLayer?.removeView(it) }
+ if (clock.config.useAlternateSmartspaceAODTransition) {
+ clock.largeClock.layout.views.forEach { burnInLayer?.addView(it) }
+ }
+ }
+ SMALL -> {
+ clock.smallClock.layout.views.forEach { burnInLayer?.addView(it) }
+ clock.largeClock.layout.views.forEach { burnInLayer?.removeView(it) }
+ }
+ }
+ }
+ viewModel.burnInLayer?.updatePostLayout(keyguardRootView)
+ }
private fun cleanupClockViews(
clockController: ClockController?,
@@ -116,7 +141,6 @@
fun addClockViews(
clockController: ClockController?,
rootView: ConstraintLayout,
- burnInLayer: Layer?
) {
clockController?.let { clock ->
clock.smallClock.layout.views[0].id = R.id.lockscreen_clock_view
@@ -125,17 +149,10 @@
}
// small clock should either be a single view or container with id
// `lockscreen_clock_view`
- clock.smallClock.layout.views.forEach {
- rootView.addView(it)
- burnInLayer?.addView(it)
- }
+ clock.smallClock.layout.views.forEach { rootView.addView(it) }
clock.largeClock.layout.views.forEach { rootView.addView(it) }
- if (clock.config.useAlternateSmartspaceAODTransition) {
- clock.largeClock.layout.views.forEach { burnInLayer?.addView(it) }
- }
}
}
-
fun applyConstraints(
clockSection: ClockSection,
rootView: ConstraintLayout,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
index 81ce8f0..10392e3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
@@ -16,15 +16,13 @@
package com.android.systemui.keyguard.ui.binder
-import android.transition.TransitionManager
import android.view.View
import androidx.constraintlayout.helper.widget.Layer
import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.Flags.migrateClocksToBlueprint
-import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -35,10 +33,10 @@
object KeyguardSmartspaceViewBinder {
@JvmStatic
fun bind(
- smartspaceSection: SmartspaceSection,
keyguardRootView: ConstraintLayout,
clockViewModel: KeyguardClockViewModel,
smartspaceViewModel: KeyguardSmartspaceViewModel,
+ blueprintInteractor: KeyguardBlueprintInteractor,
) {
keyguardRootView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
@@ -46,22 +44,56 @@
if (!migrateClocksToBlueprint()) return@launch
clockViewModel.hasCustomWeatherDataDisplay.collect { hasCustomWeatherDataDisplay
->
- if (hasCustomWeatherDataDisplay) {
- removeDateWeatherToBurnInLayer(keyguardRootView, smartspaceViewModel)
- } else {
- addDateWeatherToBurnInLayer(keyguardRootView, smartspaceViewModel)
- }
- clockViewModel.burnInLayer?.updatePostLayout(keyguardRootView)
- val constraintSet = ConstraintSet().apply { clone(keyguardRootView) }
- smartspaceSection.applyConstraints(constraintSet)
- TransitionManager.beginDelayedTransition(keyguardRootView)
- constraintSet.applyTo(keyguardRootView)
+ updateDateWeatherToBurnInLayer(
+ keyguardRootView,
+ clockViewModel,
+ smartspaceViewModel
+ )
+ blueprintInteractor.refreshBlueprint()
+ }
+ }
+
+ launch {
+ smartspaceViewModel.bcSmartspaceVisibility.collect {
+ updateBCSmartspaceInBurnInLayer(keyguardRootView, clockViewModel)
+ blueprintInteractor.refreshBlueprint()
}
}
}
}
}
+ private fun updateBCSmartspaceInBurnInLayer(
+ keyguardRootView: ConstraintLayout,
+ clockViewModel: KeyguardClockViewModel,
+ ) {
+ // Visibility is controlled by updateTargetVisibility in CardPagerAdapter
+ val burnInLayer = keyguardRootView.requireViewById<Layer>(R.id.burn_in_layer)
+ burnInLayer.apply {
+ val smartspaceView =
+ keyguardRootView.requireViewById<View>(sharedR.id.bc_smartspace_view)
+ if (smartspaceView.visibility == View.VISIBLE) {
+ addView(smartspaceView)
+ } else {
+ removeView(smartspaceView)
+ }
+ }
+ clockViewModel.burnInLayer?.updatePostLayout(keyguardRootView)
+ }
+
+ private fun updateDateWeatherToBurnInLayer(
+ keyguardRootView: ConstraintLayout,
+ clockViewModel: KeyguardClockViewModel,
+ smartspaceViewModel: KeyguardSmartspaceViewModel
+ ) {
+ if (clockViewModel.hasCustomWeatherDataDisplay.value) {
+ removeDateWeatherFromBurnInLayer(keyguardRootView, smartspaceViewModel)
+ } else {
+ addDateWeatherToBurnInLayer(keyguardRootView, smartspaceViewModel)
+ }
+ clockViewModel.burnInLayer?.updatePostLayout(keyguardRootView)
+ }
+
private fun addDateWeatherToBurnInLayer(
constraintLayout: ConstraintLayout,
smartspaceViewModel: KeyguardSmartspaceViewModel
@@ -76,13 +108,13 @@
constraintLayout.requireViewById<View>(sharedR.id.date_smartspace_view)
val weatherView =
constraintLayout.requireViewById<View>(sharedR.id.weather_smartspace_view)
- addView(weatherView)
addView(dateView)
+ addView(weatherView)
}
}
}
- private fun removeDateWeatherToBurnInLayer(
+ private fun removeDateWeatherFromBurnInLayer(
constraintLayout: ConstraintLayout,
smartspaceViewModel: KeyguardSmartspaceViewModel
) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
index 920fc04..fab60e8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
@@ -17,6 +17,7 @@
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToAodTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToPrimaryBouncerTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel
@@ -53,6 +54,12 @@
@Binds
@IntoSet
+ abstract fun alternateBouncerToPrimaryBouncer(
+ impl: AlternateBouncerToPrimaryBouncerTransitionViewModel
+ ): DeviceEntryIconTransition
+
+ @Binds
+ @IntoSet
abstract fun aodToLockscreen(
impl: AodToLockscreenTransitionViewModel
): DeviceEntryIconTransition
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
index 9b40433..d118d4d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
@@ -17,12 +17,14 @@
package com.android.systemui.keyguard.ui.view.layout.blueprints
+import com.android.systemui.communal.ui.view.layout.sections.CommunalTutorialIndicatorSection
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.view.layout.sections.AlignShortcutsToUdfpsSection
import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
+import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntrySection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection
@@ -31,7 +33,7 @@
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultUdfpsAccessibilityOverlaySection
import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule
-import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
+import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
import com.android.systemui.util.kotlin.getOrNull
import java.util.Optional
import javax.inject.Inject
@@ -44,18 +46,20 @@
class ShortcutsBesideUdfpsKeyguardBlueprint
@Inject
constructor(
+ alignShortcutsToUdfpsSection: AlignShortcutsToUdfpsSection,
defaultIndicationAreaSection: DefaultIndicationAreaSection,
defaultDeviceEntrySection: DefaultDeviceEntrySection,
@Named(KeyguardSectionsModule.KEYGUARD_AMBIENT_INDICATION_AREA_SECTION)
defaultAmbientIndicationAreaSection: Optional<KeyguardSection>,
defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection,
- alignShortcutsToUdfpsSection: AlignShortcutsToUdfpsSection,
defaultStatusViewSection: DefaultStatusViewSection,
defaultStatusBarSection: DefaultStatusBarSection,
- splitShadeGuidelines: SplitShadeGuidelines,
defaultNotificationStackScrollLayoutSection: DefaultNotificationStackScrollLayoutSection,
aodNotificationIconsSection: AodNotificationIconsSection,
aodBurnInSection: AodBurnInSection,
+ communalTutorialIndicatorSection: CommunalTutorialIndicatorSection,
+ clockSection: ClockSection,
+ smartspaceSection: SmartspaceSection,
udfpsAccessibilityOverlaySection: DefaultUdfpsAccessibilityOverlaySection,
) : KeyguardBlueprint {
override val id: String = SHORTCUTS_BESIDE_UDFPS
@@ -63,15 +67,17 @@
override val sections =
listOfNotNull(
defaultIndicationAreaSection,
+ alignShortcutsToUdfpsSection,
defaultAmbientIndicationAreaSection.getOrNull(),
defaultSettingsPopupMenuSection,
- alignShortcutsToUdfpsSection,
defaultStatusViewSection,
defaultStatusBarSection,
defaultNotificationStackScrollLayoutSection,
- splitShadeGuidelines,
aodNotificationIconsSection,
+ smartspaceSection,
aodBurnInSection,
+ communalTutorialIndicatorSection,
+ clockSection,
defaultDeviceEntrySection,
udfpsAccessibilityOverlaySection, // Add LAST: Intentionally has z-order above others
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
index 24d0602..8472a9f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
@@ -23,6 +23,7 @@
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
+import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntrySection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
@@ -30,11 +31,10 @@
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule
-import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeClockSection
+import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeMediaSection
import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeNotificationStackScrollLayoutSection
-import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeSmartspaceSection
import com.android.systemui.util.kotlin.getOrNull
import java.util.Optional
import javax.inject.Inject
@@ -62,8 +62,8 @@
aodNotificationIconsSection: AodNotificationIconsSection,
aodBurnInSection: AodBurnInSection,
communalTutorialIndicatorSection: CommunalTutorialIndicatorSection,
- smartspaceSection: SplitShadeSmartspaceSection,
- clockSection: SplitShadeClockSection,
+ clockSection: ClockSection,
+ smartspaceSection: SmartspaceSection,
mediaSection: SplitShadeMediaSection,
) : KeyguardBlueprint {
override val id: String = ID
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/BaseBlueprintTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/BaseBlueprintTransition.kt
index d0626d5..fd530b7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/BaseBlueprintTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/BaseBlueprintTransition.kt
@@ -25,6 +25,8 @@
import android.view.View
import android.view.ViewGroup
import androidx.constraintlayout.helper.widget.Layer
+import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
+import com.android.systemui.res.R
class BaseBlueprintTransition : TransitionSet() {
init {
@@ -33,7 +35,16 @@
.addTransition(ChangeBounds())
.addTransition(AlphaInVisibility())
excludeTarget(Layer::class.java, /* exclude= */ true)
+ excludeClockAndSmartspaceViews()
}
+
+ private fun excludeClockAndSmartspaceViews() {
+ excludeTarget(R.id.lockscreen_clock_view, true)
+ excludeTarget(R.id.lockscreen_clock_view_large, true)
+ excludeTarget(SmartspaceView::class.java, true)
+ // TODO(b/319468190): need to exclude views from large weather clock
+ }
+
class AlphaOutVisibility : Visibility() {
override fun onDisappear(
sceneRoot: ViewGroup?,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInLayer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInLayer.kt
new file mode 100644
index 0000000..67a20e5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInLayer.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.sections
+
+import android.content.Context
+import android.view.View
+import androidx.constraintlayout.helper.widget.Layer
+
+class AodBurnInLayer(context: Context) : Layer(context) {
+ // For setScale in Layer class, it stores it in mScaleX/Y and directly apply scale to
+ // referenceViews instead of keeping the value in fields of View class
+ // when we try to clone ConstraintSet, it will call getScaleX from View class and return 1.0
+ // and when we clone and apply, it will reset everything in the layer
+ // which cause the flicker from AOD to LS
+ private var _scaleX = 1F
+ private var _scaleY = 1F
+ // As described for _scaleX and _scaleY, we have similar issue with translation
+ private var _translationX = 1F
+ private var _translationY = 1F
+ // avoid adding views with same ids
+ override fun addView(view: View?) {
+ view?.let { if (it.id !in referencedIds) super.addView(view) }
+ }
+ override fun setScaleX(scaleX: Float) {
+ _scaleX = scaleX
+ super.setScaleX(scaleX)
+ }
+
+ override fun getScaleX(): Float {
+ return _scaleX
+ }
+
+ override fun setScaleY(scaleY: Float) {
+ _scaleY = scaleY
+ super.setScaleY(scaleY)
+ }
+
+ override fun getScaleY(): Float {
+ return _scaleY
+ }
+
+ override fun setTranslationX(dx: Float) {
+ _translationX = dx
+ super.setTranslationX(dx)
+ }
+
+ override fun getTranslationX(): Float {
+ return _translationX
+ }
+
+ override fun setTranslationY(dy: Float) {
+ _translationY = dy
+ super.setTranslationY(dy)
+ }
+
+ override fun getTranslationY(): Float {
+ return _translationY
+ }
+}
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 1ccc6cc..3d36eb0 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
@@ -19,16 +19,13 @@
import android.content.Context
import android.view.View
-import androidx.constraintlayout.helper.widget.Layer
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.res.R
-import com.android.systemui.shared.R as sharedR
import javax.inject.Inject
/** Adds a layer to group elements for translation for burn-in preventation */
@@ -37,10 +34,8 @@
constructor(
private val context: Context,
private val clockViewModel: KeyguardClockViewModel,
- private val smartspaceViewModel: KeyguardSmartspaceViewModel,
) : KeyguardSection() {
- lateinit var burnInLayer: Layer
-
+ private lateinit var burnInLayer: AodBurnInLayer
override fun addViews(constraintLayout: ConstraintLayout) {
if (!KeyguardShadeMigrationNssl.isEnabled) {
return
@@ -48,7 +43,7 @@
val nic = constraintLayout.requireViewById<View>(R.id.aod_notification_icon_container)
burnInLayer =
- Layer(context).apply {
+ AodBurnInLayer(context).apply {
id = R.id.burn_in_layer
addView(nic)
if (!migrateClocksToBlueprint()) {
@@ -57,11 +52,6 @@
addView(statusView)
}
}
- if (migrateClocksToBlueprint()) {
- // weather and date parts won't be added here, cause their visibility doesn't align
- // with others in burnInLayer
- addSmartspaceViews(constraintLayout)
- }
constraintLayout.addView(burnInLayer)
}
@@ -83,14 +73,4 @@
override fun removeViews(constraintLayout: ConstraintLayout) {
constraintLayout.removeView(R.id.burn_in_layer)
}
-
- private fun addSmartspaceViews(constraintLayout: ConstraintLayout) {
- burnInLayer.apply {
- if (smartspaceViewModel.isSmartspaceEnabled) {
- val smartspaceView =
- constraintLayout.requireViewById<View>(sharedR.id.bc_smartspace_view)
- addView(smartspaceView)
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
index f560b5f..ed7abff 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
@@ -30,9 +30,7 @@
import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.keyguard.shared.model.KeyguardSection
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.res.R
-import com.android.systemui.shared.R as sharedR
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker
@@ -53,13 +51,13 @@
private val nicAodViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore,
private val notificationIconAreaController: NotificationIconAreaController,
- private val smartspaceViewModel: KeyguardSmartspaceViewModel,
private val systemBarUtilsState: SystemBarUtilsState,
) : KeyguardSection() {
private var nicBindingDisposable: DisposableHandle? = null
private val nicId = R.id.aod_notification_icon_container
private lateinit var nic: NotificationIconContainer
+ private val smartSpaceBarrier = View.generateViewId()
override fun addViews(constraintLayout: ConstraintLayout) {
if (!KeyguardShadeMigrationNssl.isEnabled) {
@@ -118,7 +116,7 @@
}
constraintSet.apply {
if (migrateClocksToBlueprint()) {
- connect(nicId, TOP, sharedR.id.bc_smartspace_view, BOTTOM, bottomMargin)
+ connect(nicId, TOP, R.id.smart_space_barrier_bottom, BOTTOM, bottomMargin)
setGoneMargin(nicId, BOTTOM, bottomMargin)
} else {
connect(nicId, TOP, R.id.keyguard_status_view, topAlignment, bottomMargin)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index b5f32c8..b344d3b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -23,11 +23,14 @@
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
import androidx.constraintlayout.widget.ConstraintSet.END
+import androidx.constraintlayout.widget.ConstraintSet.INVISIBLE
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
+import androidx.constraintlayout.widget.ConstraintSet.VISIBLE
import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
import com.android.systemui.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.binder.KeyguardClockViewBinder
@@ -35,8 +38,10 @@
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockFaceLayout
import com.android.systemui.res.R
+import com.android.systemui.shared.R as sharedR
import com.android.systemui.statusbar.policy.SplitShadeStateController
import com.android.systemui.util.Utils
+import dagger.Lazy
import javax.inject.Inject
internal fun ConstraintSet.setVisibility(
@@ -56,6 +61,7 @@
protected val keyguardClockViewModel: KeyguardClockViewModel,
private val context: Context,
private val splitShadeStateController: SplitShadeStateController,
+ val blueprintInteractor: Lazy<KeyguardBlueprintInteractor>,
) : KeyguardSection() {
override fun addViews(constraintLayout: ConstraintLayout) {}
@@ -68,6 +74,7 @@
constraintLayout,
keyguardClockViewModel,
clockInteractor,
+ blueprintInteractor.get()
)
}
@@ -88,12 +95,16 @@
): ConstraintSet {
// Add constraint between rootView and clockContainer
applyDefaultConstraints(constraintSet)
+ getNonTargetClockFace(clock).applyConstraints(constraintSet)
getTargetClockFace(clock).applyConstraints(constraintSet)
// Add constraint between elements in clock and clock container
return constraintSet.apply {
- setAlpha(getTargetClockFace(clock).views, 1F)
- setAlpha(getNonTargetClockFace(clock).views, 0F)
+ setVisibility(getTargetClockFace(clock).views, VISIBLE)
+ setVisibility(getNonTargetClockFace(clock).views, INVISIBLE)
+ if (!keyguardClockViewModel.useLargeClock) {
+ connect(sharedR.id.bc_smartspace_view, TOP, sharedR.id.date_smartspace_view, BOTTOM)
+ }
}
}
@@ -107,9 +118,12 @@
private fun getLargeClockFace(clock: ClockController): ClockFaceLayout = clock.largeClock.layout
private fun getSmallClockFace(clock: ClockController): ClockFaceLayout = clock.smallClock.layout
open fun applyDefaultConstraints(constraints: ConstraintSet) {
+ val guideline =
+ if (keyguardClockViewModel.clockShouldBeCentered.value) PARENT_ID
+ else R.id.split_shade_guideline
constraints.apply {
connect(R.id.lockscreen_clock_view_large, START, PARENT_ID, START)
- connect(R.id.lockscreen_clock_view_large, END, PARENT_ID, END)
+ connect(R.id.lockscreen_clock_view_large, END, guideline, END)
connect(R.id.lockscreen_clock_view_large, BOTTOM, R.id.lock_icon_view, TOP)
var largeClockTopMargin =
context.resources.getDimensionPixelSize(R.dimen.status_bar_height) +
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
index b0eee0a..8c5e9b4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.ui.view.layout.sections
import android.content.Context
+import android.view.View
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
import androidx.constraintlayout.widget.ConstraintSet.END
@@ -27,11 +28,9 @@
import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.shade.NotificationPanelView
-import com.android.systemui.shared.R as sharedR
import com.android.systemui.statusbar.notification.stack.AmbientState
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator
@@ -54,7 +53,6 @@
ambientState: AmbientState,
controller: NotificationStackScrollLayoutController,
notificationStackSizeCalculator: NotificationStackSizeCalculator,
- private val smartspaceViewModel: KeyguardSmartspaceViewModel,
@Main mainDispatcher: CoroutineDispatcher,
) :
NotificationStackScrollLayoutSection(
@@ -69,6 +67,7 @@
notificationStackSizeCalculator,
mainDispatcher,
) {
+ private val smartSpaceBarrier = View.generateViewId()
override fun applyConstraints(constraintSet: ConstraintSet) {
if (!KeyguardShadeMigrationNssl.isEnabled) {
return
@@ -76,16 +75,14 @@
constraintSet.apply {
val bottomMargin =
context.resources.getDimensionPixelSize(R.dimen.keyguard_status_view_bottom_margin)
-
if (migrateClocksToBlueprint()) {
connect(
R.id.nssl_placeholder,
TOP,
- sharedR.id.bc_smartspace_view,
+ R.id.smart_space_barrier_bottom,
BOTTOM,
bottomMargin
)
- setGoneMargin(R.id.nssl_placeholder, TOP, bottomMargin)
} else {
connect(R.id.nssl_placeholder, TOP, R.id.keyguard_status_view, BOTTOM, bottomMargin)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
index 2a68f26..0c0eb8a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
@@ -24,6 +24,7 @@
import androidx.constraintlayout.widget.ConstraintSet.LEFT
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.RIGHT
+import androidx.constraintlayout.widget.ConstraintSet.VISIBILITY_MODE_IGNORE
import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
@@ -96,6 +97,11 @@
constrainHeight(R.id.end_button, height)
connect(R.id.end_button, RIGHT, PARENT_ID, RIGHT, horizontalOffsetMargin)
connect(R.id.end_button, BOTTOM, PARENT_ID, BOTTOM, verticalOffsetMargin)
+
+ // The constraint set visibility for start and end button are default visible, set to
+ // ignore so the view's own initial visibility (invisible) is used
+ setVisibilityMode(R.id.start_button, VISIBILITY_MODE_IGNORE)
+ setVisibilityMode(R.id.end_button, VISIBILITY_MODE_IGNORE)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
index eacd466..37842a8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
@@ -18,37 +18,41 @@
import android.content.Context
import android.view.View
+import android.view.View.GONE
+import android.view.ViewTreeObserver.OnGlobalLayoutListener
+import androidx.constraintlayout.widget.Barrier
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
-import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
-import androidx.constraintlayout.widget.ConstraintSet.END
-import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
-import androidx.constraintlayout.widget.ConstraintSet.START
-import androidx.constraintlayout.widget.ConstraintSet.TOP
-import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardSmartspaceInteractor
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.binder.KeyguardSmartspaceViewBinder
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
-import com.android.systemui.res.R
+import com.android.systemui.shared.R
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
+import dagger.Lazy
import javax.inject.Inject
open class SmartspaceSection
@Inject
constructor(
+ val context: Context,
val keyguardClockViewModel: KeyguardClockViewModel,
val keyguardSmartspaceViewModel: KeyguardSmartspaceViewModel,
- private val context: Context,
+ val keyguardSmartspaceInteractor: KeyguardSmartspaceInteractor,
val smartspaceController: LockscreenSmartspaceController,
val keyguardUnlockAnimationController: KeyguardUnlockAnimationController,
+ val blueprintInteractor: Lazy<KeyguardBlueprintInteractor>,
) : KeyguardSection() {
private var smartspaceView: View? = null
private var weatherView: View? = null
private var dateView: View? = null
+ private var smartspaceVisibilityListener: OnGlobalLayoutListener? = null
+
override fun addViews(constraintLayout: ConstraintLayout) {
if (!migrateClocksToBlueprint()) {
return
@@ -64,6 +68,20 @@
}
}
keyguardUnlockAnimationController.lockscreenSmartspace = smartspaceView
+ smartspaceVisibilityListener =
+ object : OnGlobalLayoutListener {
+ var pastVisibility = GONE
+ override fun onGlobalLayout() {
+ smartspaceView?.let {
+ val newVisibility = it.visibility
+ if (pastVisibility != newVisibility) {
+ keyguardSmartspaceInteractor.setBcSmartspaceVisibility(newVisibility)
+ pastVisibility = newVisibility
+ }
+ }
+ }
+ }
+ smartspaceView?.viewTreeObserver?.addOnGlobalLayoutListener(smartspaceVisibilityListener)
}
override fun bindData(constraintLayout: ConstraintLayout) {
@@ -71,10 +89,10 @@
return
}
KeyguardSmartspaceViewBinder.bind(
- this,
constraintLayout,
keyguardClockViewModel,
keyguardSmartspaceViewModel,
+ blueprintInteractor.get(),
)
}
@@ -82,65 +100,96 @@
if (!migrateClocksToBlueprint()) {
return
}
- // Generally, weather should be next to dateView
- // smartspace should be below date & weather views
constraintSet.apply {
// migrate addDateWeatherView, addWeatherView from KeyguardClockSwitchController
- dateView?.let { dateView ->
- constrainHeight(dateView.id, WRAP_CONTENT)
- constrainWidth(dateView.id, WRAP_CONTENT)
- connect(
- dateView.id,
- START,
- PARENT_ID,
- START,
- context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_start)
+ constrainHeight(R.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT)
+ constrainWidth(R.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT)
+ connect(
+ R.id.date_smartspace_view,
+ ConstraintSet.START,
+ ConstraintSet.PARENT_ID,
+ ConstraintSet.START,
+ context.resources.getDimensionPixelSize(
+ com.android.systemui.res.R.dimen.below_clock_padding_start
)
- }
- weatherView?.let {
- constrainWidth(it.id, WRAP_CONTENT)
- dateView?.let { dateView ->
- connect(it.id, TOP, dateView.id, TOP)
- connect(it.id, BOTTOM, dateView.id, BOTTOM)
- connect(it.id, START, dateView.id, END, 4)
- }
- }
+ )
+ constrainWidth(R.id.weather_smartspace_view, ConstraintSet.WRAP_CONTENT)
+ connect(
+ R.id.weather_smartspace_view,
+ ConstraintSet.TOP,
+ R.id.date_smartspace_view,
+ ConstraintSet.TOP
+ )
+ connect(
+ R.id.weather_smartspace_view,
+ ConstraintSet.BOTTOM,
+ R.id.date_smartspace_view,
+ ConstraintSet.BOTTOM
+ )
+ connect(
+ R.id.weather_smartspace_view,
+ ConstraintSet.START,
+ R.id.date_smartspace_view,
+ ConstraintSet.END,
+ 4
+ )
+
// migrate addSmartspaceView from KeyguardClockSwitchController
- smartspaceView?.let {
- constrainHeight(it.id, WRAP_CONTENT)
+ constrainHeight(R.id.bc_smartspace_view, ConstraintSet.WRAP_CONTENT)
+ connect(
+ R.id.bc_smartspace_view,
+ ConstraintSet.START,
+ ConstraintSet.PARENT_ID,
+ ConstraintSet.START,
+ context.resources.getDimensionPixelSize(
+ com.android.systemui.res.R.dimen.below_clock_padding_start
+ )
+ )
+ connect(
+ R.id.bc_smartspace_view,
+ ConstraintSet.END,
+ if (keyguardClockViewModel.clockShouldBeCentered.value) ConstraintSet.PARENT_ID
+ else com.android.systemui.res.R.id.split_shade_guideline,
+ ConstraintSet.END,
+ context.resources.getDimensionPixelSize(
+ com.android.systemui.res.R.dimen.below_clock_padding_end
+ )
+ )
+
+ if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) {
+ clear(R.id.date_smartspace_view, ConstraintSet.TOP)
connect(
- it.id,
- START,
- PARENT_ID,
- START,
- context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_start)
+ R.id.date_smartspace_view,
+ ConstraintSet.BOTTOM,
+ R.id.bc_smartspace_view,
+ ConstraintSet.TOP
+ )
+ } else {
+ clear(R.id.date_smartspace_view, ConstraintSet.BOTTOM)
+ connect(
+ R.id.date_smartspace_view,
+ ConstraintSet.TOP,
+ com.android.systemui.res.R.id.lockscreen_clock_view,
+ ConstraintSet.BOTTOM
)
connect(
- it.id,
- END,
- PARENT_ID,
- END,
- context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_end)
+ R.id.bc_smartspace_view,
+ ConstraintSet.TOP,
+ R.id.date_smartspace_view,
+ ConstraintSet.BOTTOM
)
}
- if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) {
- dateView?.let { dateView ->
- smartspaceView?.let { smartspaceView ->
- connect(dateView.id, BOTTOM, smartspaceView.id, TOP)
- }
- }
- } else {
- dateView?.let { dateView ->
- clear(dateView.id, BOTTOM)
- connect(dateView.id, TOP, R.id.lockscreen_clock_view, BOTTOM)
- constrainHeight(dateView.id, WRAP_CONTENT)
- smartspaceView?.let { smartspaceView ->
- clear(smartspaceView.id, TOP)
- connect(smartspaceView.id, TOP, dateView.id, BOTTOM)
- }
- }
- }
+ createBarrier(
+ com.android.systemui.res.R.id.smart_space_barrier_bottom,
+ Barrier.BOTTOM,
+ 0,
+ *intArrayOf(
+ R.id.bc_smartspace_view,
+ R.id.date_smartspace_view,
+ R.id.weather_smartspace_view,
+ )
+ )
}
updateVisibility(constraintSet)
}
@@ -156,30 +205,28 @@
}
}
}
+ smartspaceView?.viewTreeObserver?.removeOnGlobalLayoutListener(smartspaceVisibilityListener)
+ smartspaceVisibilityListener = null
}
private fun updateVisibility(constraintSet: ConstraintSet) {
constraintSet.apply {
- weatherView?.let {
- setVisibility(
- it.id,
- when (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) {
- true -> ConstraintSet.GONE
- false ->
- when (keyguardSmartspaceViewModel.isWeatherEnabled) {
- true -> ConstraintSet.VISIBLE
- false -> ConstraintSet.GONE
- }
- }
- )
- }
- dateView?.let {
- setVisibility(
- it.id,
- if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) ConstraintSet.GONE
- else ConstraintSet.VISIBLE
- )
- }
+ setVisibility(
+ R.id.weather_smartspace_view,
+ when (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) {
+ true -> ConstraintSet.GONE
+ false ->
+ when (keyguardSmartspaceViewModel.isWeatherEnabled) {
+ true -> ConstraintSet.VISIBLE
+ false -> ConstraintSet.GONE
+ }
+ }
+ )
+ setVisibility(
+ R.id.date_smartspace_view,
+ if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) ConstraintSet.GONE
+ else ConstraintSet.VISIBLE
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt
deleted file mode 100644
index 19ba1aa..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.ui.view.layout.sections
-
-import android.content.Context
-import androidx.constraintlayout.widget.ConstraintSet
-import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.policy.SplitShadeStateController
-import javax.inject.Inject
-
-class SplitShadeClockSection
-@Inject
-constructor(
- clockInteractor: KeyguardClockInteractor,
- keyguardClockViewModel: KeyguardClockViewModel,
- context: Context,
- splitShadeStateController: SplitShadeStateController,
-) : ClockSection(clockInteractor, keyguardClockViewModel, context, splitShadeStateController) {
- override fun applyDefaultConstraints(constraints: ConstraintSet) {
- super.applyDefaultConstraints(constraints)
- val largeClockEndGuideline =
- if (keyguardClockViewModel.clockShouldBeCentered.value) ConstraintSet.PARENT_ID
- else R.id.split_shade_guideline
- constraints.apply {
- connect(
- R.id.lockscreen_clock_view_large,
- ConstraintSet.END,
- largeClockEndGuideline,
- ConstraintSet.END
- )
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
index f20ab06..b12a8a8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
@@ -20,7 +20,6 @@
import android.view.View
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.FrameLayout
-import androidx.constraintlayout.widget.Barrier
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
@@ -31,11 +30,9 @@
import androidx.constraintlayout.widget.ConstraintSet.TOP
import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.keyguard.shared.model.KeyguardSection
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.media.controls.ui.KeyguardMediaController
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationPanelView
-import com.android.systemui.shared.R as sharedR
import javax.inject.Inject
/** Aligns media on left side for split shade, below smartspace, date, and weather. */
@@ -44,11 +41,9 @@
constructor(
private val context: Context,
private val notificationPanelView: NotificationPanelView,
- private val keyguardSmartspaceViewModel: KeyguardSmartspaceViewModel,
private val keyguardMediaController: KeyguardMediaController
) : KeyguardSection() {
private val mediaContainerId = R.id.status_view_media_container
- private val smartSpaceBarrier = R.id.smart_space_barrier_bottom
override fun addViews(constraintLayout: ConstraintLayout) {
if (!migrateClocksToBlueprint()) {
@@ -85,18 +80,7 @@
constraintSet.apply {
constrainWidth(mediaContainerId, MATCH_CONSTRAINT)
constrainHeight(mediaContainerId, WRAP_CONTENT)
-
- createBarrier(
- smartSpaceBarrier,
- Barrier.BOTTOM,
- 0,
- *intArrayOf(
- sharedR.id.bc_smartspace_view,
- sharedR.id.date_smartspace_view,
- sharedR.id.weather_smartspace_view,
- )
- )
- connect(mediaContainerId, TOP, smartSpaceBarrier, BOTTOM)
+ connect(mediaContainerId, TOP, R.id.smart_space_barrier_bottom, BOTTOM)
connect(mediaContainerId, START, PARENT_ID, START)
connect(mediaContainerId, END, R.id.split_shade_guideline, END)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeSmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeSmartspaceSection.kt
deleted file mode 100644
index 8728ada..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeSmartspaceSection.kt
+++ /dev/null
@@ -1,45 +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.keyguard.ui.view.layout.sections
-
-import android.content.Context
-import com.android.systemui.keyguard.KeyguardUnlockAnimationController
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
-import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
-import javax.inject.Inject
-
-/*
- * We need this class for the splitShadeBlueprint so `addViews` and `removeViews` will be called
- * when switching to and from splitShade.
- */
-class SplitShadeSmartspaceSection
-@Inject
-constructor(
- keyguardClockViewModel: KeyguardClockViewModel,
- keyguardSmartspaceViewModel: KeyguardSmartspaceViewModel,
- context: Context,
- smartspaceController: LockscreenSmartspaceController,
- keyguardUnlockAnimationController: KeyguardUnlockAnimationController,
-) :
- SmartspaceSection(
- keyguardClockViewModel,
- keyguardSmartspaceViewModel,
- context,
- smartspaceController,
- keyguardUnlockAnimationController,
- )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt
index a8e3be7..b4b48a8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt
@@ -19,7 +19,6 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
@@ -38,14 +37,14 @@
class AlternateBouncerToAodTransitionViewModel
@Inject
constructor(
- interactor: KeyguardTransitionInteractor,
deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
) : DeviceEntryIconTransition {
private val transitionAnimation =
animationFlow.setup(
duration = FromAlternateBouncerTransitionInteractor.TO_AOD_DURATION,
- stepFlow = interactor.transition(KeyguardState.ALTERNATE_BOUNCER, KeyguardState.AOD),
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = KeyguardState.AOD,
)
val deviceEntryBackgroundViewAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
index 5d6b0ce..3737e6f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
@@ -18,7 +18,6 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor.Companion.TO_GONE_DURATION
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
import com.android.systemui.keyguard.shared.model.ScrimAlpha
@@ -37,14 +36,14 @@
class AlternateBouncerToGoneTransitionViewModel
@Inject
constructor(
- interactor: KeyguardTransitionInteractor,
bouncerToGoneFlows: BouncerToGoneFlows,
animationFlow: KeyguardTransitionAnimationFlow,
) : DeviceEntryIconTransition {
private val transitionAnimation =
animationFlow.setup(
duration = TO_GONE_DURATION,
- stepFlow = interactor.transition(ALTERNATE_BOUNCER, KeyguardState.GONE),
+ from = ALTERNATE_BOUNCER,
+ to = KeyguardState.GONE,
)
/** Scrim alpha values */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt
new file mode 100644
index 0000000..7592881
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Breaks down ALTERNATE BOUNCER->PRIMARY BOUNCER transition into discrete steps for corresponding
+ * views to consume.
+ */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class AlternateBouncerToPrimaryBouncerTransitionViewModel
+@Inject
+constructor(
+ animationFlow: KeyguardTransitionAnimationFlow,
+) : DeviceEntryIconTransition {
+ private val transitionAnimation =
+ animationFlow.setup(
+ duration = FromAlternateBouncerTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ )
+
+ override val deviceEntryParentViewAlpha: Flow<Float> =
+ transitionAnimation.immediatelyTransitionTo(0f)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
index fa4de04..ce45112 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
@@ -17,14 +17,12 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.content.Context
-import android.hardware.biometrics.SensorLocationInternal
import com.android.settingslib.Utils
-import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
-import com.android.systemui.res.R
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -44,21 +42,24 @@
configurationInteractor: ConfigurationInteractor,
deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
deviceEntryBackgroundViewModel: DeviceEntryBackgroundViewModel,
- fingerprintPropertyRepository: FingerprintPropertyRepository,
+ fingerprintPropertyInteractor: FingerprintPropertyInteractor,
udfpsOverlayInteractor: UdfpsOverlayInteractor,
) {
private val isSupported: Flow<Boolean> = deviceEntryUdfpsInteractor.isUdfpsSupported
+
+ /**
+ * UDFPS icon location in pixels for the current display and screen resolution, in natural
+ * orientation.
+ */
val iconLocation: Flow<IconLocation> =
isSupported.flatMapLatest { supportsUI ->
if (supportsUI) {
- fingerprintPropertyRepository.sensorLocations.map { sensorLocations ->
- val sensorLocation =
- sensorLocations.getOrDefault("", SensorLocationInternal.DEFAULT)
+ fingerprintPropertyInteractor.sensorLocation.map { sensorLocation ->
IconLocation(
- left = sensorLocation.sensorLocationX - sensorLocation.sensorRadius,
- top = sensorLocation.sensorLocationY - sensorLocation.sensorRadius,
- right = sensorLocation.sensorLocationX + sensorLocation.sensorRadius,
- bottom = sensorLocation.sensorLocationY + sensorLocation.sensorRadius,
+ left = (sensorLocation.centerX - sensorLocation.radius).toInt(),
+ top = (sensorLocation.centerY - sensorLocation.radius).toInt(),
+ right = (sensorLocation.centerX + sensorLocation.radius).toInt(),
+ bottom = (sensorLocation.centerY + sensorLocation.radius).toInt(),
)
}
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
index 8e729f7..2526f0a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
@@ -19,7 +19,6 @@
import android.graphics.Color
import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor.Companion.TRANSITION_DURATION_MS
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
@@ -37,7 +36,6 @@
@Inject
constructor(
private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
- transitionInteractor: KeyguardTransitionInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
) {
// When we're fully transitioned to the AlternateBouncer, the alpha of the scrim should be:
@@ -46,7 +44,8 @@
animationFlow
.setup(
duration = TRANSITION_DURATION_MS,
- stepFlow = transitionInteractor.anyStateToAlternateBouncerTransition,
+ from = null,
+ to = ALTERNATE_BOUNCER,
)
.sharedFlow(
duration = TRANSITION_DURATION_MS,
@@ -60,7 +59,8 @@
animationFlow
.setup(
TRANSITION_DURATION_MS,
- transitionInteractor.transitionStepsFromState(ALTERNATE_BOUNCER),
+ from = ALTERNATE_BOUNCER,
+ to = null,
)
.sharedFlow(
duration = TRANSITION_DURATION_MS,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt
index 2b14521..b92a9a0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt
@@ -18,7 +18,6 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
@@ -31,14 +30,14 @@
class AodToGoneTransitionViewModel
@Inject
constructor(
- interactor: KeyguardTransitionInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
) : DeviceEntryIconTransition {
private val transitionAnimation =
animationFlow.setup(
duration = FromAodTransitionInteractor.TO_GONE_DURATION,
- stepFlow = interactor.transition(KeyguardState.AOD, KeyguardState.GONE),
+ from = KeyguardState.AOD,
+ to = KeyguardState.GONE,
)
override val deviceEntryParentViewAlpha = transitionAnimation.immediatelyTransitionTo(0f)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
index 5e552e1..266fd02 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
@@ -19,7 +19,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
@@ -37,7 +37,6 @@
class AodToLockscreenTransitionViewModel
@Inject
constructor(
- interactor: KeyguardTransitionInteractor,
deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
) : DeviceEntryIconTransition {
@@ -45,7 +44,8 @@
private val transitionAnimation =
animationFlow.setup(
duration = TO_LOCKSCREEN_DURATION,
- stepFlow = interactor.aodToLockscreenTransition,
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
)
/** Ensure alpha is set to be visible */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt
index d283af3..105a7ed 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt
@@ -18,7 +18,6 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
@@ -29,13 +28,13 @@
class AodToOccludedTransitionViewModel
@Inject
constructor(
- interactor: KeyguardTransitionInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
) : DeviceEntryIconTransition {
private val transitionAnimation =
animationFlow.setup(
duration = FromAodTransitionInteractor.TO_OCCLUDED_DURATION,
- stepFlow = interactor.transition(KeyguardState.AOD, KeyguardState.OCCLUDED),
+ from = KeyguardState.AOD,
+ to = KeyguardState.OCCLUDED,
)
override val deviceEntryParentViewAlpha = transitionAnimation.immediatelyTransitionTo(0f)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
index 41dc157..924fc5d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
@@ -21,7 +21,6 @@
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.ScrimAlpha
@@ -41,7 +40,6 @@
class BouncerToGoneFlows
@Inject
constructor(
- private val interactor: KeyguardTransitionInteractor,
private val statusBarStateController: SysuiStatusBarStateController,
private val primaryBouncerInteractor: PrimaryBouncerInteractor,
private val keyguardDismissActionInteractor: Lazy<KeyguardDismissActionInteractor>,
@@ -76,7 +74,8 @@
val transitionAnimation =
animationFlow.setup(
duration = duration,
- stepFlow = interactor.transition(fromState, GONE)
+ from = fromState,
+ to = GONE,
)
return shadeInteractor.shadeExpansion.flatMapLatest { shadeExpansion ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
index fe0b365..310ec95 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
@@ -20,7 +20,7 @@
import android.content.Context
import com.android.settingslib.Utils
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
-import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -41,7 +41,7 @@
@Inject
constructor(
val context: Context,
- configurationRepository: ConfigurationRepository, // TODO (b/309655554): create & use interactor
+ configurationInteractor: ConfigurationInteractor,
deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
transitionInteractor: KeyguardTransitionInteractor,
deviceEntryIconViewModel: DeviceEntryIconViewModel,
@@ -62,7 +62,7 @@
private val color: Flow<Int> =
deviceEntryIconViewModel.useBackgroundProtection.flatMapLatest { useBgProtection ->
- configurationRepository.onAnyConfigurationChange
+ configurationInteractor.onAnyConfigurationChange
.map { getColor(useBgProtection) }
.onStart { emit(getColor(useBgProtection)) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt
index 0b34326..e4610c1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt
@@ -18,7 +18,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromDozingTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
@@ -34,13 +34,13 @@
class DozingToLockscreenTransitionViewModel
@Inject
constructor(
- interactor: KeyguardTransitionInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
) : DeviceEntryIconTransition {
private val transitionAnimation =
animationFlow.setup(
duration = FromDozingTransitionInteractor.TO_LOCKSCREEN_DURATION,
- stepFlow = interactor.dozingToLockscreenTransition,
+ from = KeyguardState.DOZING,
+ to = KeyguardState.LOCKSCREEN,
)
val shortcutsAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingHostedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingHostedToLockscreenTransitionViewModel.kt
index 8bcf3f8..67568e1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingHostedToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingHostedToLockscreenTransitionViewModel.kt
@@ -18,7 +18,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromDreamingLockscreenHostedTransitionInteractor.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.ui.KeyguardTransitionAnimationFlow
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
@@ -28,14 +28,14 @@
class DreamingHostedToLockscreenTransitionViewModel
@Inject
constructor(
- interactor: KeyguardTransitionInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
) {
private val transitionAnimation =
animationFlow.setup(
duration = TO_LOCKSCREEN_DURATION,
- stepFlow = interactor.dreamingLockscreenHostedToLockscreenTransition,
+ from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+ to = KeyguardState.LOCKSCREEN,
)
val shortcutsAlpha: Flow<Float> =
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 5f620af..ead2d48 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
@@ -22,6 +22,7 @@
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
@@ -52,7 +53,8 @@
private val transitionAnimation =
animationFlow.setup(
duration = TO_LOCKSCREEN_DURATION,
- stepFlow = keyguardTransitionInteractor.dreamingToLockscreenTransition,
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.LOCKSCREEN,
)
val transitionEnded =
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
new file mode 100644
index 0000000..bc51821
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Breaks down GLANCEABLE_HUB->LOCKSCREEN transition into discrete steps for corresponding views to
+ * consume.
+ */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class GlanceableHubToLockscreenTransitionViewModel
+@Inject
+constructor(
+ animationFlow: KeyguardTransitionAnimationFlow,
+) {
+ private val transitionAnimation =
+ animationFlow.setup(
+ duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION,
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.LOCKSCREEN,
+ )
+
+ // TODO(b/315205222): implement full animation spec instead of just a simple fade.
+ val keyguardAlpha: Flow<Float> =
+ transitionAnimation.sharedFlow(
+ duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION,
+ onStep = { it },
+ onFinish = { 1f },
+ onCancel = { 0f },
+ name = "GLANCEABLE_HUB->LOCKSCREEN: keyguardAlpha",
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
index 3f27eb0..ba04fd3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
@@ -20,7 +20,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_AOD_DURATION
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
@@ -36,7 +36,6 @@
class GoneToAodTransitionViewModel
@Inject
constructor(
- interactor: KeyguardTransitionInteractor,
deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
) : DeviceEntryIconTransition {
@@ -44,7 +43,8 @@
private val transitionAnimation =
animationFlow.setup(
duration = TO_AOD_DURATION,
- stepFlow = interactor.goneToAodTransition,
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
)
/** y-translation from the top of the screen for AOD */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingLockscreenHostedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingLockscreenHostedTransitionViewModel.kt
index bba790a..b527463 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingLockscreenHostedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingLockscreenHostedTransitionViewModel.kt
@@ -18,7 +18,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
@@ -32,14 +32,14 @@
class GoneToDreamingLockscreenHostedTransitionViewModel
@Inject
constructor(
- interactor: KeyguardTransitionInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
) {
private val transitionAnimation =
animationFlow.setup(
duration = TO_DREAMING_DURATION,
- stepFlow = interactor.goneToDreamingLockscreenHostedTransition,
+ from = KeyguardState.GONE,
+ to = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
)
/** Lockscreen views alpha - hide immediately */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
index 6762ba6..102242a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
@@ -19,7 +19,7 @@
import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
@@ -30,14 +30,14 @@
class GoneToDreamingTransitionViewModel
@Inject
constructor(
- private val interactor: KeyguardTransitionInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
) {
private val transitionAnimation =
animationFlow.setup(
duration = TO_DREAMING_DURATION,
- stepFlow = interactor.goneToDreamingTransition,
+ from = KeyguardState.GONE,
+ to = KeyguardState.DREAMING,
)
/** Lockscreen views y-translation */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModel.kt
index adae8ab..793abb4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModel.kt
@@ -18,7 +18,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.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.ui.KeyguardTransitionAnimationFlow
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
@@ -28,14 +28,14 @@
class GoneToLockscreenTransitionViewModel
@Inject
constructor(
- interactor: KeyguardTransitionInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
) {
private val transitionAnimation =
animationFlow.setup(
duration = TO_LOCKSCREEN_DURATION,
- stepFlow = interactor.goneToLockscreenTransition
+ from = KeyguardState.GONE,
+ to = KeyguardState.LOCKSCREEN
)
val shortcutsAlpha: Flow<Float> =
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 5bb2782..f37d9f8 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
@@ -99,34 +99,4 @@
context.resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) +
Utils.getStatusBarHeaderHeightKeyguard(context)
}
-
- fun getLargeClockTopMargin(context: Context): Int {
- var largeClockTopMargin =
- context.resources.getDimensionPixelSize(R.dimen.status_bar_height) +
- context.resources.getDimensionPixelSize(
- com.android.systemui.customization.R.dimen.small_clock_padding_top
- ) +
- context.resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset)
- largeClockTopMargin += getDimen(context, DATE_WEATHER_VIEW_HEIGHT)
- largeClockTopMargin += getDimen(context, ENHANCED_SMARTSPACE_HEIGHT)
- if (!useLargeClock) {
- largeClockTopMargin -=
- context.resources.getDimensionPixelSize(
- com.android.systemui.customization.R.dimen.small_clock_height
- )
- }
-
- return largeClockTopMargin
- }
-
- private fun getDimen(context: Context, name: String): Int {
- val res = context.packageManager.getResourcesForApplication(context.packageName)
- val id = res.getIdentifier(name, "dimen", context.packageName)
- return res.getDimensionPixelSize(id)
- }
-
- companion object {
- private const val DATE_WEATHER_VIEW_HEIGHT = "date_weather_view_height"
- private const val ENHANCED_SMARTSPACE_HEIGHT = "enhanced_smartspace_height"
- }
}
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 5059e6b..5d36da9 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
@@ -46,6 +46,7 @@
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
@@ -58,6 +59,8 @@
keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
+ lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel,
+ glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel,
screenOffAnimationController: ScreenOffAnimationController,
private val aodBurnInViewModel: AodBurnInViewModel,
aodAlphaViewModel: AodAlphaViewModel,
@@ -78,7 +81,13 @@
keyguardInteractor.notificationContainerBounds
/** An observable for the alpha level for the entire keyguard root view. */
- val alpha: Flow<Float> = aodAlphaViewModel.alpha
+ val alpha: Flow<Float> =
+ merge(
+ aodAlphaViewModel.alpha,
+ lockscreenToGlanceableHubTransitionViewModel.keyguardAlpha,
+ glanceableHubToLockscreenTransitionViewModel.keyguardAlpha,
+ )
+ .distinctUntilChanged()
/** Specific alpha value for elements visible during [KeyguardState.LOCKSCREEN] */
val lockscreenStateAlpha: Flow<Float> = aodToLockscreenTransitionViewModel.lockscreenAlpha
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
index a1dd720..e8c1ab5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
@@ -18,6 +18,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.domain.interactor.KeyguardSmartspaceInteractor
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -33,6 +34,7 @@
@Application applicationScope: CoroutineScope,
smartspaceController: LockscreenSmartspaceController,
keyguardClockViewModel: KeyguardClockViewModel,
+ smartspaceInteractor: KeyguardSmartspaceInteractor,
) {
/** Whether the smartspace section is available in the build. */
val isSmartspaceEnabled: Boolean = smartspaceController.isEnabled()
@@ -78,4 +80,7 @@
): Boolean {
return !clockIncludesCustomWeatherDisplay && isWeatherEnabled
}
+
+ /* trigger clock and smartspace constraints change when smartspace appears */
+ var bcSmartspaceVisibility: StateFlow<Int> = smartspaceInteractor.bcSmartspaceVisibility
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index 36bbe4e..d792889 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -16,9 +16,13 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.content.res.Resources
+import com.android.keyguard.KeyguardClockSwitch
import com.android.systemui.biometrics.AuthController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.res.R
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
@@ -31,12 +35,28 @@
class LockscreenContentViewModel
@Inject
constructor(
+ clockInteractor: KeyguardClockInteractor,
private val interactor: KeyguardBlueprintInteractor,
private val authController: AuthController,
val longPress: KeyguardLongPressViewModel,
) {
+ private val clockSize = clockInteractor.clockSize
+
val isUdfpsVisible: Boolean
get() = authController.isUdfpsSupported
+ val isLargeClockVisible: Boolean
+ get() = clockSize.value == KeyguardClockSwitch.LARGE
+ val areNotificationsVisible: Boolean
+ get() = !isLargeClockVisible
+
+ fun getSmartSpacePaddingTop(resources: Resources): Int {
+ return if (isLargeClockVisible) {
+ resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) +
+ resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin)
+ } else {
+ 0
+ }
+ }
fun blueprintId(scope: CoroutineScope): StateFlow<String> {
return interactor.blueprint
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt
index 65614f4..7bf51a7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt
@@ -19,7 +19,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
@@ -36,7 +36,6 @@
class LockscreenToAodTransitionViewModel
@Inject
constructor(
- interactor: KeyguardTransitionInteractor,
deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
shadeDependentFlows: ShadeDependentFlows,
animationFlow: KeyguardTransitionAnimationFlow,
@@ -45,7 +44,8 @@
private val transitionAnimation =
animationFlow.setup(
duration = FromLockscreenTransitionInteractor.TO_AOD_DURATION,
- stepFlow = interactor.lockscreenToAodTransition,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
)
val deviceEntryBackgroundViewAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt
index accb20c..4c0cd2f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt
@@ -18,7 +18,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DOZING_DURATION
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
@@ -28,14 +28,14 @@
class LockscreenToDozingTransitionViewModel
@Inject
constructor(
- interactor: KeyguardTransitionInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
) {
private val transitionAnimation =
animationFlow.setup(
duration = TO_DOZING_DURATION,
- stepFlow = interactor.lockscreenToDozingTransition
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DOZING,
)
val shortcutsAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingHostedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingHostedTransitionViewModel.kt
index c649b12..19b9cf47 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingHostedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingHostedTransitionViewModel.kt
@@ -18,7 +18,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_HOSTED_DURATION
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
@@ -28,14 +28,14 @@
class LockscreenToDreamingHostedTransitionViewModel
@Inject
constructor(
- interactor: KeyguardTransitionInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
) {
private val transitionAnimation =
animationFlow.setup(
duration = TO_DREAMING_HOSTED_DURATION,
- stepFlow = interactor.lockscreenToDreamingLockscreenHostedTransition
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
)
val shortcutsAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
index 7f75b54..13522a6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
@@ -19,7 +19,7 @@
import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_DURATION
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
@@ -34,14 +34,14 @@
class LockscreenToDreamingTransitionViewModel
@Inject
constructor(
- interactor: KeyguardTransitionInteractor,
shadeDependentFlows: ShadeDependentFlows,
animationFlow: KeyguardTransitionAnimationFlow,
) : DeviceEntryIconTransition {
private val transitionAnimation =
animationFlow.setup(
duration = TO_DREAMING_DURATION,
- stepFlow = interactor.lockscreenToDreamingTransition,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DREAMING,
)
/** Lockscreen views y-translation */
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
new file mode 100644
index 0000000..3ea83ae
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Breaks down LOCKSCREEN->GLANCEABLE_HUB transition into discrete steps for corresponding views to
+ * consume.
+ */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class LockscreenToGlanceableHubTransitionViewModel
+@Inject
+constructor(
+ animationFlow: KeyguardTransitionAnimationFlow,
+) {
+ private val transitionAnimation =
+ animationFlow.setup(
+ duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ )
+
+ // TODO(b/315205222): implement full animation spec instead of just a simple fade.
+ val keyguardAlpha: Flow<Float> =
+ transitionAnimation.sharedFlow(
+ duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION,
+ onStep = { 1f - it },
+ onFinish = { 0f },
+ onCancel = { 1f },
+ name = "LOCKSCREEN->GLANCEABLE_HUB: keyguardAlpha",
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
index 9e197138..a26ef07 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
@@ -18,7 +18,6 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
@@ -35,14 +34,14 @@
class LockscreenToGoneTransitionViewModel
@Inject
constructor(
- interactor: KeyguardTransitionInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
) : DeviceEntryIconTransition {
private val transitionAnimation =
animationFlow.setup(
duration = FromLockscreenTransitionInteractor.TO_GONE_DURATION,
- stepFlow = interactor.transition(KeyguardState.LOCKSCREEN, KeyguardState.GONE),
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
)
val shortcutsAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
index 9db0b77..dd6652e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
@@ -20,7 +20,7 @@
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_OCCLUDED_DURATION
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import com.android.systemui.res.R
@@ -37,7 +37,6 @@
class LockscreenToOccludedTransitionViewModel
@Inject
constructor(
- interactor: KeyguardTransitionInteractor,
shadeDependentFlows: ShadeDependentFlows,
configurationInteractor: ConfigurationInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
@@ -46,7 +45,8 @@
private val transitionAnimation =
animationFlow.setup(
duration = TO_OCCLUDED_DURATION,
- stepFlow = interactor.lockscreenToOccludedTransition,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
)
/** Lockscreen views alpha */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
index 52e3257..ce47f3c6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
@@ -18,7 +18,6 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
@@ -26,7 +25,6 @@
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
/**
* Breaks down LOCKSCREEN->PRIMARY BOUNCER transition into discrete steps for corresponding views to
@@ -37,21 +35,21 @@
class LockscreenToPrimaryBouncerTransitionViewModel
@Inject
constructor(
- interactor: KeyguardTransitionInteractor,
shadeDependentFlows: ShadeDependentFlows,
animationFlow: KeyguardTransitionAnimationFlow,
) : DeviceEntryIconTransition {
private val transitionAnimation =
animationFlow.setup(
duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
- stepFlow =
- interactor.transition(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER),
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.PRIMARY_BOUNCER,
)
val shortcutsAlpha: Flow<Float> =
- interactor.transition(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER).map {
- 1 - it.value
- }
+ transitionAnimation.sharedFlow(
+ duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
+ onStep = { 1f - it }
+ )
override val deviceEntryParentViewAlpha: Flow<Float> =
shadeDependentFlows.transitionFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt
index ed5e83c..07c1141 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt
@@ -19,7 +19,6 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
@@ -35,14 +34,14 @@
class OccludedToAodTransitionViewModel
@Inject
constructor(
- interactor: KeyguardTransitionInteractor,
deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
) : DeviceEntryIconTransition {
private val transitionAnimation =
animationFlow.setup(
duration = FromOccludedTransitionInteractor.TO_AOD_DURATION,
- stepFlow = interactor.transition(KeyguardState.OCCLUDED, KeyguardState.AOD),
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.AOD,
)
val deviceEntryBackgroundViewAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
index 4c24f83..90195bd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
@@ -21,7 +21,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import com.android.systemui.res.R
@@ -41,7 +41,6 @@
class OccludedToLockscreenTransitionViewModel
@Inject
constructor(
- interactor: KeyguardTransitionInteractor,
deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
configurationInteractor: ConfigurationInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
@@ -50,7 +49,8 @@
private val transitionAnimation =
animationFlow.setup(
duration = TO_LOCKSCREEN_DURATION,
- stepFlow = interactor.occludedToLockscreenTransition,
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.LOCKSCREEN,
)
/** Lockscreen views y-translation */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt
index 93482ea..74094be 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt
@@ -17,7 +17,7 @@
package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
@@ -27,14 +27,14 @@
class OffToLockscreenTransitionViewModel
@Inject
constructor(
- interactor: KeyguardTransitionInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
) {
private val transitionAnimation =
animationFlow.setup(
duration = 250.milliseconds,
- stepFlow = interactor.offToLockscreenTransition
+ from = KeyguardState.OFF,
+ to = KeyguardState.LOCKSCREEN,
)
val shortcutsAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt
index b0e2aa2..cd8e2f1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt
@@ -19,7 +19,6 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
@@ -39,14 +38,14 @@
class PrimaryBouncerToAodTransitionViewModel
@Inject
constructor(
- interactor: KeyguardTransitionInteractor,
deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
) : DeviceEntryIconTransition {
private val transitionAnimation =
animationFlow.setup(
duration = FromPrimaryBouncerTransitionInteractor.TO_AOD_DURATION,
- stepFlow = interactor.transition(KeyguardState.PRIMARY_BOUNCER, KeyguardState.AOD),
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.AOD,
)
val deviceEntryBackgroundViewAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index 9dbe97f..4f28b46 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -22,7 +22,6 @@
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.shared.model.ScrimAlpha
@@ -44,7 +43,6 @@
class PrimaryBouncerToGoneTransitionViewModel
@Inject
constructor(
- interactor: KeyguardTransitionInteractor,
private val statusBarStateController: SysuiStatusBarStateController,
private val primaryBouncerInteractor: PrimaryBouncerInteractor,
keyguardDismissActionInteractor: Lazy<KeyguardDismissActionInteractor>,
@@ -55,7 +53,8 @@
private val transitionAnimation =
animationFlow.setup(
duration = TO_GONE_DURATION,
- stepFlow = interactor.transition(PRIMARY_BOUNCER, GONE)
+ from = PRIMARY_BOUNCER,
+ to = GONE,
)
private var leaveShadeOpen: Boolean = false
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
index b2eed60..284a134 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
@@ -19,7 +19,6 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
@@ -28,7 +27,6 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.map
/**
* Breaks down PRIMARY BOUNCER->LOCKSCREEN transition into discrete steps for corresponding views to
@@ -39,15 +37,14 @@
class PrimaryBouncerToLockscreenTransitionViewModel
@Inject
constructor(
- interactor: KeyguardTransitionInteractor,
deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
) : DeviceEntryIconTransition {
private val transitionAnimation =
animationFlow.setup(
duration = FromPrimaryBouncerTransitionInteractor.TO_LOCKSCREEN_DURATION,
- stepFlow =
- interactor.transition(KeyguardState.PRIMARY_BOUNCER, KeyguardState.LOCKSCREEN),
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.LOCKSCREEN,
)
val deviceEntryBackgroundViewAlpha: Flow<Float> =
@@ -60,9 +57,10 @@
}
val shortcutsAlpha: Flow<Float> =
- interactor.transition(KeyguardState.PRIMARY_BOUNCER, KeyguardState.LOCKSCREEN).map {
- it.value
- }
+ transitionAnimation.sharedFlow(
+ duration = FromPrimaryBouncerTransitionInteractor.TO_LOCKSCREEN_DURATION,
+ onStep = { it }
+ )
override val deviceEntryParentViewAlpha: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(1f)
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyboardLog.kt
similarity index 64%
copy from core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
copy to packages/SystemUI/src/com/android/systemui/log/dagger/KeyboardLog.kt
index ce92b6d..5910701 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyboardLog.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.
@@ -13,10 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.companion.virtual.camera;
-/**
- * The configuration of a single virtual camera stream.
- * @hide
- */
-parcelable VirtualCameraStreamConfig;
\ No newline at end of file
+package com.android.systemui.log.dagger
+
+import javax.inject.Qualifier
+
+/** A [com.android.systemui.log.LogBuffer] for keyboard-related functionality. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class KeyboardLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 24cb8ff..3e00940 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -603,6 +603,14 @@
return factory.create("BluetoothTileDialogLog", 50);
}
+ /** Provides a {@link LogBuffer} for the keyboard functionalities. */
+ @Provides
+ @SysUISingleton
+ @KeyboardLog
+ public static LogBuffer provideKeyboardLogBuffer(LogBufferFactory factory) {
+ return factory.create("KeyboardLog", 50);
+ }
+
/** Provides a {@link LogBuffer} for {@link PackageChangeRepository} */
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 2551da8..5720cc7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -87,8 +87,6 @@
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.media.controls.models.GutsViewHolder;
import com.android.systemui.media.controls.models.player.MediaAction;
import com.android.systemui.media.controls.models.player.MediaButton;
@@ -247,7 +245,6 @@
private String mCurrentBroadcastApp;
private MultiRippleController mMultiRippleController;
private TurbulenceNoiseController mTurbulenceNoiseController;
- private final FeatureFlags mFeatureFlags;
private final GlobalSettings mGlobalSettings;
private TurbulenceNoiseAnimationConfig mTurbulenceNoiseAnimationConfig;
@@ -281,7 +278,6 @@
ActivityIntentHelper activityIntentHelper,
NotificationLockscreenUserManager lockscreenUserManager,
BroadcastDialogController broadcastDialogController,
- FeatureFlags featureFlags,
GlobalSettings globalSettings,
MediaFlags mediaFlags
) {
@@ -312,8 +308,6 @@
return Unit.INSTANCE;
});
- mFeatureFlags = featureFlags;
-
mGlobalSettings = globalSettings;
updateAnimatorDurationScale();
}
@@ -1187,9 +1181,7 @@
action.run();
- if (mFeatureFlags.isEnabled(Flags.UMO_SURFACE_RIPPLE)) {
- mMultiRippleController.play(createTouchRippleAnimation(button));
- }
+ mMultiRippleController.play(createTouchRippleAnimation(button));
if (icon instanceof Animatable) {
((Animatable) icon).start();
@@ -1228,8 +1220,7 @@
}
private boolean shouldPlayTurbulenceNoise() {
- return mFeatureFlags.isEnabled(Flags.UMO_TURBULENCE_NOISE) && mButtonClicked && !mWasPlaying
- && isPlaying();
+ return mButtonClicked && !mWasPlaying && isPlaying();
}
private TurbulenceNoiseAnimationConfig createTurbulenceNoiseAnimation() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index 0385aeb..523414c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -1153,7 +1153,7 @@
qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
onLockscreen && isSplitShadeExpanding() -> LOCATION_QS
onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS
- // TODO(b/308813166): revisit logic once interactions between the hub and
+ // TODO(b/311234666): revisit logic once interactions between the hub and
// shade/keyguard state are finalized
isCommunalShowing && communalInteractor.isCommunalEnabled -> LOCATION_COMMUNAL_HUB
onLockscreen && allowMediaPlayerOnLockScreen -> LOCATION_LOCKSCREEN
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
index 0320dec..092f1ed 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
@@ -302,9 +302,6 @@
final NavigationBarView navBarView = getNavigationBarView(displayId);
if (navBarView != null) {
navBarView.showPinningEnterExitToast(entering);
- } else if (displayId == mDisplayTracker.getDefaultDisplayId()
- && mTaskbarDelegate.isInitialized()) {
- mTaskbarDelegate.showPinningEnterExitToast(entering);
}
}
@@ -314,9 +311,6 @@
final NavigationBarView navBarView = getNavigationBarView(displayId);
if (navBarView != null) {
navBarView.showPinningEscapeToast();
- } else if (displayId == mDisplayTracker.getDefaultDisplayId()
- && mTaskbarDelegate.isInitialized()) {
- mTaskbarDelegate.showPinningEscapeToast();
}
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 62c7343..0167287 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -504,6 +504,11 @@
}
@Override
+ public void setNavigationBarLumaSamplingEnabled(int displayId, boolean enable) {
+ mOverviewProxyService.onNavigationBarLumaSamplingEnabled(displayId, enable);
+ }
+
+ @Override
public void showPinningEnterExitToast(boolean entering) {
updateSysuiFlags();
if (mScreenPinningNotify == null) {
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 0d641ac..58e0428 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -624,7 +624,7 @@
}
if (!mIsEnabled) {
- mGestureNavigationSettingsObserver.unregister();
+ mBackgroundExecutor.execute(mGestureNavigationSettingsObserver::unregister);
if (DEBUG_MISSING_GESTURE) {
Log.d(DEBUG_MISSING_GESTURE_TAG, "Unregister display listener");
}
@@ -642,7 +642,7 @@
}
} else {
- mGestureNavigationSettingsObserver.register();
+ mBackgroundExecutor.execute(mGestureNavigationSettingsObserver::register);
updateDisplaySize();
if (DEBUG_MISSING_GESTURE) {
Log.d(DEBUG_MISSING_GESTURE_TAG, "Register display listener");
diff --git a/packages/SystemUI/src/com/android/systemui/qs/LeftRightArrowPressedListener.kt b/packages/SystemUI/src/com/android/systemui/qs/LeftRightArrowPressedListener.kt
new file mode 100644
index 0000000..ca790e8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/LeftRightArrowPressedListener.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs
+
+import android.view.KeyEvent
+import android.view.View
+import androidx.core.util.Consumer
+
+/**
+ * Listens for left and right arrow keys pressed while focus is on the view.
+ *
+ * Key press is treated as correct when its full lifecycle happened on the view: first
+ * [KeyEvent.ACTION_DOWN] was performed, view didn't lose focus in the meantime and then
+ * [KeyEvent.ACTION_UP] was performed with the same [KeyEvent.getKeyCode]
+ */
+class LeftRightArrowPressedListener private constructor() :
+ View.OnKeyListener, View.OnFocusChangeListener {
+
+ private var lastKeyCode: Int? = 0
+ private var listener: Consumer<Int>? = null
+
+ fun setArrowKeyPressedListener(arrowPressedListener: Consumer<Int>) {
+ listener = arrowPressedListener
+ }
+
+ override fun onKey(view: View, keyCode: Int, keyEvent: KeyEvent): Boolean {
+ if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
+ // only scroll on ACTION_UP as we don't handle longpressing for now. Still we need
+ // to intercept even ACTION_DOWN otherwise keyboard focus will be moved before we
+ // have a chance to intercept ACTION_UP.
+ if (keyEvent.action == KeyEvent.ACTION_UP && keyCode == lastKeyCode) {
+ listener?.accept(keyCode)
+ lastKeyCode = null
+ } else if (keyEvent.repeatCount == 0) {
+ // we only read key events that are NOT coming from long pressing because that also
+ // causes reading ACTION_DOWN event (with repeated count > 0) when moving focus with
+ // arrow from another sibling view
+ lastKeyCode = keyCode
+ }
+ return true
+ }
+ return false
+ }
+
+ override fun onFocusChange(view: View, hasFocus: Boolean) {
+ // resetting lastKeyCode so we get fresh cleared state on focus
+ if (hasFocus) {
+ lastKeyCode = null
+ }
+ }
+
+ companion object {
+ @JvmStatic
+ fun createAndRegisterListenerForView(view: View): LeftRightArrowPressedListener {
+ val listener = LeftRightArrowPressedListener()
+ view.setOnKeyListener(listener)
+ view.onFocusChangeListener = listener
+ return listener
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
index 4770d52..6fb5174 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
@@ -1,7 +1,11 @@
package com.android.systemui.qs;
+import static com.android.systemui.qs.PageIndicator.PageScrollActionListener.LEFT;
+import static com.android.systemui.qs.PageIndicator.PageScrollActionListener.RIGHT;
+
import android.content.Context;
import android.content.res.ColorStateList;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.drawable.Animatable2;
@@ -9,10 +13,12 @@
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
+import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import com.android.settingslib.Utils;
@@ -36,13 +42,14 @@
private final ArrayList<Integer> mQueuedPositions = new ArrayList<>();
- private final int mPageIndicatorWidth;
- private final int mPageIndicatorHeight;
- private final int mPageDotWidth;
+ private int mPageIndicatorWidth;
+ private int mPageIndicatorHeight;
+ private int mPageDotWidth;
private @NonNull ColorStateList mTint;
private int mPosition = -1;
private boolean mAnimating;
+ private PageScrollActionListener mPageScrollActionListener;
private final Animatable2.AnimationCallback mAnimationCallback =
new Animatable2.AnimationCallback() {
@@ -77,6 +84,43 @@
mPageIndicatorWidth = res.getDimensionPixelSize(R.dimen.qs_page_indicator_width);
mPageIndicatorHeight = res.getDimensionPixelSize(R.dimen.qs_page_indicator_height);
mPageDotWidth = res.getDimensionPixelSize(R.dimen.qs_page_indicator_dot_width);
+ LeftRightArrowPressedListener arrowListener =
+ LeftRightArrowPressedListener.createAndRegisterListenerForView(this);
+ arrowListener.setArrowKeyPressedListener(keyCode -> {
+ if (mPageScrollActionListener != null) {
+ int swipeDirection = keyCode == KeyEvent.KEYCODE_DPAD_LEFT ? LEFT : RIGHT;
+ mPageScrollActionListener.onScrollActionTriggered(swipeDirection);
+ }
+ });
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ updateResources();
+ }
+
+ private void updateResources() {
+ Resources res = getResources();
+ boolean changed = false;
+ int pageIndicatorWidth = res.getDimensionPixelSize(R.dimen.qs_page_indicator_width);
+ if (pageIndicatorWidth != mPageIndicatorWidth) {
+ mPageIndicatorWidth = pageIndicatorWidth;
+ changed = true;
+ }
+ int pageIndicatorHeight = res.getDimensionPixelSize(R.dimen.qs_page_indicator_height);
+ if (pageIndicatorHeight != mPageIndicatorHeight) {
+ mPageIndicatorHeight = pageIndicatorHeight;
+ changed = true;
+ }
+ int pageIndicatorDotWidth = res.getDimensionPixelSize(R.dimen.qs_page_indicator_dot_width);
+ if (pageIndicatorDotWidth != mPageDotWidth) {
+ mPageDotWidth = pageIndicatorDotWidth;
+ changed = true;
+ }
+ if (changed) {
+ invalidate();
+ }
}
public void setNumPages(int numPages) {
@@ -280,4 +324,19 @@
getChildAt(i).layout(left, 0, mPageIndicatorWidth + left, mPageIndicatorHeight);
}
}
+
+ void setPageScrollActionListener(PageScrollActionListener listener) {
+ mPageScrollActionListener = listener;
+ }
+
+ interface PageScrollActionListener {
+
+ @IntDef({LEFT, RIGHT})
+ @interface Direction { }
+
+ int LEFT = 0;
+ int RIGHT = 1;
+
+ void onScrollActionTriggered(@Direction int swipeDirection);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 052c0da..43f3a22 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -1,6 +1,8 @@
package com.android.systemui.qs;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE;
+import static com.android.systemui.qs.PageIndicator.PageScrollActionListener.LEFT;
+import static com.android.systemui.qs.PageIndicator.PageScrollActionListener.RIGHT;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -12,7 +14,6 @@
import android.content.res.Configuration;
import android.os.Bundle;
import android.util.AttributeSet;
-import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -30,6 +31,7 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.plugins.qs.QSTile;
+import com.android.systemui.qs.PageIndicator.PageScrollActionListener.Direction;
import com.android.systemui.qs.QSPanel.QSTileLayout;
import com.android.systemui.qs.QSPanelControllerBase.TileRecord;
import com.android.systemui.qs.logging.QSLogger;
@@ -310,26 +312,18 @@
mPageIndicator = indicator;
mPageIndicator.setNumPages(mPages.size());
mPageIndicator.setLocation(mPageIndicatorPosition);
- mPageIndicator.setOnKeyListener((view, keyCode, keyEvent) -> {
- if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
- // only scroll on ACTION_UP as we don't handle longpressing for now. Still we need
- // to intercept even ACTION_DOWN otherwise keyboard focus will be moved before we
- // have a chance to intercept ACTION_UP.
- if (keyEvent.getAction() == KeyEvent.ACTION_UP && mScroller.isFinished()) {
- scrollByX(getDeltaXForKeyboardScrolling(keyCode),
- SINGLE_PAGE_SCROLL_DURATION_MILLIS);
- }
- return true;
+ mPageIndicator.setPageScrollActionListener(swipeDirection -> {
+ if (mScroller.isFinished()) {
+ scrollByX(getDeltaXForPageScrolling(swipeDirection),
+ SINGLE_PAGE_SCROLL_DURATION_MILLIS);
}
- return false;
});
}
- private int getDeltaXForKeyboardScrolling(int keyCode) {
- if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT && getCurrentItem() != 0) {
+ private int getDeltaXForPageScrolling(@Direction int swipeDirection) {
+ if (swipeDirection == LEFT && getCurrentItem() != 0) {
return -getWidth();
- } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
- && getCurrentItem() != mPages.size() - 1) {
+ } else if (swipeDirection == RIGHT && getCurrentItem() != mPages.size() - 1) {
return getWidth();
}
return 0;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
index c908e6e..5a872d6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
@@ -35,6 +35,7 @@
import androidx.annotation.VisibleForTesting;
import com.android.settingslib.development.DevelopmentSettingsEnabler;
+import com.android.systemui.FontSizeUtils;
import com.android.systemui.res.R;
/**
@@ -109,11 +110,31 @@
private void updateResources() {
updateFooterAnimator();
+ updateEditButtonResources();
+ updateBuildTextResources();
MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
+ lp.height = getResources().getDimensionPixelSize(R.dimen.qs_footer_height);
+ int sideMargin = getResources().getDimensionPixelSize(R.dimen.qs_footer_margin);
+ lp.leftMargin = sideMargin;
+ lp.rightMargin = sideMargin;
lp.bottomMargin = getResources().getDimensionPixelSize(R.dimen.qs_footers_margin_bottom);
setLayoutParams(lp);
}
+ private void updateEditButtonResources() {
+ int size = getResources().getDimensionPixelSize(R.dimen.qs_footer_action_button_size);
+ int padding = getResources().getDimensionPixelSize(R.dimen.qs_footer_icon_padding);
+ MarginLayoutParams lp = (MarginLayoutParams) mEditButton.getLayoutParams();
+ lp.height = size;
+ lp.width = size;
+ mEditButton.setLayoutParams(lp);
+ mEditButton.setPadding(padding, padding, padding, padding);
+ }
+
+ private void updateBuildTextResources() {
+ FontSizeUtils.updateFontSizeFromStyle(mBuildText, R.style.TextAppearance_QS_Status_Build);
+ }
+
private void updateFooterAnimator() {
mFooterAnimator = createFooterAnimator();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapterDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapterDelegate.java
index 92f17f9..e098929 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapterDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapterDelegate.java
@@ -119,6 +119,8 @@
info.removeAction(listOfActions.get(i));
}
}
+ // We really don't want it to be clickable in this case.
+ info.setClickable(false);
return;
}
@@ -126,6 +128,7 @@
new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
AccessibilityNodeInfo.ACTION_CLICK, clickActionString);
info.addAction(action);
+ info.setClickable(true);
}
private void maybeAddActionMoveToPosition(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
index aff4a67..2077d73 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -88,9 +88,9 @@
/** Called when the expansion of the Quick Settings changed. */
fun onQuickSettingsExpansionChanged(expansion: Float, isInSplitShade: Boolean) {
if (isInSplitShade) {
- // In split shade, we want to fade in the background only at the very end (see
- // b/240563302).
- val delay = 0.99f
+ // In split shade, we want to fade in the background when the QS background starts to
+ // show.
+ val delay = 0.15f
_alpha.value = expansion
_backgroundAlpha.value = max(0f, expansion - delay) / (1f - delay)
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
index 6e7e099..bcd09bd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
@@ -18,23 +18,21 @@
import android.Manifest.permission.BIND_QUICK_SETTINGS_TILE
import android.annotation.WorkerThread
-import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.Context
import android.content.Intent
-import android.content.IntentFilter
import android.content.pm.PackageManager
import android.content.pm.PackageManager.ResolveInfoFlags
import android.os.UserHandle
import android.service.quicksettings.TileService
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.common.data.repository.PackageChangeRepository
+import com.android.systemui.common.data.shared.model.PackageChangeModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.util.kotlin.isComponentActuallyEnabled
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
@@ -52,6 +50,7 @@
constructor(
@Application private val applicationContext: Context,
@Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val packageChangeRepository: PackageChangeRepository
) : InstalledTilesComponentRepository {
override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> {
@@ -70,24 +69,9 @@
)
.packageManager
}
- return conflatedCallbackFlow {
- val receiver =
- object : BroadcastReceiver() {
- override fun onReceive(context: Context?, intent: Intent?) {
- trySend(Unit)
- }
- }
- applicationContext.registerReceiverAsUser(
- receiver,
- UserHandle.of(userId),
- INTENT_FILTER,
- /* broadcastPermission = */ null,
- /* scheduler = */ null
- )
-
- awaitClose { applicationContext.unregisterReceiver(receiver) }
- }
- .onStart { emit(Unit) }
+ return packageChangeRepository
+ .packageChanged(UserHandle.of(userId))
+ .onStart { emit(PackageChangeModel.Empty) }
.map { reloadComponents(userId, packageManager) }
.distinctUntilChanged()
.flowOn(backgroundDispatcher)
@@ -104,14 +88,6 @@
}
companion object {
- private val INTENT_FILTER =
- IntentFilter().apply {
- addAction(Intent.ACTION_PACKAGE_ADDED)
- addAction(Intent.ACTION_PACKAGE_CHANGED)
- addAction(Intent.ACTION_PACKAGE_REMOVED)
- addAction(Intent.ACTION_PACKAGE_REPLACED)
- addDataScheme("package")
- }
private val INTENT = Intent(TileService.ACTION_QS_TILE)
private val FLAGS =
ResolveInfoFlags.of(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
index fc06090..fe10eaa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
@@ -18,6 +18,8 @@
import android.app.PendingIntent
import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.UserHandle
import android.view.View
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.animation.ActivityLaunchAnimator
@@ -32,13 +34,23 @@
interface QSTileIntentUserInputHandler {
fun handle(view: View?, intent: Intent)
- fun handle(view: View?, pendingIntent: PendingIntent)
+
+ /** @param requestLaunchingDefaultActivity used in case !pendingIndent.isActivity */
+ fun handle(
+ view: View?,
+ pendingIntent: PendingIntent,
+ requestLaunchingDefaultActivity: Boolean = false
+ )
}
@SysUISingleton
class QSTileIntentUserInputHandlerImpl
@Inject
-constructor(private val activityStarter: ActivityStarter) : QSTileIntentUserInputHandler {
+constructor(
+ private val activityStarter: ActivityStarter,
+ private val packageManager: PackageManager,
+ private val userHandle: UserHandle,
+) : QSTileIntentUserInputHandler {
override fun handle(view: View?, intent: Intent) {
val animationController: ActivityLaunchAnimator.Controller? =
@@ -52,21 +64,41 @@
}
// TODO(b/249804373): make sure to allow showing activities over the lockscreen. See b/292112939
- override fun handle(view: View?, pendingIntent: PendingIntent) {
- if (!pendingIntent.isActivity) {
- return
- }
- val animationController: ActivityLaunchAnimator.Controller? =
- view?.let {
- ActivityLaunchAnimator.Controller.fromView(
- it,
- InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE,
+ override fun handle(
+ view: View?,
+ pendingIntent: PendingIntent,
+ requestLaunchingDefaultActivity: Boolean
+ ) {
+ if (pendingIntent.isActivity) {
+ val animationController: ActivityLaunchAnimator.Controller? =
+ view?.let {
+ ActivityLaunchAnimator.Controller.fromView(
+ it,
+ InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE,
+ )
+ }
+ activityStarter.postStartActivityDismissingKeyguard(pendingIntent, animationController)
+ } else if (requestLaunchingDefaultActivity) {
+ val intent =
+ Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_LAUNCHER)
+ .setPackage(pendingIntent.creatorPackage)
+ .addFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+ )
+ val intents =
+ packageManager.queryIntentActivitiesAsUser(
+ intent,
+ PackageManager.ResolveInfoFlags.of(0L),
+ userHandle.identifier
)
- }
- activityStarter.startPendingIntentMaybeDismissingKeyguard(
- pendingIntent,
- null,
- animationController
- )
+ intents
+ .firstOrNull { it.activityInfo.exported }
+ ?.let { resolved ->
+ intent.setPackage(null)
+ intent.setComponent(resolved.activityInfo.componentName)
+ handle(view, intent)
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt
index 1805eb1..1a06c38 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt
@@ -22,12 +22,14 @@
import android.view.View
import android.view.View.AccessibilityDelegate
import android.view.View.GONE
+import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.view.accessibility.AccessibilityNodeInfo
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
import android.widget.ImageView
+import android.widget.ProgressBar
import android.widget.Switch
import android.widget.TextView
import androidx.recyclerview.widget.AsyncListDiffer
@@ -92,6 +94,8 @@
private lateinit var pairNewDeviceButton: View
private lateinit var deviceListView: RecyclerView
private lateinit var scrollViewContent: View
+ private lateinit var progressBarAnimation: ProgressBar
+ private lateinit var progressBarBackground: View
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -122,6 +126,8 @@
scrollViewContent = this
layoutParams.height = cachedContentHeight
}
+ progressBarAnimation = requireViewById(R.id.bluetooth_tile_dialog_progress_animation)
+ progressBarBackground = requireViewById(R.id.bluetooth_tile_dialog_progress_background)
}
override fun start() {
@@ -135,6 +141,17 @@
super.dismiss()
}
+ internal suspend fun animateProgressBar(animate: Boolean) {
+ withContext(mainDispatcher) {
+ if (animate) {
+ showProgressBar()
+ } else {
+ delay(PROGRESS_BAR_ANIMATION_DURATION_MS)
+ hideProgressBar()
+ }
+ }
+ }
+
internal suspend fun onDeviceItemUpdated(
deviceItem: List<DeviceItem>,
showSeeAll: Boolean,
@@ -190,6 +207,28 @@
}
}
+ private fun showProgressBar() {
+ if (
+ ::progressBarAnimation.isInitialized &&
+ ::progressBarBackground.isInitialized &&
+ progressBarAnimation.visibility != VISIBLE
+ ) {
+ progressBarAnimation.visibility = VISIBLE
+ progressBarBackground.visibility = INVISIBLE
+ }
+ }
+
+ private fun hideProgressBar() {
+ if (
+ ::progressBarAnimation.isInitialized &&
+ ::progressBarBackground.isInitialized &&
+ progressBarAnimation.visibility != INVISIBLE
+ ) {
+ progressBarAnimation.visibility = INVISIBLE
+ progressBarBackground.visibility = VISIBLE
+ }
+ }
+
internal inner class Adapter(private val onClickCallback: BluetoothTileDialogCallback) :
RecyclerView.Adapter<Adapter.DeviceItemViewHolder>() {
@@ -300,6 +339,7 @@
const val ACTION_PAIR_NEW_DEVICE = "android.settings.BLUETOOTH_PAIRING_SETTINGS"
const val DISABLED_ALPHA = 0.3f
const val ENABLED_ALPHA = 1f
+ const val PROGRESS_BAR_ANIMATION_DURATION_MS = 1500L
private fun Boolean.toInt(): Int {
return if (this) 1 else 0
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
index 6d08f59..194e7bc 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
@@ -105,6 +105,41 @@
deviceItemInteractor.updateDeviceItems(context, DeviceFetchTrigger.FIRST_LOAD)
}
+ // deviceItemUpdate is emitted when device item list is done fetching, update UI and
+ // stop the progress bar.
+ deviceItemInteractor.deviceItemUpdate
+ .onEach {
+ updateDialogUiJob?.cancel()
+ updateDialogUiJob = launch {
+ dialog.apply {
+ onDeviceItemUpdated(
+ it.take(MAX_DEVICE_ITEM_ENTRY),
+ showSeeAll = it.size > MAX_DEVICE_ITEM_ENTRY,
+ showPairNewDevice = bluetoothStateInteractor.isBluetoothEnabled
+ )
+ animateProgressBar(false)
+ }
+ }
+ }
+ .launchIn(this)
+
+ // deviceItemUpdateRequest is emitted when a bluetooth callback is called, re-fetch
+ // the device item list and animiate the progress bar.
+ deviceItemInteractor.deviceItemUpdateRequest
+ .onEach {
+ dialog.animateProgressBar(true)
+ updateDeviceItemJob?.cancel()
+ updateDeviceItemJob = launch {
+ deviceItemInteractor.updateDeviceItems(
+ context,
+ DeviceFetchTrigger.BLUETOOTH_CALLBACK_RECEIVED
+ )
+ }
+ }
+ .launchIn(this)
+
+ // bluetoothStateUpdate is emitted when bluetooth on/off state is changed, re-fetch
+ // the device item list.
bluetoothStateInteractor.bluetoothStateUpdate
.filterNotNull()
.onEach {
@@ -119,39 +154,21 @@
}
.launchIn(this)
- deviceItemInteractor.deviceItemUpdateRequest
- .onEach {
- updateDeviceItemJob?.cancel()
- updateDeviceItemJob = launch {
- deviceItemInteractor.updateDeviceItems(
- context,
- DeviceFetchTrigger.BLUETOOTH_CALLBACK_RECEIVED
- )
- }
- }
- .launchIn(this)
-
- deviceItemInteractor.deviceItemUpdate
- .onEach {
- updateDialogUiJob?.cancel()
- updateDialogUiJob = launch {
- dialog.onDeviceItemUpdated(
- it.take(MAX_DEVICE_ITEM_ENTRY),
- showSeeAll = it.size > MAX_DEVICE_ITEM_ENTRY,
- showPairNewDevice = bluetoothStateInteractor.isBluetoothEnabled
- )
- }
- }
- .launchIn(this)
-
+ // bluetoothStateToggle is emitted when user toggles the bluetooth state switch,
+ // send the new value to the bluetoothStateInteractor and animate the progress bar.
dialog.bluetoothStateToggle
- .onEach { bluetoothStateInteractor.isBluetoothEnabled = it }
+ .onEach {
+ dialog.animateProgressBar(true)
+ bluetoothStateInteractor.isBluetoothEnabled = it
+ }
.launchIn(this)
+ // deviceItemClick is emitted when user clicked on a device item.
dialog.deviceItemClick
.onEach { deviceItemInteractor.updateDeviceItemOnClick(it) }
.launchIn(this)
+ // contentHeight is emitted when the dialog is dismissed.
dialog.contentHeight
.onEach {
withContext(backgroundDispatcher) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractor.kt
index afca57c..0ad520b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractor.kt
@@ -18,9 +18,7 @@
import android.content.Intent
import android.provider.AlarmClock
-import com.android.internal.jank.InteractionJankMonitor
-import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
import com.android.systemui.qs.tiles.base.interactor.QSTileInput
import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
import com.android.systemui.qs.tiles.impl.alarm.domain.model.AlarmTileModel
@@ -31,34 +29,20 @@
class AlarmTileUserActionInteractor
@Inject
constructor(
- private val activityStarter: ActivityStarter,
+ private val inputHandler: QSTileIntentUserInputHandler,
) : QSTileUserActionInteractor<AlarmTileModel> {
override suspend fun handleInput(input: QSTileInput<AlarmTileModel>): Unit =
with(input) {
when (action) {
is QSTileUserAction.Click -> {
- val animationController =
- action.view?.let {
- ActivityLaunchAnimator.Controller.fromView(
- it,
- InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE
- )
- }
if (
data is AlarmTileModel.NextAlarmSet &&
data.alarmClockInfo.showIntent != null
) {
val pendingIndent = data.alarmClockInfo.showIntent
- activityStarter.postStartActivityDismissingKeyguard(
- pendingIndent,
- animationController
- )
+ inputHandler.handle(action.view, pendingIndent, true)
} else {
- activityStarter.postStartActivityDismissingKeyguard(
- Intent(AlarmClock.ACTION_SHOW_ALARMS),
- 0,
- animationController
- )
+ inputHandler.handle(action.view, Intent(AlarmClock.ACTION_SHOW_ALARMS))
}
}
is QSTileUserAction.LongClick -> {}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 45917e8..fd53423 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -1037,6 +1037,19 @@
}
}
+ public void onNavigationBarLumaSamplingEnabled(int displayId, boolean enable) {
+ try {
+ if (mOverviewProxy != null) {
+ mOverviewProxy.onNavigationBarLumaSamplingEnabled(displayId, enable);
+ } else {
+ Log.e(TAG_OPS, "Failed to get overview proxy to enable/disable nav bar luma"
+ + "sampling");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG_OPS, "Failed to call onNavigationBarLumaSamplingEnabled()", e);
+ }
+ }
+
private void updateEnabledState() {
final int currentUser = mUserTracker.getUserId();
mIsEnabled = mContext.getPackageManager().resolveServiceAsUser(mQuickStepIntent,
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 0abde4d..b3d2e09 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -18,6 +18,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.data.repository.SceneContainerRepository
import com.android.systemui.scene.shared.logger.SceneLogger
@@ -50,6 +51,7 @@
private val repository: SceneContainerRepository,
private val powerInteractor: PowerInteractor,
private val logger: SceneLogger,
+ private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
) {
/**
@@ -222,6 +224,11 @@
loggingReason: String,
log: (from: SceneKey, to: SceneKey, loggingReason: String) -> Unit,
) {
+ check(scene.key != SceneKey.Gone || deviceUnlockedInteractor.isDeviceUnlocked.value) {
+ "Cannot change to the Gone scene while the device is locked. Logging reason for scene" +
+ " change was: $loggingReason"
+ }
+
val currentSceneKey = desiredScene.value.key
if (currentSceneKey == scene.key) {
return
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
index a6cccf1..e2959fe 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
@@ -24,7 +24,9 @@
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.init.NotificationsController
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import com.android.systemui.statusbar.policy.HeadsUpManager
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -44,6 +46,7 @@
private val keyguardRepository: KeyguardRepository,
private val headsUpManager: HeadsUpManager,
private val powerInteractor: PowerInteractor,
+ private val activeNotificationsInteractor: ActiveNotificationsInteractor,
) : CoreStartable {
private var notificationPresenter: NotificationPresenter? = null
@@ -117,6 +120,14 @@
return if (headsUpManager.hasPinnedHeadsUp() && isNotifPresenterFullyCollapsed) {
1
} else {
+ getActiveNotificationsCount()
+ }
+ }
+
+ private fun getActiveNotificationsCount(): Int {
+ return if (NotificationsLiveDataStoreRefactor.isEnabled) {
+ activeNotificationsInteractor.allNotificationsCountValue
+ } else {
notificationsController?.getActiveNotificationsCount() ?: 0
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
index 8c3e4a5..a755805 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
@@ -37,6 +37,7 @@
/** The flag description -- not an aconfig flag name */
const val DESCRIPTION = "SceneContainerFlag"
+ @JvmStatic
inline val isEnabled
get() =
SCENE_CONTAINER_ENABLED && // mainStaticFlag
@@ -44,6 +45,7 @@
keyguardBottomAreaRefactor() &&
KeyguardShadeMigrationNssl.isEnabled &&
MediaInSceneContainerFlag.isEnabled &&
+ // NOTE: Changes should also be made in getSecondaryFlags and @EnableSceneContainer
ComposeFacade.isComposeAvailable()
/**
@@ -63,6 +65,7 @@
FlagToken(FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor()),
KeyguardShadeMigrationNssl.token,
MediaInSceneContainerFlag.token,
+ // NOTE: Changes should also be made in isEnabled and @EnableSceneContainer
)
/** The full set of requirements for SceneContainer */
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
index 4a839b8..93cfc5d 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -85,12 +85,13 @@
view.addView(
ComposeFacade.createSceneContainerView(
- scope = this,
- context = view.context,
- viewModel = viewModel,
- windowInsets = windowInsets,
- sceneByKey = sortedSceneByKey,
- )
+ scope = this,
+ context = view.context,
+ viewModel = viewModel,
+ windowInsets = windowInsets,
+ sceneByKey = sortedSceneByKey,
+ )
+ .also { it.id = R.id.scene_container_root_composable }
)
val legacyView = view.requireViewById<View>(R.id.legacy_window_root)
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index bd43307..7aa0dad 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -32,6 +32,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -143,6 +144,7 @@
channel.enableVibration(true);
mNotificationManager.createNotificationChannel(channel);
+ int currentUid = Process.myUid();
int currentUserId = mUserContextTracker.getUserContext().getUserId();
UserHandle currentUser = new UserHandle(currentUserId);
switch (action) {
@@ -166,7 +168,7 @@
mRecorder = new ScreenMediaRecorder(
mUserContextTracker.getUserContext(),
mMainHandler,
- currentUserId,
+ currentUid,
mAudioSource,
captureTarget,
this
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
index 3aab3bf..a170d0da 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
@@ -83,7 +83,7 @@
private Surface mInputSurface;
private VirtualDisplay mVirtualDisplay;
private MediaRecorder mMediaRecorder;
- private int mUser;
+ private int mUid;
private ScreenRecordingMuxer mMuxer;
private ScreenInternalAudioRecorder mAudio;
private ScreenRecordingAudioSource mAudioSource;
@@ -94,12 +94,12 @@
ScreenMediaRecorderListener mListener;
public ScreenMediaRecorder(Context context, Handler handler,
- int user, ScreenRecordingAudioSource audioSource,
+ int uid, ScreenRecordingAudioSource audioSource,
MediaProjectionCaptureTarget captureRegion,
ScreenMediaRecorderListener listener) {
mContext = context;
mHandler = handler;
- mUser = user;
+ mUid = uid;
mCaptureRegion = captureRegion;
mListener = listener;
mAudioSource = audioSource;
@@ -111,7 +111,7 @@
IMediaProjectionManager mediaService =
IMediaProjectionManager.Stub.asInterface(b);
IMediaProjection proj = null;
- proj = mediaService.createProjection(mUser, mContext.getPackageName(),
+ proj = mediaService.createProjection(mUid, mContext.getPackageName(),
MediaProjectionManager.TYPE_SCREEN_CAPTURE, false);
IMediaProjection projection = IMediaProjection.Stub.asInterface(proj.asBinder());
if (mCaptureRegion != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt
new file mode 100644
index 0000000..b09bfe2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.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.settings
+
+import android.annotation.UserIdInt
+import android.content.Context
+import android.content.SharedPreferences
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** Extension functions for [UserFileManager]. */
+object UserFileManagerExt {
+
+ /** Returns a flow of [Unit] that is invoked each time the shared preference is updated. */
+ fun UserFileManager.observeSharedPreferences(
+ fileName: String,
+ @Context.PreferencesMode mode: Int,
+ @UserIdInt userId: Int
+ ): Flow<Unit> = conflatedCallbackFlow {
+ val sharedPrefs = getSharedPreferences(fileName, mode, userId)
+
+ val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, _ -> trySend(Unit) }
+
+ sharedPrefs.registerOnSharedPreferenceChangeListener(listener)
+ awaitClose { sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index 3be60b7..782d651 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -17,6 +17,8 @@
package com.android.systemui.shade
import android.content.Context
+import android.os.PowerManager
+import android.os.SystemClock
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
@@ -44,6 +46,7 @@
private val communalViewModel: CommunalViewModel,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val shadeInteractor: ShadeInteractor,
+ private val powerManager: PowerManager,
) {
/** The container view for the hub. This will not be initialized until [initView] is called. */
private lateinit var communalContainerView: View
@@ -157,7 +160,7 @@
// If the hub is fully visible, send all touch events to it.
val communalVisible = hubShowing && !hubOccluded
if (communalVisible) {
- communalContainerView.dispatchTouchEvent(ev)
+ dispatchTouchEvent(ev)
// Return true regardless of dispatch result as some touches at the start of a gesture
// may return false from dispatchTouchEvent.
return true
@@ -175,7 +178,7 @@
x >= communalContainerView.width - edgeSwipeRegionWidth
if (inOpeningSwipeRegion && !hubOccluded) {
isTrackingOpenGesture = true
- communalContainerView.dispatchTouchEvent(ev)
+ dispatchTouchEvent(ev)
// Return true regardless of dispatch result as some touches at the start of a
// gesture may return false from dispatchTouchEvent.
return true
@@ -184,7 +187,7 @@
if (isUp || isCancel) {
isTrackingOpenGesture = false
}
- communalContainerView.dispatchTouchEvent(ev)
+ dispatchTouchEvent(ev)
// Return true regardless of dispatch result as some touches at the start of a gesture
// may return false from dispatchTouchEvent.
return true
@@ -192,4 +195,17 @@
return false
}
+
+ /**
+ * Dispatches the touch event to the communal container and sends a user activity event to reset
+ * the screen timeout.
+ */
+ private fun dispatchTouchEvent(ev: MotionEvent) {
+ communalContainerView.dispatchTouchEvent(ev)
+ powerManager.userActivity(
+ SystemClock.uptimeMillis(),
+ PowerManager.USER_ACTIVITY_EVENT_TOUCH,
+ 0
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 5fbb60d..60feb82 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -373,7 +373,9 @@
boolean onKeyguard = state.statusBarState == StatusBarState.KEYGUARD
&& !state.keyguardFadingAway && !state.keyguardGoingAway;
if (onKeyguard
- && mAuthController.isUdfpsEnrolled(mUserInteractor.get().getSelectedUserId())) {
+ && mAuthController.isOpticalUdfpsEnrolled(
+ mUserInteractor.get().getSelectedUserId())
+ ) {
// Requests the max refresh rate (ie: for smooth display). Note: By setting
// the preferred refresh rates below, the refresh rate will not override the max
// refresh rate in settings (ie: if smooth display is OFF).
@@ -892,6 +894,8 @@
pw.println(TAG + ":");
pw.println(" mKeyguardMaxRefreshRate=" + mKeyguardMaxRefreshRate);
pw.println(" mKeyguardPreferredRefreshRate=" + mKeyguardPreferredRefreshRate);
+ pw.println(" preferredMinDisplayRefreshRate=" + mLpChanged.preferredMinDisplayRefreshRate);
+ pw.println(" preferredMaxDisplayRefreshRate=" + mLpChanged.preferredMaxDisplayRefreshRate);
pw.println(" mDeferWindowLayoutParams=" + mDeferWindowLayoutParams);
pw.println(mCurrentState);
if (mWindowRootView != null && mWindowRootView.getViewRootImpl() != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index cde2a62..8c852cd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -31,16 +31,12 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.AuthKeyguardMessageArea;
-import com.android.keyguard.KeyguardMessageAreaController;
import com.android.keyguard.LockIconViewController;
-import com.android.keyguard.dagger.KeyguardBouncerComponent;
import com.android.systemui.Dumpable;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
-import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
-import com.android.systemui.bouncer.ui.binder.KeyguardBouncerViewBinder;
-import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel;
+import com.android.systemui.bouncer.ui.binder.BouncerViewBinder;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor;
@@ -56,8 +52,6 @@
import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.keyguard.ui.binder.AlternateBouncerViewBinder;
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies;
-import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
-import com.android.systemui.log.BouncerLogger;
import com.android.systemui.res.R;
import com.android.systemui.shared.animation.DisableSubpixelTextTransitionListener;
import com.android.systemui.statusbar.DragDownHelper;
@@ -77,7 +71,6 @@
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.time.SystemClock;
@@ -183,24 +176,18 @@
DumpManager dumpManager,
PulsingGestureListener pulsingGestureListener,
LockscreenHostedDreamGestureListener lockscreenHostedDreamGestureListener,
- KeyguardBouncerViewModel keyguardBouncerViewModel,
- KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory,
- KeyguardMessageAreaController.Factory messageAreaControllerFactory,
KeyguardTransitionInteractor keyguardTransitionInteractor,
- PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
GlanceableHubContainerController glanceableHubContainerController,
NotificationLaunchAnimationInteractor notificationLaunchAnimationInteractor,
FeatureFlagsClassic featureFlagsClassic,
SystemClock clock,
- BouncerMessageInteractor bouncerMessageInteractor,
- BouncerLogger bouncerLogger,
SysUIKeyEventHandler sysUIKeyEventHandler,
QuickSettingsController quickSettingsController,
PrimaryBouncerInteractor primaryBouncerInteractor,
AlternateBouncerInteractor alternateBouncerInteractor,
- SelectedUserInteractor selectedUserInteractor,
Lazy<JavaAdapter> javaAdapter,
- Lazy<AlternateBouncerDependencies> alternateBouncerDependencies) {
+ Lazy<AlternateBouncerDependencies> alternateBouncerDependencies,
+ BouncerViewBinder bouncerViewBinder) {
mLockscreenShadeTransitionController = transitionController;
mFalsingCollector = falsingCollector;
mStatusBarStateController = statusBarStateController;
@@ -234,15 +221,7 @@
// This view is not part of the newly inflated expanded status bar.
mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
mDisableSubpixelTextTransitionListener = new DisableSubpixelTextTransitionListener(mView);
- KeyguardBouncerViewBinder.bind(
- mView.findViewById(R.id.keyguard_bouncer_container),
- keyguardBouncerViewModel,
- primaryBouncerToGoneTransitionViewModel,
- keyguardBouncerComponentFactory,
- messageAreaControllerFactory,
- bouncerMessageInteractor,
- bouncerLogger,
- selectedUserInteractor);
+ bouncerViewBinder.bind(mView.findViewById(R.id.keyguard_bouncer_container));
if (DeviceEntryUdfpsRefactor.isEnabled()) {
AlternateBouncerViewBinder.bind(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
index 7a340d2..6407b5a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
@@ -25,8 +25,8 @@
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository
+import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -94,7 +94,7 @@
disableFlagsRepository.disableFlags,
isShadeEnabled,
keyguardRepository.isDozing,
- userSetupRepository.isUserSetupFlow,
+ userSetupRepository.isUserSetUp,
deviceProvisioningRepository.isDeviceProvisioned,
) { disableFlags, isShadeEnabled, isDozing, isUserSetup, isDeviceProvisioned ->
isDeviceProvisioned &&
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 d0da945..9af2d58 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
@@ -20,15 +20,10 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.media.controls.pipeline.MediaDataManager
-import com.android.systemui.media.controls.ui.MediaHierarchyManager
-import com.android.systemui.media.controls.ui.MediaHost
-import com.android.systemui.media.controls.ui.MediaHostState
-import com.android.systemui.media.dagger.MediaModule
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import javax.inject.Inject
-import javax.inject.Named
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -46,7 +41,6 @@
val shadeHeaderViewModel: ShadeHeaderViewModel,
val notifications: NotificationsPlaceholderViewModel,
val mediaDataManager: MediaDataManager,
- @Named(MediaModule.QUICK_QS_PANEL) private val mediaHost: MediaHost,
) {
/** The key of the scene we should switch to when swiping up. */
val upDestinationSceneKey: StateFlow<SceneKey> =
@@ -83,12 +77,6 @@
}
}
- init {
- mediaHost.expansion = MediaHostState.EXPANDED
- mediaHost.showsOnlyActiveMedia = true
- mediaHost.init(MediaHierarchyManager.LOCATION_QQS)
- }
-
fun isMediaVisible(): Boolean {
// TODO(b/296122467): handle updates to carousel visibility while scene is still visible
return mediaDataManager.hasActiveMediaOrRecommendation()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
deleted file mode 100644
index cfbd015..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
+++ /dev/null
@@ -1,409 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Log;
-import android.view.accessibility.AccessibilityEvent;
-
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
-import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.util.time.SystemClock;
-
-import java.util.stream.Stream;
-
-/**
- * A manager which contains notification alerting functionality, providing methods to add and
- * remove notifications that appear on screen for a period of time and dismiss themselves at the
- * appropriate time. These include heads up notifications and ambient pulses.
- */
-public abstract class AlertingNotificationManager {
- private static final String TAG = "AlertNotifManager";
- protected final SystemClock mSystemClock;
- protected final ArrayMap<String, AlertEntry> mAlertEntries = new ArrayMap<>();
- protected final HeadsUpManagerLogger mLogger;
-
- protected int mMinimumDisplayTime;
- protected int mStickyForSomeTimeAutoDismissTime;
- protected int mAutoDismissTime;
- private DelayableExecutor mExecutor;
-
- public AlertingNotificationManager(HeadsUpManagerLogger logger,
- SystemClock systemClock, @Main DelayableExecutor executor) {
- mLogger = logger;
- mExecutor = executor;
- mSystemClock = systemClock;
- }
-
- /**
- * Called when posting a new notification that should alert the user and appear on screen.
- * Adds the notification to be managed.
- * @param entry entry to show
- */
- public void showNotification(@NonNull NotificationEntry entry) {
- mLogger.logShowNotification(entry);
- addAlertEntry(entry);
- updateNotification(entry.getKey(), true /* alert */);
- entry.setInterruption();
- }
-
- /**
- * Try to remove the notification. May not succeed if the notification has not been shown long
- * enough and needs to be kept around.
- * @param key the key of the notification to remove
- * @param releaseImmediately force a remove regardless of earliest removal time
- * @return true if notification is removed, false otherwise
- */
- public boolean removeNotification(@NonNull String key, boolean releaseImmediately) {
- mLogger.logRemoveNotification(key, releaseImmediately);
- AlertEntry alertEntry = mAlertEntries.get(key);
- if (alertEntry == null) {
- return true;
- }
- if (releaseImmediately || canRemoveImmediately(key)) {
- removeAlertEntry(key);
- } else {
- alertEntry.removeAsSoonAsPossible();
- return false;
- }
- return true;
- }
-
- /**
- * Called when the notification state has been updated.
- * @param key the key of the entry that was updated
- * @param alert whether the notification should alert again and force reevaluation of
- * removal time
- */
- public void updateNotification(@NonNull String key, boolean alert) {
- AlertEntry alertEntry = mAlertEntries.get(key);
- mLogger.logUpdateNotification(key, alert, alertEntry != null);
- if (alertEntry == null) {
- // the entry was released before this update (i.e by a listener) This can happen
- // with the groupmanager
- return;
- }
-
- alertEntry.mEntry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
- if (alert) {
- alertEntry.updateEntry(true /* updatePostTime */, "updateNotification");
- }
- }
-
- /**
- * Clears all managed notifications.
- */
- public void releaseAllImmediately() {
- mLogger.logReleaseAllImmediately();
- // A copy is necessary here as we are changing the underlying map. This would cause
- // undefined behavior if we iterated over the key set directly.
- ArraySet<String> keysToRemove = new ArraySet<>(mAlertEntries.keySet());
- for (String key : keysToRemove) {
- removeAlertEntry(key);
- }
- }
-
- /**
- * Returns the entry if it is managed by this manager.
- * @param key key of notification
- * @return the entry
- */
- @Nullable
- public NotificationEntry getEntry(@NonNull String key) {
- AlertEntry entry = mAlertEntries.get(key);
- return entry != null ? entry.mEntry : null;
- }
-
- /**
- * Returns the stream of all current notifications managed by this manager.
- * @return all entries
- */
- @NonNull
- public Stream<NotificationEntry> getAllEntries() {
- return mAlertEntries.values().stream().map(headsUpEntry -> headsUpEntry.mEntry);
- }
-
- /**
- * Whether or not there are any active alerting notifications.
- * @return true if there is an alert, false otherwise
- */
- public boolean hasNotifications() {
- return !mAlertEntries.isEmpty();
- }
-
- /**
- * Whether or not the given notification is alerting and managed by this manager.
- * @return true if the notification is alerting
- */
- public boolean isAlerting(@NonNull String key) {
- return mAlertEntries.containsKey(key);
- }
-
- /**
- * Gets the flag corresponding to the notification content view this alert manager will show.
- *
- * @return flag corresponding to the content view
- */
- public abstract @InflationFlag int getContentFlag();
-
- /**
- * Add a new entry and begin managing it.
- * @param entry the entry to add
- */
- protected final void addAlertEntry(@NonNull NotificationEntry entry) {
- AlertEntry alertEntry = createAlertEntry();
- alertEntry.setEntry(entry);
- mAlertEntries.put(entry.getKey(), alertEntry);
- onAlertEntryAdded(alertEntry);
- entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
- entry.setIsAlerting(true);
- }
-
- /**
- * Manager-specific logic that should occur when an entry is added.
- * @param alertEntry alert entry added
- */
- protected abstract void onAlertEntryAdded(@NonNull AlertEntry alertEntry);
-
- /**
- * Remove a notification and reset the alert entry.
- * @param key key of notification to remove
- */
- protected final void removeAlertEntry(@NonNull String key) {
- AlertEntry alertEntry = mAlertEntries.get(key);
- if (alertEntry == null) {
- return;
- }
- NotificationEntry entry = alertEntry.mEntry;
-
- // If the notification is animating, we will remove it at the end of the animation.
- if (entry != null && entry.isExpandAnimationRunning()) {
- return;
- }
- entry.demoteStickyHun();
- mAlertEntries.remove(key);
- onAlertEntryRemoved(alertEntry);
- entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
- alertEntry.reset();
- }
-
- /**
- * Manager-specific logic that should occur when an alert entry is removed.
- * @param alertEntry alert entry removed
- */
- protected abstract void onAlertEntryRemoved(@NonNull AlertEntry alertEntry);
-
- /**
- * Returns a new alert entry instance.
- * @return a new AlertEntry
- */
- protected AlertEntry createAlertEntry() {
- return new AlertEntry();
- }
-
- /**
- * Whether or not the alert can be removed currently. If it hasn't been on screen long enough
- * it should not be removed unless forced
- * @param key the key to check if removable
- * @return true if the alert entry can be removed
- */
- public boolean canRemoveImmediately(String key) {
- AlertEntry alertEntry = mAlertEntries.get(key);
- return alertEntry == null || alertEntry.wasShownLongEnough()
- || alertEntry.mEntry.isRowDismissed();
- }
-
- /**
- * @param key
- * @return true if the entry is (pinned and expanded) or (has an active remote input)
- */
- public boolean isSticky(String key) {
- AlertEntry alerting = mAlertEntries.get(key);
- if (alerting != null) {
- return alerting.isSticky();
- }
- return false;
- }
-
- /**
- * @param key
- * @return When a HUN entry should be removed in milliseconds from now
- */
- public long getEarliestRemovalTime(String key) {
- AlertEntry alerting = mAlertEntries.get(key);
- if (alerting != null) {
- return Math.max(0, alerting.mEarliestRemovalTime - mSystemClock.elapsedRealtime());
- }
- return 0;
- }
-
- protected class AlertEntry implements Comparable<AlertEntry> {
- @Nullable public NotificationEntry mEntry;
- public long mPostTime;
- public long mEarliestRemovalTime;
-
- @Nullable protected Runnable mRemoveAlertRunnable;
- @Nullable private Runnable mCancelRemoveAlertRunnable;
-
- public void setEntry(@NonNull final NotificationEntry entry) {
- setEntry(entry, () -> removeAlertEntry(entry.getKey()));
- }
-
- public void setEntry(@NonNull final NotificationEntry entry,
- @Nullable Runnable removeAlertRunnable) {
- mEntry = entry;
- mRemoveAlertRunnable = removeAlertRunnable;
-
- mPostTime = calculatePostTime();
- updateEntry(true /* updatePostTime */, "setEntry");
- }
-
- /**
- * 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);
-
- final long now = mSystemClock.elapsedRealtime();
- mEarliestRemovalTime = now + mMinimumDisplayTime;
-
- if (updatePostTime) {
- mPostTime = Math.max(mPostTime, now);
- }
-
- if (isSticky()) {
- removeAutoRemovalCallbacks("updateEntry (sticky)");
- return;
- }
-
- final long finishTime = calculateFinishTime();
- final long timeLeft = Math.max(finishTime - now, mMinimumDisplayTime);
- scheduleAutoRemovalCallback(timeLeft, "updateEntry (not sticky)");
- }
-
- /**
- * Whether or not the notification is "sticky" i.e. should stay on screen regardless
- * of the timer (forever) and should be removed externally.
- * @return true if the notification is sticky
- */
- public boolean isSticky() {
- // This implementation is overridden by HeadsUpManager HeadsUpEntry #isSticky
- return false;
- }
-
- public boolean isStickyForSomeTime() {
- // This implementation is overridden by HeadsUpManager HeadsUpEntry #isStickyForSomeTime
- return false;
- }
-
- /**
- * Whether the notification has befen on screen long enough and can be removed.
- * @return true if the notification has been on screen long enough
- */
- public boolean wasShownLongEnough() {
- return mEarliestRemovalTime < mSystemClock.elapsedRealtime();
- }
-
- @Override
- public int compareTo(@NonNull AlertEntry alertEntry) {
- return (mPostTime < alertEntry.mPostTime)
- ? 1 : ((mPostTime == alertEntry.mPostTime)
- ? mEntry.getKey().compareTo(alertEntry.mEntry.getKey()) : -1);
- }
-
- public void reset() {
- removeAutoRemovalCallbacks("reset()");
- mEntry = null;
- mRemoveAlertRunnable = null;
- }
-
- /**
- * Clear any pending removal runnables.
- */
- public void removeAutoRemovalCallbacks(@Nullable String reason) {
- final boolean removed = removeAutoRemovalCallbackInternal();
-
- if (removed) {
- mLogger.logAutoRemoveCanceled(mEntry, reason);
- }
- }
-
- private void scheduleAutoRemovalCallback(long delayMillis, @NonNull String reason) {
- if (mRemoveAlertRunnable == null) {
- Log.wtf(TAG, "scheduleAutoRemovalCallback with no callback set");
- return;
- }
-
- final boolean removed = removeAutoRemovalCallbackInternal();
-
- if (removed) {
- mLogger.logAutoRemoveRescheduled(mEntry, delayMillis, reason);
- } else {
- mLogger.logAutoRemoveScheduled(mEntry, delayMillis, reason);
- }
-
-
- mCancelRemoveAlertRunnable = mExecutor.executeDelayed(mRemoveAlertRunnable,
- delayMillis);
- }
-
- private boolean removeAutoRemovalCallbackInternal() {
- final boolean scheduled = (mCancelRemoveAlertRunnable != null);
-
- if (scheduled) {
- mCancelRemoveAlertRunnable.run();
- mCancelRemoveAlertRunnable = null;
- }
-
- return scheduled;
- }
-
- /**
- * Remove the alert at the earliest allowed removal time.
- */
- public void removeAsSoonAsPossible() {
- if (mRemoveAlertRunnable != null) {
- final long timeLeft = mEarliestRemovalTime - mSystemClock.elapsedRealtime();
- scheduleAutoRemovalCallback(timeLeft, "removeAsSoonAsPossible");
- }
- }
-
- /**
- * Calculate what the post time of a notification is at some current time.
- * @return the post time
- */
- protected long calculatePostTime() {
- return mSystemClock.elapsedRealtime();
- }
-
- /**
- * @return When the notification should auto-dismiss itself, based on
- * {@link SystemClock#elapsedRealtime()}
- */
- protected long calculateFinishTime() {
- // Overridden by HeadsUpManager HeadsUpEntry #calculateFinishTime
- return 0;
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 2438298..7f8be1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -22,6 +22,7 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.media.controls.ui.MediaHierarchyManager
import com.android.systemui.navigationbar.gestural.Utilities.isTrackpadScroll
import com.android.systemui.plugins.ActivityStarter
@@ -888,6 +889,9 @@
isDraggingDown = false
isTrackpadReverseScroll = false
shadeRepository.setLegacyLockscreenShadeTracking(false)
+ if (KeyguardShadeMigrationNssl.isEnabled) {
+ return true
+ }
} else {
stopDragging()
return false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 7a2e82f..bb6ee24 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -645,6 +645,10 @@
+ "slot='" + mSlot + "' alpha=" + getAlpha() + " icon=" + mIcon
+ " visibleState=" + getVisibleStateString(getVisibleState())
+ " iconColor=#" + Integer.toHexString(mIconColor)
+ + " staticDrawableColor=#" + Integer.toHexString(mDrawableColor)
+ + " decorColor=#" + Integer.toHexString(mDecorColor)
+ + " animationStartColor=#" + Integer.toHexString(mAnimationStartColor)
+ + " currentSetColor=#" + Integer.toHexString(mCurrentSetColor)
+ " notification=" + mNotification + ')';
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
index 29d53fc..9f878b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.data
import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepositoryModule
+import com.android.systemui.statusbar.data.repository.RemoteInputRepositoryModule
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryModule
import com.android.systemui.statusbar.phone.data.StatusBarPhoneDataLayerModule
import dagger.Module
@@ -24,6 +25,7 @@
includes =
[
KeyguardStatusBarRepositoryModule::class,
+ RemoteInputRepositoryModule::class,
StatusBarModeRepositoryModule::class,
StatusBarPhoneDataLayerModule::class
]
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt
new file mode 100644
index 0000000..c0302bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.data.repository
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.NotificationRemoteInputManager
+import com.android.systemui.statusbar.RemoteInputController
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Repository used for tracking the state of notification remote input (e.g. when the user presses
+ * "reply" on a notification and the keyboard opens).
+ */
+interface RemoteInputRepository {
+ /** Whether remote input is currently active for any notification. */
+ val isRemoteInputActive: Flow<Boolean>
+}
+
+@SysUISingleton
+class RemoteInputRepositoryImpl
+@Inject
+constructor(
+ private val notificationRemoteInputManager: NotificationRemoteInputManager,
+) : RemoteInputRepository {
+ override val isRemoteInputActive: Flow<Boolean> = conflatedCallbackFlow {
+ trySend(false) // initial value is false
+ val callback =
+ object : RemoteInputController.Callback {
+ override fun onRemoteInputActive(active: Boolean) {
+ trySend(active)
+ }
+ }
+ notificationRemoteInputManager.addControllerCallback(callback)
+ awaitClose { notificationRemoteInputManager.removeControllerCallback(callback) }
+ }
+}
+
+@Module
+interface RemoteInputRepositoryModule {
+ @Binds fun bindImpl(impl: RemoteInputRepositoryImpl): RemoteInputRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt
new file mode 100644
index 0000000..68f727b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.data.repository.RemoteInputRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Interactor used for business logic pertaining to the notification remote input (e.g. when the
+ * user presses "reply" on a notification and the keyboard opens).
+ */
+@SysUISingleton
+class RemoteInputInteractor @Inject constructor(remoteInputRepository: RemoteInputRepository) {
+ /** Is remote input currently active for a notification? */
+ val isRemoteInputActive: Flow<Boolean> = remoteInputRepository.isRemoteInputActive
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
index 96279e2..5983fc1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
@@ -157,9 +157,9 @@
val summaryEntry = notificationEntry.parent?.summary
return when {
- headsUpManager.isAlerting(notificationKey) -> notification
+ headsUpManager.isHeadsUpEntry(notificationKey) -> notification
summaryEntry == null -> null
- headsUpManager.isAlerting(summaryEntry.key) -> summaryEntry.row
+ headsUpManager.isHeadsUpEntry(summaryEntry.key) -> summaryEntry.row
else -> null
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index cfe9fbe..cdacb10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -171,7 +171,7 @@
private int mBucket = BUCKET_ALERTING;
@Nullable private Long mPendingAnimationDuration;
private boolean mIsMarkedForUserTriggeredMovement;
- private boolean mIsAlerting;
+ private boolean mIsHeadsUpEntry;
public boolean mRemoteEditImeAnimatingAway;
public boolean mRemoteEditImeVisible;
@@ -965,12 +965,12 @@
mIsMarkedForUserTriggeredMovement = marked;
}
- public void setIsAlerting(boolean isAlerting) {
- mIsAlerting = isAlerting;
+ public void setIsHeadsUpEntry(boolean isHeadsUpEntry) {
+ mIsHeadsUpEntry = isHeadsUpEntry;
}
- public boolean isAlerting() {
- return mIsAlerting;
+ public boolean isHeadsUpEntry() {
+ return mIsHeadsUpEntry;
}
/** Set whether this notification is currently used to animate a launch. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 314566e..46806e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -221,7 +221,7 @@
wasUpdated = false,
shouldHeadsUpEver = false,
shouldHeadsUpAgain = false,
- isAlerting = mHeadsUpManager.isAlerting(logicalSummary.key),
+ isAlerting = mHeadsUpManager.isHeadsUpEntry(logicalSummary.key),
isBinding = isEntryBinding(logicalSummary),
)
// If we transfer the alert and the summary isn't even attached, that means we
@@ -268,7 +268,7 @@
wasUpdated = false,
shouldHeadsUpEver = true,
shouldHeadsUpAgain = true,
- isAlerting = mHeadsUpManager.isAlerting(childToReceiveParentAlert.key),
+ isAlerting = mHeadsUpManager.isHeadsUpEntry(childToReceiveParentAlert.key),
isBinding = isEntryBinding(childToReceiveParentAlert),
)
handlePostedEntry(
@@ -425,7 +425,7 @@
val shouldHeadsUpEver =
mVisualInterruptionDecisionProvider.makeAndLogHeadsUpDecision(entry).shouldInterrupt
val shouldHeadsUpAgain = shouldHunAgain(entry)
- val isAlerting = mHeadsUpManager.isAlerting(entry.key)
+ val isAlerting = mHeadsUpManager.isHeadsUpEntry(entry.key)
val isBinding = isEntryBinding(entry)
val posted = mPostedEntries.compute(entry.key) { _, value ->
value?.also { update ->
@@ -469,7 +469,7 @@
mEntriesUpdateTimes.remove(entry.key)
cancelHeadsUpBind(entry)
val entryKey = entry.key
- if (mHeadsUpManager.isAlerting(entryKey)) {
+ if (mHeadsUpManager.isHeadsUpEntry(entryKey)) {
// TODO: This should probably know the RemoteInputCoordinator's conditions,
// or otherwise reference that coordinator's state, rather than replicate its logic
val removeImmediatelyForRemoteInput = (mRemoteInputManager.isSpinning(entryKey) &&
@@ -719,7 +719,7 @@
* Whether the notification is already alerting or binding so that it can imminently alert
*/
private fun isAttemptingToShowHun(entry: ListEntry) =
- mHeadsUpManager.isAlerting(entry.key) || isEntryBinding(entry)
+ mHeadsUpManager.isHeadsUpEntry(entry.key) || isEntryBinding(entry)
/**
* Whether the notification is already alerting/binding per [isAttemptingToShowHun] OR if it
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index a0129ff..8189fe0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -134,7 +134,7 @@
private final NotifStabilityManager mNotifStabilityManager =
new NotifStabilityManager("VisualStabilityCoordinator") {
private boolean canMoveForHeadsUp(NotificationEntry entry) {
- return entry != null && mHeadsUpManager.isAlerting(entry.getKey())
+ return entry != null && mHeadsUpManager.isHeadsUpEntry(entry.getKey())
&& !mVisibilityLocationProvider.isInVisibleLocation(entry);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
index e71d80c..5743ab0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
@@ -68,7 +68,7 @@
@NonNull
private DismissedByUserStats getDismissedByUserStats(NotificationEntry entry) {
int dismissalSurface = NotificationStats.DISMISSAL_SHADE;
- if (mHeadsUpManager.isAlerting(entry.getKey())) {
+ if (mHeadsUpManager.isHeadsUpEntry(entry.getKey())) {
dismissalSurface = NotificationStats.DISMISSAL_PEEK;
} else if (mStatusBarStateController.isDozing()) {
dismissalSurface = NotificationStats.DISMISSAL_AOD;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationVisibilityProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationVisibilityProviderImpl.kt
index ec10aaf..88e94e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationVisibilityProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationVisibilityProviderImpl.kt
@@ -22,12 +22,17 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.logging.NotificationLogger
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import javax.inject.Inject
/** pipeline-agnostic implementation for getting [NotificationVisibility]. */
@SysUISingleton
-class NotificationVisibilityProviderImpl @Inject constructor(
+class NotificationVisibilityProviderImpl
+@Inject
+constructor(
+ private val activeNotificationsInteractor: ActiveNotificationsInteractor,
private val notifDataStore: NotifLiveDataStore,
private val notifCollection: CommonNotifCollection
) : NotificationVisibilityProvider {
@@ -47,5 +52,10 @@
override fun getLocation(key: String): NotificationVisibility.NotificationLocation =
NotificationLogger.getNotificationLocation(notifCollection.getEntry(key))
- private fun getCount() = notifDataStore.activeNotifCount.value
+ private fun getCount() =
+ if (NotificationsLiveDataStoreRefactor.isEnabled) {
+ activeNotificationsInteractor.allNotificationsCountValue
+ } else {
+ notifDataStore.activeNotifCount.value
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationStatsLoggerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationStatsLoggerModule.kt
new file mode 100644
index 0000000..cbd9887
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationStatsLoggerModule.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.dagger
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.NoOpCoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.UiBackground
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.statusbar.NotificationListener
+import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider
+import com.android.systemui.statusbar.notification.logging.NotificationLogger
+import com.android.systemui.statusbar.notification.logging.NotificationLogger.ExpansionStateLogger
+import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationRowStatsLogger
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationStatsLogger
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationStatsLoggerImpl
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationLoggerViewModel
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.util.kotlin.getOrNull
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import java.util.Optional
+import java.util.concurrent.Executor
+import javax.inject.Provider
+
+@Module
+interface NotificationStatsLoggerModule {
+
+ /** Binds an implementation to the [NotificationStatsLogger]. */
+ @Binds fun bindsStatsLoggerImpl(impl: NotificationStatsLoggerImpl): NotificationStatsLogger
+
+ companion object {
+
+ /** Provides a [NotificationStatsLogger] if the refactor flag is on. */
+ @Provides
+ fun provideStatsLogger(
+ provider: Provider<NotificationStatsLogger>
+ ): Optional<NotificationStatsLogger> {
+ return if (NotificationsLiveDataStoreRefactor.isEnabled) {
+ Optional.of(provider.get())
+ } else {
+ Optional.empty()
+ }
+ }
+
+ /** Provides a [NotificationLoggerViewModel] if the refactor flag is on. */
+ @Provides
+ fun provideViewModel(
+ provider: Provider<NotificationLoggerViewModel>
+ ): Optional<NotificationLoggerViewModel> {
+ return if (NotificationsLiveDataStoreRefactor.isEnabled) {
+ Optional.of(provider.get())
+ } else {
+ Optional.empty()
+ }
+ }
+
+ /** Provides the legacy [NotificationLogger] if the refactor flag is off. */
+ @Provides
+ @SysUISingleton
+ fun provideLegacyLoggerOptional(
+ notificationListener: NotificationListener?,
+ @UiBackground uiBgExecutor: Executor?,
+ notifLiveDataStore: NotifLiveDataStore?,
+ visibilityProvider: NotificationVisibilityProvider?,
+ notifPipeline: NotifPipeline?,
+ statusBarStateController: StatusBarStateController?,
+ windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor?,
+ javaAdapter: JavaAdapter?,
+ expansionStateLogger: ExpansionStateLogger?,
+ notificationPanelLogger: NotificationPanelLogger?
+ ): Optional<NotificationLogger> {
+ return if (NotificationsLiveDataStoreRefactor.isEnabled) {
+ Optional.empty()
+ } else {
+ Optional.of(
+ NotificationLogger(
+ notificationListener,
+ uiBgExecutor,
+ notifLiveDataStore,
+ visibilityProvider,
+ notifPipeline,
+ statusBarStateController,
+ windowRootViewVisibilityInteractor,
+ javaAdapter,
+ expansionStateLogger,
+ notificationPanelLogger
+ )
+ )
+ }
+ }
+
+ /**
+ * Provides a the legacy [NotificationLogger] or the new [NotificationStatsLogger] to the
+ * notification row.
+ *
+ * TODO(b/308623704) remove the [NotificationRowStatsLogger] interface, and provide a
+ * [NotificationStatsLogger] to the row directly.
+ */
+ @Provides
+ fun provideRowStatsLogger(
+ newProvider: Provider<NotificationStatsLogger>,
+ legacyLoggerOptional: Optional<NotificationLogger>,
+ ): NotificationRowStatsLogger {
+ return legacyLoggerOptional.getOrNull() ?: newProvider.get()
+ }
+
+ /**
+ * Binds the legacy [NotificationLogger] as a [CoreStartable] if the feature flag is off, or
+ * binds a no-op [CoreStartable] otherwise.
+ *
+ * The old [NotificationLogger] is a [CoreStartable], because it's managing its own data
+ * updates, but the new [NotificationStatsLogger] is not. Currently Dagger doesn't support
+ * optionally binding entries with @[IntoMap], therefore we provide a no-op [CoreStartable]
+ * here if the feature flag is on, but this can be removed once the flag is released.
+ */
+ @Provides
+ @IntoMap
+ @ClassKey(NotificationLogger::class)
+ fun provideCoreStartable(
+ legacyLoggerOptional: Optional<NotificationLogger>
+ ): CoreStartable {
+ return legacyLoggerOptional.getOrNull() ?: NoOpCoreStartable()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 3a72205..6bba72b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -17,14 +17,12 @@
package com.android.systemui.statusbar.notification.dagger;
import android.content.Context;
+import android.service.notification.NotificationListenerService;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.UiBackground;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
-import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
@@ -67,7 +65,6 @@
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProviderImpl;
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerImpl;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -80,7 +77,8 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.util.kotlin.JavaAdapter;
+
+import javax.inject.Provider;
import dagger.Binds;
import dagger.Module;
@@ -88,10 +86,6 @@
import dagger.multibindings.ClassKey;
import dagger.multibindings.IntoMap;
-import java.util.concurrent.Executor;
-
-import javax.inject.Provider;
-
/**
* Dagger Module for classes found within the com.android.systemui.statusbar.notification package.
*/
@@ -104,6 +98,7 @@
NotificationSectionHeadersModule.class,
ActivatableNotificationViewModelModule.class,
NotificationMemoryModule.class,
+ NotificationStatsLoggerModule.class,
})
public interface NotificationsModule {
@Binds
@@ -128,39 +123,6 @@
VisibilityLocationProvider bindVisibilityLocationProvider(
VisibilityLocationProviderDelegator visibilityLocationProviderDelegator);
- /** Provides an instance of {@link NotificationLogger} */
- @SysUISingleton
- @Provides
- static NotificationLogger provideNotificationLogger(
- NotificationListener notificationListener,
- @UiBackground Executor uiBgExecutor,
- NotifLiveDataStore notifLiveDataStore,
- NotificationVisibilityProvider visibilityProvider,
- NotifPipeline notifPipeline,
- StatusBarStateController statusBarStateController,
- WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
- JavaAdapter javaAdapter,
- NotificationLogger.ExpansionStateLogger expansionStateLogger,
- NotificationPanelLogger notificationPanelLogger) {
- return new NotificationLogger(
- notificationListener,
- uiBgExecutor,
- notifLiveDataStore,
- visibilityProvider,
- notifPipeline,
- statusBarStateController,
- windowRootViewVisibilityInteractor,
- javaAdapter,
- expansionStateLogger,
- notificationPanelLogger);
- }
-
- /** Binds {@link NotificationLogger} as a {@link CoreStartable}. */
- @Binds
- @IntoMap
- @ClassKey(NotificationLogger.class)
- CoreStartable bindsNotificationLogger(NotificationLogger notificationLogger);
-
/** Provides an instance of {@link NotificationPanelLogger} */
@SysUISingleton
@Provides
@@ -272,6 +234,10 @@
NotifLiveDataStore bindNotifLiveDataStore(NotifLiveDataStoreImpl notifLiveDataStoreImpl);
/** */
+ @Binds
+ NotificationListenerService bindNotificationListener(NotificationListener notificationListener);
+
+ /** */
@Provides
@SysUISingleton
static VisualInterruptionDecisionProvider provideVisualInterruptionDecisionProvider(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
index 5ed82cc..5c844bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
@@ -55,6 +55,12 @@
* invoking [get].
*/
val renderList: List<Key> = emptyList(),
+
+ /**
+ * Map of notification key to rank, where rank is the 0-based index of the notification on the
+ * system server, meaning that in the unfiltered flattened list of notification entries.
+ */
+ val rankingsMap: Map<String, Int> = emptyMap()
) {
operator fun get(key: Key): ActiveNotificationEntryModel? {
return when (key) {
@@ -74,8 +80,9 @@
private val groups = mutableMapOf<String, ActiveNotificationGroupModel>()
private val individuals = mutableMapOf<String, ActiveNotificationModel>()
private val renderList = mutableListOf<Key>()
+ private var rankingsMap: Map<String, Int> = emptyMap()
- fun build() = ActiveNotificationsStore(groups, individuals, renderList)
+ fun build() = ActiveNotificationsStore(groups, individuals, renderList, rankingsMap)
fun addEntry(entry: ActiveNotificationEntryModel) {
when (entry) {
@@ -95,5 +102,9 @@
individuals[group.summary.key] = group.summary
group.children.forEach { individuals[it.key] = it }
}
+
+ fun setRankingsMap(map: Map<String, Int>) {
+ rankingsMap = map.toMap()
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
index e90ddf9..b22e9fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
@@ -33,7 +33,10 @@
private val repository: ActiveNotificationListRepository,
@Background private val backgroundDispatcher: CoroutineDispatcher,
) {
- /** Notifications actively presented to the user in the notification stack, in order. */
+ /**
+ * Top level list of Notifications actively presented to the user in the notification stack, in
+ * order.
+ */
val topLevelRepresentativeNotifications: Flow<List<ActiveNotificationModel>> =
repository.activeNotifications
.map { store ->
@@ -51,6 +54,23 @@
}
.flowOn(backgroundDispatcher)
+ /**
+ * Flattened list of Notifications actively presented to the user in the notification stack, in
+ * order.
+ */
+ val allRepresentativeNotifications: Flow<Map<String, ActiveNotificationModel>> =
+ repository.activeNotifications.map { store -> store.individuals }
+
+ /** Size of the flattened list of Notifications actively presented in the stack. */
+ val allNotificationsCount: Flow<Int> =
+ repository.activeNotifications.map { store -> store.individuals.size }
+
+ /**
+ * The same as [allNotificationsCount], but without flows, for easy access in synchronous code.
+ */
+ val allNotificationsCountValue: Int
+ get() = repository.activeNotifications.value.individuals.size
+
/** Are any notifications being actively presented in the notification stack? */
val areAnyNotificationsPresent: Flow<Boolean> =
repository.activeNotifications
@@ -65,6 +85,16 @@
val areAnyNotificationsPresentValue: Boolean
get() = repository.activeNotifications.value.renderList.isNotEmpty()
+ /**
+ * Map of notification key to rank, where rank is the 0-based index of the notification in the
+ * system server, meaning that in the unfiltered flattened list of notification entries. Used
+ * for logging purposes.
+ *
+ * @see [com.android.systemui.statusbar.notification.stack.ui.view.NotificationStatsLogger].
+ */
+ val activeNotificationRanks: Flow<Map<String, Int>> =
+ repository.activeNotifications.map { store -> store.rankingsMap }
+
/** Are there are any notifications that can be cleared by the "Clear all" button? */
val hasClearableNotifications: Flow<Boolean> =
repository.notifStats
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
index 6f4ed9d..ab54bda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.domain.interactor
import android.graphics.drawable.Icon
+import android.util.ArrayMap
+import com.android.app.tracing.traceSection
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -43,9 +45,12 @@
* Sets the current list of rendered notification entries as displayed in the notification list.
*/
fun setRenderedList(entries: List<ListEntry>) {
- repository.activeNotifications.update { existingModels ->
- buildActiveNotificationsStore(existingModels, sectionStyleProvider) {
- entries.forEach(::addListEntry)
+ traceSection("RenderNotificationListInteractor.setRenderedList") {
+ repository.activeNotifications.update { existingModels ->
+ buildActiveNotificationsStore(existingModels, sectionStyleProvider) {
+ entries.forEach(::addListEntry)
+ setRankingsMap(entries)
+ }
}
}
}
@@ -94,6 +99,27 @@
}
}
+ fun setRankingsMap(entries: List<ListEntry>) {
+ builder.setRankingsMap(flatMapToRankingsMap(entries))
+ }
+
+ fun flatMapToRankingsMap(entries: List<ListEntry>): Map<String, Int> {
+ val result = ArrayMap<String, Int>()
+ for (entry in entries) {
+ if (entry is NotificationEntry) {
+ entry.representativeEntry?.let { representativeEntry ->
+ result[representativeEntry.key] = representativeEntry.ranking.rank
+ }
+ } else if (entry is GroupEntry) {
+ entry.summary?.let { summary -> result[summary.key] = summary.ranking.rank }
+ for (child in entry.children) {
+ result[child.key] = child.ranking.rank
+ }
+ }
+ }
+ return result
+ }
+
private fun NotificationEntry.toModel(): ActiveNotificationModel =
existingModels.createOrReuse(
key = key,
@@ -107,6 +133,11 @@
aodIcon = icons.aodIcon?.sourceIcon,
shelfIcon = icons.shelfIcon?.sourceIcon,
statusBarIcon = icons.statusBarIcon?.sourceIcon,
+ uid = sbn.uid,
+ packageName = sbn.packageName,
+ instanceId = sbn.instanceId?.id,
+ isGroupSummary = sbn.notification.isGroupSummary,
+ bucket = bucket,
)
}
@@ -121,7 +152,12 @@
isPulsing: Boolean,
aodIcon: Icon?,
shelfIcon: Icon?,
- statusBarIcon: Icon?
+ statusBarIcon: Icon?,
+ uid: Int,
+ packageName: String,
+ instanceId: Int?,
+ isGroupSummary: Boolean,
+ bucket: Int,
): ActiveNotificationModel {
return individuals[key]?.takeIf {
it.isCurrent(
@@ -135,7 +171,12 @@
isPulsing = isPulsing,
aodIcon = aodIcon,
shelfIcon = shelfIcon,
- statusBarIcon = statusBarIcon
+ statusBarIcon = statusBarIcon,
+ uid = uid,
+ instanceId = instanceId,
+ isGroupSummary = isGroupSummary,
+ packageName = packageName,
+ bucket = bucket,
)
}
?: ActiveNotificationModel(
@@ -150,6 +191,11 @@
aodIcon = aodIcon,
shelfIcon = shelfIcon,
statusBarIcon = statusBarIcon,
+ uid = uid,
+ instanceId = instanceId,
+ isGroupSummary = isGroupSummary,
+ packageName = packageName,
+ bucket = bucket,
)
}
@@ -164,7 +210,12 @@
isPulsing: Boolean,
aodIcon: Icon?,
shelfIcon: Icon?,
- statusBarIcon: Icon?
+ statusBarIcon: Icon?,
+ uid: Int,
+ packageName: String,
+ instanceId: Int?,
+ isGroupSummary: Boolean,
+ bucket: Int,
): Boolean {
return when {
key != this.key -> false
@@ -178,6 +229,11 @@
aodIcon != this.aodIcon -> false
shelfIcon != this.shelfIcon -> false
statusBarIcon != this.statusBarIcon -> false
+ uid != this.uid -> false
+ instanceId != this.instanceId -> false
+ isGroupSummary != this.isGroupSummary -> false
+ packageName != this.packageName -> false
+ bucket != this.bucket -> false
else -> true
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 7d1cca8..1677418 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -18,7 +18,6 @@
import android.service.notification.StatusBarNotification
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
import com.android.systemui.people.widget.PeopleSpaceWidgetManager
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
import com.android.systemui.statusbar.NotificationListener
@@ -39,6 +38,7 @@
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
import com.android.systemui.statusbar.notification.logging.NotificationLogger
import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import com.android.wm.shell.bubbles.Bubbles
import dagger.Lazy
@@ -53,7 +53,9 @@
* any initialization work that notifications require.
*/
@SysUISingleton
-class NotificationsControllerImpl @Inject constructor(
+class NotificationsControllerImpl
+@Inject
+constructor(
private val notificationListener: NotificationListener,
private val commonNotifCollection: Lazy<CommonNotifCollection>,
private val notifPipeline: Lazy<NotifPipeline>,
@@ -61,7 +63,7 @@
private val targetSdkResolver: TargetSdkResolver,
private val notifPipelineInitializer: Lazy<NotifPipelineInitializer>,
private val notifBindPipelineInitializer: NotifBindPipelineInitializer,
- private val notificationLogger: NotificationLogger,
+ private val notificationLoggerOptional: Optional<NotificationLogger>,
private val notificationRowBinder: NotificationRowBinderImpl,
private val notificationsMediaManager: NotificationMediaManager,
private val headsUpViewBinder: HeadsUpViewBinder,
@@ -69,7 +71,6 @@
private val animatedImageNotificationManager: AnimatedImageNotificationManager,
private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
private val bubblesOptional: Optional<Bubbles>,
- private val featureFlags: FeatureFlags
) : NotificationsController {
override fun initialize(
@@ -80,28 +81,35 @@
) {
notificationListener.registerAsSystemService()
- notifPipeline.get().addCollectionListener(object : NotifCollectionListener {
- override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
- listContainer.cleanUpViewStateForEntry(entry)
- }
- })
+ notifPipeline
+ .get()
+ .addCollectionListener(
+ object : NotifCollectionListener {
+ override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
+ listContainer.cleanUpViewStateForEntry(entry)
+ }
+ }
+ )
notificationRowBinder.setNotificationClicker(
- clickerBuilder.build(bubblesOptional, notificationActivityStarter))
+ clickerBuilder.build(bubblesOptional, notificationActivityStarter)
+ )
notificationRowBinder.setUpWithPresenter(presenter, listContainer)
headsUpViewBinder.setPresenter(presenter)
notifBindPipelineInitializer.initialize()
animatedImageNotificationManager.bind()
- notifPipelineInitializer.get().initialize(
- notificationListener,
- notificationRowBinder,
- listContainer,
- stackController)
+ notifPipelineInitializer
+ .get()
+ .initialize(notificationListener, notificationRowBinder, listContainer, stackController)
targetSdkResolver.initialize(notifPipeline.get())
notificationsMediaManager.setUpWithPresenter(presenter)
- notificationLogger.setUpWithContainer(listContainer)
+ if (!NotificationsLiveDataStoreRefactor.isEnabled) {
+ notificationLoggerOptional.ifPresent { logger ->
+ logger.setUpWithContainer(listContainer)
+ }
+ }
peopleSpaceWidgetManager.attach(notificationListener)
}
@@ -120,11 +128,14 @@
notificationListener.snoozeNotification(sbn.key, snoozeOption.snoozeCriterion.id)
} else {
notificationListener.snoozeNotification(
- sbn.key,
- snoozeOption.minutesToSnoozeFor * 60 * 1000.toLong())
+ sbn.key,
+ snoozeOption.minutesToSnoozeFor * 60 * 1000.toLong()
+ )
}
}
- override fun getActiveNotificationsCount(): Int =
- notifLiveDataStore.activeNotifCount.value
+ override fun getActiveNotificationsCount(): Int {
+ NotificationsLiveDataStoreRefactor.assertInLegacyMode()
+ return notifLiveDataStore.activeNotifCount.value
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 8d2a63e..4349b3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -33,6 +33,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
+import com.android.internal.statusbar.NotificationVisibility.NotificationLocation;
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -46,8 +47,10 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationRowStatsLogger;
import com.android.systemui.util.Compile;
import com.android.systemui.util.kotlin.JavaAdapter;
@@ -64,7 +67,8 @@
* Handles notification logging, in particular, logging which notifications are visible and which
* are not.
*/
-public class NotificationLogger implements StateListener, CoreStartable {
+public class NotificationLogger implements StateListener, CoreStartable,
+ NotificationRowStatsLogger {
static final String TAG = "NotificationLogger";
private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
@@ -166,31 +170,31 @@
/**
* Returns the location of the notification referenced by the given {@link NotificationEntry}.
*/
- public static NotificationVisibility.NotificationLocation getNotificationLocation(
+ public static NotificationLocation getNotificationLocation(
NotificationEntry entry) {
if (entry == null || entry.getRow() == null || entry.getRow().getViewState() == null) {
- return NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN;
+ return NotificationLocation.LOCATION_UNKNOWN;
}
return convertNotificationLocation(entry.getRow().getViewState().location);
}
- private static NotificationVisibility.NotificationLocation convertNotificationLocation(
+ private static NotificationLocation convertNotificationLocation(
int location) {
switch (location) {
case ExpandableViewState.LOCATION_FIRST_HUN:
- return NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP;
+ return NotificationLocation.LOCATION_FIRST_HEADS_UP;
case ExpandableViewState.LOCATION_HIDDEN_TOP:
- return NotificationVisibility.NotificationLocation.LOCATION_HIDDEN_TOP;
+ return NotificationLocation.LOCATION_HIDDEN_TOP;
case ExpandableViewState.LOCATION_MAIN_AREA:
- return NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA;
+ return NotificationLocation.LOCATION_MAIN_AREA;
case ExpandableViewState.LOCATION_BOTTOM_STACK_PEEKING:
- return NotificationVisibility.NotificationLocation.LOCATION_BOTTOM_STACK_PEEKING;
+ return NotificationLocation.LOCATION_BOTTOM_STACK_PEEKING;
case ExpandableViewState.LOCATION_BOTTOM_STACK_HIDDEN:
- return NotificationVisibility.NotificationLocation.LOCATION_BOTTOM_STACK_HIDDEN;
+ return NotificationLocation.LOCATION_BOTTOM_STACK_HIDDEN;
case ExpandableViewState.LOCATION_GONE:
- return NotificationVisibility.NotificationLocation.LOCATION_GONE;
+ return NotificationLocation.LOCATION_GONE;
default:
- return NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN;
+ return NotificationLocation.LOCATION_UNKNOWN;
}
}
@@ -207,6 +211,9 @@
JavaAdapter javaAdapter,
ExpansionStateLogger expansionStateLogger,
NotificationPanelLogger notificationPanelLogger) {
+ // Not expected to be constructed if the feature flag is on
+ NotificationsLiveDataStoreRefactor.assertInLegacyMode();
+
mNotificationListener = notificationListener;
mUiBgExecutor = uiBgExecutor;
mNotifLiveDataStore = notifLiveDataStore;
@@ -382,9 +389,11 @@
/**
* Called when the notification is expanded / collapsed.
*/
- public void onExpansionChanged(String key, boolean isUserAction, boolean isExpanded) {
- NotificationVisibility.NotificationLocation location = mVisibilityProvider.getLocation(key);
- mExpansionStateLogger.onExpansionChanged(key, isUserAction, isExpanded, location);
+ @Override
+ public void onNotificationExpansionChanged(@NonNull String key, boolean isExpanded,
+ int location, boolean isUserAction) {
+ NotificationLocation notifLocation = mVisibilityProvider.getLocation(key);
+ mExpansionStateLogger.onExpansionChanged(key, isUserAction, isExpanded, notifLocation);
}
@VisibleForTesting
@@ -440,7 +449,7 @@
@VisibleForTesting
void onExpansionChanged(String key, boolean isUserAction, boolean isExpanded,
- NotificationVisibility.NotificationLocation location) {
+ NotificationLocation location) {
State state = getState(key);
state.mIsUserAction = isUserAction;
state.mIsExpanded = isExpanded;
@@ -528,7 +537,7 @@
@Nullable
Boolean mIsVisible;
@Nullable
- NotificationVisibility.NotificationLocation mLocation;
+ NotificationLocation mLocation;
private State() {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
index 5ca13c9..6c63d1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
@@ -40,6 +40,11 @@
/**
* Log a NOTIFICATION_PANEL_REPORTED statsd event.
+ */
+ void logPanelShown(boolean isLockscreen, Notifications.NotificationList proto);
+
+ /**
+ * Log a NOTIFICATION_PANEL_REPORTED statsd event.
* @param visibleNotifications as provided by NotificationEntryManager.getVisibleNotifications()
*/
void logPanelShown(boolean isLockscreen,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerImpl.java
index 9a632282..d7f7b76 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerImpl.java
@@ -21,6 +21,7 @@
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.nano.Notifications;
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
import com.google.protobuf.nano.MessageNano;
@@ -31,15 +32,25 @@
* Normal implementation of NotificationPanelLogger.
*/
public class NotificationPanelLoggerImpl implements NotificationPanelLogger {
+
+ @Override
+ public void logPanelShown(boolean isLockscreen, Notifications.NotificationList proto) {
+ SysUiStatsLog.write(SysUiStatsLog.NOTIFICATION_PANEL_REPORTED,
+ /* event_id = */ NotificationPanelEvent.fromLockscreen(isLockscreen).getId(),
+ /* num_notifications = */ proto.notifications.length,
+ /* notifications = */ MessageNano.toByteArray(proto));
+ }
+
@Override
public void logPanelShown(boolean isLockscreen,
List<NotificationEntry> visibleNotifications) {
+ NotificationsLiveDataStoreRefactor.assertInLegacyMode();
final Notifications.NotificationList proto = NotificationPanelLogger.toNotificationProto(
visibleNotifications);
SysUiStatsLog.write(SysUiStatsLog.NOTIFICATION_PANEL_REPORTED,
- /* int event_id */ NotificationPanelEvent.fromLockscreen(isLockscreen).getId(),
- /* int num_notifications*/ proto.notifications.length,
- /* byte[] notifications*/ MessageNano.toByteArray(proto));
+ /* event_id = */ NotificationPanelEvent.fromLockscreen(isLockscreen).getId(),
+ /* num_notifications = */ proto.notifications.length,
+ /* notifications = */ MessageNano.toByteArray(proto));
}
@Override
@@ -47,8 +58,8 @@
final Notifications.NotificationList proto = NotificationPanelLogger.toNotificationProto(
Collections.singletonList(draggedNotification));
SysUiStatsLog.write(SysUiStatsLog.NOTIFICATION_PANEL_REPORTED,
- /* int event_id */ NOTIFICATION_DRAG.getId(),
- /* int num_notifications*/ proto.notifications.length,
- /* byte[] notifications*/ MessageNano.toByteArray(proto));
+ /* event_id = */ NOTIFICATION_DRAG.getId(),
+ /* num_notifications = */ proto.notifications.length,
+ /* notifications = */ MessageNano.toByteArray(proto));
}
}
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 31ca106..5eeb066 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
@@ -118,6 +118,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;
@@ -273,12 +274,15 @@
private final RefactorFlag mInlineReplyAnimation =
RefactorFlag.forView(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION);
- private static final boolean mSimulateSlowMeasure = Compile.IS_DEBUG && RefactorFlag.forView(
- Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE).isEnabled();
+ private static boolean shouldSimulateSlowMeasure() {
+ return Compile.IS_DEBUG && RefactorFlag.forView(
+ Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE).isEnabled();
+ }
+
private static final String SLOW_MEASURE_SIMULATE_DELAY_PROPERTY =
"persist.notifications.extra_measure_delay_ms";
- private static final int SLOW_MEASURE_SIMULATE_DELAY_MS = mSimulateSlowMeasure ?
- SystemProperties.getInt(SLOW_MEASURE_SIMULATE_DELAY_PROPERTY, 150) : 0;
+ private static final int SLOW_MEASURE_SIMULATE_DELAY_MS =
+ SystemProperties.getInt(SLOW_MEASURE_SIMULATE_DELAY_PROPERTY, 150);
// Listener will be called when receiving a long click event.
// Use #setLongPressPosition to optionally assign positional data with the long press.
@@ -985,6 +989,25 @@
}
/**
+ * Recursively collects the [{@link ExpandableViewState#location}]s populating the provided
+ * map.
+ * The visibility of each child is determined by the {@link View#getVisibility()}.
+ * Locations are added to the provided map including locations from child views, that are
+ * visible.
+ */
+ public void collectVisibleLocations(Map<String, Integer> locationsMap) {
+ if (getVisibility() == View.VISIBLE) {
+ locationsMap.put(getEntry().getKey(), getViewState().location);
+ if (mChildrenContainer != null) {
+ List<ExpandableNotificationRow> children = mChildrenContainer.getAttachedChildren();
+ for (int i = 0; i < children.size(); i++) {
+ children.get(i).collectVisibleLocations(locationsMap);
+ }
+ }
+ }
+ }
+
+ /**
* Updates states of all children.
*/
public void updateChildrenStates() {
@@ -1612,7 +1635,8 @@
/**
* Called when the notification is expanded / collapsed.
*/
- void logNotificationExpansion(String key, boolean userAction, boolean expanded);
+ void logNotificationExpansion(String key, int location, boolean userAction,
+ boolean expanded);
/**
* Called when a notification which was previously kept in its parent for the
@@ -1886,7 +1910,7 @@
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- if (Compile.IS_DEBUG && mSimulateSlowMeasure) {
+ if (shouldSimulateSlowMeasure()) {
simulateExtraMeasureDelay();
}
Trace.endSection();
@@ -3309,7 +3333,8 @@
if (nowExpanded != wasExpanded) {
updateShelfIconColor();
if (mLogger != null) {
- mLogger.logNotificationExpansion(mLoggingKey, userAction, nowExpanded);
+ mLogger.logNotificationExpansion(mLoggingKey, getViewState().location, userAction,
+ nowExpanded);
}
if (mIsSummaryWithChildren) {
mChildrenContainer.onExpansionChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index af55f44..0afdefa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -48,13 +48,13 @@
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.collection.render.NodeController;
import com.android.systemui.statusbar.notification.collection.render.NotifViewController;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.dagger.AppName;
import com.android.systemui.statusbar.notification.row.dagger.NotificationKey;
import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope;
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationRowStatsLogger;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.SmartReplyConstants;
@@ -90,7 +90,7 @@
private final GroupMembershipManager mGroupMembershipManager;
private final GroupExpansionManager mGroupExpansionManager;
private final RowContentBindStage mRowContentBindStage;
- private final NotificationLogger mNotificationLogger;
+ private final NotificationRowStatsLogger mStatsLogger;
private final NotificationRowLogger mLogBufferLogger;
private final HeadsUpManager mHeadsUpManager;
private final ExpandableNotificationRow.OnExpandClickListener mOnExpandClickListener;
@@ -130,9 +130,10 @@
private final ExpandableNotificationRow.ExpandableNotificationRowLogger mLoggerCallback =
new ExpandableNotificationRow.ExpandableNotificationRowLogger() {
@Override
- public void logNotificationExpansion(String key, boolean userAction,
+ public void logNotificationExpansion(String key, int location, boolean userAction,
boolean expanded) {
- mNotificationLogger.onExpansionChanged(key, userAction, expanded);
+ mStatsLogger.onNotificationExpansionChanged(key, expanded, location,
+ userAction);
}
@Override
@@ -212,7 +213,7 @@
GroupMembershipManager groupMembershipManager,
GroupExpansionManager groupExpansionManager,
RowContentBindStage rowContentBindStage,
- NotificationLogger notificationLogger,
+ NotificationRowStatsLogger statsLogger,
HeadsUpManager headsUpManager,
ExpandableNotificationRow.OnExpandClickListener onExpandClickListener,
StatusBarStateController statusBarStateController,
@@ -239,7 +240,7 @@
mGroupMembershipManager = groupMembershipManager;
mGroupExpansionManager = groupExpansionManager;
mRowContentBindStage = rowContentBindStage;
- mNotificationLogger = notificationLogger;
+ mStatsLogger = statsLogger;
mHeadsUpManager = headsUpManager;
mOnExpandClickListener = onExpandClickListener;
mStatusBarStateController = statusBarStateController;
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 6528cef3..a1718b9 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
@@ -37,6 +37,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
+import android.view.accessibility.AccessibilityEvent;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -45,8 +46,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.res.R;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.TransformableView;
@@ -898,6 +899,7 @@
// forceUpdateVisibilities cancels outstanding animations without updating the
// mAnimationStartVisibleType. Do so here instead.
mAnimationStartVisibleType = VISIBLE_TYPE_NONE;
+ notifySubtreeForAccessibilityContentChange();
}
private void fireExpandedVisibleListenerIfVisible() {
@@ -980,6 +982,7 @@
// updateViewVisibilities cancels outstanding animations without updating the
// mAnimationStartVisibleType. Do so here instead.
mAnimationStartVisibleType = VISIBLE_TYPE_NONE;
+ notifySubtreeForAccessibilityContentChange();
}
private void updateViewVisibility(int visibleType, int type, View view,
@@ -1029,6 +1032,7 @@
hiddenView.setVisible(false);
}
mAnimationStartVisibleType = VISIBLE_TYPE_NONE;
+ notifySubtreeForAccessibilityContentChange();
}
});
fireExpandedVisibleListenerIfVisible();
@@ -1049,6 +1053,22 @@
}
}
+ @Override
+ public void notifySubtreeAccessibilityStateChanged(View child, View source, int changeType) {
+ if (isAnimatingVisibleType()) {
+ // Don't send A11y events while animating to reduce Jank.
+ return;
+ }
+ super.notifySubtreeAccessibilityStateChanged(child, source, changeType);
+ }
+
+ private void notifySubtreeForAccessibilityContentChange() {
+ if (mParent != null) {
+ mParent.notifySubtreeAccessibilityStateChanged(this, this,
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
+ }
+ }
+
/**
* @param visibleType one of the static enum types in this view
* @return the corresponding transformable view according to the given visible type
@@ -2277,6 +2297,11 @@
mHeadsUpWrapper = headsUpWrapper;
}
+ @VisibleForTesting
+ protected void setAnimationStartVisibleType(int animationStartVisibleType) {
+ mAnimationStartVisibleType = animationStartVisibleType;
+ }
+
@Override
protected void dispatchDraw(Canvas canvas) {
try {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/AsyncGroupHeaderViewInflation.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/AsyncGroupHeaderViewInflation.kt
new file mode 100644
index 0000000..44fc77f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/AsyncGroupHeaderViewInflation.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the Async Group Header Inflation flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object AsyncGroupHeaderViewInflation {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_NOTIFICATION_ASYNC_GROUP_HEADER_INFLATION
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the async inflation of group header views enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.notificationAsyncGroupHeaderInflation()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
index eb1c1ba..5527efc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.shared
import android.graphics.drawable.Icon
+import com.android.systemui.statusbar.notification.stack.PriorityBucket
/**
* Model for a top-level "entry" in the notification list, either an
@@ -55,6 +56,16 @@
val shelfIcon: Icon?,
/** Icon to display in the status bar. */
val statusBarIcon: Icon?,
+ /** The notifying app's [packageName]'s uid. */
+ val uid: Int,
+ /** The notifying app's packageName. */
+ val packageName: String,
+ /** A small per-notification ID, used for statsd logging. */
+ val instanceId: Int?,
+ /** If this notification is the group summary for a group of notifications. */
+ val isGroupSummary: Boolean,
+ /** Indicates in which section the notification is displayed in. @see [PriorityBucket]. */
+ @PriorityBucket val bucket: Int,
) : ActiveNotificationEntryModel()
/** Model for a group of notifications. */
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 d9c5108..20fae88 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
@@ -26,9 +26,9 @@
import androidx.annotation.VisibleForTesting;
import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.res.R;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarState;
@@ -578,7 +578,7 @@
}
public boolean isPulsing(NotificationEntry entry) {
- return mPulsing && entry.isAlerting();
+ return mPulsing && entry.isHeadsUpEntry();
}
public void setPulsingRow(ExpandableNotificationRow row) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/DisplaySwitchNotificationsHiderTracker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/DisplaySwitchNotificationsHiderTracker.kt
new file mode 100644
index 0000000..a1fb983
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/DisplaySwitchNotificationsHiderTracker.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.notification.stack
+
+import com.android.internal.util.LatencyTracker
+import com.android.internal.util.LatencyTracker.ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE
+import com.android.internal.util.LatencyTracker.ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import javax.inject.Inject
+
+/**
+ * Tracks latencies related to temporary hiding notifications while measuring
+ * them, which is an optimization to show some content as early as possible
+ * and perform notifications measurement later.
+ * See [HideNotificationsInteractor].
+ */
+class DisplaySwitchNotificationsHiderTracker @Inject constructor(
+ private val notificationsInteractor: ShadeInteractor,
+ private val latencyTracker: LatencyTracker
+) {
+
+ suspend fun trackNotificationHideTime(shouldHideNotifications: Flow<Boolean>) {
+ shouldHideNotifications
+ .collect { shouldHide ->
+ if (shouldHide) {
+ latencyTracker.onActionStart(ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE)
+ } else {
+ latencyTracker.onActionEnd(ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE)
+ }
+ }
+ }
+
+ suspend fun trackNotificationHideTimeWhenVisible(shouldHideNotifications: Flow<Boolean>) {
+ combine(shouldHideNotifications, notificationsInteractor.isAnyExpanded)
+ { hidden, shadeExpanded -> hidden && shadeExpanded }
+ .distinctUntilChanged()
+ .collect { hiddenButShouldBeVisible ->
+ if (hiddenButShouldBeVisible) {
+ latencyTracker.onActionStart(
+ ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN)
+ } else {
+ latencyTracker.onActionEnd(
+ ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
index 6bb9573..5c9a0b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
@@ -23,7 +23,6 @@
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
-import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
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 ea414d2..04db653 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
@@ -94,6 +94,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.shade.TouchLogger;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.NotificationShelf;
@@ -113,6 +114,7 @@
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -129,9 +131,12 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
+import java.util.concurrent.Callable;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@@ -183,6 +188,7 @@
private int mOverflingDistance;
private float mMaxOverScroll;
private boolean mIsBeingDragged;
+ private boolean mSendingTouchesToSceneFramework;
private int mLastMotionY;
private int mDownX;
private int mActivePointerId = INVALID_POINTER;
@@ -245,6 +251,7 @@
*/
private float mOverScrolledBottomPixels;
private NotificationLogger.OnChildLocationsChangedListener mListener;
+ private OnNotificationLocationsChangedListener mLocationsChangedListener;
private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
private Runnable mOnHeightChangedRunnable;
@@ -390,6 +397,14 @@
}
};
+ private final Callable<Map<String, Integer>> collectVisibleLocationsCallable =
+ new Callable<>() {
+ @Override
+ public Map<String, Integer> call() {
+ return collectVisibleNotificationLocations();
+ }
+ };
+
private boolean mPulsing;
private boolean mScrollable;
private View mForcedScroll;
@@ -1242,8 +1257,21 @@
}
}
+ /**
+ * @param listener to be notified after the location of Notification children might have
+ * changed.
+ */
+ public void setNotificationLocationsChangedListener(
+ @Nullable OnNotificationLocationsChangedListener listener) {
+ if (NotificationsLiveDataStoreRefactor.isUnexpectedlyInLegacyMode()) {
+ return;
+ }
+ mLocationsChangedListener = listener;
+ }
+
public void setChildLocationsChangedListener(
NotificationLogger.OnChildLocationsChangedListener listener) {
+ NotificationsLiveDataStoreRefactor.assertInLegacyMode();
mListener = listener;
}
@@ -1483,7 +1511,12 @@
*/
public void setExpandedHeight(float height) {
final boolean skipHeightUpdate = shouldSkipHeightUpdate();
- updateStackPosition();
+
+ // when scene framework is enabled, updateStackPosition is already called by
+ // updateTopPadding every time the stack moves, so skip it here to avoid flickering.
+ if (!SceneContainerFlag.isEnabled()) {
+ updateStackPosition();
+ }
if (!skipHeightUpdate) {
mExpandedHeight = height;
@@ -2424,6 +2457,7 @@
/* notificationStackScrollLayout= */ this, mMaxDisplayedNotifications,
shelfIntrinsicHeight);
mIntrinsicContentHeight = height;
+ mController.setIntrinsicContentHeight(mIntrinsicContentHeight);
// The topPadding can be bigger than the regular padding when qs is expanded, in that
// state the maxPanelHeight and the contentHeight should be bigger
@@ -3532,8 +3566,11 @@
@Override
public boolean onTouchEvent(MotionEvent ev) {
- if (mTouchHandler != null && mTouchHandler.onTouchEvent(ev)) {
- return true;
+ if (mTouchHandler != null) {
+ boolean touchHandled = mTouchHandler.onTouchEvent(ev);
+ if (SceneContainerFlag.isEnabled() || touchHandled) {
+ return touchHandled;
+ }
}
return super.onTouchEvent(ev);
@@ -3541,6 +3578,27 @@
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
+ if (SceneContainerFlag.isEnabled() && mIsBeingDragged) {
+ if (!mSendingTouchesToSceneFramework) {
+ // if this is the first touch being sent to the scene framework,
+ // convert it into a synthetic DOWN event.
+ mSendingTouchesToSceneFramework = true;
+ MotionEvent downEvent = MotionEvent.obtain(ev);
+ downEvent.setAction(MotionEvent.ACTION_DOWN);
+ mController.sendTouchToSceneFramework(downEvent);
+ downEvent.recycle();
+ } else {
+ mController.sendTouchToSceneFramework(ev);
+ }
+
+ if (
+ ev.getActionMasked() == MotionEvent.ACTION_UP
+ || ev.getActionMasked() == MotionEvent.ACTION_CANCEL
+ ) {
+ setIsBeingDragged(false);
+ }
+ return false;
+ }
return TouchLogger.logDispatchTouch(TAG, ev, super.dispatchTouchEvent(ev));
}
@@ -3607,6 +3665,18 @@
return true;
}
+ // If the scene framework is enabled, ignore all non-move gestures if we are currently
+ // dragging - they should be dispatched to the scene framework. Move gestures should be let
+ // through to determine if we are still dragging or not.
+ if (
+ SceneContainerFlag.isEnabled()
+ && mIsBeingDragged
+ && action != MotionEvent.ACTION_MOVE
+ ) {
+ setIsBeingDragged(false);
+ return false;
+ }
+
switch (action) {
case MotionEvent.ACTION_DOWN: {
if (getChildCount() == 0 || !isInContentBounds(ev)) {
@@ -3650,6 +3720,11 @@
}
}
if (mIsBeingDragged) {
+ // Defer actual scrolling to the scene framework if enabled
+ if (SceneContainerFlag.isEnabled()) {
+ setIsBeingDragged(false);
+ return false;
+ }
// Scroll to follow the motion event
mLastMotionY = y;
float scrollAmount;
@@ -3744,9 +3819,8 @@
}
protected boolean isInsideQsHeader(MotionEvent ev) {
- if (mQsHeader == null) {
- Log.wtf(TAG, "qsHeader is null while NSSL is handling a touch");
- return false;
+ if (SceneContainerFlag.isEnabled()) {
+ return ev.getY() < mController.getPlaceholderTop();
}
mQsHeader.getBoundsOnScreen(mQsHeaderBound);
@@ -4000,9 +4074,16 @@
requestDisallowInterceptTouchEvent(true);
cancelLongPress();
resetExposedMenuView(true /* animate */, true /* force */);
+ } else {
+ mSendingTouchesToSceneFramework = false;
}
}
+ @VisibleForTesting
+ boolean getIsBeingDragged() {
+ return mIsBeingDragged;
+ }
+
public void requestDisallowLongPress() {
cancelLongPress();
}
@@ -4398,15 +4479,40 @@
child.applyViewState();
}
- if (mListener != null) {
- mListener.onChildLocationsChanged();
+ if (NotificationsLiveDataStoreRefactor.isEnabled()) {
+ if (mLocationsChangedListener != null) {
+ mLocationsChangedListener.onChildLocationsChanged(collectVisibleLocationsCallable);
+ }
+ } else {
+ if (mListener != null) {
+ mListener.onChildLocationsChanged();
+ }
}
+
runAnimationFinishedRunnables();
setAnimationRunning(false);
updateBackground();
updateViewShadows();
}
+ /**
+ * Retrieves a map of visible [{@link ExpandableViewState#location}]s of the actively displayed
+ * Notification children associated by their Notification keys.
+ * Locations are collected recursively including locations from the child views of Notification
+ * Groups, that are visible.
+ */
+ private Map<String, Integer> collectVisibleNotificationLocations() {
+ Map<String, Integer> visibilities = new HashMap<>();
+ int numChildren = getChildCount();
+ for (int i = 0; i < numChildren; i++) {
+ ExpandableView child = getChildAtIndex(i);
+ if (child instanceof ExpandableNotificationRow row) {
+ row.collectVisibleLocations(visibilities);
+ }
+ }
+ return visibilities;
+ }
+
private void updateViewShadows() {
// we need to work around an issue where the shadow would not cast between siblings when
// their z difference is between 0 and 0.1
@@ -5901,7 +6007,11 @@
}
}
- protected void setLogger(StackStateLogger logger) {
+ /**
+ * Sets a {@link StackStateLogger} which is notified as the {@link StackStateAnimator} updates
+ * the views.
+ */
+ protected void setStackStateLogger(StackStateLogger logger) {
mStateAnimator.setLogger(logger);
}
@@ -5938,6 +6048,18 @@
void flingTopOverscroll(float velocity, boolean open);
}
+ /**
+ * A listener that is notified when some ExpandableNotificationRow locations might have changed.
+ */
+ public interface OnNotificationLocationsChangedListener {
+ /**
+ * Called when the location of ExpandableNotificationRows might have changed.
+ *
+ * @param locations mapping of Notification keys to locations.
+ */
+ void onChildLocationsChanged(Callable<Map<String, Integer>> locations);
+ }
+
private void updateSpeedBumpIndex() {
mSpeedBumpIndexDirty = true;
}
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 abc04b8..6a66bb7 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
@@ -69,6 +69,7 @@
import com.android.systemui.flags.FeatureFlagsClassic;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.media.controls.ui.KeyguardMediaController;
@@ -79,6 +80,9 @@
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
+import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.ui.view.WindowRootView;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.statusbar.CommandQueue;
@@ -119,6 +123,7 @@
import com.android.systemui.statusbar.notification.row.NotificationGuts;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.NotificationSnooze;
+import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor;
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
@@ -144,6 +149,7 @@
import javax.inject.Inject;
import javax.inject.Named;
+import javax.inject.Provider;
/**
* Controller for {@link NotificationStackScrollLayout}.
@@ -180,6 +186,8 @@
private final NotificationRemoteInputManager mRemoteInputManager;
private final VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator;
private final ShadeController mShadeController;
+ private final Provider<WindowRootView> mWindowRootView;
+ private final NotificationStackAppearanceInteractor mStackAppearanceInteractor;
private final KeyguardMediaController mKeyguardMediaController;
private final SysuiStatusBarStateController mStatusBarStateController;
private final KeyguardBypassController mKeyguardBypassController;
@@ -688,6 +696,9 @@
SeenNotificationsInteractor seenNotificationsInteractor,
NotificationListViewBinder viewBinder,
ShadeController shadeController,
+ SceneContainerFlags sceneContainerFlags,
+ Provider<WindowRootView> windowRootView,
+ NotificationStackAppearanceInteractor stackAppearanceInteractor,
InteractionJankMonitor jankMonitor,
StackStateLogger stackLogger,
NotificationStackScrollLogger logger,
@@ -738,6 +749,8 @@
mVisibilityLocationProviderDelegator = visibilityLocationProviderDelegator;
mSeenNotificationsInteractor = seenNotificationsInteractor;
mShadeController = shadeController;
+ mWindowRootView = windowRootView;
+ mStackAppearanceInteractor = stackAppearanceInteractor;
mFeatureFlags = featureFlags;
mNotificationTargetsHelper = notificationTargetsHelper;
mSecureSettings = secureSettings;
@@ -750,7 +763,7 @@
}
private void setUpView() {
- mView.setLogger(mStackStateLogger);
+ mView.setStackStateLogger(mStackStateLogger);
mView.setController(this);
mView.setLogger(mLogger);
mView.setTouchHandler(new TouchHandler());
@@ -1075,6 +1088,28 @@
return mView.getIntrinsicContentHeight();
}
+ /**
+ * Dispatch a touch to the scene container framework.
+ * TODO(b/316965302): Replace findViewById to avoid DFS
+ */
+ public void sendTouchToSceneFramework(MotionEvent ev) {
+ View sceneContainer = mWindowRootView.get()
+ .findViewById(R.id.scene_container_root_composable);
+ if (sceneContainer != null) {
+ sceneContainer.dispatchTouchEvent(ev);
+ }
+ }
+
+ /** Get the y-coordinate of the top bound of the stack. */
+ public float getPlaceholderTop() {
+ return mStackAppearanceInteractor.getStackBounds().getValue().getTop();
+ }
+
+ /** Set the intrinsic height of the stack content without additional padding. */
+ public void setIntrinsicContentHeight(float intrinsicContentHeight) {
+ mStackAppearanceInteractor.setIntrinsicContentHeight(intrinsicContentHeight);
+ }
+
public void setIntrinsicPadding(int intrinsicPadding) {
mView.setIntrinsicPadding(intrinsicPadding);
}
@@ -1957,18 +1992,34 @@
mView.dispatchDownEventToScroller(ev);
}
}
- boolean scrollerWantsIt = false;
- if (mLongPressedView == null && mView.isExpanded() && !mSwipeHelper.isSwiping()
- && !expandingNotification && !mView.getDisallowScrollingInThisMotion()) {
- scrollerWantsIt = mView.onScrollTouch(ev);
- }
boolean horizontalSwipeWantsIt = false;
- if (mLongPressedView == null && !mView.isBeingDragged()
- && !expandingNotification
- && !mView.getExpandedInThisMotion()
- && !onlyScrollingInThisMotion
- && !mView.getDisallowDismissInThisMotion()) {
- horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
+ boolean scrollerWantsIt = false;
+ if (KeyguardShadeMigrationNssl.isEnabled()) {
+ // Reverse the order relative to the else statement. onScrollTouch will reset on an
+ // UP event, causing horizontalSwipeWantsIt to be set to true on vertical swipes.
+ if (mLongPressedView == null && !mView.isBeingDragged()
+ && !expandingNotification
+ && !mView.getExpandedInThisMotion()
+ && !onlyScrollingInThisMotion
+ && !mView.getDisallowDismissInThisMotion()) {
+ horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
+ }
+ if (mLongPressedView == null && mView.isExpanded() && !mSwipeHelper.isSwiping()
+ && !expandingNotification && !mView.getDisallowScrollingInThisMotion()) {
+ scrollerWantsIt = mView.onScrollTouch(ev);
+ }
+ } else {
+ if (mLongPressedView == null && mView.isExpanded() && !mSwipeHelper.isSwiping()
+ && !expandingNotification && !mView.getDisallowScrollingInThisMotion()) {
+ scrollerWantsIt = mView.onScrollTouch(ev);
+ }
+ if (mLongPressedView == null && !mView.isBeingDragged()
+ && !expandingNotification
+ && !mView.getExpandedInThisMotion()
+ && !onlyScrollingInThisMotion
+ && !mView.getDisallowDismissInThisMotion()) {
+ horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
+ }
}
// Check if we need to clear any snooze leavebehinds
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
index e78a694..aac3c28 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
@@ -30,4 +30,18 @@
/** The corner radius of the notification stack, in dp. */
val cornerRadiusDp = MutableStateFlow(32f)
+
+ /**
+ * The height in px of the contents of notification stack. Depending on the number of
+ * notifications, this can exceed the space available on screen to show notifications, at which
+ * point the notification stack should become scrollable.
+ */
+ val intrinsicContentHeight = MutableStateFlow(0f)
+
+ /**
+ * The y-coordinate in px of top of the contents of the notification stack. This value can be
+ * negative, if the stack is scrolled such that its top extends beyond the top edge of the
+ * screen.
+ */
+ val contentTop = MutableStateFlow(0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
index 61a4dfc..1dfde09 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
@@ -34,12 +34,32 @@
/** The bounds of the notification stack in the current scene. */
val stackBounds: StateFlow<NotificationContainerBounds> = repository.stackBounds.asStateFlow()
+ /** The corner radius of the notification stack, in dp. */
+ val cornerRadiusDp: StateFlow<Float> = repository.cornerRadiusDp.asStateFlow()
+
+ /**
+ * The height in px of the contents of notification stack. Depending on the number of
+ * notifications, this can exceed the space available on screen to show notifications, at which
+ * point the notification stack should become scrollable.
+ */
+ val intrinsicContentHeight = repository.intrinsicContentHeight.asStateFlow()
+
+ /** The y-coordinate in px of top of the contents of the notification stack. */
+ val contentTop = repository.contentTop.asStateFlow()
+
/** Sets the position of the notification stack in the current scene. */
fun setStackBounds(bounds: NotificationContainerBounds) {
check(bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" }
repository.stackBounds.value = bounds
}
- /** The corner radius of the notification stack, in dp. */
- val cornerRadiusDp: StateFlow<Float> = repository.cornerRadiusDp.asStateFlow()
+ /** Sets the height of the contents of the notification stack. */
+ fun setIntrinsicContentHeight(height: Float) {
+ repository.intrinsicContentHeight.value = height
+ }
+
+ /** Sets the y-coord in px of the top of the contents of the notification stack. */
+ fun setContentTop(startY: Float) {
+ repository.contentTop.value = startY
+ }
}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationRowStatsLogger.kt
similarity index 70%
copy from core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
copy to packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationRowStatsLogger.kt
index ce92b6d..2305c7e 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationRowStatsLogger.kt
@@ -13,10 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.companion.virtual.camera;
-/**
- * The configuration of a single virtual camera stream.
- * @hide
- */
-parcelable VirtualCameraStreamConfig;
\ No newline at end of file
+package com.android.systemui.statusbar.notification.stack.ui.view
+
+interface NotificationRowStatsLogger {
+ fun onNotificationExpansionChanged(
+ key: String,
+ isExpanded: Boolean,
+ location: Int,
+ isUserAction: Boolean
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLogger.kt
new file mode 100644
index 0000000..5418616
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLogger.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.ui.view
+
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import java.util.concurrent.Callable
+
+/**
+ * Logs UI events of Notifications, in particular, logging which Notifications are visible and which
+ * are not.
+ */
+interface NotificationStatsLogger : NotificationRowStatsLogger {
+ fun onLockscreenOrShadeInteractive(
+ isOnLockScreen: Boolean,
+ activeNotifications: List<ActiveNotificationModel>,
+ )
+ fun onLockscreenOrShadeNotInteractive(activeNotifications: List<ActiveNotificationModel>)
+ fun onNotificationRemoved(key: String)
+ fun onNotificationUpdated(key: String)
+ fun onNotificationListUpdated(
+ locationsProvider: Callable<Map<String, Int>>,
+ notificationRanks: Map<String, Int>,
+ )
+ override fun onNotificationExpansionChanged(
+ key: String,
+ isExpanded: Boolean,
+ location: Int,
+ isUserAction: Boolean
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerImpl.kt
new file mode 100644
index 0000000..0cb00bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerImpl.kt
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.ui.view
+
+import android.service.notification.NotificationListenerService
+import androidx.annotation.VisibleForTesting
+import com.android.internal.statusbar.IStatusBarService
+import com.android.internal.statusbar.NotificationVisibility
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger
+import com.android.systemui.statusbar.notification.logging.nano.Notifications
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import com.android.systemui.statusbar.notification.stack.ExpandableViewState
+import java.util.concurrent.Callable
+import java.util.concurrent.ConcurrentHashMap
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+@VisibleForTesting const val UNKNOWN_RANK = -1
+
+@SysUISingleton
+class NotificationStatsLoggerImpl
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ private val notificationListenerService: NotificationListenerService,
+ private val notificationPanelLogger: NotificationPanelLogger,
+ private val statusBarService: IStatusBarService,
+) : NotificationStatsLogger {
+ private val lastLoggedVisibilities = mutableMapOf<String, VisibilityState>()
+ private var logVisibilitiesJob: Job? = null
+
+ private val expansionStates: MutableMap<String, ExpansionState> =
+ ConcurrentHashMap<String, ExpansionState>()
+ private val lastReportedExpansionValues: MutableMap<String, Boolean> =
+ ConcurrentHashMap<String, Boolean>()
+
+ override fun onNotificationListUpdated(
+ locationsProvider: Callable<Map<String, Int>>,
+ notificationRanks: Map<String, Int>,
+ ) {
+ if (logVisibilitiesJob?.isActive == true) {
+ return
+ }
+
+ logVisibilitiesJob =
+ startLogVisibilitiesJob(
+ newVisibilities =
+ combine(
+ visibilities = locationsProvider.call(),
+ rankingsMap = notificationRanks
+ ),
+ activeNotifCount = notificationRanks.size,
+ )
+ }
+
+ override fun onNotificationExpansionChanged(
+ key: String,
+ isExpanded: Boolean,
+ location: Int,
+ isUserAction: Boolean,
+ ) {
+ val expansionState =
+ ExpansionState(
+ key = key,
+ isExpanded = isExpanded,
+ isUserAction = isUserAction,
+ location = location,
+ )
+ expansionStates[key] = expansionState
+ maybeLogNotificationExpansionChange(expansionState)
+ }
+
+ private fun maybeLogNotificationExpansionChange(expansionState: ExpansionState) {
+ if (expansionState.visible.not()) {
+ // Only log visible expansion changes
+ return
+ }
+
+ val loggedExpansionValue: Boolean? = lastReportedExpansionValues[expansionState.key]
+ if (loggedExpansionValue == null && !expansionState.isExpanded) {
+ // Consider the Notification initially collapsed, so only expanded is logged in the
+ // first time.
+ return
+ }
+
+ if (loggedExpansionValue != null && loggedExpansionValue == expansionState.isExpanded) {
+ // We have already logged this state, don't log it again
+ return
+ }
+
+ logNotificationExpansionChange(expansionState)
+ lastReportedExpansionValues[expansionState.key] = expansionState.isExpanded
+ }
+
+ private fun logNotificationExpansionChange(expansionState: ExpansionState) =
+ applicationScope.launch {
+ withContext(bgDispatcher) {
+ statusBarService.onNotificationExpansionChanged(
+ /* key = */ expansionState.key,
+ /* userAction = */ expansionState.isUserAction,
+ /* expanded = */ expansionState.isExpanded,
+ /* notificationLocation = */ expansionState.location
+ .toNotificationLocation()
+ .ordinal
+ )
+ }
+ }
+
+ override fun onLockscreenOrShadeInteractive(
+ isOnLockScreen: Boolean,
+ activeNotifications: List<ActiveNotificationModel>,
+ ) {
+ applicationScope.launch {
+ withContext(bgDispatcher) {
+ notificationPanelLogger.logPanelShown(
+ isOnLockScreen,
+ activeNotifications.toNotificationProto()
+ )
+ }
+ }
+ }
+
+ override fun onLockscreenOrShadeNotInteractive(
+ activeNotifications: List<ActiveNotificationModel>
+ ) {
+ logVisibilitiesJob =
+ startLogVisibilitiesJob(
+ newVisibilities = emptyMap(),
+ activeNotifCount = activeNotifications.size
+ )
+ }
+
+ // TODO(b/308623704) wire this in with NotifPipeline updates
+ override fun onNotificationRemoved(key: String) {
+ // No need to track expansion states for Notifications that are removed.
+ expansionStates.remove(key)
+ lastReportedExpansionValues.remove(key)
+ }
+
+ // TODO(b/308623704) wire this in with NotifPipeline updates
+ override fun onNotificationUpdated(key: String) {
+ // When the Notification is updated, we should consider it as not yet logged.
+ lastReportedExpansionValues.remove(key)
+ }
+
+ private fun combine(
+ visibilities: Map<String, Int>,
+ rankingsMap: Map<String, Int>
+ ): Map<String, VisibilityState> =
+ visibilities.mapValues { entry ->
+ VisibilityState(entry.key, entry.value, rankingsMap[entry.key] ?: UNKNOWN_RANK)
+ }
+
+ private fun startLogVisibilitiesJob(
+ newVisibilities: Map<String, VisibilityState>,
+ activeNotifCount: Int,
+ ) =
+ applicationScope.launch {
+ val newlyVisible = newVisibilities - lastLoggedVisibilities.keys
+ val noLongerVisible = lastLoggedVisibilities - newVisibilities.keys
+
+ maybeLogVisibilityChanges(newlyVisible, noLongerVisible, activeNotifCount)
+ updateExpansionStates(newlyVisible, noLongerVisible)
+
+ lastLoggedVisibilities.clear()
+ lastLoggedVisibilities.putAll(newVisibilities)
+ }
+
+ private suspend fun maybeLogVisibilityChanges(
+ newlyVisible: Map<String, VisibilityState>,
+ noLongerVisible: Map<String, VisibilityState>,
+ activeNotifCount: Int,
+ ) {
+ if (newlyVisible.isEmpty() && noLongerVisible.isEmpty()) {
+ return
+ }
+
+ val newlyVisibleAr =
+ newlyVisible.mapToNotificationVisibilitiesAr(visible = true, count = activeNotifCount)
+
+ val noLongerVisibleAr =
+ noLongerVisible.mapToNotificationVisibilitiesAr(
+ visible = false,
+ count = activeNotifCount
+ )
+
+ withContext(bgDispatcher) {
+ statusBarService.onNotificationVisibilityChanged(newlyVisibleAr, noLongerVisibleAr)
+ if (newlyVisible.isNotEmpty()) {
+ notificationListenerService.setNotificationsShown(newlyVisible.keys.toTypedArray())
+ }
+ }
+ }
+
+ private fun updateExpansionStates(
+ newlyVisible: Map<String, VisibilityState>,
+ noLongerVisible: Map<String, VisibilityState>
+ ) {
+ expansionStates.forEach { (key, expansionState) ->
+ if (newlyVisible.contains(key)) {
+ val newState =
+ expansionState.copy(
+ visible = true,
+ location = newlyVisible.getValue(key).location,
+ )
+ expansionStates[key] = newState
+ maybeLogNotificationExpansionChange(newState)
+ }
+
+ if (noLongerVisible.contains(key)) {
+ expansionStates[key] =
+ expansionState.copy(
+ visible = false,
+ location = noLongerVisible.getValue(key).location,
+ )
+ }
+ }
+ }
+
+ private data class VisibilityState(
+ val key: String,
+ val location: Int,
+ val rank: Int,
+ )
+
+ private data class ExpansionState(
+ val key: String,
+ val isUserAction: Boolean,
+ val isExpanded: Boolean,
+ val visible: Boolean,
+ val location: Int,
+ ) {
+ constructor(
+ key: String,
+ isExpanded: Boolean,
+ location: Int,
+ isUserAction: Boolean,
+ ) : this(
+ key = key,
+ isExpanded = isExpanded,
+ isUserAction = isUserAction,
+ visible = isVisibleLocation(location),
+ location = location,
+ )
+ }
+
+ private fun Map<String, VisibilityState>.mapToNotificationVisibilitiesAr(
+ visible: Boolean,
+ count: Int,
+ ): Array<NotificationVisibility> =
+ this.map { (key, state) ->
+ NotificationVisibility.obtain(
+ /* key = */ key,
+ /* rank = */ state.rank,
+ /* count = */ count,
+ /* visible = */ visible,
+ /* location = */ state.location.toNotificationLocation()
+ )
+ }
+ .toTypedArray()
+}
+
+private fun Int.toNotificationLocation(): NotificationVisibility.NotificationLocation {
+ return when (this) {
+ ExpandableViewState.LOCATION_FIRST_HUN ->
+ NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP
+ ExpandableViewState.LOCATION_HIDDEN_TOP ->
+ NotificationVisibility.NotificationLocation.LOCATION_HIDDEN_TOP
+ ExpandableViewState.LOCATION_MAIN_AREA ->
+ NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA
+ ExpandableViewState.LOCATION_BOTTOM_STACK_PEEKING ->
+ NotificationVisibility.NotificationLocation.LOCATION_BOTTOM_STACK_PEEKING
+ ExpandableViewState.LOCATION_BOTTOM_STACK_HIDDEN ->
+ NotificationVisibility.NotificationLocation.LOCATION_BOTTOM_STACK_HIDDEN
+ ExpandableViewState.LOCATION_GONE ->
+ NotificationVisibility.NotificationLocation.LOCATION_GONE
+ else -> NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN
+ }
+}
+
+private fun List<ActiveNotificationModel>.toNotificationProto(): Notifications.NotificationList {
+ val notificationList = Notifications.NotificationList()
+ val protoArray: Array<Notifications.Notification> =
+ map { notification ->
+ Notifications.Notification().apply {
+ uid = notification.uid
+ packageName = notification.packageName
+ notification.instanceId?.let { instanceId = it }
+ // TODO(b/308623704) check if we can set groupInstanceId as well
+ isGroupSummary = notification.isGroupSummary
+ section = NotificationPanelLogger.toNotificationSection(notification.bucket)
+ }
+ }
+ .toTypedArray()
+
+ if (protoArray.isNotEmpty()) {
+ notificationList.notifications = protoArray
+ }
+
+ return notificationList
+}
+
+private fun isVisibleLocation(location: Int): Boolean =
+ location and ExpandableViewState.VISIBLE_LOCATIONS != 0
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/HideNotificationsBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/HideNotificationsBinder.kt
index 910b40f..c2bc9ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/HideNotificationsBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/HideNotificationsBinder.kt
@@ -16,22 +16,36 @@
package com.android.systemui.statusbar.notification.stack.ui.viewbinder
import androidx.core.view.doOnDetach
+import com.android.systemui.statusbar.notification.stack.DisplaySwitchNotificationsHiderTracker
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.launch
/**
* Binds a [NotificationStackScrollLayoutController] to its [view model][NotificationListViewModel].
*/
object HideNotificationsBinder {
- suspend fun bindHideList(
+ fun CoroutineScope.bindHideList(
viewController: NotificationStackScrollLayoutController,
- viewModel: NotificationListViewModel
+ viewModel: NotificationListViewModel,
+ hiderTracker: DisplaySwitchNotificationsHiderTracker
) {
viewController.view.doOnDetach { viewController.bindHideState(shouldHide = false) }
- viewModel.hideListViewModel.shouldHideListForPerformance.collect { shouldHide ->
- viewController.bindHideState(shouldHide)
+ val hideListFlow = viewModel.hideListViewModel.shouldHideListForPerformance
+ .shareIn(this, started = Lazily)
+
+ launch {
+ hideListFlow.collect { shouldHide ->
+ viewController.bindHideState(shouldHide)
+ }
}
+
+ launch { hiderTracker.trackNotificationHideTime(hideListFlow) }
+ launch { hiderTracker.trackNotificationHideTimeWhenVisible(hideListFlow) }
}
private fun NotificationStackScrollLayoutController.bindHideState(shouldHide: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index 1b36660..44a7e7e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -33,13 +33,17 @@
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterViewBinder
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerShelfViewBinder
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinder
+import com.android.systemui.statusbar.notification.stack.DisplaySwitchNotificationsHiderTracker
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationStatsLogger
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.HideNotificationsBinder.bindHideList
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel
import com.android.systemui.statusbar.phone.NotificationIconAreaController
import com.android.systemui.util.kotlin.getOrNull
+import java.util.Optional
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.combine
@@ -49,13 +53,15 @@
class NotificationListViewBinder
@Inject
constructor(
- private val viewModel: NotificationListViewModel,
@Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val hiderTracker: DisplaySwitchNotificationsHiderTracker,
private val configuration: ConfigurationState,
private val falsingManager: FalsingManager,
private val iconAreaController: NotificationIconAreaController,
private val metricsLogger: MetricsLogger,
private val nicBinder: NotificationIconContainerShelfViewBinder,
+ private val loggerOptional: Optional<NotificationStatsLogger>,
+ private val viewModel: NotificationListViewModel,
) {
fun bindWhileAttached(
@@ -70,15 +76,20 @@
view.repeatWhenAttached {
lifecycleScope.launch {
launch { bindShelf(shelf) }
- launch { bindHideList(viewController, viewModel) }
+ bindHideList(viewController, viewModel, hiderTracker)
if (FooterViewRefactor.isEnabled) {
launch { bindFooter(view) }
launch { bindEmptyShade(view) }
- viewModel.isImportantForAccessibility.collect { isImportantForAccessibility ->
- view.setImportantForAccessibilityYesNo(isImportantForAccessibility)
+ launch {
+ viewModel.isImportantForAccessibility.collect { isImportantForAccessibility
+ ->
+ view.setImportantForAccessibilityYesNo(isImportantForAccessibility)
+ }
}
}
+
+ launch { bindLogger(view) }
}
}
}
@@ -136,4 +147,18 @@
)
}
}
+
+ private suspend fun bindLogger(view: NotificationStackScrollLayout) {
+ if (NotificationsLiveDataStoreRefactor.isEnabled) {
+ viewModel.logger.getOrNull()?.let { viewModel ->
+ loggerOptional.getOrNull()?.let { logger ->
+ NotificationStatsLoggerBinder.bindLogger(
+ view,
+ logger,
+ viewModel,
+ )
+ }
+ }
+ }
+ }
}
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 a9b542d..ed15f55 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
@@ -25,6 +25,7 @@
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel
+import kotlin.math.pow
import kotlin.math.roundToInt
import kotlinx.coroutines.launch
@@ -43,24 +44,28 @@
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch {
viewModel.stackBounds.collect { bounds ->
- controller.updateTopPadding(
- bounds.top,
- controller.isAddOrRemoveAnimationPending
- )
controller.setRoundedClippingBounds(
- it.left,
- it.top,
- it.right,
- it.bottom,
+ bounds.left.roundToInt(),
+ bounds.top.roundToInt(),
+ bounds.right.roundToInt(),
+ bounds.bottom.roundToInt(),
viewModel.cornerRadiusDp.value.dpToPx(context),
viewModel.cornerRadiusDp.value.dpToPx(context),
)
}
}
+
+ launch {
+ viewModel.contentTop.collect {
+ controller.updateTopPadding(it, controller.isAddOrRemoveAnimationPending)
+ }
+ }
+
launch {
viewModel.expandFraction.collect { expandFraction ->
ambientState.expansionFraction = expandFraction
controller.expandedHeight = expandFraction * controller.view.height
+ controller.setMaxAlphaForExpansion(expandFraction.pow(0.75f))
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStatsLoggerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStatsLoggerBinder.kt
new file mode 100644
index 0000000..a05ad6e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStatsLoggerBinder.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.notification.stack.ui.viewbinder
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationStatsLogger
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationLoggerViewModel
+import com.android.systemui.util.kotlin.Utils
+import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.kotlin.throttle
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
+
+/**
+ * Binds a [NotificationStatsLogger] to its [NotificationLoggerViewModel], and wires in
+ * [NotificationStackScrollLayout.OnNotificationLocationsChangedListener] updates to it.
+ */
+object NotificationStatsLoggerBinder {
+
+ /** minimum delay in ms between Notification location updates */
+ private const val NOTIFICATION_UPDATE_PERIOD_MS = 500L
+
+ suspend fun bindLogger(
+ view: NotificationStackScrollLayout,
+ logger: NotificationStatsLogger,
+ viewModel: NotificationLoggerViewModel,
+ ) {
+ viewModel.isLockscreenOrShadeInteractive
+ .sample(
+ combine(viewModel.isOnLockScreen, viewModel.activeNotifications, ::Pair),
+ Utils.Companion::toTriple
+ )
+ .collectLatest { (isPanelInteractive, isOnLockScreen, notifications) ->
+ if (isPanelInteractive) {
+ logger.onLockscreenOrShadeInteractive(
+ isOnLockScreen = isOnLockScreen,
+ activeNotifications = notifications,
+ )
+ view.onNotificationsUpdated
+ // Delay the updates with [NOTIFICATION_UPDATES_PERIOD_MS]. If the original
+ // flow emits more than once during this period, only the latest value is
+ // emitted, meaning that we won't log the intermediate Notification states.
+ .throttle(NOTIFICATION_UPDATE_PERIOD_MS)
+ .sample(viewModel.activeNotificationRanks, ::Pair)
+ .collect { (locationsProvider, notificationRanks) ->
+ logger.onNotificationListUpdated(locationsProvider, notificationRanks)
+ }
+ } else {
+ logger.onLockscreenOrShadeNotInteractive(
+ activeNotifications = notifications,
+ )
+ }
+ }
+ }
+}
+
+private val NotificationStackScrollLayout.onNotificationsUpdated
+ get() =
+ ConflatedCallbackFlow.conflatedCallbackFlow {
+ val callback =
+ NotificationStackScrollLayout.OnNotificationLocationsChangedListener { callable ->
+ trySend(callable)
+ }
+ setNotificationLocationsChangedListener(callback)
+ awaitClose { setNotificationLocationsChangedListener(null) }
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index 569ae24..86c0a678 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -40,6 +40,7 @@
val shelf: NotificationShelfViewModel,
val hideListViewModel: HideListViewModel,
val footer: Optional<FooterViewModel>,
+ val logger: Optional<NotificationLoggerViewModel>,
activeNotificationsInteractor: ActiveNotificationsInteractor,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
seenNotificationsInteractor: SeenNotificationsInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModel.kt
new file mode 100644
index 0000000..0901a7f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModel.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+class NotificationLoggerViewModel
+@Inject
+constructor(
+ activeNotificationsInteractor: ActiveNotificationsInteractor,
+ keyguardInteractor: KeyguardInteractor,
+ windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor,
+) {
+ val activeNotifications: Flow<List<ActiveNotificationModel>> =
+ activeNotificationsInteractor.allRepresentativeNotifications.map { it.values.toList() }
+
+ val activeNotificationRanks: Flow<Map<String, Int>> =
+ activeNotificationsInteractor.activeNotificationRanks
+
+ val isLockscreenOrShadeInteractive: Flow<Boolean> =
+ windowRootViewVisibilityInteractor.isLockscreenOrShadeVisibleAndInteractive
+
+ val isOnLockScreen: Flow<Boolean> = keyguardInteractor.isKeyguardShowing
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
index 834d3ff..74db583 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
@@ -41,4 +41,7 @@
/** The corner radius of the notification stack, in dp. */
val cornerRadiusDp: StateFlow<Float> = stackAppearanceInteractor.cornerRadiusDp
+
+ /** The y-coordinate in px of top of the contents of the notification stack. */
+ val contentTop: StateFlow<Float> = stackAppearanceInteractor.contentTop
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index 9f22118..385f061 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -21,9 +21,11 @@
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
/**
@@ -35,6 +37,7 @@
@Inject
constructor(
private val interactor: NotificationStackAppearanceInteractor,
+ shadeInteractor: ShadeInteractor,
flags: SceneContainerFlags,
featureFlags: FeatureFlagsClassic,
) {
@@ -66,4 +69,22 @@
/** The corner radius of the placeholder, in dp. */
val cornerRadiusDp: StateFlow<Float> = interactor.cornerRadiusDp
+
+ /**
+ * The height in px of the contents of notification stack. Depending on the number of
+ * notifications, this can exceed the space available on screen to show notifications, at which
+ * point the notification stack should become scrollable.
+ */
+ val intrinsicContentHeight = interactor.intrinsicContentHeight
+
+ /**
+ * The amount [0-1] that the shade has been opened. At 0, the shade is closed; at 1, the shade
+ * is open.
+ */
+ val expandFraction: Flow<Float> = shadeInteractor.shadeExpansion
+
+ /** Sets the y-coord in px of the top of the contents of the notification stack. */
+ fun onContentTopChanged(padding: Float) {
+ interactor.setContentTop(padding)
+ }
}
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 5ee38be..a48fb45 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
@@ -240,13 +240,13 @@
*/
val translationY: Flow<Float> =
combine(
- isOnLockscreen,
+ isOnLockscreenWithoutShade,
merge(
keyguardInteractor.keyguardTranslationY,
occludedToLockscreenTransitionViewModel.lockscreenTranslationY,
)
- ) { isOnLockscreen, translationY ->
- if (isOnLockscreen) {
+ ) { isOnLockscreenWithoutShade, translationY ->
+ if (isOnLockscreenWithoutShade) {
translationY
} else {
0f
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 e66d9e8..b07ba3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -222,9 +222,9 @@
mReleaseOnExpandFinish = false;
} else {
for (NotificationEntry entry : mEntriesToRemoveAfterExpand) {
- if (isAlerting(entry.getKey())) {
+ if (isHeadsUpEntry(entry.getKey())) {
// Maybe the heads-up was removed already
- removeAlertEntry(entry.getKey());
+ removeEntry(entry.getKey());
}
}
}
@@ -357,9 +357,9 @@
private final OnReorderingAllowedListener mOnReorderingAllowedListener = () -> {
mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
for (NotificationEntry entry : mEntriesToRemoveWhenReorderingAllowed) {
- if (isAlerting(entry.getKey())) {
+ if (isHeadsUpEntry(entry.getKey())) {
// Maybe the heads-up was removed already
- removeAlertEntry(entry.getKey());
+ removeEntry(entry.getKey());
}
}
mEntriesToRemoveWhenReorderingAllowed.clear();
@@ -370,14 +370,14 @@
// HeadsUpManager utility (protected) methods overrides:
@Override
- protected HeadsUpEntry createAlertEntry() {
+ protected HeadsUpEntry createHeadsUpEntry() {
return mEntryPool.acquire();
}
@Override
- protected void onAlertEntryRemoved(AlertEntry alertEntry) {
- super.onAlertEntryRemoved(alertEntry);
- mEntryPool.release((HeadsUpEntryPhone) alertEntry);
+ protected void onEntryRemoved(HeadsUpEntry headsUpEntry) {
+ super.onEntryRemoved(headsUpEntry);
+ mEntryPool.release((HeadsUpEntryPhone) headsUpEntry);
}
@Override
@@ -403,7 +403,7 @@
@Nullable
private HeadsUpEntryPhone getHeadsUpEntryPhone(@NonNull String key) {
- return (HeadsUpEntryPhone) mAlertEntries.get(key);
+ return (HeadsUpEntryPhone) mHeadsUpEntryMap.get(key);
}
@Nullable
@@ -455,7 +455,7 @@
} else if (mTrackingHeadsUp) {
mEntriesToRemoveAfterExpand.add(entry);
} else {
- removeAlertEntry(entry.getKey());
+ removeEntry(entry.getKey());
}
};
@@ -529,13 +529,13 @@
mStatusBarState = newState;
if (wasKeyguard && !isKeyguard && mBypassController.getBypassEnabled()) {
ArrayList<String> keysToRemove = new ArrayList<>();
- for (AlertEntry entry : mAlertEntries.values()) {
+ for (HeadsUpEntry entry : mHeadsUpEntryMap.values()) {
if (entry.mEntry != null && entry.mEntry.isBubble() && !entry.isSticky()) {
keysToRemove.add(entry.mEntry.getKey());
}
}
for (String key : keysToRemove) {
- removeAlertEntry(key);
+ removeEntry(key);
}
}
}
@@ -545,7 +545,7 @@
if (!isDozing) {
// Let's make sure all huns we got while dozing time out within the normal timeout
// duration. Otherwise they could get stuck for a very long time
- for (AlertEntry entry : mAlertEntries.values()) {
+ for (HeadsUpEntry entry : mHeadsUpEntryMap.values()) {
entry.updateEntry(true /* updatePostTime */, "onDozingChanged(false)");
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index f34a44a..be5c6b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -918,8 +918,15 @@
}
}
icon.setVisibleState(visibleState, animationsAllowed);
- icon.setIconColor(mOverrideIconColor ? mThemedTextColorPrimary : iconColor,
- needsCannedAnimation && animationsAllowed);
+ if (NotificationIconContainerRefactor.isEnabled()) {
+ if (mOverrideIconColor) {
+ icon.setIconColor(mThemedTextColorPrimary,
+ /* animate= */ needsCannedAnimation && animationsAllowed);
+ }
+ } else {
+ icon.setIconColor(mOverrideIconColor ? mThemedTextColorPrimary : iconColor,
+ needsCannedAnimation && animationsAllowed);
+ }
if (animate) {
animateTo(icon, animationProperties);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 9da6111..4ee061d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -562,7 +562,7 @@
private void removeHunAfterClick(ExpandableNotificationRow row) {
String key = row.getEntry().getSbn().getKey();
- if (mHeadsUpManager != null && mHeadsUpManager.isAlerting(key)) {
+ if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUpEntry(key)) {
// Release the HUN notification to the shade.
if (mPresenter.isPresenterFullyCollapsed()) {
HeadsUpUtil.setNeedsHeadsUpDisappearAnimationAfterClick(row, true);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index 30a445f..703b3c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -118,15 +118,25 @@
private void updateVpn() {
boolean vpnVisible = mSecurityController.isVpnEnabled();
- int vpnIconId = currentVpnIconId(mSecurityController.isVpnBranded());
+ int vpnIconId = currentVpnIconId(
+ mSecurityController.isVpnBranded(),
+ mSecurityController.isVpnValidated());
mIconController.setIcon(mSlotVpn, vpnIconId,
mContext.getResources().getString(R.string.accessibility_vpn_on));
mIconController.setIconVisibility(mSlotVpn, vpnVisible);
}
- private int currentVpnIconId(boolean isBranded) {
- return isBranded ? R.drawable.stat_sys_branded_vpn : R.drawable.stat_sys_vpn_ic;
+ private int currentVpnIconId(boolean isBranded, boolean isValidated) {
+ if (isBranded) {
+ return isValidated
+ ? R.drawable.stat_sys_branded_vpn
+ : R.drawable.stat_sys_no_internet_branded_vpn;
+ } else {
+ return isValidated
+ ? R.drawable.stat_sys_vpn_ic
+ : R.drawable.stat_sys_no_internet_vpn_ic;
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 89a2fb7..e309c32 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -34,8 +34,6 @@
import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigCoreStartable
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileRepositorySwitcher
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepositoryImpl
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter
@@ -62,6 +60,8 @@
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryViaTrackerLib
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
+import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
+import com.android.systemui.statusbar.policy.data.repository.UserSetupRepositoryImpl
import dagger.Binds
import dagger.Module
import dagger.Provides
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index 39135c7..d555c47 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -32,9 +32,9 @@
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
+import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
import com.android.systemui.util.CarrierConfigTracker
import java.lang.ref.WeakReference
import javax.inject.Inject
@@ -105,7 +105,7 @@
val isDefaultConnectionFailed: StateFlow<Boolean>
/** True once the user has been set up */
- val isUserSetup: StateFlow<Boolean>
+ val isUserSetUp: StateFlow<Boolean>
/** True if we're configured to force-hide the mobile icons and false otherwise. */
val isForceHidden: Flow<Boolean>
@@ -362,7 +362,7 @@
)
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
- override val isUserSetup: StateFlow<Boolean> = userSetupRepo.isUserSetupFlow
+ override val isUserSetUp: StateFlow<Boolean> = userSetupRepo.isUserSetUp
override val isForceHidden: Flow<Boolean> =
connectivityRepository.forceHiddenSlots
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
index 8fc8b2f..de46a5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
@@ -19,7 +19,7 @@
import android.os.OutcomeReceiver
import android.telephony.satellite.NtnSignalStrengthCallback
import android.telephony.satellite.SatelliteManager
-import android.telephony.satellite.SatelliteStateCallback
+import android.telephony.satellite.SatelliteModemStateCallback
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -180,7 +180,7 @@
// By using the SupportedSatelliteManager here, we expect registration never to fail
private fun connectionStateFlow(sm: SupportedSatelliteManager): Flow<SatelliteConnectionState> =
conflatedCallbackFlow {
- val cb = SatelliteStateCallback { state ->
+ val cb = SatelliteModemStateCallback { state ->
trySend(SatelliteConnectionState.fromModemState(state))
}
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 e971128..1528c9b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -26,6 +26,9 @@
import android.database.ContentObserver;
import android.os.Handler;
import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import com.android.internal.logging.MetricsLogger;
@@ -34,7 +37,6 @@
import com.android.systemui.EventLogTags;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.res.R;
-import com.android.systemui.statusbar.AlertingNotificationManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
import com.android.systemui.util.ListenerSet;
@@ -43,14 +45,14 @@
import com.android.systemui.util.time.SystemClock;
import java.io.PrintWriter;
+import java.util.stream.Stream;
/**
* A manager which handles heads up notifications which is a special mode where
* they simply peek from the top of the screen.
*/
-public abstract class BaseHeadsUpManager extends AlertingNotificationManager implements
- HeadsUpManager {
- private static final String TAG = "HeadsUpManager";
+public abstract class BaseHeadsUpManager implements HeadsUpManager {
+ private static final String TAG = "BaseHeadsUpManager";
private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms";
protected final ListenerSet<OnHeadsUpChangedListener> mListeners = new ListenerSet<>();
@@ -67,6 +69,14 @@
private final UiEventLogger mUiEventLogger;
+ protected final SystemClock mSystemClock;
+ protected final ArrayMap<String, HeadsUpEntry> mHeadsUpEntryMap = new ArrayMap<>();
+ protected final HeadsUpManagerLogger mLogger;
+ protected int mMinimumDisplayTime;
+ protected int mStickyForSomeTimeAutoDismissTime;
+ protected int mAutoDismissTime;
+ protected DelayableExecutor mExecutor;
+
/**
* Enum entry for notification peek logged from this class.
*/
@@ -91,7 +101,9 @@
@Main DelayableExecutor executor,
AccessibilityManagerWrapper accessibilityManagerWrapper,
UiEventLogger uiEventLogger) {
- super(logger, systemClock, executor);
+ mLogger = logger;
+ mExecutor = executor;
+ mSystemClock = systemClock;
mContext = context;
mAccessibilityMgr = accessibilityManagerWrapper;
mUiEventLogger = uiEventLogger;
@@ -138,20 +150,136 @@
mListeners.remove(listener);
}
- /** Updates the notification with the given key. */
- public void updateNotification(@NonNull String key, boolean alert) {
- super.updateNotification(key, alert);
- HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
- if (alert && headsUpEntry != null) {
- setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUpEntry.mEntry));
+ /**
+ * Called when posting a new notification that should appear on screen.
+ * Adds the notification to be managed.
+ * @param entry entry to show
+ */
+ @Override
+ public void showNotification(@NonNull NotificationEntry entry) {
+ mLogger.logShowNotification(entry);
+ addEntry(entry);
+ updateNotification(entry.getKey(), true /* show */);
+ entry.setInterruption();
+ }
+
+ /**
+ * Try to remove the notification. May not succeed if the notification has not been shown long
+ * enough and needs to be kept around.
+ * @param key the key of the notification to remove
+ * @param releaseImmediately force a remove regardless of earliest removal time
+ * @return true if notification is removed, false otherwise
+ */
+ @Override
+ public boolean removeNotification(@NonNull String key, boolean releaseImmediately) {
+ mLogger.logRemoveNotification(key, releaseImmediately);
+ HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
+ if (headsUpEntry == null) {
+ return true;
}
+ if (releaseImmediately || canRemoveImmediately(key)) {
+ removeEntry(key);
+ } else {
+ headsUpEntry.removeAsSoonAsPossible();
+ return false;
+ }
+ return true;
+ }
+
+
+ /**
+ * Called when the notification state has been updated.
+ * @param key the key of the entry that was updated
+ * @param show whether the notification should show again and force reevaluation of
+ * removal time
+ */
+ public void updateNotification(@NonNull String key, boolean show) {
+ HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
+ mLogger.logUpdateNotification(key, show, headsUpEntry != null);
+ if (headsUpEntry == null) {
+ // the entry was released before this update (i.e by a listener) This can happen
+ // with the groupmanager
+ return;
+ }
+
+ headsUpEntry.mEntry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+
+ if (show) {
+ headsUpEntry.updateEntry(true /* updatePostTime */, "updateNotification");
+ if (headsUpEntry != null) {
+ setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUpEntry.mEntry));
+ }
+ }
+ }
+
+ /**
+ * Clears all managed notifications.
+ */
+ public void releaseAllImmediately() {
+ mLogger.logReleaseAllImmediately();
+ // A copy is necessary here as we are changing the underlying map. This would cause
+ // undefined behavior if we iterated over the key set directly.
+ ArraySet<String> keysToRemove = new ArraySet<>(mHeadsUpEntryMap.keySet());
+ for (String key : keysToRemove) {
+ removeEntry(key);
+ }
+ }
+
+ /**
+ * Returns the entry if it is managed by this manager.
+ * @param key key of notification
+ * @return the entry
+ */
+ @Nullable
+ public NotificationEntry getEntry(@NonNull String key) {
+ HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
+ return headsUpEntry != null ? headsUpEntry.mEntry : null;
+ }
+
+ /**
+ * Returns the stream of all current notifications managed by this manager.
+ * @return all entries
+ */
+ @NonNull
+ @Override
+ public Stream<NotificationEntry> getAllEntries() {
+ return mHeadsUpEntryMap.values().stream().map(headsUpEntry -> headsUpEntry.mEntry);
+ }
+
+ /**
+ * Whether or not there are any active notifications.
+ * @return true if there is an entry, false otherwise
+ */
+ @Override
+ public boolean hasNotifications() {
+ return !mHeadsUpEntryMap.isEmpty();
+ }
+
+ /**
+ * @return true if the notification is managed by this manager
+ */
+ public boolean isHeadsUpEntry(@NonNull String key) {
+ return mHeadsUpEntryMap.containsKey(key);
+ }
+
+ /**
+ * @param key
+ * @return When a HUN entry should be removed in milliseconds from now
+ */
+ @Override
+ public long getEarliestRemovalTime(String key) {
+ HeadsUpEntry entry = mHeadsUpEntryMap.get(key);
+ if (entry != null) {
+ return Math.max(0, entry.mEarliestRemovalTime - mSystemClock.elapsedRealtime());
+ }
+ return 0;
}
protected boolean shouldHeadsUpBecomePinned(@NonNull NotificationEntry entry) {
final HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.getKey());
if (headsUpEntry == null) {
// This should not happen since shouldHeadsUpBecomePinned is always called after adding
- // the NotificationEntry into AlertingNotificationManager's mAlertEntries map.
+ // the NotificationEntry into mHeadsUpEntryMap.
return hasFullScreenIntent(entry);
}
return hasFullScreenIntent(entry) && !headsUpEntry.mWasUnpinned;
@@ -190,24 +318,65 @@
return FLAG_CONTENT_VIEW_HEADS_UP;
}
- @Override
- protected void onAlertEntryAdded(AlertEntry alertEntry) {
- NotificationEntry entry = alertEntry.mEntry;
+ /**
+ * Add a new entry and begin managing it.
+ * @param entry the entry to add
+ */
+ protected final void addEntry(@NonNull NotificationEntry entry) {
+ HeadsUpEntry headsUpEntry = createHeadsUpEntry();
+ headsUpEntry.setEntry(entry);
+ mHeadsUpEntryMap.put(entry.getKey(), headsUpEntry);
+ onEntryAdded(headsUpEntry);
+ entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ entry.setIsHeadsUpEntry(true);
+ }
+
+ /**
+ * Manager-specific logic that should occur when an entry is added.
+ * @param headsUpEntry entry added
+ */
+ protected void onEntryAdded(HeadsUpEntry headsUpEntry) {
+ NotificationEntry entry = headsUpEntry.mEntry;
entry.setHeadsUp(true);
final boolean shouldPin = shouldHeadsUpBecomePinned(entry);
- setEntryPinned((HeadsUpEntry) alertEntry, shouldPin);
+ setEntryPinned(headsUpEntry, shouldPin);
EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 1 /* visible */);
for (OnHeadsUpChangedListener listener : mListeners) {
listener.onHeadsUpStateChanged(entry, true);
}
}
- @Override
- protected void onAlertEntryRemoved(AlertEntry alertEntry) {
- NotificationEntry entry = alertEntry.mEntry;
+ /**
+ * Remove a notification and reset the entry.
+ * @param key key of notification to remove
+ */
+ 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();
+ }
+
+ /**
+ * Manager-specific logic that should occur when an entry is removed.
+ * @param headsUpEntry entry removed
+ */
+ protected void onEntryRemoved(HeadsUpEntry headsUpEntry) {
+ NotificationEntry entry = headsUpEntry.mEntry;
entry.setHeadsUp(false);
- setEntryPinned((HeadsUpEntry) alertEntry, false /* isPinned */);
+ setEntryPinned(headsUpEntry, false /* isPinned */);
EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 0 /* visible */);
mLogger.logNotificationActuallyRemoved(entry);
for (OnHeadsUpChangedListener listener : mListeners) {
@@ -251,8 +420,8 @@
* Snoozes all current Heads Up Notifications.
*/
public void snooze() {
- for (String key : mAlertEntries.keySet()) {
- AlertEntry entry = getHeadsUpEntry(key);
+ for (String key : mHeadsUpEntryMap.keySet()) {
+ HeadsUpEntry entry = getHeadsUpEntry(key);
String packageName = entry.mEntry.getSbn().getPackageName();
String snoozeKey = snoozeKey(packageName, mUser);
mLogger.logPackageSnoozed(snoozeKey);
@@ -267,7 +436,7 @@
@Nullable
protected HeadsUpEntry getHeadsUpEntry(@NonNull String key) {
- return (HeadsUpEntry) mAlertEntries.get(key);
+ return (HeadsUpEntry) mHeadsUpEntryMap.get(key);
}
/**
@@ -281,11 +450,11 @@
@Nullable
protected HeadsUpEntry getTopHeadsUpEntry() {
- if (mAlertEntries.isEmpty()) {
+ if (mHeadsUpEntryMap.isEmpty()) {
return null;
}
HeadsUpEntry topEntry = null;
- for (AlertEntry entry: mAlertEntries.values()) {
+ for (HeadsUpEntry entry: mHeadsUpEntryMap.values()) {
if (topEntry == null || entry.compareTo(topEntry) < 0) {
topEntry = (HeadsUpEntry) entry;
}
@@ -316,7 +485,7 @@
pw.print(" mSnoozeLengthMs="); pw.println(mSnoozeLengthMs);
pw.print(" now="); pw.println(mSystemClock.elapsedRealtime());
pw.print(" mUser="); pw.println(mUser);
- for (AlertEntry entry: mAlertEntries.values()) {
+ for (HeadsUpEntry entry: mHeadsUpEntryMap.values()) {
pw.print(" HeadsUpEntry="); pw.println(entry.mEntry);
}
int n = mSnoozedPackages.size();
@@ -335,8 +504,8 @@
}
private boolean hasPinnedNotificationInternal() {
- for (String key : mAlertEntries.keySet()) {
- AlertEntry entry = getHeadsUpEntry(key);
+ for (String key : mHeadsUpEntryMap.keySet()) {
+ HeadsUpEntry entry = getHeadsUpEntry(key);
if (entry.mEntry.isRowPinned()) {
return true;
}
@@ -349,7 +518,7 @@
* @param userUnPinned The unpinned action is trigger by user real operation.
*/
public void unpinAll(boolean userUnPinned) {
- for (String key : mAlertEntries.keySet()) {
+ for (String key : mHeadsUpEntryMap.keySet()) {
HeadsUpEntry entry = getHeadsUpEntry(key);
setEntryPinned(entry, false /* isPinned */);
// maybe it got un sticky
@@ -384,8 +553,8 @@
if (a == null || b == null) {
return Boolean.compare(a == null, b == null);
}
- AlertEntry aEntry = getHeadsUpEntry(a.getKey());
- AlertEntry bEntry = getHeadsUpEntry(b.getKey());
+ HeadsUpEntry aEntry = getHeadsUpEntry(a.getKey());
+ HeadsUpEntry bEntry = getHeadsUpEntry(b.getKey());
if (aEntry == null || bEntry == null) {
return Boolean.compare(aEntry == null, bEntry == null);
}
@@ -419,18 +588,37 @@
}
}
+ /**
+ * Whether or not the entry can be removed currently. If it hasn't been on screen long enough
+ * it should not be removed unless forced
+ * @param key the key to check if removable
+ * @return true if the entry can be removed
+ */
@Override
public boolean canRemoveImmediately(@NonNull String key) {
HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
if (headsUpEntry != null && headsUpEntry.mUserActionMayIndirectlyRemove) {
return true;
}
- return super.canRemoveImmediately(key);
+ return headsUpEntry == null || headsUpEntry.wasShownLongEnough()
+ || headsUpEntry.mEntry.isRowDismissed();
+ }
+
+ /**
+ * @param key
+ * @return true if the entry is (pinned and expanded) or (has an active remote input)
+ */
+ @Override
+ public boolean isSticky(String key) {
+ HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
+ if (headsUpEntry != null) {
+ return headsUpEntry.isSticky();
+ }
+ return false;
}
@NonNull
- @Override
- protected HeadsUpEntry createAlertEntry() {
+ protected HeadsUpEntry createHeadsUpEntry() {
return new HeadsUpEntry();
}
@@ -451,28 +639,82 @@
* This represents a notification and how long it is in a heads up mode. It also manages its
* lifecycle automatically when created.
*/
- protected class HeadsUpEntry extends AlertEntry {
+ protected class HeadsUpEntry implements Comparable<HeadsUpEntry> {
public boolean mRemoteInputActive;
public boolean mUserActionMayIndirectlyRemove;
protected boolean mExpanded;
protected boolean mWasUnpinned;
- @Override
+ @Nullable public NotificationEntry mEntry;
+ public long mPostTime;
+ public long mEarliestRemovalTime;
+
+ @Nullable protected Runnable mRemoveRunnable;
+
+ @Nullable private Runnable mCancelRemoveRunnable;
+
+ public void setEntry(@NonNull final NotificationEntry entry) {
+ setEntry(entry, () -> removeEntry(entry.getKey()));
+ }
+
+ public void setEntry(@NonNull final NotificationEntry entry,
+ @Nullable Runnable removeRunnable) {
+ mEntry = entry;
+ mRemoveRunnable = removeRunnable;
+
+ mPostTime = calculatePostTime();
+ updateEntry(true /* updatePostTime */, "setEntry");
+ }
+
+ /**
+ * 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);
+
+ final long now = mSystemClock.elapsedRealtime();
+ mEarliestRemovalTime = now + mMinimumDisplayTime;
+
+ if (updatePostTime) {
+ mPostTime = Math.max(mPostTime, now);
+ }
+
+ if (isSticky()) {
+ removeAutoRemovalCallbacks("updateEntry (sticky)");
+ return;
+ }
+
+ final long finishTime = calculateFinishTime();
+ final long timeLeft = Math.max(finishTime - now, mMinimumDisplayTime);
+ scheduleAutoRemovalCallback(timeLeft, "updateEntry (not sticky)");
+ }
+
+ /**
+ * Whether or not the notification is "sticky" i.e. should stay on screen regardless
+ * of the timer (forever) and should be removed externally.
+ * @return true if the notification is sticky
+ */
public boolean isSticky() {
return (mEntry.isRowPinned() && mExpanded)
|| mRemoteInputActive
|| hasFullScreenIntent(mEntry);
}
- @Override
public boolean isStickyForSomeTime() {
return mEntry.isStickyAndNotDemoted();
}
- @Override
- public int compareTo(@NonNull AlertEntry alertEntry) {
- HeadsUpEntry headsUpEntry = (HeadsUpEntry) alertEntry;
+ /**
+ * Whether the notification has been on screen long enough and can be removed.
+ * @return true if the notification has been on screen long enough
+ */
+ public boolean wasShownLongEnough() {
+ return mEarliestRemovalTime < mSystemClock.elapsedRealtime();
+ }
+
+ public int compareTo(@NonNull HeadsUpEntry headsUpEntry) {
boolean isPinned = mEntry.isRowPinned();
boolean otherPinned = headsUpEntry.mEntry.isRowPinned();
if (isPinned && !otherPinned) {
@@ -503,31 +745,90 @@
return 1;
}
- return super.compareTo(headsUpEntry);
+ if (mPostTime > headsUpEntry.mPostTime) {
+ return -1;
+ } else if (mPostTime == headsUpEntry.mPostTime) {
+ return mEntry.getKey().compareTo(headsUpEntry.mEntry.getKey());
+ } else {
+ return 1;
+ }
}
public void setExpanded(boolean expanded) {
this.mExpanded = expanded;
}
- @Override
public void reset() {
- super.reset();
+ removeAutoRemovalCallbacks("reset()");
+ mEntry = null;
+ mRemoveRunnable = null;
mExpanded = false;
mRemoteInputActive = false;
}
- @Override
+ /**
+ * Clear any pending removal runnables.
+ */
+ public void removeAutoRemovalCallbacks(@Nullable String reason) {
+ final boolean removed = removeAutoRemovalCallbackInternal();
+
+ if (removed) {
+ mLogger.logAutoRemoveCanceled(mEntry, reason);
+ }
+ }
+
+ public void scheduleAutoRemovalCallback(long delayMillis, @NonNull String reason) {
+ if (mRemoveRunnable == null) {
+ Log.wtf(TAG, "scheduleAutoRemovalCallback with no callback set");
+ return;
+ }
+
+ final boolean removed = removeAutoRemovalCallbackInternal();
+
+ if (removed) {
+ mLogger.logAutoRemoveRescheduled(mEntry, delayMillis, reason);
+ } else {
+ mLogger.logAutoRemoveScheduled(mEntry, delayMillis, reason);
+ }
+
+ mCancelRemoveRunnable = mExecutor.executeDelayed(mRemoveRunnable,
+ delayMillis);
+ }
+
+ public boolean removeAutoRemovalCallbackInternal() {
+ final boolean scheduled = (mCancelRemoveRunnable != null);
+
+ if (scheduled) {
+ mCancelRemoveRunnable.run();
+ mCancelRemoveRunnable = null;
+ }
+
+ return scheduled;
+ }
+
+ /**
+ * Remove the entry at the earliest allowed removal time.
+ */
+ public void removeAsSoonAsPossible() {
+ if (mRemoveRunnable != null) {
+ final long timeLeft = mEarliestRemovalTime - mSystemClock.elapsedRealtime();
+ scheduleAutoRemovalCallback(timeLeft, "removeAsSoonAsPossible");
+ }
+ }
+
+ /**
+ * Calculate what the post time of a notification is at some current time.
+ * @return the post time
+ */
protected long calculatePostTime() {
// The actual post time will be just after the heads-up really slided in
- return super.calculatePostTime() + mTouchAcceptanceDelay;
+ return mSystemClock.elapsedRealtime() + mTouchAcceptanceDelay;
}
/**
* @return When the notification should auto-dismiss itself, based on
* {@link SystemClock#elapsedRealtime()}
*/
- @Override
protected long calculateFinishTime() {
final long duration = getRecommendedHeadsUpTimeoutMs(
isStickyForSomeTime() ? mStickyForSomeTimeAutoDismissTime : mAutoDismissTime);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
index d9527fe..b8c7e20 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
@@ -71,7 +71,7 @@
fun hasPinnedHeadsUp(): Boolean
/** Returns whether or not the given notification is alerting and managed by this manager. */
- fun isAlerting(key: String): Boolean
+ fun isHeadsUpEntry(key: String): Boolean
fun isHeadsUpGoingAway(): Boolean
@@ -213,7 +213,7 @@
override fun getTouchableRegion(): Region? = null
override fun getTopEntry() = null
override fun hasPinnedHeadsUp() = false
- override fun isAlerting(key: String) = false
+ override fun isHeadsUpEntry(key: String) = false
override fun isHeadsUpGoingAway() = false
override fun isSnoozed(packageName: String) = false
override fun isSticky(key: String?) = false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
index 3be14bc..10bf0680 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
@@ -48,6 +48,8 @@
boolean isNetworkLoggingEnabled();
boolean isVpnEnabled();
boolean isVpnRestricted();
+ /** Whether the VPN network is validated. */
+ boolean isVpnValidated();
/** Whether the VPN app should use branded VPN iconography. */
boolean isVpnBranded();
String getPrimaryVpnName();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 5d69f36..9f4a906 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -15,6 +15,9 @@
*/
package com.android.systemui.statusbar.policy;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+
import android.annotation.Nullable;
import android.app.admin.DeviceAdminInfo;
import android.app.admin.DevicePolicyManager;
@@ -32,7 +35,9 @@
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
+import android.net.LinkProperties;
import android.net.Network;
+import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.VpnManager;
import android.os.Handler;
@@ -76,7 +81,10 @@
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final NetworkRequest REQUEST =
- new NetworkRequest.Builder().clearCapabilities().build();
+ new NetworkRequest.Builder()
+ .clearCapabilities()
+ .addTransportType(TRANSPORT_VPN)
+ .build();
private static final int NO_NETWORK = -1;
private static final String VPN_BRANDED_META_DATA = "com.android.systemui.IS_BRANDED";
@@ -99,6 +107,8 @@
private SparseArray<VpnConfig> mCurrentVpns = new SparseArray<>();
private int mCurrentUserId;
private int mVpnUserId;
+ @GuardedBy("mNetworkProperties")
+ private final SparseArray<NetworkProperties> mNetworkProperties = new SparseArray<>();
// Key: userId, Value: whether the user has CACerts installed
// Needs to be cached here since the query has to be asynchronous
@@ -162,6 +172,21 @@
pw.print(mCurrentVpns.valueAt(i).user);
}
pw.println("}");
+ pw.print(" mNetworkProperties={");
+ synchronized (mNetworkProperties) {
+ for (int i = 0; i < mNetworkProperties.size(); ++i) {
+ if (i > 0) {
+ pw.print(", ");
+ }
+ pw.print(mNetworkProperties.keyAt(i));
+ pw.print("={");
+ pw.print(mNetworkProperties.valueAt(i).interfaceName);
+ pw.print(", ");
+ pw.print(mNetworkProperties.valueAt(i).validated);
+ pw.print("}");
+ }
+ }
+ pw.println("}");
}
@Override
@@ -304,6 +329,26 @@
}
@Override
+ public boolean isVpnValidated() {
+ // Prioritize reporting the network status of the parent user.
+ final VpnConfig primaryVpnConfig = mCurrentVpns.get(mVpnUserId);
+ if (primaryVpnConfig != null) {
+ return getVpnValidationStatus(primaryVpnConfig);
+ }
+ // Identify any Unvalidated status in each active VPN network within other profiles.
+ for (int profileId : mUserManager.getEnabledProfileIds(mVpnUserId)) {
+ final VpnConfig vpnConfig = mCurrentVpns.get(profileId);
+ if (vpnConfig == null) {
+ continue;
+ }
+ if (!getVpnValidationStatus(vpnConfig)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
public boolean hasCACertInCurrentUser() {
Boolean hasCACerts = mHasCACerts.get(mCurrentUserId);
return hasCACerts != null && hasCACerts.booleanValue();
@@ -493,11 +538,74 @@
@Override
public void onLost(Network network) {
if (DEBUG) Log.d(TAG, "onLost " + network.getNetId());
+ synchronized (mNetworkProperties) {
+ mNetworkProperties.delete(network.getNetId());
+ }
updateState();
fireCallbacks();
};
+
+
+ @Override
+ public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
+ if (DEBUG) Log.d(TAG, "onCapabilitiesChanged " + network.getNetId());
+ final NetworkProperties properties;
+ synchronized (mNetworkProperties) {
+ properties = mNetworkProperties.get(network.getNetId());
+ }
+ // When a new network appears, the system first notifies the application about
+ // its capabilities through onCapabilitiesChanged. This initial notification
+ // will be skipped because the interface information is included in the
+ // subsequent onLinkPropertiesChanged call. After validating the network, the
+ // system might send another onCapabilitiesChanged notification if the network
+ // becomes validated.
+ if (properties == null) {
+ return;
+ }
+ final boolean validated = nc.hasCapability(NET_CAPABILITY_VALIDATED);
+ if (properties.validated != validated) {
+ properties.validated = validated;
+ fireCallbacks();
+ }
+ }
+
+ @Override
+ public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) {
+ if (DEBUG) Log.d(TAG, "onLinkPropertiesChanged " + network.getNetId());
+ final String interfaceName = linkProperties.getInterfaceName();
+ if (interfaceName == null) {
+ Log.w(TAG, "onLinkPropertiesChanged event with null interface");
+ return;
+ }
+ synchronized (mNetworkProperties) {
+ final NetworkProperties properties = mNetworkProperties.get(network.getNetId());
+ if (properties == null) {
+ mNetworkProperties.put(
+ network.getNetId(),
+ new NetworkProperties(interfaceName, false));
+ } else {
+ properties.interfaceName = interfaceName;
+ }
+ }
+ }
};
+ /**
+ * Retrieve the validation status of the VPN network associated with the given VpnConfig.
+ */
+ private boolean getVpnValidationStatus(@NonNull VpnConfig vpnConfig) {
+ synchronized (mNetworkProperties) {
+ // Find the network has the same interface as the VpnConfig
+ for (int i = 0; i < mNetworkProperties.size(); ++i) {
+ if (mNetworkProperties.valueAt(i).interfaceName.equals(vpnConfig.interfaze)) {
+ return mNetworkProperties.valueAt(i).validated;
+ }
+ }
+ }
+ // If no matching network is found, consider it validated.
+ return true;
+ }
+
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override public void onReceive(Context context, Intent intent) {
if (KeyChain.ACTION_TRUST_STORE_CHANGED.equals(intent.getAction())) {
@@ -508,4 +616,17 @@
}
}
};
+
+ /**
+ * A data class to hold specific Network properties received through the NetworkCallback.
+ */
+ private static class NetworkProperties {
+ public String interfaceName;
+ public boolean validated;
+
+ NetworkProperties(@NonNull String interfaceName, boolean validated) {
+ this.interfaceName = interfaceName;
+ this.validated = validated;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/UserSetupRepository.kt
similarity index 83%
rename from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/UserSetupRepository.kt
index 91886bb..2a0812b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/UserSetupRepository.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.mobile.data.repository
+package com.android.systemui.statusbar.policy.data.repository
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
@@ -34,15 +34,14 @@
import kotlinx.coroutines.withContext
/**
- * Repository to observe the state of [DeviceProvisionedController.isUserSetup]. This information
- * can change some policy related to display
+ * Repository to observe whether the user has completed the setup steps. This information can change
+ * some policy related to display.
*/
interface UserSetupRepository {
- /** Observable tracking [DeviceProvisionedController.isUserSetup] */
- val isUserSetupFlow: StateFlow<Boolean>
+ /** Whether the user has completed the setup steps. */
+ val isUserSetUp: StateFlow<Boolean>
}
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class UserSetupRepositoryImpl
@@ -52,8 +51,7 @@
@Background private val bgDispatcher: CoroutineDispatcher,
@Application scope: CoroutineScope,
) : UserSetupRepository {
- /** State flow that tracks [DeviceProvisionedController.isUserSetup] */
- override val isUserSetupFlow: StateFlow<Boolean> =
+ override val isUserSetUp: StateFlow<Boolean> =
conflatedCallbackFlow {
val callback =
object : DeviceProvisionedController.DeviceProvisionedListener {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/UserSetupInteractor.kt
similarity index 60%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
copy to packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/UserSetupInteractor.kt
index d98f496..ca36e39 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/UserSetupInteractor.kt
@@ -14,11 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+package com.android.systemui.statusbar.policy.domain.interactor
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.collection.NotifCollection
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
-var Kosmos.notifCollection by Fixture { mock<NotifCollection>() }
+class UserSetupInteractor @Inject constructor(repository: UserSetupRepository) {
+ /** Whether the user has completed the setup steps. */
+ val isUserSetUp: Flow<Boolean> = repository.isUserSetUp
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/OtherPrefs.java b/packages/SystemUI/src/com/android/systemui/tuner/OtherPrefs.java
deleted file mode 100644
index 8d85999..0000000
--- a/packages/SystemUI/src/com/android/systemui/tuner/OtherPrefs.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.tuner;
-
-import android.os.Bundle;
-
-import androidx.preference.PreferenceFragment;
-
-import com.android.systemui.res.R;
-import com.android.tools.r8.keepanno.annotations.KeepTarget;
-import com.android.tools.r8.keepanno.annotations.UsesReflection;
-
-public class OtherPrefs extends PreferenceFragment {
- // aapt doesn't generate keep rules for android:fragment references in <Preference> tags, so
- // explicitly declare references per usage in `R.xml.other_settings`. See b/120445169.
- @UsesReflection(@KeepTarget(classConstant = PowerNotificationControlsFragment.class))
- @Override
- public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
- addPreferencesFromResource(R.xml.other_settings);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/PowerNotificationControlsFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/PowerNotificationControlsFragment.java
deleted file mode 100644
index ce1a2e9..0000000
--- a/packages/SystemUI/src/com/android/systemui/tuner/PowerNotificationControlsFragment.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.tuner;
-
-import android.annotation.Nullable;
-import android.app.Fragment;
-import android.os.Bundle;
-import android.provider.Settings;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Switch;
-import android.widget.TextView;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.res.R;
-
-public class PowerNotificationControlsFragment extends Fragment {
-
- private static final String KEY_SHOW_PNC = "show_importance_slider";
-
- @Override
- public void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- return inflater.inflate(R.layout.power_notification_controls_settings, container, false);
- }
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- final View switchBar = view.findViewById(R.id.switch_bar);
- final Switch switchWidget = (Switch) switchBar.findViewById(android.R.id.switch_widget);
- final TextView switchText = (TextView) switchBar.findViewById(R.id.switch_text);
- switchWidget.setChecked(isEnabled());
- switchText.setText(isEnabled()
- ? getString(R.string.switch_bar_on)
- : getString(R.string.switch_bar_off));
-
- switchWidget.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- boolean newState = !isEnabled();
- MetricsLogger.action(getContext(),
- MetricsEvent.ACTION_TUNER_POWER_NOTIFICATION_CONTROLS, newState);
- Settings.Secure.putInt(getContext().getContentResolver(),
- KEY_SHOW_PNC, newState ? 1 : 0);
- switchWidget.setChecked(newState);
- switchText.setText(newState
- ? getString(R.string.switch_bar_on)
- : getString(R.string.switch_bar_off));
- }
- });
- }
-
- @Override
- public void onResume() {
- super.onResume();
- MetricsLogger.visibility(
- getContext(), MetricsEvent.TUNER_POWER_NOTIFICATION_CONTROLS, true);
- }
-
- @Override
- public void onPause() {
- super.onPause();
- MetricsLogger.visibility(
- getContext(), MetricsEvent.TUNER_POWER_NOTIFICATION_CONTROLS, false);
- }
-
- private boolean isEnabled() {
- int setting = Settings.Secure.getInt(getContext().getContentResolver(), KEY_SHOW_PNC, 0);
- return setting == 1;
- }
-
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
index 3d7d701..da6bfe8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
@@ -48,10 +48,10 @@
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
-import com.android.systemui.scene.SceneTestUtils;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -76,7 +76,7 @@
protected MockitoSession mStaticMockSession;
- protected final SceneTestUtils mSceneTestUtils = new SceneTestUtils(this);
+ protected final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
protected @Mock DeviceEntryInteractor mDeviceEntryInteractor;
protected @Mock LockIconView mLockIconView;
protected @Mock AnimatedStateListDrawable mIconDrawable;
@@ -175,7 +175,7 @@
mPrimaryBouncerInteractor,
mContext,
() -> mDeviceEntryInteractor,
- mSceneTestUtils.getSceneContainerFlags()
+ mKosmos.getFakeSceneContainerFlags()
);
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
index 93a5393..b0887ef 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
@@ -373,7 +373,7 @@
@Test
public void longPress_showBouncer_sceneContainerNotEnabled() {
init(/* useMigrationFlag= */ false);
- mSceneTestUtils.getSceneContainerFlags().setEnabled(false);
+ mKosmos.getFakeSceneContainerFlags().setEnabled(false);
when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(false);
// WHEN longPress
@@ -387,7 +387,7 @@
@Test
public void longPress_showBouncer() {
init(/* useMigrationFlag= */ false);
- mSceneTestUtils.getSceneContainerFlags().setEnabled(true);
+ mKosmos.getFakeSceneContainerFlags().setEnabled(true);
when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(false);
// WHEN longPress
@@ -401,7 +401,7 @@
@Test
public void longPress_falsingTriggered_doesNotShowBouncer() {
init(/* useMigrationFlag= */ false);
- mSceneTestUtils.getSceneContainerFlags().setEnabled(true);
+ mKosmos.getFakeSceneContainerFlags().setEnabled(true);
when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(true);
// WHEN longPress
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index 2afb3a1..d86d123 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -640,11 +640,10 @@
@Test
public void deleteWindowMagnification_enabling_expectedValuesAndInvokeCallback()
throws RemoteException {
-
enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
- Mockito.reset(mSpyController);
+ resetMockObjects();
getInstrumentation().runOnMainSync(() -> {
mWindowMagnificationAnimationController.deleteWindowMagnification(
mAnimationCallback2);
@@ -658,6 +657,11 @@
mValueAnimator.end();
});
+ // wait for animation returns
+ waitForIdleSync();
+
+ // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)} will only
+ // be triggered once in {@link ValueAnimator#end()}
verify(mSpyController).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
@@ -717,7 +721,11 @@
deleteWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
- deleteWindowMagnificationAndWaitAnimating(0, null);
+ // Verifying that WindowMagnificationController#deleteWindowMagnification is never called
+ // in previous steps
+ verify(mSpyController, never()).deleteWindowMagnification();
+
+ deleteWindowMagnificationWithoutAnimation();
verify(mSpyController).deleteWindowMagnification();
verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN);
@@ -810,6 +818,8 @@
mWindowMagnificationAnimationController.enableWindowMagnification(
targetScale, targetCenterX, targetCenterY, null);
});
+ // wait for animation returns
+ waitForIdleSync();
}
private void enableWindowMagnificationAndWaitAnimating(long duration,
@@ -829,12 +839,16 @@
targetScale, targetCenterX, targetCenterY, callback);
advanceTimeBy(duration);
});
+ // wait for animation returns
+ waitForIdleSync();
}
private void deleteWindowMagnificationWithoutAnimation() {
getInstrumentation().runOnMainSync(() -> {
mWindowMagnificationAnimationController.deleteWindowMagnification(null);
});
+ // wait for animation returns
+ waitForIdleSync();
}
private void deleteWindowMagnificationAndWaitAnimating(long duration,
@@ -843,6 +857,8 @@
mWindowMagnificationAnimationController.deleteWindowMagnification(callback);
advanceTimeBy(duration);
});
+ // wait for animation returns
+ waitForIdleSync();
}
private void verifyStartValue(ArgumentCaptor<Float> captor, float startValue) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuNotificationFactoryTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuNotificationFactoryTest.java
new file mode 100644
index 0000000..9dd337e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuNotificationFactoryTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.floatingmenu;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Notification;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class MenuNotificationFactoryTest extends SysuiTestCase {
+ private MenuNotificationFactory mMenuNotificationFactory;
+
+ @Before
+ public void setUp() {
+ mMenuNotificationFactory = new MenuNotificationFactory(mContext);
+ }
+
+ @Test
+ public void createHiddenNotification_hasUndoAndDeleteAction() {
+ Notification notification = mMenuNotificationFactory.createHiddenNotification();
+
+ assertThat(notification.contentIntent.getIntent().getAction()).isEqualTo(
+ MenuNotificationFactory.ACTION_UNDO);
+ assertThat(notification.deleteIntent.getIntent().getAction()).isEqualTo(
+ MenuNotificationFactory.ACTION_DELETE);
+ }
+}
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 be6f3ff..5e5273b 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
@@ -21,15 +21,30 @@
import static android.view.WindowInsets.Type.displayCutout;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.systemBars;
+
+import static com.android.systemui.accessibility.floatingmenu.MenuNotificationFactory.ACTION_DELETE;
+import static com.android.systemui.accessibility.floatingmenu.MenuNotificationFactory.ACTION_UNDO;
import static com.android.systemui.accessibility.floatingmenu.MenuViewLayer.LayerIndex;
+
import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.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.accessibilityservice.AccessibilityServiceInfo;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
@@ -53,16 +68,21 @@
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.test.filters.SmallTest;
+import com.android.internal.messages.nano.SystemMessageProto;
import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.SysuiTestableContext;
import com.android.systemui.util.settings.SecureSettings;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
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.Mock;
+import org.mockito.Spy;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -98,6 +118,8 @@
@Rule
public MockitoRule mockito = MockitoJUnit.rule();
+ @Spy
+ private SysuiTestableContext mSpyContext = getContext();
@Mock
private IAccessibilityFloatingMenu mFloatingMenu;
@@ -110,8 +132,12 @@
@Mock
private AccessibilityManager mStubAccessibilityManager;
+ private final NotificationManager mMockNotificationManager = mock(NotificationManager.class);
+
@Before
public void setUp() throws Exception {
+ mSpyContext.addMockSystemService(Context.NOTIFICATION_SERVICE, mMockNotificationManager);
+
final Rect mDisplayBounds = new Rect();
mDisplayBounds.set(/* left= */ 0, /* top= */ 0, DISPLAY_WINDOW_WIDTH,
DISPLAY_WINDOW_HEIGHT);
@@ -119,31 +145,31 @@
new WindowMetrics(mDisplayBounds, fakeDisplayInsets(), /* density = */ 0.0f));
doReturn(mWindowMetrics).when(mStubWindowManager).getCurrentWindowMetrics();
- mMenuViewLayer = new MenuViewLayer(mContext, mStubWindowManager, mStubAccessibilityManager,
- mFloatingMenu, mSecureSettings);
+ mMenuViewLayer = new MenuViewLayer(mSpyContext, mStubWindowManager,
+ mStubAccessibilityManager, mFloatingMenu, mSecureSettings);
mMenuView = (MenuView) mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW);
mMenuAnimationController = mMenuView.getMenuAnimationController();
mLastAccessibilityButtonTargets =
- Settings.Secure.getStringForUser(mContext.getContentResolver(),
+ Settings.Secure.getStringForUser(mSpyContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT);
mLastEnabledAccessibilityServices =
- Settings.Secure.getStringForUser(mContext.getContentResolver(),
+ Settings.Secure.getStringForUser(mSpyContext.getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, UserHandle.USER_CURRENT);
mMenuViewLayer.onAttachedToWindow();
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.putStringForUser(mSpyContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, "", UserHandle.USER_CURRENT);
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.putStringForUser(mSpyContext.getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, "", UserHandle.USER_CURRENT);
}
@After
public void tearDown() throws Exception {
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.putStringForUser(mSpyContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, mLastAccessibilityButtonTargets,
UserHandle.USER_CURRENT);
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.putStringForUser(mSpyContext.getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, mLastEnabledAccessibilityServices,
UserHandle.USER_CURRENT);
@@ -188,7 +214,7 @@
setupEnabledAccessibilityServiceList();
mMenuViewLayer.mDismissMenuAction.run();
- final String value = Settings.Secure.getString(mContext.getContentResolver(),
+ final String value = Settings.Secure.getString(mSpyContext.getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
assertThat(value).isEqualTo("");
@@ -203,7 +229,7 @@
AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY)).thenReturn(stubShortcutTargets);
mMenuViewLayer.mDismissMenuAction.run();
- final String value = Settings.Secure.getString(mContext.getContentResolver(),
+ final String value = Settings.Secure.getString(mSpyContext.getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
assertThat(value).isEqualTo(TEST_SELECT_TO_SPEAK_COMPONENT_NAME.flattenToString());
@@ -278,9 +304,60 @@
assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x);
assertThat(mMenuView.getTranslationY()).isEqualTo(beforePosition.y);
}
+ @Test
+ @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE)
+ public void onReleasedInTarget_hideMenuAndShowNotificationWithExpectedActions() {
+ dragMenuThenReleasedInTarget();
+
+ verify(mMockNotificationManager).notify(
+ eq(SystemMessageProto.SystemMessage.NOTE_A11Y_FLOATING_MENU_HIDDEN),
+ any(Notification.class));
+ ArgumentCaptor<IntentFilter> intentFilterCaptor = ArgumentCaptor.forClass(
+ IntentFilter.class);
+ verify(mSpyContext).registerReceiver(
+ any(BroadcastReceiver.class),
+ intentFilterCaptor.capture(),
+ anyInt());
+ assertThat(intentFilterCaptor.getValue().matchAction(ACTION_UNDO)).isTrue();
+ assertThat(intentFilterCaptor.getValue().matchAction(ACTION_DELETE)).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE)
+ public void receiveActionUndo_dismissNotificationAndMenuVisible() {
+ ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = ArgumentCaptor.forClass(
+ BroadcastReceiver.class);
+ dragMenuThenReleasedInTarget();
+
+ verify(mSpyContext).registerReceiver(broadcastReceiverCaptor.capture(),
+ any(IntentFilter.class), anyInt());
+ broadcastReceiverCaptor.getValue().onReceive(mSpyContext, new Intent(ACTION_UNDO));
+
+ verify(mSpyContext).unregisterReceiver(broadcastReceiverCaptor.getValue());
+ verify(mMockNotificationManager).cancel(
+ SystemMessageProto.SystemMessage.NOTE_A11Y_FLOATING_MENU_HIDDEN);
+ assertThat(mMenuView.getVisibility()).isEqualTo(VISIBLE);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE)
+ public void receiveActionDelete_dismissNotificationAndHideMenu() {
+ ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = ArgumentCaptor.forClass(
+ BroadcastReceiver.class);
+ dragMenuThenReleasedInTarget();
+
+ verify(mSpyContext).registerReceiver(broadcastReceiverCaptor.capture(),
+ any(IntentFilter.class), anyInt());
+ broadcastReceiverCaptor.getValue().onReceive(mSpyContext, new Intent(ACTION_DELETE));
+
+ verify(mSpyContext).unregisterReceiver(broadcastReceiverCaptor.getValue());
+ verify(mMockNotificationManager).cancel(
+ SystemMessageProto.SystemMessage.NOTE_A11Y_FLOATING_MENU_HIDDEN);
+ verify(mFloatingMenu).hide();
+ }
private void setupEnabledAccessibilityServiceList() {
- Settings.Secure.putString(mContext.getContentResolver(),
+ Settings.Secure.putString(mSpyContext.getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
TEST_SELECT_TO_SPEAK_COMPONENT_NAME.flattenToString());
@@ -344,6 +421,12 @@
springAnimation.skipToEnd();
springAnimation.doAnimationFrame(500);
});
+ }
+ private void dragMenuThenReleasedInTarget() {
+ MagnetizedObject.MagnetListener magnetListener =
+ mMenuViewLayer.getDragToInteractAnimationController().getMagnetListener();
+ magnetListener.onReleasedInTarget(
+ new MagnetizedObject.MagneticTarget(mock(View.class), 200));
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
index 7c626a1..e0c6bba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
@@ -44,6 +44,8 @@
import com.android.systemui.shade.ShadeViewController
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.util.concurrency.FakeExecutor
@@ -56,6 +58,7 @@
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import org.junit.Before
@@ -89,6 +92,9 @@
@Mock private lateinit var onBackInvokedDispatcher: WindowOnBackInvokedDispatcher
@Mock private lateinit var iStatusBarService: IStatusBarService
@Mock private lateinit var headsUpManager: HeadsUpManager
+ private val activeNotificationsRepository = ActiveNotificationListRepository()
+ private val activeNotificationsInteractor =
+ ActiveNotificationsInteractor(activeNotificationsRepository, StandardTestDispatcher())
private val keyguardRepository = FakeKeyguardRepository()
private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor by lazy {
@@ -98,6 +104,7 @@
keyguardRepository,
headsUpManager,
powerInteractor,
+ activeNotificationsInteractor,
)
}
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 0ee0939..43f7c60 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.biometrics
import android.app.admin.DevicePolicyManager
+import android.content.pm.PackageManager
import android.hardware.biometrics.BiometricAuthenticator
import android.hardware.biometrics.BiometricConstants
import android.hardware.biometrics.BiometricManager
@@ -79,6 +80,8 @@
import org.mockito.junit.MockitoJUnit
import org.mockito.Mockito.`when` as whenever
+private const val OP_PACKAGE_NAME = "biometric.testapp"
+
@RunWith(AndroidJUnit4::class)
@RunWithLooper(setAsMainLooper = true)
@SmallTest
@@ -109,6 +112,8 @@
lateinit var authController: AuthController
@Mock
lateinit var selectedUserInteractor: SelectedUserInteractor
+ @Mock
+ private lateinit var packageManager: PackageManager
private val testScope = TestScope(StandardTestDispatcher())
private val fakeExecutor = FakeExecutor(FakeSystemClock())
@@ -134,6 +139,7 @@
private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor)
+ private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android)
private var authContainer: TestAuthContainerView? = null
@@ -156,6 +162,9 @@
selectedUserInteractor,
testScope.backgroundScope,
)
+ // Set up default logo icon
+ whenever(packageManager.getApplicationIcon(OP_PACKAGE_NAME)).thenReturn(defaultLogoIcon)
+ context.setMockPackageManager(packageManager)
}
@After
@@ -533,6 +542,7 @@
mPromptInfo = PromptInfo().apply {
this.authenticators = authenticators
}
+ mOpPackageName = OP_PACKAGE_NAME
},
testScope.backgroundScope,
fingerprintProps,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
index 647dae6..13306be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
@@ -20,6 +20,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -44,6 +45,7 @@
@Mock lateinit var shadeInteractor: ShadeInteractor
@Mock lateinit var systemUIDialogManager: SystemUIDialogManager
@Mock lateinit var dumpManager: DumpManager
+ @Mock lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
private lateinit var udfpsBpViewController: UdfpsBpViewController
@@ -55,7 +57,8 @@
statusBarStateController,
shadeInteractor,
systemUIDialogManager,
- dumpManager
+ dumpManager,
+ udfpsOverlayInteractor,
)
}
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 ec7ce63..b39e09d 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
@@ -43,6 +43,7 @@
private const val USER_ID = 9
private const val CHALLENGE = 90L
+private const val OP_PACKAGE_NAME = "biometric.testapp"
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -102,7 +103,8 @@
PromptInfo().apply { isConfirmationRequested = case },
USER_ID,
CHALLENGE,
- PromptKind.Biometric()
+ PromptKind.Biometric(),
+ OP_PACKAGE_NAME
)
assertThat(isConfirmationRequired).isEqualTo(case)
@@ -120,7 +122,8 @@
PromptInfo().apply { isConfirmationRequested = case },
USER_ID,
CHALLENGE,
- PromptKind.Biometric()
+ PromptKind.Biometric(),
+ OP_PACKAGE_NAME
)
assertThat(isConfirmationRequired).isTrue()
@@ -133,17 +136,19 @@
val kind = PromptKind.Pin
val promptInfo = PromptInfo()
- repository.setPrompt(promptInfo, USER_ID, CHALLENGE, kind)
+ repository.setPrompt(promptInfo, USER_ID, CHALLENGE, kind, OP_PACKAGE_NAME)
assertThat(repository.kind.value).isEqualTo(kind)
assertThat(repository.userId.value).isEqualTo(USER_ID)
assertThat(repository.challenge.value).isEqualTo(CHALLENGE)
assertThat(repository.promptInfo.value).isSameInstanceAs(promptInfo)
+ assertThat(repository.opPackageName.value).isEqualTo(OP_PACKAGE_NAME)
repository.unsetPrompt()
assertThat(repository.promptInfo.value).isNull()
assertThat(repository.userId.value).isNull()
assertThat(repository.challenge.value).isNull()
+ assertThat(repository.opPackageName.value).isNull()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
index 8f8004f..b1e471a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
@@ -5,6 +5,7 @@
import android.hardware.biometrics.IBiometricContextListener.FoldState
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.AuthController
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
@@ -17,6 +18,7 @@
import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING
import com.android.systemui.unfold.updates.FOLD_UPDATE_START_OPENING
import com.android.systemui.unfold.updates.FoldStateProvider
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -42,7 +44,10 @@
private val testScope = TestScope()
@Mock private lateinit var foldProvider: FoldStateProvider
+ @Mock private lateinit var authController: AuthController
+ @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
+ private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
private lateinit var interactor: LogContextInteractorImpl
@@ -50,6 +55,13 @@
@Before
fun setup() {
keyguardTransitionRepository = FakeKeyguardTransitionRepository()
+ udfpsOverlayInteractor =
+ UdfpsOverlayInteractor(
+ context,
+ authController,
+ selectedUserInteractor,
+ testScope.backgroundScope,
+ )
interactor =
LogContextInteractorImpl(
testScope.backgroundScope,
@@ -59,6 +71,7 @@
scope = testScope.backgroundScope,
)
.keyguardTransitionInteractor,
+ udfpsOverlayInteractor,
)
}
@@ -162,6 +175,18 @@
}
@Test
+ fun isHardwareIgnoringTouchesChanges() =
+ testScope.runTest {
+ val isHardwareIgnoringTouches by collectLastValue(interactor.isHardwareIgnoringTouches)
+
+ udfpsOverlayInteractor.setHandleTouches(true)
+ assertThat(isHardwareIgnoringTouches).isFalse()
+
+ udfpsOverlayInteractor.setHandleTouches(false)
+ assertThat(isHardwareIgnoringTouches).isTrue()
+ }
+
+ @Test
fun foldStateChanges() =
testScope.runTest {
val foldState = collectLastValue(interactor.foldState)
@@ -195,6 +220,7 @@
var folded: Int? = null
var displayState: Int? = null
+ var ignoreTouches: Boolean? = null
val job =
interactor.addBiometricContextListener(
object : IBiometricContextListener.Stub() {
@@ -205,12 +231,17 @@
override fun onDisplayStateChanged(newDisplayState: Int) {
displayState = newDisplayState
}
+
+ override fun onHardwareIgnoreTouchesChanged(newIgnoreTouches: Boolean) {
+ ignoreTouches = newIgnoreTouches
+ }
}
)
runCurrent()
assertThat(folded).isEqualTo(FoldState.FULLY_CLOSED)
assertThat(displayState).isEqualTo(AuthenticateOptions.DISPLAY_STATE_AOD)
+ assertThat(ignoreTouches).isFalse()
foldListener.onFoldUpdate(FOLD_UPDATE_START_OPENING)
foldListener.onFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN)
@@ -220,6 +251,11 @@
assertThat(folded).isEqualTo(FoldState.HALF_OPENED)
assertThat(displayState).isEqualTo(AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN)
+ udfpsOverlayInteractor.setHandleTouches(false)
+ runCurrent()
+
+ assertThat(ignoreTouches).isTrue()
+
job.cancel()
// stale updates should be ignored
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
index dcefea2..8a46c0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
@@ -30,6 +30,7 @@
private const val USER_ID = 22
private const val OPERATION_ID = 100L
+private const val OP_PACKAGE_NAME = "biometric.testapp"
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -114,7 +115,8 @@
},
kind = kind,
userId = USER_ID,
- challenge = OPERATION_ID
+ challenge = OPERATION_ID,
+ opPackageName = OP_PACKAGE_NAME
)
assertThat(prompt?.title).isEqualTo(title)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
index f15b738..52b4275 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
@@ -51,6 +51,7 @@
private const val USER_ID = 8
private const val CHALLENGE = 999L
+private const val OP_PACKAGE_NAME = "biometric.testapp"
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -113,13 +114,20 @@
assertThat(currentPrompt).isNull()
- interactor.useBiometricsForAuthentication(info, USER_ID, CHALLENGE, modalities)
+ interactor.useBiometricsForAuthentication(
+ info,
+ USER_ID,
+ CHALLENGE,
+ modalities,
+ OP_PACKAGE_NAME
+ )
assertThat(currentPrompt).isNotNull()
assertThat(currentPrompt?.title).isEqualTo(TITLE)
assertThat(currentPrompt?.description).isEqualTo(DESCRIPTION)
assertThat(currentPrompt?.subtitle).isEqualTo(SUBTITLE)
assertThat(currentPrompt?.negativeButtonText).isEqualTo(NEGATIVE_TEXT)
+ assertThat(currentPrompt?.opPackageName).isEqualTo(OP_PACKAGE_NAME)
if (allowCredentialFallback) {
assertThat(credentialKind).isSameInstanceAs(PromptKind.Password)
@@ -167,7 +175,7 @@
assertThat(currentPrompt).isNull()
- interactor.useCredentialsForAuthentication(info, kind, USER_ID, CHALLENGE)
+ interactor.useCredentialsForAuthentication(info, kind, USER_ID, CHALLENGE, OP_PACKAGE_NAME)
// not using biometrics, should be null with no fallback option
assertThat(currentPrompt).isNull()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
index 6a68672..c0e108e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
@@ -68,7 +68,7 @@
@Test
fun testShouldInterceptTouch() =
testScope.runTest {
- createUdpfsOverlayInteractor()
+ createUdfpsOverlayInteractor()
// When fingerprint enrolled and touch is within bounds
verify(authController).addCallback(authControllerCallback.capture())
@@ -92,7 +92,7 @@
@Test
fun testUdfpsOverlayParamsChange() =
testScope.runTest {
- createUdpfsOverlayInteractor()
+ createUdfpsOverlayInteractor()
val udfpsOverlayParams = collectLastValue(underTest.udfpsOverlayParams)
runCurrent()
@@ -105,7 +105,7 @@
assertThat(udfpsOverlayParams()).isEqualTo(firstParams)
}
- private fun createUdpfsOverlayInteractor() {
+ private fun createUdfpsOverlayInteractor() {
underTest =
UdfpsOverlayInteractor(
context,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
index 9e3c576..a46167a42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
@@ -1,11 +1,15 @@
package com.android.systemui.biometrics.domain.model
+import android.graphics.Bitmap
+import android.hardware.biometrics.PromptContentItemBulletedText
+import android.hardware.biometrics.PromptVerticalListContentView
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal
import com.android.systemui.biometrics.promptInfo
import com.android.systemui.biometrics.shared.model.BiometricModalities
import com.android.systemui.biometrics.shared.model.BiometricUserInfo
+import com.android.systemui.res.R
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -13,6 +17,7 @@
private const val USER_ID = 2
private const val OPERATION_ID = 8L
+private const val OP_PACKAGE_NAME = "biometric.testapp"
@SmallTest
@RunWith(JUnit4::class)
@@ -20,22 +25,38 @@
@Test
fun biometricRequestFromPromptInfo() {
+ val logoRes = R.drawable.ic_cake
val title = "what"
val subtitle = "a"
val description = "request"
+ val contentView =
+ PromptVerticalListContentView.Builder()
+ .setDescription("content description")
+ .addListItem(PromptContentItemBulletedText("content item 1"))
+ .addListItem(PromptContentItemBulletedText("content item 2"), 1)
+ .build()
val fpPros = fingerprintSensorPropertiesInternal().first()
val request =
BiometricPromptRequest.Biometric(
- promptInfo(title = title, subtitle = subtitle, description = description),
+ promptInfo(
+ logoRes = logoRes,
+ title = title,
+ subtitle = subtitle,
+ description = description,
+ contentView = contentView
+ ),
BiometricUserInfo(USER_ID),
BiometricOperationInfo(OPERATION_ID),
BiometricModalities(fingerprintProperties = fpPros),
+ OP_PACKAGE_NAME,
)
+ assertThat(request.logoRes).isEqualTo(logoRes)
assertThat(request.title).isEqualTo(title)
assertThat(request.subtitle).isEqualTo(subtitle)
assertThat(request.description).isEqualTo(description)
+ assertThat(request.contentView).isEqualTo(contentView)
assertThat(request.userInfo).isEqualTo(BiometricUserInfo(USER_ID))
assertThat(request.operationInfo).isEqualTo(BiometricOperationInfo(OPERATION_ID))
assertThat(request.modalities)
@@ -43,6 +64,23 @@
}
@Test
+ fun biometricRequestLogoBitmapFromPromptInfo() {
+ val logoBitmap = Bitmap.createBitmap(400, 400, Bitmap.Config.ARGB_8888)
+ val fpPros = fingerprintSensorPropertiesInternal().first()
+ val request =
+ BiometricPromptRequest.Biometric(
+ promptInfo(
+ logoBitmap = logoBitmap,
+ ),
+ BiometricUserInfo(USER_ID),
+ BiometricOperationInfo(OPERATION_ID),
+ BiometricModalities(fingerprintProperties = fpPros),
+ OP_PACKAGE_NAME,
+ )
+ assertThat(request.logoBitmap).isEqualTo(logoBitmap)
+ }
+
+ @Test
fun credentialRequestFromPromptInfo() {
val title = "what"
val subtitle = "a"
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 6170e0c..3888f2b 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
@@ -16,9 +16,15 @@
package com.android.systemui.biometrics.ui.viewmodel
+import android.content.pm.PackageManager
import android.content.res.Configuration
+import android.graphics.Bitmap
import android.graphics.Point
+import android.graphics.drawable.BitmapDrawable
+import android.hardware.biometrics.PromptContentItemBulletedText
+import android.hardware.biometrics.PromptContentView
import android.hardware.biometrics.PromptInfo
+import android.hardware.biometrics.PromptVerticalListContentView
import android.hardware.face.FaceSensorPropertiesInternal
import android.hardware.fingerprint.FingerprintSensorProperties
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
@@ -60,7 +66,6 @@
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -69,12 +74,12 @@
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.mockito.Mock
-import org.mockito.Mockito.times
import org.mockito.junit.MockitoJUnit
private const val USER_ID = 4
private const val CHALLENGE = 2L
private const val DELAY = 1000L
+private const val OP_PACKAGE_NAME = "biometric.testapp"
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -87,9 +92,14 @@
@Mock private lateinit var authController: AuthController
@Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
@Mock private lateinit var udfpsUtils: UdfpsUtils
+ @Mock private lateinit var packageManager: PackageManager
private val fakeExecutor = FakeExecutor(FakeSystemClock())
private val testScope = TestScope()
+ private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android)
+ private val logoResFromApp = R.drawable.ic_cake
+ private val logoFromApp = context.getDrawable(logoResFromApp)
+ private val logoBitmapFromApp = Bitmap.createBitmap(400, 400, Bitmap.Config.RGB_565)
private lateinit var fingerprintRepository: FakeFingerprintPropertyRepository
private lateinit var promptRepository: FakePromptRepository
@@ -101,6 +111,7 @@
private lateinit var selector: PromptSelectorInteractor
private lateinit var viewModel: PromptViewModel
private lateinit var iconViewModel: PromptIconViewModel
+ private lateinit var promptContentView: PromptContentView
@Before
fun setup() {
@@ -136,6 +147,11 @@
selector =
PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils)
selector.resetPrompt()
+ promptContentView =
+ PromptVerticalListContentView.Builder()
+ .addListItem(PromptContentItemBulletedText("content item 1"))
+ .addListItem(PromptContentItemBulletedText("content item 2"), 1)
+ .build()
viewModel =
PromptViewModel(
@@ -146,6 +162,12 @@
udfpsUtils
)
iconViewModel = viewModel.iconViewModel
+
+ // Set up default logo icon and app customized icon
+ whenever(packageManager.getApplicationIcon(OP_PACKAGE_NAME)).thenReturn(defaultLogoIcon)
+ context.setMockPackageManager(packageManager)
+ val resources = context.getOrCreateTestableResources()
+ resources.addOverride(logoResFromApp, logoFromApp)
}
@Test
@@ -1200,6 +1222,46 @@
}
}
+ @Test
+ fun descriptionOverriddenByContentView() =
+ runGenericTest(contentView = promptContentView, description = "test description") {
+ val contentView by collectLastValue(viewModel.contentView)
+ val description by collectLastValue(viewModel.description)
+
+ assertThat(description).isEqualTo("")
+ assertThat(contentView).isEqualTo(promptContentView)
+ }
+
+ @Test
+ fun descriptionWithoutContentView() =
+ runGenericTest(description = "test description") {
+ val contentView by collectLastValue(viewModel.contentView)
+ val description by collectLastValue(viewModel.description)
+
+ assertThat(description).isEqualTo("test description")
+ assertThat(contentView).isNull()
+ }
+
+ @Test
+ fun defaultLogoIfNoLogoSet() = runGenericTest {
+ val logo by collectLastValue(viewModel.logo)
+ assertThat(logo).isEqualTo(defaultLogoIcon)
+ }
+
+ @Test
+ fun logoResSetByApp() =
+ runGenericTest(logoRes = logoResFromApp) {
+ val logo by collectLastValue(viewModel.logo)
+ assertThat(logo).isEqualTo(logoFromApp)
+ }
+
+ @Test
+ fun logoBitmapSetByApp() =
+ runGenericTest(logoBitmap = logoBitmapFromApp) {
+ val logo by collectLastValue(viewModel.logo)
+ assertThat((logo as BitmapDrawable).bitmap).isEqualTo(logoBitmapFromApp)
+ }
+
/** Asserts that the selected buttons are visible now. */
private suspend fun TestScope.assertButtonsVisible(
tryAgain: Boolean = false,
@@ -1219,6 +1281,10 @@
private fun runGenericTest(
doNotStart: Boolean = false,
allowCredentialFallback: Boolean = false,
+ description: String? = null,
+ contentView: PromptContentView? = null,
+ logoRes: Int = -1,
+ logoBitmap: Bitmap? = null,
block: suspend TestScope.() -> Unit
) {
selector.initializePrompt(
@@ -1226,6 +1292,10 @@
allowCredentialFallback = allowCredentialFallback,
fingerprint = testCase.fingerprint,
face = testCase.face,
+ descriptionFromApp = description,
+ contentViewFromApp = contentView,
+ logoResFromApp = logoRes,
+ logoBitmapFromApp = logoBitmap,
)
// put the view model in the initial authenticating state, unless explicitly skipped
@@ -1401,20 +1471,30 @@
face: FaceSensorPropertiesInternal? = null,
requireConfirmation: Boolean = false,
allowCredentialFallback: Boolean = false,
+ descriptionFromApp: String? = null,
+ contentViewFromApp: PromptContentView? = null,
+ logoResFromApp: Int = -1,
+ logoBitmapFromApp: Bitmap? = null,
) {
val info =
PromptInfo().apply {
+ logoRes = logoResFromApp
+ logoBitmap = logoBitmapFromApp
title = "t"
subtitle = "s"
+ description = descriptionFromApp
+ contentView = contentViewFromApp
authenticators = listOf(face, fingerprint).extractAuthenticatorTypes()
isDeviceCredentialAllowed = allowCredentialFallback
isConfirmationRequested = requireConfirmation
}
+
useBiometricsForAuthentication(
info,
USER_ID,
CHALLENGE,
BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face),
+ OP_PACKAGE_NAME,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
index 44c57f3..134c40d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
@@ -3,8 +3,9 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.SystemClock
@@ -23,8 +24,8 @@
@Mock private lateinit var systemClock: SystemClock
@Mock private lateinit var bouncerLogger: TableLogBuffer
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
lateinit var underTest: KeyguardBouncerRepository
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
deleted file mode 100644
index 30a5497..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.communal.data.repository
-
-import android.content.pm.UserInfo
-import android.provider.Settings
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.core.FakeLogBuffer
-import com.android.systemui.settings.FakeUserTracker
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.util.settings.FakeSettings
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class CommunalTutorialRepositoryImplTest : SysuiTestCase() {
- private lateinit var secureSettings: FakeSettings
- private lateinit var userRepository: FakeUserRepository
- private lateinit var userTracker: FakeUserTracker
- private lateinit var logBuffer: LogBuffer
-
- private val testDispatcher = StandardTestDispatcher()
- private val testScope = TestScope(testDispatcher)
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- logBuffer = FakeLogBuffer.Factory.create()
- secureSettings = FakeSettings()
- userRepository = FakeUserRepository()
- val listOfUserInfo = listOf(MAIN_USER_INFO)
- userRepository.setUserInfos(listOfUserInfo)
-
- userTracker = FakeUserTracker()
- userTracker.set(
- userInfos = listOfUserInfo,
- selectedUserIndex = 0,
- )
- }
-
- @Test
- fun tutorialSettingState_defaultToNotStarted() =
- testScope.runTest {
- val repository = initCommunalTutorialRepository()
- val tutorialSettingState = collectLastValue(repository.tutorialSettingState)()
- assertThat(tutorialSettingState)
- .isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED)
- }
-
- @Test
- fun tutorialSettingState_whenTutorialSettingsUpdatedToStarted() =
- testScope.runTest {
- val repository = initCommunalTutorialRepository()
- setTutorialStateSetting(Settings.Secure.HUB_MODE_TUTORIAL_STARTED)
- val tutorialSettingState = collectLastValue(repository.tutorialSettingState)()
- assertThat(tutorialSettingState).isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_STARTED)
- }
-
- @Test
- fun tutorialSettingState_whenTutorialSettingsUpdatedToCompleted() =
- testScope.runTest {
- val repository = initCommunalTutorialRepository()
- setTutorialStateSetting(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
- val tutorialSettingState = collectLastValue(repository.tutorialSettingState)()
- assertThat(tutorialSettingState).isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
- }
-
- private fun initCommunalTutorialRepository(): CommunalTutorialRepositoryImpl {
- return CommunalTutorialRepositoryImpl(
- testScope.backgroundScope,
- testDispatcher,
- userRepository,
- secureSettings,
- userTracker,
- logBuffer
- )
- }
-
- private fun setTutorialStateSetting(
- @Settings.Secure.HubModeTutorialState state: Int,
- user: UserInfo = MAIN_USER_INFO
- ) {
- secureSettings.putIntForUser(Settings.Secure.HUB_MODE_TUTORIAL_STATE, state, user.id)
- }
-
- companion object {
- private val MAIN_USER_INFO =
- UserInfo(/* id= */ 0, /* name= */ "primary", /* flags= */ UserInfo.FLAG_MAIN)
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt
index a7677cc..002862e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt
@@ -16,27 +16,55 @@
package com.android.systemui.controls.panels
+import android.os.UserHandle
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
class FakeSelectedComponentRepository : SelectedComponentRepository {
-
- private var selectedComponent: SelectedComponentRepository.SelectedComponent? = null
private var shouldAddDefaultPanel: Boolean = true
+ private val _selectedComponentFlows =
+ mutableMapOf<UserHandle, MutableStateFlow<SelectedComponentRepository.SelectedComponent?>>()
+ private var currentUserHandle: UserHandle = UserHandle.of(0)
- override fun getSelectedComponent(): SelectedComponentRepository.SelectedComponent? =
- selectedComponent
+ override fun selectedComponentFlow(
+ userHandle: UserHandle
+ ): Flow<SelectedComponentRepository.SelectedComponent?> {
+ // Return an existing flow for the user or create a new one
+ return _selectedComponentFlows.getOrPut(getUserHandle(userHandle)) {
+ MutableStateFlow(null)
+ }
+ }
+
+ override fun getSelectedComponent(
+ userHandle: UserHandle
+ ): SelectedComponentRepository.SelectedComponent? {
+ return _selectedComponentFlows[getUserHandle(userHandle)]?.value
+ }
override fun setSelectedComponent(
selectedComponent: SelectedComponentRepository.SelectedComponent
) {
- this.selectedComponent = selectedComponent
+ val flow = _selectedComponentFlows.getOrPut(currentUserHandle) { MutableStateFlow(null) }
+ flow.value = selectedComponent
}
override fun removeSelectedComponent() {
- selectedComponent = null
+ _selectedComponentFlows[currentUserHandle]?.value = null
}
-
override fun shouldAddDefaultComponent(): Boolean = shouldAddDefaultPanel
override fun setShouldAddDefaultComponent(shouldAdd: Boolean) {
shouldAddDefaultPanel = shouldAdd
}
+
+ fun setCurrentUserHandle(userHandle: UserHandle) {
+ currentUserHandle = userHandle
+ }
+ private fun getUserHandle(userHandle: UserHandle): UserHandle {
+ return if (userHandle == UserHandle.CURRENT) {
+ currentUserHandle
+ } else {
+ userHandle
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
index 6230ea7..b463adf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
@@ -18,28 +18,43 @@
import android.content.ComponentName
import android.content.SharedPreferences
+import android.os.UserHandle
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
+import com.android.systemui.testKosmos
import com.android.systemui.util.FakeSharedPreferences
-import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import java.io.File
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
+@ExperimentalCoroutinesApi
@RunWith(AndroidTestingRunner::class)
@SmallTest
class SelectedComponentRepositoryTest : SysuiTestCase() {
private companion object {
+ const val PREF_COMPONENT = "controls_component"
+ const val PREF_STRUCTURE_OR_APP_NAME = "controls_structure"
+ const val PREF_IS_PANEL = "controls_is_panel"
+ val PRIMARY_USER: UserHandle = UserHandle.of(0)
+ val SECONDARY_USER: UserHandle = UserHandle.of(12)
val COMPONENT_A =
SelectedComponentRepository.SelectedComponent(
name = "a",
@@ -53,24 +68,40 @@
isPanel = false,
)
}
+ private lateinit var primaryUserSharedPref: FakeSharedPreferences
+ private lateinit var secondaryUserSharedPref: FakeSharedPreferences
@Mock private lateinit var userTracker: UserTracker
- @Mock private lateinit var userFileManager: UserFileManager
+ private lateinit var userFileManager: UserFileManager
private val featureFlags = FakeFeatureFlags()
- private val sharedPreferences: SharedPreferences = FakeSharedPreferences()
-
// under test
private lateinit var repository: SelectedComponentRepository
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- whenever(userFileManager.getSharedPreferences(any(), any(), any()))
- .thenReturn(sharedPreferences)
+ private val kosmos = testKosmos()
- repository = SelectedComponentRepositoryImpl(userFileManager, userTracker, featureFlags)
- }
+ @Before
+ fun setUp() =
+ with(kosmos) {
+ primaryUserSharedPref = FakeSharedPreferences()
+ secondaryUserSharedPref = FakeSharedPreferences()
+ MockitoAnnotations.initMocks(this@SelectedComponentRepositoryTest)
+ userFileManager =
+ FakeUserFileManager(
+ mapOf(
+ PRIMARY_USER.identifier to primaryUserSharedPref,
+ SECONDARY_USER.identifier to secondaryUserSharedPref
+ )
+ )
+ repository =
+ SelectedComponentRepositoryImpl(
+ userFileManager,
+ userTracker,
+ featureFlags,
+ bgDispatcher = testDispatcher,
+ applicationScope = applicationCoroutineScope
+ )
+ }
@Test
fun testUnsetIsNull() {
@@ -115,18 +146,10 @@
@Test
fun testGetPreferredStructure_differentUserId() {
- sharedPreferences.savePanel(COMPONENT_A)
- whenever(
- userFileManager.getSharedPreferences(
- DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
- 0,
- 1,
- )
- )
- .thenReturn(FakeSharedPreferences().also { it.savePanel(COMPONENT_B) })
-
+ primaryUserSharedPref.savePanel(COMPONENT_A)
+ secondaryUserSharedPref.savePanel(COMPONENT_B)
val previousPreferredStructure = repository.getSelectedComponent()
- whenever(userTracker.userId).thenReturn(1)
+ whenever(userTracker.userId).thenReturn(SECONDARY_USER.identifier)
val currentPreferredStructure = repository.getSelectedComponent()
assertThat(previousPreferredStructure).isEqualTo(COMPONENT_A)
@@ -134,11 +157,90 @@
assertThat(currentPreferredStructure).isEqualTo(COMPONENT_B)
}
+ @Test
+ fun testEmitValueFromGetSelectedComponent() =
+ with(kosmos) {
+ testScope.runTest {
+ primaryUserSharedPref.savePanel(COMPONENT_A)
+ val emittedValue by collectLastValue(repository.selectedComponentFlow(PRIMARY_USER))
+ assertThat(emittedValue).isEqualTo(COMPONENT_A)
+ }
+ }
+
+ @Test
+ fun testEmitNullWhenRemoveSelectedComponentIsCalled() =
+ with(kosmos) {
+ testScope.runTest {
+ primaryUserSharedPref.savePanel(COMPONENT_A)
+ primaryUserSharedPref.removePanel()
+ val emittedValue by collectLastValue(repository.selectedComponentFlow(PRIMARY_USER))
+ assertThat(emittedValue).isEqualTo(null)
+ }
+ }
+
+ @Test
+ fun testChangeEmitValueChangeWhenANewComponentIsSelected() =
+ with(kosmos) {
+ testScope.runTest {
+ primaryUserSharedPref.savePanel(COMPONENT_A)
+ val emittedValue by collectLastValue(repository.selectedComponentFlow(PRIMARY_USER))
+ advanceUntilIdle()
+ assertThat(emittedValue).isEqualTo(COMPONENT_A)
+ primaryUserSharedPref.savePanel(COMPONENT_B)
+ advanceUntilIdle()
+ assertThat(emittedValue).isEqualTo(COMPONENT_B)
+ }
+ }
+
+ @Test
+ fun testDifferentUsersWithDifferentComponentSelected() =
+ with(kosmos) {
+ testScope.runTest {
+ primaryUserSharedPref.savePanel(COMPONENT_A)
+ secondaryUserSharedPref.savePanel(COMPONENT_B)
+ val primaryUserValue by
+ collectLastValue(repository.selectedComponentFlow(PRIMARY_USER))
+ val secondaryUserValue by
+ collectLastValue(repository.selectedComponentFlow(SECONDARY_USER))
+ assertThat(primaryUserValue).isEqualTo(COMPONENT_A)
+ assertThat(secondaryUserValue).isEqualTo(COMPONENT_B)
+ }
+ }
+
private fun SharedPreferences.savePanel(panel: SelectedComponentRepository.SelectedComponent) {
edit()
- .putString("controls_component", panel.componentName?.flattenToString())
- .putString("controls_structure", panel.name)
- .putBoolean("controls_is_panel", panel.isPanel)
+ .putString(PREF_COMPONENT, panel.componentName?.flattenToString())
+ .putString(PREF_STRUCTURE_OR_APP_NAME, panel.name)
+ .putBoolean(PREF_IS_PANEL, panel.isPanel)
.commit()
}
+
+ private fun SharedPreferences.removePanel() {
+ edit()
+ .remove(PREF_COMPONENT)
+ .remove(PREF_STRUCTURE_OR_APP_NAME)
+ .remove(PREF_IS_PANEL)
+ .commit()
+ }
+
+ private class FakeUserFileManager(private val sharedPrefs: Map<Int, SharedPreferences>) :
+ UserFileManager {
+ override fun getFile(fileName: String, userId: Int): File {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getSharedPreferences(
+ fileName: String,
+ mode: Int,
+ userId: Int
+ ): SharedPreferences {
+ if (fileName != DeviceControlsControllerImpl.PREFS_CONTROLS_FILE) {
+ throw IllegalArgumentException(
+ "Preference files must be " +
+ "$DeviceControlsControllerImpl.PREFS_CONTROLS_FILE"
+ )
+ }
+ return sharedPrefs.getValue(userId)
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt
index d5c3641..0dfdeca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt
@@ -36,9 +36,9 @@
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.testKosmos
-import com.android.systemui.util.time.fakeSystemClock
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.Test
@@ -72,8 +72,8 @@
// It's been 10 seconds since the last power button wakeup
setAwakeFromPowerButton()
+ advanceTimeBy(10000)
runCurrent()
- kosmos.fakeSystemClock.setUptimeMillis(kosmos.fakeSystemClock.uptimeMillis() + 10000)
enterDeviceFromBiometricUnlock()
assertThat(playSuccessHaptic).isNotNull()
@@ -89,8 +89,8 @@
// It's been 10 seconds since the last power button wakeup
setAwakeFromPowerButton()
+ advanceTimeBy(10000)
runCurrent()
- kosmos.fakeSystemClock.setUptimeMillis(kosmos.fakeSystemClock.uptimeMillis() + 10000)
enterDeviceFromBiometricUnlock()
assertThat(playSuccessHaptic).isNull()
@@ -106,8 +106,8 @@
// It's only been 50ms since the last power button wakeup
setAwakeFromPowerButton()
+ advanceTimeBy(50)
runCurrent()
- kosmos.fakeSystemClock.setUptimeMillis(kosmos.fakeSystemClock.uptimeMillis() + 50)
enterDeviceFromBiometricUnlock()
assertThat(playSuccessHaptic).isNull()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
new file mode 100644
index 0000000..d397fc2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
@@ -0,0 +1,211 @@
+/*
+ * 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.keyboard.stickykeys.ui.viewmodel
+
+import android.hardware.input.InputManager
+import android.hardware.input.StickyModifierState
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
+import com.android.systemui.keyboard.stickykeys.StickyKeysLogger
+import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepositoryImpl
+import com.android.systemui.keyboard.stickykeys.shared.model.Locked
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT_GR
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.CTRL
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+
+@SmallTest
+@RunWith(JUnit4::class)
+class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
+
+ private val dispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(dispatcher)
+ private lateinit var viewModel: StickyKeysIndicatorViewModel
+ private val inputManager = mock<InputManager>()
+ private val keyboardRepository = FakeKeyboardRepository()
+ private val captor =
+ ArgumentCaptor.forClass(InputManager.StickyModifierStateListener::class.java)
+
+ @Before
+ fun setup() {
+ val stickyKeysRepository = StickyKeysRepositoryImpl(
+ inputManager,
+ dispatcher,
+ mock<StickyKeysLogger>()
+ )
+ viewModel =
+ StickyKeysIndicatorViewModel(
+ stickyKeysRepository = stickyKeysRepository,
+ keyboardRepository = keyboardRepository,
+ applicationScope = testScope.backgroundScope,
+ )
+ }
+
+ @Test
+ fun startsListeningToStickyKeysOnlyWhenKeyboardIsConnected() {
+ testScope.runTest {
+ collectLastValue(viewModel.indicatorContent)
+ runCurrent()
+ verifyZeroInteractions(inputManager)
+
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+ runCurrent()
+
+ verify(inputManager)
+ .registerStickyModifierStateListener(
+ any(),
+ any(InputManager.StickyModifierStateListener::class.java)
+ )
+ }
+ }
+
+ @Test
+ fun stopsListeningToStickyKeysWhenKeyboardDisconnects() {
+ testScope.runTest {
+ collectLastValue(viewModel.indicatorContent)
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+ runCurrent()
+
+ keyboardRepository.setIsAnyKeyboardConnected(false)
+ runCurrent()
+
+ verify(inputManager).unregisterStickyModifierStateListener(any())
+ }
+ }
+
+ @Test
+ fun emitsStickyKeysListWhenStickyKeyIsPressed() {
+ testScope.runTest {
+ val stickyKeys by collectLastValue(viewModel.indicatorContent)
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+
+ setStickyKeys(mapOf(ALT to false))
+
+ assertThat(stickyKeys).isEqualTo(mapOf(ALT to Locked(false)))
+ }
+ }
+
+ @Test
+ fun emitsEmptyListWhenNoStickyKeysAreActive() {
+ testScope.runTest {
+ val stickyKeys by collectLastValue(viewModel.indicatorContent)
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+
+ setStickyKeys(emptyMap())
+
+ assertThat(stickyKeys).isEqualTo(emptyMap<ModifierKey, Locked>())
+ }
+ }
+
+ @Test
+ fun passesAllStickyKeysToDialog() {
+ testScope.runTest {
+ val stickyKeys by collectLastValue(viewModel.indicatorContent)
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+
+ setStickyKeys(mapOf(
+ ALT to false,
+ META to false,
+ SHIFT to false))
+
+ assertThat(stickyKeys).isEqualTo(mapOf(
+ ALT to Locked(false),
+ META to Locked(false),
+ SHIFT to Locked(false),
+ ))
+ }
+ }
+
+ @Test
+ fun showsOnlyLockedStateIfKeyIsStickyAndLocked() {
+ testScope.runTest {
+ val stickyKeys by collectLastValue(viewModel.indicatorContent)
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+
+ setStickyKeys(mapOf(
+ ALT to false,
+ ALT to true))
+
+ assertThat(stickyKeys).isEqualTo(mapOf(ALT to Locked(true)))
+ }
+ }
+
+ @Test
+ fun doesNotChangeOrderOfKeysIfTheyBecomeLocked() {
+ testScope.runTest {
+ val stickyKeys by collectLastValue(viewModel.indicatorContent)
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+
+ setStickyKeys(mapOf(
+ META to false,
+ SHIFT to false, // shift is sticky but not locked
+ CTRL to false))
+ val previousShiftIndex = stickyKeys?.toList()?.indexOf(SHIFT to Locked(false))
+
+ setStickyKeys(mapOf(
+ SHIFT to false,
+ SHIFT to true, // shift is now locked
+ META to false,
+ CTRL to false))
+ assertThat(stickyKeys?.toList()?.indexOf(SHIFT to Locked(true)))
+ .isEqualTo(previousShiftIndex)
+ }
+ }
+
+ private fun TestScope.setStickyKeys(keys: Map<ModifierKey, Boolean>) {
+ runCurrent()
+ verify(inputManager).registerStickyModifierStateListener(any(), captor.capture())
+ captor.value.onStickyModifierStateChanged(TestStickyModifierState(keys))
+ runCurrent()
+ }
+
+ private class TestStickyModifierState(private val keys: Map<ModifierKey, Boolean>) :
+ StickyModifierState() {
+
+ private fun isOn(key: ModifierKey) = keys.any { it.key == key && !it.value }
+ private fun isLocked(key: ModifierKey) = keys.any { it.key == key && it.value }
+
+ override fun isAltGrModifierLocked() = isLocked(ALT_GR)
+ override fun isAltGrModifierOn() = isOn(ALT_GR)
+ override fun isAltModifierLocked() = isLocked(ALT)
+ override fun isAltModifierOn() = isOn(ALT)
+ override fun isCtrlModifierLocked() = isLocked(CTRL)
+ override fun isCtrlModifierOn() = isOn(CTRL)
+ override fun isMetaModifierLocked() = isLocked(META)
+ override fun isMetaModifierOn() = isOn(META)
+ override fun isShiftModifierLocked() = isLocked(SHIFT)
+ override fun isShiftModifierOn() = isOn(SHIFT)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
index c4df27c..cb8c40c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.bouncer.ui.BouncerView
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
@@ -69,6 +70,7 @@
private val bouncerRepository = FakeKeyguardBouncerRepository()
private val biometricSettingsRepository = FakeBiometricSettingsRepository()
+ private val deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
@@ -112,7 +114,7 @@
DeviceEntrySideFpsOverlayInteractor(
testScope.backgroundScope,
mContext,
- FakeDeviceEntryFingerprintAuthRepository(),
+ deviceEntryFingerprintAuthRepository,
primaryBouncerInteractor,
alternateBouncerInteractor,
keyguardUpdateMonitor
@@ -216,6 +218,30 @@
assertThat(showIndicatorForDeviceEntry).isEqualTo(false)
}
+ @Test
+ fun ignoresDuplicateRequestsToShowIndicatorForDeviceEntry() =
+ testScope.runTest {
+ val showIndicatorForDeviceEntry by collectValues(underTest.showIndicatorForDeviceEntry)
+ runCurrent()
+
+ // Request to show indicator for primary bouncer showing
+ updatePrimaryBouncer(
+ isShowing = true,
+ isAnimatingAway = false,
+ fpsDetectionRunning = true,
+ isUnlockingWithFpAllowed = true
+ )
+
+ // Another request to show indicator for deviceEntryFingerprintAuthRepository update
+ deviceEntryFingerprintAuthRepository.setShouldUpdateIndicatorVisibility(true)
+
+ // Request to show indicator for alternate bouncer showing
+ bouncerRepository.setAlternateVisible(true)
+
+ // Ensure only one show request is sent
+ assertThat(showIndicatorForDeviceEntry).containsExactly(false, true)
+ }
+
private fun updatePrimaryBouncer(
isShowing: Boolean,
isAnimatingAway: Boolean,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
index 8e81185..809947d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
@@ -63,6 +63,8 @@
transitionRepository = super.transitionRepository,
transitionInteractor = super.transitionInteractor,
scope = super.testScope.backgroundScope,
+ bgDispatcher = super.testDispatcher,
+ mainDispatcher = super.testDispatcher,
keyguardInteractor = super.keyguardInteractor,
flags = FakeFeatureFlags(),
keyguardSecurityModel = mock(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index b8a8bdf..8b6611f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -20,9 +20,13 @@
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
-import com.android.keyguard.TestScopeProvider
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
+import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
@@ -51,6 +55,9 @@
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancelChildren
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runCurrent
@@ -102,14 +109,18 @@
FromPrimaryBouncerTransitionInteractor
private lateinit var fromDreamingLockscreenHostedTransitionInteractor:
FromDreamingLockscreenHostedTransitionInteractor
+ private lateinit var fromGlanceableHubTransitionInteractor:
+ FromGlanceableHubTransitionInteractor
private lateinit var powerInteractor: PowerInteractor
private lateinit var keyguardInteractor: KeyguardInteractor
+ private lateinit var communalInteractor: CommunalInteractor
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- testScope = TestScopeProvider.getTestScope()
+ val testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
keyguardRepository = FakeKeyguardRepository()
bouncerRepository = FakeKeyguardBouncerRepository()
@@ -117,10 +128,13 @@
shadeRepository = FakeShadeRepository()
transitionRepository = spy(FakeKeyguardTransitionRepository())
powerInteractor = PowerInteractorFactory.create().powerInteractor
+ communalInteractor =
+ CommunalInteractorFactory.create(testScope = testScope).communalInteractor
whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN)
featureFlags = FakeFeatureFlags().apply { set(Flags.KEYGUARD_WM_STATE_REFACTOR, false) }
+ mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
keyguardInteractor = createKeyguardInteractor()
@@ -136,15 +150,25 @@
)
.keyguardTransitionInteractor
+ val glanceableHubTransitions =
+ GlanceableHubTransitions(
+ testScope,
+ transitionInteractor,
+ transitionRepository,
+ communalInteractor
+ )
fromLockscreenTransitionInteractor =
FromLockscreenTransitionInteractor(
scope = testScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
keyguardInteractor = keyguardInteractor,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
flags = featureFlags,
shadeRepository = shadeRepository,
powerInteractor = powerInteractor,
+ glanceableHubTransitions = glanceableHubTransitions,
inWindowLauncherUnlockAnimationInteractor = {
InWindowLauncherUnlockAnimationInteractor(
InWindowLauncherUnlockAnimationRepository(),
@@ -160,6 +184,8 @@
fromPrimaryBouncerTransitionInteractor =
FromPrimaryBouncerTransitionInteractor(
scope = testScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
keyguardInteractor = keyguardInteractor,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
@@ -173,6 +199,8 @@
fromDreamingTransitionInteractor =
FromDreamingTransitionInteractor(
scope = testScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
keyguardInteractor = keyguardInteractor,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
@@ -182,6 +210,8 @@
fromDreamingLockscreenHostedTransitionInteractor =
FromDreamingLockscreenHostedTransitionInteractor(
scope = testScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
keyguardInteractor = keyguardInteractor,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
@@ -191,6 +221,8 @@
fromAodTransitionInteractor =
FromAodTransitionInteractor(
scope = testScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
keyguardInteractor = keyguardInteractor,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
@@ -200,6 +232,8 @@
fromGoneTransitionInteractor =
FromGoneTransitionInteractor(
scope = testScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
keyguardInteractor = keyguardInteractor,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
@@ -210,6 +244,8 @@
fromDozingTransitionInteractor =
FromDozingTransitionInteractor(
scope = testScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
keyguardInteractor = keyguardInteractor,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
@@ -220,6 +256,8 @@
fromOccludedTransitionInteractor =
FromOccludedTransitionInteractor(
scope = testScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
keyguardInteractor = keyguardInteractor,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
@@ -230,12 +268,24 @@
fromAlternateBouncerTransitionInteractor =
FromAlternateBouncerTransitionInteractor(
scope = testScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
keyguardInteractor = keyguardInteractor,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
powerInteractor = powerInteractor,
)
.apply { start() }
+
+ fromGlanceableHubTransitionInteractor =
+ FromGlanceableHubTransitionInteractor(
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
+ glanceableHubTransitions = glanceableHubTransitions,
+ transitionRepository = transitionRepository,
+ transitionInteractor = transitionInteractor,
+ )
+ .apply { start() }
}
@Test
@@ -1292,14 +1342,8 @@
runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.AOD)
runCurrent()
- // WHEN the keyguard is occluded and aod ends
+ // WHEN the keyguard is occluded
keyguardRepository.setKeyguardOccluded(true)
- keyguardRepository.setDozeTransitionModel(
- DozeTransitionModel(
- from = DozeStateModel.DOZE_AOD,
- to = DozeStateModel.FINISH,
- )
- )
runCurrent()
val info =
@@ -1316,6 +1360,30 @@
}
@Test
+ fun aodToPrimaryBouncer() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to AOD
+ runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.AOD)
+ runCurrent()
+
+ // WHEN the primary bouncer is set to show
+ bouncerRepository.setPrimaryShow(true)
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture())
+ }
+ // THEN a transition to OCCLUDED should occur
+ assertThat(info.ownerName).isEqualTo("FromAodTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.AOD)
+ assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
fun lockscreenToOccluded_fromCameraGesture() =
testScope.runTest {
// GIVEN a prior transition has run to LOCKSCREEN
@@ -1391,6 +1459,124 @@
coroutineContext.cancelChildren()
}
+ @Test
+ fun lockscreenToGlanceableHub() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to LOCKSCREEN
+ runTransitionAndSetWakefulness(KeyguardState.AOD, KeyguardState.LOCKSCREEN)
+ runCurrent()
+
+ // WHEN a glanceable hub transition starts
+ val currentScene = CommunalSceneKey.Blank
+ val targetScene = CommunalSceneKey.Communal
+
+ val progress = MutableStateFlow(0f)
+ val transitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(
+ ObservableCommunalTransitionState.Transition(
+ fromScene = currentScene,
+ toScene = targetScene,
+ progress = progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+ communalInteractor.setTransitionState(transitionState)
+ progress.value = .1f
+ runCurrent()
+
+ // THEN a transition from LOCKSCREEN => GLANCEABLE_HUB should occur
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture())
+ }
+ assertThat(info.ownerName)
+ .isEqualTo(FromLockscreenTransitionInteractor::class.simpleName)
+ assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
+ assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+ assertThat(info.animator).isNull() // transition should be manually animated
+
+ // WHEN the user stops dragging and the glanceable hub opening is cancelled
+ clearInvocations(transitionRepository)
+ runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GLANCEABLE_HUB)
+ val idleTransitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(
+ ObservableCommunalTransitionState.Idle(currentScene)
+ )
+ communalInteractor.setTransitionState(idleTransitionState)
+ runCurrent()
+
+ // THEN a transition from GLANCEABLE_HUB => LOCKSCREEN should occur
+ val info2 =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture())
+ }
+ assertThat(info2.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+ assertThat(info2.to).isEqualTo(KeyguardState.LOCKSCREEN)
+ assertThat(info.animator).isNull() // transition should be manually animated
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun glanceableHubToLockscreen() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to GLANCEABLE_HUB
+ runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GLANCEABLE_HUB)
+ runCurrent()
+
+ // WHEN a transition away from glanceable hub starts
+ val currentScene = CommunalSceneKey.Communal
+ val targetScene = CommunalSceneKey.Blank
+
+ val progress = MutableStateFlow(0f)
+ val transitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(
+ ObservableCommunalTransitionState.Transition(
+ fromScene = currentScene,
+ toScene = targetScene,
+ progress = progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+ communalInteractor.setTransitionState(transitionState)
+ progress.value = .1f
+ runCurrent()
+
+ // THEN a transition from GLANCEABLE_HUB => LOCKSCREEN should occur
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture())
+ }
+ assertThat(info.ownerName)
+ .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
+ assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+ assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
+ assertThat(info.animator).isNull() // transition should be manually animated
+
+ // WHEN the user stops dragging and the glanceable hub closing is cancelled
+ clearInvocations(transitionRepository)
+ runTransitionAndSetWakefulness(KeyguardState.GLANCEABLE_HUB, KeyguardState.LOCKSCREEN)
+ val idleTransitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(
+ ObservableCommunalTransitionState.Idle(currentScene)
+ )
+ communalInteractor.setTransitionState(idleTransitionState)
+ runCurrent()
+
+ // THEN a transition from LOCKSCREEN => GLANCEABLE_HUB should occur
+ val info2 =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture())
+ }
+ assertThat(info2.from).isEqualTo(KeyguardState.LOCKSCREEN)
+ assertThat(info2.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+ assertThat(info.animator).isNull() // transition should be manually animated
+
+ coroutineContext.cancelChildren()
+ }
+
private fun createKeyguardInteractor(): KeyguardInteractor {
return KeyguardInteractorFactory.create(
featureFlags = featureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
index edd781d..2d9d5ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
@@ -20,14 +20,16 @@
import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.milliseconds
-import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -37,23 +39,21 @@
@SmallTest
@RunWith(JUnit4::class)
class KeyguardTransitionAnimationFlowTest : SysuiTestCase() {
- private lateinit var underTest: KeyguardTransitionAnimationFlow.SharedFlowBuilder
- private lateinit var repository: FakeKeyguardTransitionRepository
- private lateinit var testScope: TestScope
+ val kosmos = testKosmos()
+ val testScope = kosmos.testScope
+ val animationFlow = kosmos.keyguardTransitionAnimationFlow
+ val repository = kosmos.fakeKeyguardTransitionRepository
+
+ private lateinit var underTest: KeyguardTransitionAnimationFlow.FlowBuilder
@Before
fun setUp() {
- testScope = TestScope()
- repository = FakeKeyguardTransitionRepository()
underTest =
- KeyguardTransitionAnimationFlow(
- testScope.backgroundScope,
- mock(),
- )
- .setup(
- duration = 1000.milliseconds,
- stepFlow = repository.transitions,
- )
+ animationFlow.setup(
+ duration = 1000.milliseconds,
+ from = KeyguardState.GONE,
+ to = KeyguardState.DREAMING,
+ )
}
@Test(expected = IllegalArgumentException::class)
@@ -83,6 +83,8 @@
onFinish = { 10f },
)
var animationValues = collectLastValue(flow)
+ runCurrent()
+
repository.sendTransitionStep(step(1f, TransitionState.FINISHED), validateStep = false)
assertThat(animationValues()).isEqualTo(10f)
}
@@ -97,6 +99,8 @@
onCancel = { 100f },
)
var animationValues = collectLastValue(flow)
+ runCurrent()
+
repository.sendTransitionStep(step(0.5f, TransitionState.CANCELED))
assertThat(animationValues()).isEqualTo(100f)
}
@@ -111,6 +115,8 @@
onStep = { it },
)
var animationValues = collectLastValue(flow)
+ runCurrent()
+
repository.sendTransitionStep(step(0f, TransitionState.STARTED))
assertThat(animationValues()).isEqualTo(0f)
@@ -137,6 +143,8 @@
onStep = { it },
)
var animationValues = collectLastValue(flow)
+ runCurrent()
+
repository.sendTransitionStep(step(0f, TransitionState.STARTED))
assertFloat(animationValues(), EMPHASIZED_ACCELERATE.getInterpolation(0f))
repository.sendTransitionStep(step(0.5f, TransitionState.RUNNING))
@@ -157,17 +165,56 @@
duration = 1000.milliseconds,
onStep = { it * 2 },
)
- var animationValues = collectLastValue(flow)
+ val animationValues by collectLastValue(flow)
+ runCurrent()
+
repository.sendTransitionStep(step(0f, TransitionState.STARTED))
- assertFloat(animationValues(), 0f)
+ assertFloat(animationValues, 0f)
repository.sendTransitionStep(step(0.3f, TransitionState.RUNNING))
- assertFloat(animationValues(), 0.6f)
+ assertFloat(animationValues, 0.6f)
repository.sendTransitionStep(step(0.6f, TransitionState.RUNNING))
- assertFloat(animationValues(), 1.2f)
+ assertFloat(animationValues, 1.2f)
repository.sendTransitionStep(step(0.8f, TransitionState.RUNNING))
- assertFloat(animationValues(), 1.6f)
+ assertFloat(animationValues, 1.6f)
repository.sendTransitionStep(step(1f, TransitionState.RUNNING))
- assertFloat(animationValues(), 2f)
+ assertFloat(animationValues, 2f)
+ }
+
+ @Test
+ fun sameFloatValueWithTheSameTransitionStateDoesNotEmitTwice() =
+ testScope.runTest {
+ val flow =
+ underTest.sharedFlow(
+ duration = 1000.milliseconds,
+ onStep = { it },
+ )
+ val values by collectValues(flow)
+ runCurrent()
+
+ repository.sendTransitionStep(step(0.3f, TransitionState.RUNNING))
+ repository.sendTransitionStep(step(0.3f, TransitionState.RUNNING))
+
+ assertThat(values.size).isEqualTo(1)
+ assertThat(values[0]).isEqualTo(0.3f)
+ }
+
+ @Test
+ fun sameFloatValueWithADifferentTransitionStateDoesEmitTwice() =
+ testScope.runTest {
+ val flow =
+ underTest.sharedFlow(
+ duration = 1000.milliseconds,
+ onStep = { it },
+ )
+ val values by collectValues(flow)
+ runCurrent()
+
+ repository.sendTransitionStep(step(0.3f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(0.3f, TransitionState.RUNNING))
+
+ assertThat(values.size).isEqualTo(2)
+ assertThat(values[0]).isEqualTo(0.3f)
+ assertThat(values[0]).isEqualTo(0.3f)
}
private fun assertFloat(actual: Float?, expected: Float) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt
index a4d217f..5dd37ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt
@@ -21,13 +21,17 @@
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardClockSwitch.LARGE
+import com.android.keyguard.KeyguardClockSwitch.SMALL
import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.plugins.clocks.ClockConfig
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockFaceController
import com.android.systemui.plugins.clocks.ClockFaceLayout
import com.android.systemui.util.mockito.whenever
import kotlin.test.Test
+import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.Before
import org.junit.runner.RunWith
import org.mockito.Mock
@@ -48,32 +52,60 @@
@Mock private lateinit var smallClockView: View
@Mock private lateinit var smallClockFaceLayout: ClockFaceLayout
@Mock private lateinit var largeClockFaceLayout: ClockFaceLayout
+ @Mock private lateinit var clockViewModel: KeyguardClockViewModel
+ private val clockSize = MutableStateFlow(LARGE)
+ private val currentClock: MutableStateFlow<ClockController?> = MutableStateFlow(null)
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- }
-
- @Test
- fun addClockViews_nonWeatherClock() {
- setupNonWeatherClock()
- KeyguardClockViewBinder.addClockViews(clock, rootView, burnInLayer)
- verify(rootView).addView(smallClockView)
- verify(rootView).addView(largeClockView)
- verify(burnInLayer).addView(smallClockView)
- verify(burnInLayer, never()).addView(largeClockView)
+ whenever(clockViewModel.clockSize).thenReturn(clockSize)
+ whenever(clockViewModel.currentClock).thenReturn(currentClock)
+ whenever(clockViewModel.burnInLayer).thenReturn(burnInLayer)
}
@Test
fun addClockViews_WeatherClock() {
setupWeatherClock()
- KeyguardClockViewBinder.addClockViews(clock, rootView, burnInLayer)
+ KeyguardClockViewBinder.addClockViews(clock, rootView)
verify(rootView).addView(smallClockView)
verify(rootView).addView(largeClockView)
- verify(burnInLayer).addView(smallClockView)
+ }
+
+ @Test
+ fun addClockViews_nonWeatherClock() {
+ setupNonWeatherClock()
+ KeyguardClockViewBinder.addClockViews(clock, rootView)
+ verify(rootView).addView(smallClockView)
+ verify(rootView).addView(largeClockView)
+ }
+ @Test
+ fun addClockViewsToBurnInLayer_LargeWeatherClock() {
+ setupWeatherClock()
+ clockSize.value = LARGE
+ KeyguardClockViewBinder.updateBurnInLayer(rootView, clockViewModel)
+ verify(burnInLayer).removeView(smallClockView)
verify(burnInLayer).addView(largeClockView)
}
+ @Test
+ fun addClockViewsToBurnInLayer_LargeNonWeatherClock() {
+ setupNonWeatherClock()
+ clockSize.value = LARGE
+ KeyguardClockViewBinder.updateBurnInLayer(rootView, clockViewModel)
+ verify(burnInLayer).removeView(smallClockView)
+ verify(burnInLayer, never()).addView(largeClockView)
+ }
+
+ @Test
+ fun addClockViewsToBurnInLayer_SmallClock() {
+ setupNonWeatherClock()
+ clockSize.value = SMALL
+ KeyguardClockViewBinder.updateBurnInLayer(rootView, clockViewModel)
+ verify(burnInLayer).addView(smallClockView)
+ verify(burnInLayer).removeView(largeClockView)
+ }
+
private fun setupWeatherClock() {
setupClock()
val clockConfig =
@@ -99,5 +131,7 @@
whenever(clock.smallClock).thenReturn(smallClock)
whenever(largeClock.layout).thenReturn(largeClockFaceLayout)
whenever(smallClock.layout).thenReturn(smallClockFaceLayout)
+ whenever(clockViewModel.clock).thenReturn(clock)
+ currentClock.value = clock
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
index 070a0cc..57b5559 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
@@ -22,6 +22,7 @@
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.res.R
@@ -31,6 +32,8 @@
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import dagger.Lazy
+import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -46,6 +49,8 @@
@Mock private lateinit var keyguardClockInteractor: KeyguardClockInteractor
@Mock private lateinit var keyguardClockViewModel: KeyguardClockViewModel
@Mock private lateinit var splitShadeStateController: SplitShadeStateController
+ @Mock private lateinit var blueprintInteractor: Lazy<KeyguardBlueprintInteractor>
+ private val clockShouldBeCentered: MutableStateFlow<Boolean> = MutableStateFlow(true)
private lateinit var underTest: ClockSection
@@ -104,12 +109,15 @@
whenever(packageManager.getResourcesForApplication(anyString())).thenReturn(remoteResources)
mContext.setMockPackageManager(packageManager)
+ whenever(keyguardClockViewModel.clockShouldBeCentered).thenReturn(clockShouldBeCentered)
+
underTest =
ClockSection(
keyguardClockInteractor,
keyguardClockViewModel,
mContext,
splitShadeStateController,
+ blueprintInteractor
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
index 28da957..deb3a83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
@@ -26,6 +26,8 @@
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardSmartspaceInteractor
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.res.R
@@ -34,7 +36,8 @@
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.StateFlow
+import dagger.Lazy
+import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -50,7 +53,8 @@
@Mock private lateinit var keyguardSmartspaceViewModel: KeyguardSmartspaceViewModel
@Mock private lateinit var lockscreenSmartspaceController: LockscreenSmartspaceController
@Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
- @Mock private lateinit var hasCustomWeatherDataDisplay: StateFlow<Boolean>
+ @Mock private lateinit var keyguardSmartspaceInteractor: KeyguardSmartspaceInteractor
+ @Mock private lateinit var blueprintInteractor: Lazy<KeyguardBlueprintInteractor>
private val smartspaceView = View(mContext).also { it.id = sharedR.id.bc_smartspace_view }
private val weatherView = View(mContext).also { it.id = sharedR.id.weather_smartspace_view }
@@ -58,17 +62,22 @@
private lateinit var constraintLayout: ConstraintLayout
private lateinit var constraintSet: ConstraintSet
+ private val clockShouldBeCentered = MutableStateFlow(false)
+ private val hasCustomWeatherDataDisplay = MutableStateFlow(false)
+
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
mSetFlagsRule.enableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
underTest =
SmartspaceSection(
+ mContext,
keyguardClockViewModel,
keyguardSmartspaceViewModel,
- mContext,
+ keyguardSmartspaceInteractor,
lockscreenSmartspaceController,
keyguardUnlockAnimationController,
+ blueprintInteractor
)
constraintLayout = ConstraintLayout(mContext)
whenever(lockscreenSmartspaceController.buildAndConnectView(any()))
@@ -78,6 +87,7 @@
whenever(lockscreenSmartspaceController.buildAndConnectDateView(any())).thenReturn(dateView)
whenever(keyguardClockViewModel.hasCustomWeatherDataDisplay)
.thenReturn(hasCustomWeatherDataDisplay)
+ whenever(keyguardClockViewModel.clockShouldBeCentered).thenReturn(clockShouldBeCentered)
constraintSet = ConstraintSet()
}
@@ -115,7 +125,7 @@
fun testConstraintsWhenNotHasCustomWeatherDataDisplay() {
whenever(keyguardSmartspaceViewModel.isSmartspaceEnabled).thenReturn(true)
whenever(keyguardSmartspaceViewModel.isDateWeatherDecoupled).thenReturn(true)
- whenever(keyguardClockViewModel.hasCustomWeatherDataDisplay.value).thenReturn(false)
+ hasCustomWeatherDataDisplay.value = false
underTest.addViews(constraintLayout)
underTest.applyConstraints(constraintSet)
assertWeatherSmartspaceConstrains(constraintSet)
@@ -129,7 +139,7 @@
@Test
fun testConstraintsWhenHasCustomWeatherDataDisplay() {
- whenever(keyguardClockViewModel.hasCustomWeatherDataDisplay.value).thenReturn(true)
+ hasCustomWeatherDataDisplay.value = true
underTest.addViews(constraintLayout)
underTest.applyConstraints(constraintSet)
assertWeatherSmartspaceConstrains(constraintSet)
@@ -140,7 +150,7 @@
@Test
fun testNormalDateWeatherVisibility() {
- whenever(keyguardClockViewModel.hasCustomWeatherDataDisplay.value).thenReturn(false)
+ hasCustomWeatherDataDisplay.value = false
whenever(keyguardSmartspaceViewModel.isWeatherEnabled).thenReturn(true)
underTest.addViews(constraintLayout)
underTest.applyConstraints(constraintSet)
@@ -153,7 +163,7 @@
}
@Test
fun testCustomDateWeatherVisibility() {
- whenever(keyguardClockViewModel.hasCustomWeatherDataDisplay.value).thenReturn(true)
+ hasCustomWeatherDataDisplay.value = true
underTest.addViews(constraintLayout)
underTest.applyConstraints(constraintSet)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
index d959872..87391cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
@@ -31,6 +31,7 @@
import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@@ -50,6 +51,7 @@
fun transitionToAlternateBouncer_scrimAlphaUpdate() =
testScope.runTest {
val scrimAlphas by collectValues(underTest.scrimAlpha)
+ runCurrent()
transitionRepository.sendTransitionSteps(
listOf(
@@ -69,17 +71,17 @@
fun transitionFromAlternateBouncer_scrimAlphaUpdate() =
testScope.runTest {
val scrimAlphas by collectValues(underTest.scrimAlpha)
+ runCurrent()
transitionRepository.sendTransitionSteps(
listOf(
- stepToAlternateBouncer(0f, TransitionState.STARTED),
- stepToAlternateBouncer(.4f),
- stepToAlternateBouncer(.6f),
- stepToAlternateBouncer(1f),
+ stepFromAlternateBouncer(0f, TransitionState.STARTED),
+ stepFromAlternateBouncer(.4f),
+ stepFromAlternateBouncer(.6f),
+ stepFromAlternateBouncer(1f),
),
testScope,
)
-
assertThat(scrimAlphas.size).isEqualTo(4)
scrimAlphas.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
index af8d8a8..795e68d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
@@ -30,6 +30,7 @@
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@@ -63,6 +64,7 @@
fingerprintPropertyRepository.supportsUdfps()
val deviceEntryBackgroundViewAlpha by
collectLastValue(underTest.deviceEntryBackgroundViewAlpha)
+ runCurrent()
// fade in
repository.sendTransitionStep(step(0f, TransitionState.STARTED))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
index daafe12..75994da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
@@ -39,6 +39,7 @@
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -78,6 +79,8 @@
fun scrimAlpha_runDimissFromKeyguard_shadeExpanded() =
testScope.runTest {
val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
+ runCurrent()
+
shadeRepository.setLockscreenShadeExpansion(1f)
whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
@@ -101,6 +104,8 @@
fun scrimAlpha_runDimissFromKeyguard_shadeNotExpanded() =
testScope.runTest {
val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
+ runCurrent()
+
shadeRepository.setLockscreenShadeExpansion(0f)
whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
@@ -123,6 +128,7 @@
fun scrimBehindAlpha_leaveShadeOpen() =
testScope.runTest {
val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
+ runCurrent()
sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(true)
@@ -146,6 +152,8 @@
fun scrimBehindAlpha_doNotLeaveShadeOpen() =
testScope.runTest {
val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
+ runCurrent()
+
keyguardTransitionRepository.sendTransitionSteps(
listOf(
step(0f, TransitionState.STARTED),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt
index dd542d4..471029b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt
@@ -20,18 +20,15 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
-import com.android.systemui.util.mockito.mock
+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.TestScope
import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -39,29 +36,10 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class DozingToLockscreenTransitionViewModelTest : SysuiTestCase() {
- private lateinit var testScope: TestScope
- private lateinit var underTest: DozingToLockscreenTransitionViewModel
- private lateinit var repository: FakeKeyguardTransitionRepository
-
- @Before
- fun setUp() {
- testScope = TestScope()
- repository = FakeKeyguardTransitionRepository()
- underTest =
- DozingToLockscreenTransitionViewModel(
- interactor =
- KeyguardTransitionInteractorFactory.create(
- scope = testScope.backgroundScope,
- repository = repository,
- )
- .keyguardTransitionInteractor,
- animationFlow =
- KeyguardTransitionAnimationFlow(
- scope = testScope.backgroundScope,
- logger = mock()
- ),
- )
- }
+ val kosmos = testKosmos()
+ val testScope = kosmos.testScope
+ val repository = kosmos.fakeKeyguardTransitionRepository
+ val underTest = kosmos.dozingToLockscreenTransitionViewModel
@Test
fun deviceEntryParentViewShows() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt
index a105008..1c9c942 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt
@@ -31,6 +31,7 @@
import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@@ -52,6 +53,7 @@
val pixels = -100f
val enterFromTopTranslationY by
collectLastValue(underTest.enterFromTopTranslationY(pixels.toInt()))
+ runCurrent()
// The animation should only start > .4f way through
repository.sendTransitionStep(step(0f, TransitionState.STARTED))
@@ -72,6 +74,7 @@
fun enterFromTopAnimationAlpha() =
testScope.runTest {
val enterFromTopAnimationAlpha by collectLastValue(underTest.enterFromTopAnimationAlpha)
+ runCurrent()
// The animation should only start > .4f way through
repository.sendTransitionStep(step(0f, TransitionState.STARTED))
@@ -92,6 +95,7 @@
testScope.runTest {
val deviceEntryBackgroundViewAlpha by
collectLastValue(underTest.deviceEntryBackgroundViewAlpha)
+ runCurrent()
// immediately 0f
repository.sendTransitionStep(step(0f, TransitionState.STARTED))
@@ -113,6 +117,7 @@
fingerprintPropertyRepository.supportsUdfps()
biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha)
+ runCurrent()
// animation doesn't start until the end
repository.sendTransitionStep(step(0f, TransitionState.STARTED))
@@ -137,6 +142,7 @@
fingerprintPropertyRepository.supportsRearFps()
biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha)
+ runCurrent()
// animation doesn't start until the end
repository.sendTransitionStep(step(0f, TransitionState.STARTED))
@@ -161,6 +167,7 @@
fingerprintPropertyRepository.supportsUdfps()
biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha)
+ runCurrent()
// animation doesn't start until the end
repository.sendTransitionStep(step(0f, TransitionState.STARTED))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelTest.kt
index 5e62317..1912987 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelTest.kt
@@ -30,6 +30,7 @@
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@@ -51,6 +52,7 @@
testScope.runTest {
val deviceEntryBackgroundViewAlpha by
collectLastValue(underTest.deviceEntryBackgroundViewAlpha)
+ runCurrent()
// immediately 0f
keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
@@ -72,6 +74,7 @@
fingerprintPropertyRepository.supportsUdfps()
biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha)
+ runCurrent()
// immediately 1f
keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
@@ -96,6 +99,7 @@
fingerprintPropertyRepository.supportsRearFps()
biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha)
+ runCurrent()
// no updates
keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
@@ -120,6 +124,7 @@
fingerprintPropertyRepository.supportsUdfps()
biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha)
+ runCurrent()
// no updates
keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt
index 9729022..c55c27c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt
@@ -30,6 +30,7 @@
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@@ -54,6 +55,7 @@
fingerprintPropertyRepository.supportsUdfps()
val deviceEntryBackgroundViewAlpha by
collectLastValue(underTest.deviceEntryBackgroundViewAlpha)
+ runCurrent()
// immediately 0f
keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
@@ -75,6 +77,7 @@
fingerprintPropertyRepository.supportsUdfps()
biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha)
+ runCurrent()
keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
@@ -92,6 +95,7 @@
fingerprintPropertyRepository.supportsRearFps()
biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha)
+ runCurrent()
// animation doesn't start until the end
keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
@@ -116,6 +120,7 @@
fingerprintPropertyRepository.supportsUdfps()
biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha)
+ runCurrent()
keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
assertThat(deviceEntryParentViewAlpha).isNull()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt
index 2c6436e..0796af0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt
@@ -30,6 +30,7 @@
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@@ -71,6 +72,7 @@
testScope.runTest {
fingerprintPropertyRepository.supportsUdfps()
val bgViewAlpha by collectLastValue(underTest.deviceEntryBackgroundViewAlpha)
+ runCurrent()
// immediately 1f
keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index 2f35380..5996502 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -64,8 +64,6 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.bluetooth.BroadcastDialogController
import com.android.systemui.broadcast.BroadcastSender
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.media.controls.MediaTestUtils
import com.android.systemui.media.controls.models.GutsViewHolder
import com.android.systemui.media.controls.models.player.MediaAction
@@ -227,11 +225,6 @@
@Mock private lateinit var recProgressBar2: SeekBar
@Mock private lateinit var recProgressBar3: SeekBar
private var shouldShowBroadcastButton: Boolean = false
- private val fakeFeatureFlag =
- FakeFeatureFlags().apply {
- this.set(Flags.UMO_SURFACE_RIPPLE, false)
- this.set(Flags.UMO_TURBULENCE_NOISE, false)
- }
@Mock private lateinit var globalSettings: GlobalSettings
@Mock private lateinit var mediaFlags: MediaFlags
@@ -275,7 +268,6 @@
activityIntentHelper,
lockscreenUserManager,
broadcastDialogController,
- fakeFeatureFlag,
globalSettings,
mediaFlags,
) {
@@ -2397,8 +2389,7 @@
}
@Test
- fun onButtonClick_touchRippleFlagEnabled_playsTouchRipple() {
- fakeFeatureFlag.set(Flags.UMO_SURFACE_RIPPLE, true)
+ fun onButtonClick_playsTouchRipple() {
val semanticActions =
MediaButton(
playOrPause =
@@ -2419,31 +2410,7 @@
}
@Test
- fun onButtonClick_touchRippleFlagDisabled_doesNotPlayTouchRipple() {
- fakeFeatureFlag.set(Flags.UMO_SURFACE_RIPPLE, false)
- val semanticActions =
- MediaButton(
- playOrPause =
- MediaAction(
- icon = null,
- action = {},
- contentDescription = "play",
- background = null
- )
- )
- val data = mediaData.copy(semanticActions = semanticActions)
- player.attachPlayer(viewHolder)
- player.bindPlayer(data, KEY)
-
- viewHolder.actionPlayPause.callOnClick()
-
- assertThat(viewHolder.multiRippleView.ripples.size).isEqualTo(0)
- }
-
- @Test
fun playTurbulenceNoise_finishesAfterDuration() {
- fakeFeatureFlag.set(Flags.UMO_TURBULENCE_NOISE, true)
-
val semanticActions =
MediaButton(
playOrPause =
@@ -2474,8 +2441,6 @@
@Test
fun playTurbulenceNoise_whenPlaybackStateIsNotPlaying_doesNotPlayTurbulence() {
- fakeFeatureFlag.set(Flags.UMO_TURBULENCE_NOISE, true)
-
val semanticActions =
MediaButton(
custom0 =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
index 73885f4..6be9275 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
@@ -104,18 +104,19 @@
private lateinit var dreamOverlayCallback:
ArgumentCaptor<(DreamOverlayStateController.Callback)>
@JvmField @Rule val mockito = MockitoJUnit.rule()
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
private lateinit var mediaHierarchyManager: MediaHierarchyManager
private lateinit var isQsBypassingShade: MutableStateFlow<Boolean>
private lateinit var mediaFrame: ViewGroup
private val configurationController = FakeConfigurationController()
- private val communalRepository = FakeCommunalRepository(isCommunalEnabled = true)
+ private val communalRepository =
+ FakeCommunalRepository(applicationScope = testScope.backgroundScope)
private val communalInteractor =
CommunalInteractorFactory.create(communalRepository = communalRepository).communalInteractor
private val settings = FakeSettings()
private lateinit var testableLooper: TestableLooper
private lateinit var fakeHandler: FakeHandler
- private val testDispatcher = StandardTestDispatcher()
- private val testScope = TestScope(testDispatcher)
@Before
fun setup() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
index 45f0a8c6..44c411f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
@@ -66,6 +66,11 @@
private const val DEFAULT_PACKAGE_NAME = "com.media.projection.test"
private val DEFAULT_USER_HANDLE = UserHandle.getUserHandleForUid(UserHandle.myUserId())
- private val DEFAULT_INFO = MediaProjectionInfo(DEFAULT_PACKAGE_NAME, DEFAULT_USER_HANDLE)
+ private val DEFAULT_INFO =
+ MediaProjectionInfo(
+ DEFAULT_PACKAGE_NAME,
+ DEFAULT_USER_HANDLE,
+ /* launchCookie = */ null
+ )
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt
new file mode 100644
index 0000000..60eb3ae
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs
+
+import android.testing.AndroidTestingRunner
+import android.view.KeyEvent
+import android.view.KeyEvent.KEYCODE_DPAD_LEFT
+import android.view.View
+import androidx.core.util.Consumer
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class LeftRightArrowPressedListenerTest : SysuiTestCase() {
+
+ private lateinit var underTest: LeftRightArrowPressedListener
+ private val callback =
+ object : Consumer<Int> {
+ var lastValue: Int? = null
+
+ override fun accept(keyCode: Int?) {
+ lastValue = keyCode
+ }
+ }
+
+ private val view = View(context)
+
+ @Before
+ fun setUp() {
+ underTest = LeftRightArrowPressedListener.createAndRegisterListenerForView(view)
+ underTest.setArrowKeyPressedListener(callback)
+ }
+
+ @Test
+ fun shouldTriggerCallback_whenArrowUpReceived_afterArrowDownReceived() {
+ underTest.sendKey(KeyEvent.ACTION_DOWN, KEYCODE_DPAD_LEFT)
+
+ underTest.sendKey(KeyEvent.ACTION_UP, KEYCODE_DPAD_LEFT)
+
+ assertThat(callback.lastValue).isEqualTo(KEYCODE_DPAD_LEFT)
+ }
+
+ @Test
+ fun shouldNotTriggerCallback_whenKeyUpReceived_ifKeyDownNotReceived() {
+ underTest.sendKey(KeyEvent.ACTION_UP, KEYCODE_DPAD_LEFT)
+
+ assertThat(callback.lastValue).isNull()
+ }
+
+ @Test
+ fun shouldNotTriggerCallback_whenKeyUpReceived_ifKeyDownWasRepeated() {
+ underTest.sendKeyWithRepeat(KeyEvent.ACTION_UP, KEYCODE_DPAD_LEFT, repeat = 2)
+ underTest.sendKey(KeyEvent.ACTION_UP, KEYCODE_DPAD_LEFT)
+
+ assertThat(callback.lastValue).isNull()
+ }
+
+ @Test
+ fun shouldNotTriggerCallback_whenKeyUpReceived_ifKeyDownReceivedBeforeLosingFocus() {
+ underTest.sendKey(KeyEvent.ACTION_DOWN, KEYCODE_DPAD_LEFT)
+ underTest.onFocusChange(view, hasFocus = false)
+ underTest.onFocusChange(view, hasFocus = true)
+
+ underTest.sendKey(KeyEvent.ACTION_UP, KEYCODE_DPAD_LEFT)
+
+ assertThat(callback.lastValue).isNull()
+ }
+
+ private fun LeftRightArrowPressedListener.sendKey(action: Int, keyCode: Int) {
+ onKey(view, keyCode, KeyEvent(action, keyCode))
+ }
+
+ private fun LeftRightArrowPressedListener.sendKeyWithRepeat(
+ action: Int,
+ keyCode: Int,
+ repeat: Int
+ ) {
+ val keyEvent =
+ KeyEvent(
+ /* downTime= */ 0L,
+ /* eventTime= */ 0L,
+ /* action= */ action,
+ /* code= */ KEYCODE_DPAD_LEFT,
+ /* repeat= */ repeat
+ )
+ onKey(view, keyCode, keyEvent)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt
index db9e548..8ef3f57 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt
@@ -2,11 +2,13 @@
import android.content.Context
import android.testing.AndroidTestingRunner
-import android.view.KeyEvent
import android.view.View
import android.widget.Scroller
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.PageIndicator.PageScrollActionListener
+import com.android.systemui.qs.PageIndicator.PageScrollActionListener.LEFT
+import com.android.systemui.qs.PageIndicator.PageScrollActionListener.RIGHT
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -22,7 +24,7 @@
class PagedTileLayoutTest : SysuiTestCase() {
@Mock private lateinit var pageIndicator: PageIndicator
- @Captor private lateinit var captor: ArgumentCaptor<View.OnKeyListener>
+ @Captor private lateinit var captor: ArgumentCaptor<PageScrollActionListener>
private lateinit var pageTileLayout: TestPagedTileLayout
private lateinit var scroller: Scroller
@@ -32,7 +34,7 @@
MockitoAnnotations.initMocks(this)
pageTileLayout = TestPagedTileLayout(mContext)
pageTileLayout.setPageIndicator(pageIndicator)
- verify(pageIndicator).setOnKeyListener(captor.capture())
+ verify(pageIndicator).setPageScrollActionListener(captor.capture())
setViewWidth(pageTileLayout, width = PAGE_WIDTH)
scroller = pageTileLayout.mScroller
}
@@ -43,28 +45,27 @@
}
@Test
- fun scrollsRight_afterRightArrowPressed_whenFocusOnPagerIndicator() {
+ fun scrollsRight_afterRightScrollActionTriggered() {
pageTileLayout.currentPageIndex = 0
- sendUpEvent(KeyEvent.KEYCODE_DPAD_RIGHT)
+ sendScrollActionEvent(RIGHT)
assertThat(scroller.isFinished).isFalse() // aka we're scrolling
assertThat(scroller.finalX).isEqualTo(scroller.currX + PAGE_WIDTH)
}
@Test
- fun scrollsLeft_afterLeftArrowPressed_whenFocusOnPagerIndicator() {
+ fun scrollsLeft_afterLeftScrollActionTriggered() {
pageTileLayout.currentPageIndex = 1 // we won't scroll left if we're on the first page
- sendUpEvent(KeyEvent.KEYCODE_DPAD_LEFT)
+ sendScrollActionEvent(LEFT)
assertThat(scroller.isFinished).isFalse() // aka we're scrolling
assertThat(scroller.finalX).isEqualTo(scroller.currX - PAGE_WIDTH)
}
- private fun sendUpEvent(keyCode: Int) {
- val event = KeyEvent(KeyEvent.ACTION_UP, keyCode)
- captor.value.onKey(pageIndicator, keyCode, event)
+ private fun sendScrollActionEvent(@PageScrollActionListener.Direction direction: Int) {
+ captor.value.onScrollActionTriggered(direction)
}
/**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java
index 6cad985..6e2f5db2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java
@@ -31,8 +31,8 @@
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.test.filters.SmallTest;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
import org.junit.Before;
import org.junit.Test;
@@ -136,6 +136,7 @@
AccessibilityNodeInfoCompat.AccessibilityActionCompat action =
getActionForId(mInfo, AccessibilityNodeInfo.ACTION_CLICK);
assertThat(action.getLabel().toString()).contains(expectedString);
+ assertThat(mInfo.isClickable()).isTrue();
}
@Test
@@ -152,10 +153,11 @@
AccessibilityNodeInfoCompat.AccessibilityActionCompat action =
getActionForId(mInfo, AccessibilityNodeInfo.ACTION_CLICK);
assertThat(action.getLabel().toString()).contains(expectedString);
+ assertThat(mInfo.isClickable()).isTrue();
}
@Test
- public void testNoClickAction() {
+ public void testNoClickActionAndNotClickable() {
mView.setTag(mHolder);
when(mHolder.canTakeAccessibleAction()).thenReturn(true);
when(mHolder.canAdd()).thenReturn(false);
@@ -167,6 +169,7 @@
AccessibilityNodeInfoCompat.AccessibilityActionCompat action =
getActionForId(mInfo, AccessibilityNodeInfo.ACTION_CLICK);
assertThat(action).isNull();
+ assertThat(mInfo.isClickable()).isFalse();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
index c711806..1cb3bf6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
@@ -393,23 +393,23 @@
}
@Test
- fun backgroundAlpha_inSplitShade_followsExpansion_with_0_99_delay() {
+ fun backgroundAlpha_inSplitShade_followsExpansion_with_0_15_delay() {
val underTest = utils.footerActionsViewModel()
val floatTolerance = 0.01f
underTest.onQuickSettingsExpansionChanged(0f, isInSplitShade = true)
assertThat(underTest.backgroundAlpha.value).isEqualTo(0f)
- underTest.onQuickSettingsExpansionChanged(0.5f, isInSplitShade = true)
+ underTest.onQuickSettingsExpansionChanged(0.1f, isInSplitShade = true)
assertThat(underTest.backgroundAlpha.value).isEqualTo(0f)
- underTest.onQuickSettingsExpansionChanged(0.9f, isInSplitShade = true)
+ underTest.onQuickSettingsExpansionChanged(0.14f, isInSplitShade = true)
assertThat(underTest.backgroundAlpha.value).isEqualTo(0f)
- underTest.onQuickSettingsExpansionChanged(0.991f, isInSplitShade = true)
+ underTest.onQuickSettingsExpansionChanged(0.235f, isInSplitShade = true)
assertThat(underTest.backgroundAlpha.value).isWithin(floatTolerance).of(0.1f)
- underTest.onQuickSettingsExpansionChanged(0.995f, isInSplitShade = true)
+ underTest.onQuickSettingsExpansionChanged(0.575f, isInSplitShade = true)
assertThat(underTest.backgroundAlpha.value).isWithin(floatTolerance).of(0.5f)
underTest.onQuickSettingsExpansionChanged(1f, isInSplitShade = true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt
index 33066d2..9563ceb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt
@@ -162,13 +162,15 @@
@Test
fun testStartSettingsActivity_activityLaunched_dialogDismissed() {
- `when`(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
- bluetoothTileDialogViewModel.showDialog(context, null)
+ testScope.runTest {
+ `when`(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
+ bluetoothTileDialogViewModel.showDialog(context, null)
- val clickedView = View(context)
- bluetoothTileDialogViewModel.onPairNewDeviceClicked(clickedView)
+ val clickedView = View(context)
+ bluetoothTileDialogViewModel.onPairNewDeviceClicked(clickedView)
- verify(uiEventLogger).log(BluetoothTileDialogUiEvent.PAIR_NEW_DEVICE_CLICKED)
- verify(activityStarter).postStartActivityDismissingKeyguard(any(), anyInt(), nullable())
+ verify(uiEventLogger).log(BluetoothTileDialogUiEvent.PAIR_NEW_DEVICE_CLICKED)
+ verify(activityStarter).postStartActivityDismissingKeyguard(any(), anyInt(), nullable())
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
index 9941661..543f6c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
@@ -16,17 +16,13 @@
package com.android.systemui.scene.shared.flag
+import android.platform.test.annotations.DisableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
import com.android.systemui.SysuiTestCase
-import com.android.systemui.compose.ComposeFacade
-import com.android.systemui.flags.Flags
-import com.android.systemui.flags.setFlagValue
-import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
-import com.android.systemui.media.controls.util.MediaInSceneContainerFlag
+import com.android.systemui.flags.EnableSceneContainer
import com.google.common.truth.Truth
-import org.junit.Assume
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -34,45 +30,17 @@
@RunWith(AndroidJUnit4::class)
internal class SceneContainerFlagsTest : SysuiTestCase() {
- @Before
- fun setUp() {
- // TODO(b/283300105): remove this reflection setting once the hard-coded
- // Flags.SCENE_CONTAINER_ENABLED is no longer needed.
- val field = Flags::class.java.getField("SCENE_CONTAINER_ENABLED")
- field.isAccessible = true
- field.set(null, true) // note: this does not work with multivalent tests
- }
-
- private fun setAconfigFlagsEnabled(enabled: Boolean) {
- listOf(
- com.android.systemui.Flags.FLAG_SCENE_CONTAINER,
- com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
- KeyguardShadeMigrationNssl.FLAG_NAME,
- MediaInSceneContainerFlag.FLAG_NAME,
- )
- .forEach { flagName -> mSetFlagsRule.setFlagValue(flagName, enabled) }
- }
-
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER)
fun isNotEnabled_withoutAconfigFlags() {
- setAconfigFlagsEnabled(false)
Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(false)
Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(false)
}
@Test
- fun isEnabled_withAconfigFlags_withCompose() {
- Assume.assumeTrue(ComposeFacade.isComposeAvailable())
- setAconfigFlagsEnabled(true)
+ @EnableSceneContainer
+ fun isEnabled_withAconfigFlags() {
Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(true)
Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(true)
}
-
- @Test
- fun isNotEnabled_withAconfigFlags_withoutCompose() {
- Assume.assumeFalse(ComposeFacade.isComposeAvailable())
- setAconfigFlagsEnabled(true)
- Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(false)
- Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(false)
- }
}
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 5569ca9..b7a9ea7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.shade
+import android.os.PowerManager
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.ViewUtils
@@ -43,6 +44,8 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@@ -52,6 +55,7 @@
@Mock private lateinit var communalViewModel: CommunalViewModel
@Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@Mock private lateinit var shadeInteractor: ShadeInteractor
+ @Mock private lateinit var powerManager: PowerManager
private lateinit var containerView: View
private lateinit var testableLooper: TestableLooper
@@ -76,7 +80,8 @@
communalInteractor,
communalViewModel,
keyguardTransitionInteractor,
- shadeInteractor
+ shadeInteractor,
+ powerManager
)
testableLooper = TestableLooper.get(this)
@@ -90,14 +95,14 @@
}
@Test
- fun isEnabled_interactorEnabled_returnsTrue() {
+ fun isEnabled_interactorEnabled_interceptsTouches() {
communalRepository.setIsCommunalEnabled(true)
assertThat(underTest.isEnabled()).isTrue()
}
@Test
- fun isEnabled_interactorDisabled_returnsFalse() {
+ fun isEnabled_interactorDisabled_doesNotIntercept() {
communalRepository.setIsCommunalEnabled(false)
assertThat(underTest.isEnabled()).isFalse()
@@ -120,7 +125,7 @@
}
@Test
- fun onTouchEvent_touchInsideGestureRegion_returnsTrue() {
+ fun onTouchEvent_touchInsideGestureRegion_interceptsTouches() {
// Communal is open.
communalRepository.setDesiredScene(CommunalSceneKey.Communal)
@@ -131,7 +136,7 @@
}
@Test
- fun onTouchEvent_subsequentTouchesAfterGestureStart_returnsTrue() {
+ fun onTouchEvent_subsequentTouchesAfterGestureStart_interceptsTouches() {
// Communal is open.
communalRepository.setDesiredScene(CommunalSceneKey.Communal)
@@ -146,7 +151,7 @@
}
@Test
- fun onTouchEvent_communalOpen_returnsTrue() {
+ fun onTouchEvent_communalOpen_interceptsTouches() {
// Communal is open.
communalRepository.setDesiredScene(CommunalSceneKey.Communal)
@@ -155,10 +160,12 @@
// Touch events are intercepted.
assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
+ // User activity sent to PowerManager.
+ verify(powerManager).userActivity(any(), any(), any())
}
@Test
- fun onTouchEvent_communalAndBouncerShowing_returnsFalse() {
+ fun onTouchEvent_communalAndBouncerShowing_doesNotIntercept() {
// Communal is open.
communalRepository.setDesiredScene(CommunalSceneKey.Communal)
@@ -170,10 +177,12 @@
// Touch events are not intercepted.
assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
+ // User activity is not sent to PowerManager.
+ verify(powerManager, times(0)).userActivity(any(), any(), any())
}
@Test
- fun onTouchEvent_communalAndShadeShowing_returnsFalse() {
+ fun onTouchEvent_communalAndShadeShowing_doesNotIntercept() {
// Communal is open.
communalRepository.setDesiredScene(CommunalSceneKey.Communal)
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 b239482..b3e386e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -113,6 +113,7 @@
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.media.controls.pipeline.MediaDataManager;
import com.android.systemui.media.controls.ui.KeyguardMediaController;
import com.android.systemui.media.controls.ui.MediaHierarchyManager;
@@ -125,7 +126,6 @@
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.qs.QSFragmentLegacy;
import com.android.systemui.res.R;
-import com.android.systemui.scene.SceneTestUtils;
import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.shade.data.repository.FakeShadeRepository;
import com.android.systemui.shade.data.repository.ShadeAnimationRepository;
@@ -181,7 +181,6 @@
import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
import com.android.systemui.statusbar.phone.TapAgainViewController;
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -191,6 +190,7 @@
import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
+import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
@@ -355,8 +355,8 @@
protected FakeKeyguardRepository mFakeKeyguardRepository;
protected KeyguardInteractor mKeyguardInteractor;
protected ShadeAnimationInteractor mShadeAnimationInteractor;
- protected SceneTestUtils mUtils = new SceneTestUtils(this);
- protected TestScope mTestScope = mUtils.getTestScope();
+ protected KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+ protected TestScope mTestScope = mKosmos.getTestScope();
protected ShadeInteractor mShadeInteractor;
protected PowerInteractor mPowerInteractor;
protected NotificationPanelViewController.TouchHandler mTouchHandler;
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 437d00a..11da237 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -23,8 +23,6 @@
import static com.google.common.truth.Truth.assertThat;
-import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -38,6 +36,8 @@
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
import android.app.IActivityManager;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
@@ -56,6 +56,8 @@
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
+import com.android.systemui.communal.domain.interactor.CommunalInteractor;
+import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlagsClassic;
@@ -67,14 +69,15 @@
import com.android.systemui.keyguard.data.repository.InWindowLauncherUnlockAnimationRepository;
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
+import com.android.systemui.keyguard.domain.interactor.GlanceableHubTransitions;
import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.res.R;
import com.android.systemui.scene.FakeWindowRootViewComponent;
-import com.android.systemui.scene.SceneTestUtils;
import com.android.systemui.scene.data.repository.SceneContainerRepository;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
@@ -94,11 +97,11 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
+import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
@@ -128,7 +131,6 @@
@Spy private final NotificationShadeWindowView mNotificationShadeWindowView = spy(
new NotificationShadeWindowView(mContext, null));
@Mock private IActivityManager mActivityManager;
- @Mock private SysuiStatusBarStateController mStatusBarStateController;
@Mock private ConfigurationController mConfigurationController;
@Mock private KeyguardViewMediator mKeyguardViewMediator;
@Mock private KeyguardBypassController mKeyguardBypassController;
@@ -137,7 +139,6 @@
@Mock private DumpManager mDumpManager;
@Mock private KeyguardSecurityModel mKeyguardSecurityModel;
@Mock private KeyguardStateController mKeyguardStateController;
- @Mock private ScreenOffAnimationController mScreenOffAnimationController;
@Mock private AuthController mAuthController;
@Mock private ShadeWindowLogger mShadeWindowLogger;
@Mock private SelectedUserInteractor mSelectedUserInteractor;
@@ -149,14 +150,16 @@
private final Executor mMainExecutor = MoreExecutors.directExecutor();
private final Executor mBackgroundExecutor = MoreExecutors.directExecutor();
- private final SceneTestUtils mUtils = new SceneTestUtils(this);
- private final TestScope mTestScope = mUtils.getTestScope();
+ private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+ private final TestScope mTestScope = mKosmos.getTestScope();
private ShadeInteractor mShadeInteractor;
private NotificationShadeWindowControllerImpl mNotificationShadeWindowController;
private float mPreferredRefreshRate = -1;
private FromLockscreenTransitionInteractor mFromLockscreenTransitionInteractor;
private FromPrimaryBouncerTransitionInteractor mFromPrimaryBouncerTransitionInteractor;
+ private ScreenOffAnimationController mScreenOffAnimationController;
+ private SysuiStatusBarStateController mStatusBarStateController;
@Before
public void setUp() {
@@ -175,19 +178,18 @@
FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic();
FakeShadeRepository shadeRepository = new FakeShadeRepository();
- PowerInteractor powerInteractor = mUtils.powerInteractor(
- mUtils.getPowerRepository(),
- mUtils.falsingCollector(),
- mScreenOffAnimationController,
- mStatusBarStateController);
+ mScreenOffAnimationController = mKosmos.getScreenOffAnimationController();
+ mStatusBarStateController = spy(mKosmos.getStatusBarStateController());
+ PowerInteractor powerInteractor = mKosmos.getPowerInteractor();
SceneInteractor sceneInteractor = new SceneInteractor(
mTestScope.getBackgroundScope(),
new SceneContainerRepository(
mTestScope.getBackgroundScope(),
- mUtils.fakeSceneContainerConfig()),
+ mKosmos.getFakeSceneContainerConfig()),
powerInteractor,
- mock(SceneLogger.class));
+ mock(SceneLogger.class),
+ mKosmos.getDeviceUnlockedInteractor());
FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository();
FakeSceneContainerFlags sceneContainerFlags = new FakeSceneContainerFlags();
@@ -200,6 +202,8 @@
new ConfigurationInteractor(configurationRepository),
shadeRepository,
() -> sceneInteractor);
+ CommunalInteractor communalInteractor =
+ CommunalInteractorFactory.create().getCommunalInteractor();
FakeKeyguardTransitionRepository keyguardTransitionRepository =
new FakeKeyguardTransitionRepository();
@@ -216,10 +220,18 @@
keyguardTransitionRepository,
keyguardTransitionInteractor,
mTestScope.getBackgroundScope(),
+ mKosmos.getTestDispatcher(),
+ mKosmos.getTestDispatcher(),
keyguardInteractor,
featureFlags,
shadeRepository,
powerInteractor,
+ new GlanceableHubTransitions(
+ mTestScope,
+ keyguardTransitionInteractor,
+ keyguardTransitionRepository,
+ communalInteractor
+ ),
() ->
new InWindowLauncherUnlockAnimationInteractor(
new InWindowLauncherUnlockAnimationRepository(),
@@ -234,6 +246,8 @@
keyguardTransitionRepository,
keyguardTransitionInteractor,
mTestScope.getBackgroundScope(),
+ mKosmos.getTestDispatcher(),
+ mKosmos.getTestDispatcher(),
keyguardInteractor,
featureFlags,
mKeyguardSecurityModel,
@@ -521,8 +535,8 @@
@Test
public void udfpsEnrolled_minAndMaxRefreshRateSetToPreferredRefreshRate() {
- // GIVEN udfps is enrolled
- when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(true);
+ // GIVEN optical udfps is enrolled
+ when(mAuthController.isOpticalUdfpsEnrolled(anyInt())).thenReturn(true);
// WHEN keyguard is showing
setKeyguardShowing();
@@ -536,9 +550,9 @@
}
@Test
- public void udfpsNotEnrolled_refreshRateUnset() {
+ public void opticalUdfpsNotEnrolled_refreshRateUnset() {
// GIVEN udfps is NOT enrolled
- when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(false);
+ when(mAuthController.isOpticalUdfpsEnrolled(anyInt())).thenReturn(false);
// WHEN keyguard is showing
setKeyguardShowing();
@@ -553,8 +567,8 @@
@Test
public void keyguardNotShowing_refreshRateUnset() {
- // GIVEN UDFPS is enrolled
- when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(true);
+ // GIVEN optical UDFPS is enrolled
+ when(mAuthController.isOpticalUdfpsEnrolled(anyInt())).thenReturn(true);
// WHEN keyguard is NOT showing
mNotificationShadeWindowController.setKeyguardShowing(false);
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 ee7c6c8..a11839c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -17,7 +17,6 @@
package com.android.systemui.shade
import android.content.Context
-import android.os.Handler
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.KeyEvent
@@ -25,50 +24,29 @@
import android.view.View
import android.view.ViewGroup
import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardMessageAreaController
import com.android.keyguard.KeyguardSecurityContainerController
-import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.LockIconViewController
import com.android.keyguard.dagger.KeyguardBouncerComponent
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
-import com.android.systemui.bouncer.data.repository.BouncerMessageRepositoryImpl
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor
-import com.android.systemui.bouncer.domain.interactor.CountDownTimerUtil
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.bouncer.ui.BouncerView
-import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
-import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.bouncer.ui.binder.BouncerViewBinder
import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.compose.ComposeFacade.isComposeAvailable
import com.android.systemui.dock.DockManager
import com.android.systemui.dump.DumpManager
-import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED
import com.android.systemui.flags.Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION
import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON
import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_FEATURES
-import com.android.systemui.flags.SystemPropertiesHelper
import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler
-import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
-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.data.repository.FakeTrustRepository
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies
-import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
-import com.android.systemui.log.BouncerLogger
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
import com.android.systemui.statusbar.DragDownHelper
@@ -86,16 +64,15 @@
import com.android.systemui.statusbar.phone.DozeServiceHost
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
-import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.window.StatusBarWindowStateController
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
-import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.kotlin.JavaAdapter
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
+import java.util.Optional
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.test.TestScope
@@ -110,9 +87,8 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-import java.util.Optional
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -133,7 +109,6 @@
@Mock private lateinit var shadeLogger: ShadeLogger
@Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var ambientState: AmbientState
- @Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
@Mock private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController
@Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
@Mock private lateinit var statusBarWindowStateController: StatusBarWindowStateController
@@ -156,8 +131,6 @@
@Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@Mock lateinit var dragDownHelper: DragDownHelper
@Mock lateinit var mSelectedUserInteractor: SelectedUserInteractor
- @Mock
- lateinit var primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel
@Mock lateinit var sysUIKeyEventHandler: SysUIKeyEventHandler
@Mock lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
@Mock lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
@@ -224,55 +197,18 @@
dumpManager,
pulsingGestureListener,
mLockscreenHostedDreamGestureListener,
- keyguardBouncerViewModel,
- keyguardBouncerComponentFactory,
- mock(KeyguardMessageAreaController.Factory::class.java),
keyguardTransitionInteractor,
- primaryBouncerToGoneTransitionViewModel,
mGlanceableHubContainerController,
notificationLaunchAnimationInteractor,
featureFlagsClassic,
fakeClock,
- BouncerMessageInteractor(
- repository = BouncerMessageRepositoryImpl(),
- userRepository = FakeUserRepository(),
- countDownTimerUtil = mock(CountDownTimerUtil::class.java),
- updateMonitor = mock(KeyguardUpdateMonitor::class.java),
- biometricSettingsRepository = FakeBiometricSettingsRepository(),
- applicationScope = testScope.backgroundScope,
- trustRepository = FakeTrustRepository(),
- systemPropertiesHelper = mock(SystemPropertiesHelper::class.java),
- primaryBouncerInteractor =
- PrimaryBouncerInteractor(
- FakeKeyguardBouncerRepository(),
- mock(BouncerView::class.java),
- mock(Handler::class.java),
- mock(KeyguardStateController::class.java),
- mock(KeyguardSecurityModel::class.java),
- mock(PrimaryBouncerCallbackInteractor::class.java),
- mock(FalsingCollector::class.java),
- mock(DismissCallbackRegistry::class.java),
- context,
- mock(KeyguardUpdateMonitor::class.java),
- FakeTrustRepository(),
- testScope.backgroundScope,
- mSelectedUserInteractor,
- mock(DeviceEntryFaceAuthInteractor::class.java)
- ),
- facePropertyRepository = FakeFacePropertyRepository(),
- deviceEntryFingerprintAuthRepository =
- FakeDeviceEntryFingerprintAuthRepository(),
- faceAuthRepository = FakeDeviceEntryFaceAuthRepository(),
- securityModel = mock(KeyguardSecurityModel::class.java),
- ),
- BouncerLogger(logcatLogBuffer("BouncerLog")),
sysUIKeyEventHandler,
quickSettingsController,
primaryBouncerInteractor,
alternateBouncerInteractor,
- mSelectedUserInteractor,
- { mock (JavaAdapter::class.java )},
+ { mock(JavaAdapter::class.java) },
{ mock(AlternateBouncerDependencies::class.java) },
+ mock(BouncerViewBinder::class.java)
)
underTest.setupExpandedStatusBar()
underTest.setDragDownHelper(dragDownHelper)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 33d60ea..0c4bf81 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -15,51 +15,28 @@
*/
package com.android.systemui.shade
-import android.os.Handler
import android.os.SystemClock
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.MotionEvent
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardMessageAreaController
import com.android.keyguard.KeyguardSecurityContainerController
-import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.LockIconViewController
import com.android.keyguard.dagger.KeyguardBouncerComponent
import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
-import com.android.systemui.bouncer.data.repository.BouncerMessageRepositoryImpl
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor
-import com.android.systemui.bouncer.domain.interactor.CountDownTimerUtil
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.bouncer.ui.BouncerView
-import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
-import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.dock.DockManager
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.flags.SystemPropertiesHelper
import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler
-import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
-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.data.repository.FakeTrustRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies
-import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
-import com.android.systemui.log.BouncerLogger
-import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
import com.android.systemui.statusbar.DragDownHelper
@@ -77,11 +54,8 @@
import com.android.systemui.statusbar.phone.DozeScrimController
import com.android.systemui.statusbar.phone.DozeServiceHost
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
-import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.window.StatusBarWindowStateController
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.kotlin.JavaAdapter
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
@@ -137,7 +111,6 @@
@Mock private lateinit var pulsingGestureListener: PulsingGestureListener
@Mock
private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener
- @Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
@Mock private lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
@Mock private lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
@Mock
@@ -150,10 +123,6 @@
@Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@Mock lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
@Mock lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
- @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
- @Mock
- private lateinit var primaryBouncerToGoneTransitionViewModel:
- PrimaryBouncerToGoneTransitionViewModel
@Captor
private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
@@ -217,55 +186,18 @@
dumpManager,
pulsingGestureListener,
mLockscreenHostedDreamGestureListener,
- keyguardBouncerViewModel,
- keyguardBouncerComponentFactory,
- Mockito.mock(KeyguardMessageAreaController.Factory::class.java),
keyguardTransitionInteractor,
- primaryBouncerToGoneTransitionViewModel,
mGlanceableHubContainerController,
NotificationLaunchAnimationInteractor(NotificationLaunchAnimationRepository()),
featureFlags,
FakeSystemClock(),
- BouncerMessageInteractor(
- repository = BouncerMessageRepositoryImpl(),
- userRepository = FakeUserRepository(),
- countDownTimerUtil = Mockito.mock(CountDownTimerUtil::class.java),
- updateMonitor = Mockito.mock(KeyguardUpdateMonitor::class.java),
- biometricSettingsRepository = FakeBiometricSettingsRepository(),
- applicationScope = testScope.backgroundScope,
- trustRepository = FakeTrustRepository(),
- systemPropertiesHelper = Mockito.mock(SystemPropertiesHelper::class.java),
- primaryBouncerInteractor =
- PrimaryBouncerInteractor(
- FakeKeyguardBouncerRepository(),
- Mockito.mock(BouncerView::class.java),
- Mockito.mock(Handler::class.java),
- Mockito.mock(KeyguardStateController::class.java),
- Mockito.mock(KeyguardSecurityModel::class.java),
- Mockito.mock(PrimaryBouncerCallbackInteractor::class.java),
- Mockito.mock(FalsingCollector::class.java),
- Mockito.mock(DismissCallbackRegistry::class.java),
- context,
- Mockito.mock(KeyguardUpdateMonitor::class.java),
- FakeTrustRepository(),
- testScope.backgroundScope,
- mSelectedUserInteractor,
- mock(),
- ),
- facePropertyRepository = FakeFacePropertyRepository(),
- deviceEntryFingerprintAuthRepository =
- FakeDeviceEntryFingerprintAuthRepository(),
- faceAuthRepository = FakeDeviceEntryFaceAuthRepository(),
- securityModel = Mockito.mock(KeyguardSecurityModel::class.java),
- ),
- BouncerLogger(logcatLogBuffer("BouncerLog")),
Mockito.mock(SysUIKeyEventHandler::class.java),
quickSettingsController,
primaryBouncerInteractor,
alternateBouncerInteractor,
- mSelectedUserInteractor,
{ Mockito.mock(JavaAdapter::class.java) },
{ Mockito.mock(AlternateBouncerDependencies::class.java) },
+ mock()
)
controller.setupExpandedStatusBar()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
index ea3caa3..697b05a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
@@ -26,6 +26,7 @@
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
@@ -167,10 +168,15 @@
}
@Test
- fun testLargeScreen_updateResources_splitShadeHeightIsSet() {
+ fun testLargeScreen_updateResources_refactorFlagOff_splitShadeHeightIsSetBasedOnResource() {
+ val headerResourceHeight = 20
+ val headerHelperHeight = 30
+ mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+ whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+ .thenReturn(headerHelperHeight)
overrideResource(R.bool.config_use_large_screen_shade_header, true)
overrideResource(R.dimen.qs_header_height, 10)
- overrideResource(R.dimen.large_screen_shade_header_height, 20)
+ overrideResource(R.dimen.large_screen_shade_header_height, headerResourceHeight)
// ensure the estimated height (would be 3 here) wouldn't impact this test case
overrideResource(R.dimen.large_screen_shade_header_min_height, 1)
@@ -180,7 +186,31 @@
val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
verify(view).applyConstraints(capture(captor))
- assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(20)
+ assertThat(captor.value.getHeight(R.id.split_shade_status_bar))
+ .isEqualTo(headerResourceHeight)
+ }
+
+ @Test
+ fun testLargeScreen_updateResources_refactorFlagOn_splitShadeHeightIsSetBasedOnHelper() {
+ val headerResourceHeight = 20
+ val headerHelperHeight = 30
+ mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+ whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+ .thenReturn(headerHelperHeight)
+ overrideResource(R.bool.config_use_large_screen_shade_header, true)
+ overrideResource(R.dimen.qs_header_height, 10)
+ overrideResource(R.dimen.large_screen_shade_header_height, headerResourceHeight)
+
+ // ensure the estimated height (would be 3 here) wouldn't impact this test case
+ overrideResource(R.dimen.large_screen_shade_header_min_height, 1)
+ overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 1)
+
+ underTest.updateResources()
+
+ val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
+ verify(view).applyConstraints(capture(captor))
+ assertThat(captor.value.getHeight(R.id.split_shade_status_bar))
+ .isEqualTo(headerHelperHeight)
}
@Test
@@ -416,10 +446,14 @@
}
@Test
- fun testLargeScreenLayout_qsAndNotifsTopMarginIsOfHeaderHeight() {
+ fun testLargeScreenLayout_refactorFlagOff_qsAndNotifsTopMarginIsOfHeaderHeightResource() {
+ mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
setLargeScreen()
- val largeScreenHeaderHeight = 100
- overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderHeight)
+ val largeScreenHeaderResourceHeight = 100
+ val largeScreenHeaderHelperHeight = 200
+ whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+ .thenReturn(largeScreenHeaderHelperHeight)
+ overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderResourceHeight)
// ensure the estimated height (would be 30 here) wouldn't impact this test case
overrideResource(R.dimen.large_screen_shade_header_min_height, 10)
@@ -428,9 +462,31 @@
underTest.updateResources()
assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
- .isEqualTo(largeScreenHeaderHeight)
+ .isEqualTo(largeScreenHeaderResourceHeight)
assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin)
- .isEqualTo(largeScreenHeaderHeight)
+ .isEqualTo(largeScreenHeaderResourceHeight)
+ }
+
+ @Test
+ fun testLargeScreenLayout_refactorFlagOn_qsAndNotifsTopMarginIsOfHeaderHeightHelper() {
+ mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+ setLargeScreen()
+ val largeScreenHeaderResourceHeight = 100
+ val largeScreenHeaderHelperHeight = 200
+ whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+ .thenReturn(largeScreenHeaderHelperHeight)
+ overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderResourceHeight)
+
+ // ensure the estimated height (would be 30 here) wouldn't impact this test case
+ overrideResource(R.dimen.large_screen_shade_header_min_height, 10)
+ overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 10)
+
+ underTest.updateResources()
+
+ assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
+ .isEqualTo(largeScreenHeaderHelperHeight)
+ assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin)
+ .isEqualTo(largeScreenHeaderHelperHeight)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
index c1bc303..e66251a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
@@ -26,6 +26,7 @@
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
@@ -166,10 +167,14 @@
}
@Test
- fun testLargeScreen_updateResources_splitShadeHeightIsSet() {
+ fun testLargeScreen_updateResources_refactorFlagOff_splitShadeHeightIsSet_basedOnResource() {
+ mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+ val helperHeight = 30
+ val resourceHeight = 20
+ whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(helperHeight)
overrideResource(R.bool.config_use_large_screen_shade_header, true)
overrideResource(R.dimen.qs_header_height, 10)
- overrideResource(R.dimen.large_screen_shade_header_height, 20)
+ overrideResource(R.dimen.large_screen_shade_header_height, resourceHeight)
// ensure the estimated height (would be 3 here) wouldn't impact this test case
overrideResource(R.dimen.large_screen_shade_header_min_height, 1)
@@ -179,7 +184,28 @@
val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
verify(view).applyConstraints(capture(captor))
- assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(20)
+ assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(resourceHeight)
+ }
+
+ @Test
+ fun testLargeScreen_updateResources_refactorFlagOn_splitShadeHeightIsSet_basedOnHelper() {
+ mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+ val helperHeight = 30
+ val resourceHeight = 20
+ whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(helperHeight)
+ overrideResource(R.bool.config_use_large_screen_shade_header, true)
+ overrideResource(R.dimen.qs_header_height, 10)
+ overrideResource(R.dimen.large_screen_shade_header_height, resourceHeight)
+
+ // ensure the estimated height (would be 3 here) wouldn't impact this test case
+ overrideResource(R.dimen.large_screen_shade_header_min_height, 1)
+ overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 1)
+
+ underTest.updateResources()
+
+ val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
+ verify(view).applyConstraints(capture(captor))
+ assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(helperHeight)
}
@Test
@@ -404,10 +430,14 @@
}
@Test
- fun testLargeScreenLayout_qsAndNotifsTopMarginIsOfHeaderHeight() {
+ fun testLargeScreenLayout_refactorFlagOff_qsAndNotifsTopMarginIsOfHeaderResourceHeight() {
+ mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
setLargeScreen()
- val largeScreenHeaderHeight = 100
- overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderHeight)
+ val largeScreenHeaderHelperHeight = 200
+ val largeScreenHeaderResourceHeight = 100
+ whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+ .thenReturn(largeScreenHeaderHelperHeight)
+ overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderResourceHeight)
// ensure the estimated height (would be 30 here) wouldn't impact this test case
overrideResource(R.dimen.large_screen_shade_header_min_height, 10)
@@ -416,7 +446,27 @@
underTest.updateResources()
assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
- .isEqualTo(largeScreenHeaderHeight)
+ .isEqualTo(largeScreenHeaderResourceHeight)
+ }
+
+ @Test
+ fun testLargeScreenLayout_refactorFlagOn_qsAndNotifsTopMarginIsOfHeaderHelperHeight() {
+ mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+ setLargeScreen()
+ val largeScreenHeaderHelperHeight = 200
+ val largeScreenHeaderResourceHeight = 100
+ whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+ .thenReturn(largeScreenHeaderHelperHeight)
+ overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderResourceHeight)
+
+ // ensure the estimated height (would be 30 here) wouldn't impact this test case
+ overrideResource(R.dimen.large_screen_shade_header_min_height, 10)
+ overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 10)
+
+ underTest.updateResources()
+
+ assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
+ .isEqualTo(largeScreenHeaderHelperHeight)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index 39051eb..8f46a37 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -42,6 +42,8 @@
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository;
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
+import com.android.systemui.communal.domain.interactor.CommunalInteractor;
+import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
import com.android.systemui.dump.DumpManager;
@@ -55,9 +57,11 @@
import com.android.systemui.keyguard.data.repository.InWindowLauncherUnlockAnimationRepository;
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
+import com.android.systemui.keyguard.domain.interactor.GlanceableHubTransitions;
import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.media.controls.pipeline.MediaDataManager;
import com.android.systemui.media.controls.ui.MediaHierarchyManager;
import com.android.systemui.plugins.FalsingManager;
@@ -65,7 +69,6 @@
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.qs.QSFragmentLegacy;
import com.android.systemui.res.R;
-import com.android.systemui.scene.SceneTestUtils;
import com.android.systemui.scene.data.repository.SceneContainerRepository;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
@@ -82,7 +85,6 @@
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.PulseExpansionHandler;
import com.android.systemui.statusbar.QsFrameTranslateController;
-import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository;
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
@@ -96,15 +98,14 @@
import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
+import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
import com.android.systemui.util.kotlin.JavaAdapter;
@@ -130,8 +131,8 @@
protected QuickSettingsController mQsController;
- protected SceneTestUtils mUtils = new SceneTestUtils(this);
- protected TestScope mTestScope = mUtils.getTestScope();
+ protected KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+ protected TestScope mTestScope = mKosmos.getTestScope();
@Mock protected Resources mResources;
@Mock protected KeyguardBottomAreaView mQsFrame;
@@ -169,7 +170,6 @@
@Mock protected LockscreenGestureLogger mLockscreenGestureLogger;
@Mock protected MetricsLogger mMetricsLogger;
@Mock protected FeatureFlags mFeatureFlags;
- @Mock protected InteractionJankMonitor mInteractionJankMonitor;
@Mock protected ShadeLogger mShadeLogger;
@Mock protected DumpManager mDumpManager;
@Mock protected UiEventLogger mUiEventLogger;
@@ -183,6 +183,7 @@
protected FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository();
protected FakeShadeRepository mShadeRepository = new FakeShadeRepository();
+ protected InteractionJankMonitor mInteractionJankMonitor;
protected SysuiStatusBarStateController mStatusBarStateController;
protected ShadeInteractor mShadeInteractor;
@@ -202,8 +203,8 @@
public void setup() {
MockitoAnnotations.initMocks(this);
when(mPanelViewControllerLazy.get()).thenReturn(mNotificationPanelViewController);
- mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger,
- mInteractionJankMonitor, mock(JavaAdapter.class), () -> mShadeInteractor);
+ mStatusBarStateController = mKosmos.getStatusBarStateController();
+ mInteractionJankMonitor = mKosmos.getInteractionJankMonitor();
FakeDeviceProvisioningRepository deviceProvisioningRepository =
new FakeDeviceProvisioningRepository();
@@ -211,19 +212,16 @@
FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic();
FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository();
- PowerInteractor powerInteractor = mUtils.powerInteractor(
- mUtils.getPowerRepository(),
- mUtils.falsingCollector(),
- mock(ScreenOffAnimationController.class),
- mStatusBarStateController);
+ PowerInteractor powerInteractor = mKosmos.getPowerInteractor();
SceneInteractor sceneInteractor = new SceneInteractor(
mTestScope.getBackgroundScope(),
new SceneContainerRepository(
mTestScope.getBackgroundScope(),
- mUtils.fakeSceneContainerConfig()),
+ mKosmos.getFakeSceneContainerConfig()),
powerInteractor,
- mock(SceneLogger.class));
+ mock(SceneLogger.class),
+ mKosmos.getDeviceUnlockedInteractor());
FakeSceneContainerFlags sceneContainerFlags = new FakeSceneContainerFlags();
KeyguardInteractor keyguardInteractor = new KeyguardInteractor(
@@ -235,6 +233,8 @@
new ConfigurationInteractor(configurationRepository),
mShadeRepository,
() -> sceneInteractor);
+ CommunalInteractor communalInteractor =
+ CommunalInteractorFactory.create().getCommunalInteractor();
FakeKeyguardTransitionRepository keyguardTransitionRepository =
new FakeKeyguardTransitionRepository();
@@ -251,10 +251,18 @@
keyguardTransitionRepository,
keyguardTransitionInteractor,
mTestScope.getBackgroundScope(),
+ mKosmos.getTestDispatcher(),
+ mKosmos.getTestDispatcher(),
keyguardInteractor,
featureFlags,
mShadeRepository,
powerInteractor,
+ new GlanceableHubTransitions(
+ mTestScope,
+ keyguardTransitionInteractor,
+ keyguardTransitionRepository,
+ communalInteractor
+ ),
() ->
new InWindowLauncherUnlockAnimationInteractor(
new InWindowLauncherUnlockAnimationRepository(),
@@ -269,6 +277,8 @@
keyguardTransitionRepository,
keyguardTransitionInteractor,
mTestScope.getBackgroundScope(),
+ mKosmos.getTestDispatcher(),
+ mKosmos.getTestDispatcher(),
keyguardInteractor,
featureFlags,
mock(KeyguardSecurityModel.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
index 215f8b1..c4911a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
@@ -33,6 +33,8 @@
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.row.NotificationGutsManager
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.policy.DeviceProvisionedController
@@ -45,6 +47,7 @@
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import dagger.Lazy
+import kotlinx.coroutines.test.StandardTestDispatcher
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -59,6 +62,8 @@
@SmallTest
class ShadeControllerImplTest : SysuiTestCase() {
private val executor = FakeExecutor(FakeSystemClock())
+ private val testDispatcher = StandardTestDispatcher()
+ private val activeNotificationsRepository = ActiveNotificationListRepository()
@Mock private lateinit var commandQueue: CommandQueue
@Mock private lateinit var keyguardStateController: KeyguardStateController
@@ -84,6 +89,7 @@
FakeKeyguardRepository(),
headsUpManager,
PowerInteractorFactory.create().powerInteractor,
+ ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher)
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
index 65e0fa1..71a7420 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
@@ -50,8 +50,8 @@
import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
+import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.domain.UserDomainLayerModule
@@ -163,7 +163,7 @@
testComponent.runTest {
deviceProvisioningRepository.setDeviceProvisioned(true)
- userSetupRepository.setUserSetup(false)
+ userSetupRepository.setUserSetUp(false)
userRepository.setSettings(UserSwitcherSettingsModel(isSimpleUserSwitcher = true))
val actual by collectLastValue(underTest.isExpandToQsEnabled)
@@ -175,7 +175,7 @@
fun isExpandToQsEnabled_shadeNotEnabled_false() =
testComponent.runTest {
deviceProvisioningRepository.setDeviceProvisioned(true)
- userSetupRepository.setUserSetup(true)
+ userSetupRepository.setUserSetUp(true)
disableFlagsRepository.disableFlags.value =
DisableFlagsModel(
@@ -191,7 +191,7 @@
fun isExpandToQsEnabled_quickSettingsNotEnabled_false() =
testComponent.runTest {
deviceProvisioningRepository.setDeviceProvisioned(true)
- userSetupRepository.setUserSetup(true)
+ userSetupRepository.setUserSetUp(true)
disableFlagsRepository.disableFlags.value =
DisableFlagsModel(
@@ -206,7 +206,7 @@
fun isExpandToQsEnabled_dozing_false() =
testComponent.runTest {
deviceProvisioningRepository.setDeviceProvisioned(true)
- userSetupRepository.setUserSetup(true)
+ userSetupRepository.setUserSetUp(true)
disableFlagsRepository.disableFlags.value =
DisableFlagsModel(
disable2 = DISABLE2_NONE,
@@ -229,7 +229,7 @@
disable2 = DISABLE2_NONE,
)
- userSetupRepository.setUserSetup(true)
+ userSetupRepository.setUserSetUp(true)
val actual by collectLastValue(underTest.isExpandToQsEnabled)
@@ -262,7 +262,7 @@
DisableFlagsModel(
disable2 = DISABLE2_NONE,
)
- userSetupRepository.setUserSetup(true)
+ userSetupRepository.setUserSetUp(true)
val actual by collectLastValue(underTest.isExpandToQsEnabled)
@@ -290,7 +290,7 @@
DisableFlagsModel(
disable2 = DISABLE2_NONE,
)
- userSetupRepository.setUserSetup(true)
+ userSetupRepository.setUserSetUp(true)
val actual by collectLastValue(underTest.isExpandToQsEnabled)
@@ -322,21 +322,21 @@
DisableFlagsModel(
disable2 = DISABLE2_NONE,
)
- userSetupRepository.setUserSetup(true)
+ userSetupRepository.setUserSetUp(true)
val actual by collectLastValue(underTest.isExpandToQsEnabled)
assertThat(actual).isTrue()
// WHEN the user is no longer setup
- userSetupRepository.setUserSetup(false)
+ userSetupRepository.setUserSetUp(false)
userRepository.setSettings(UserSwitcherSettingsModel(isSimpleUserSwitcher = true))
// THEN expand is disabled
assertThat(actual).isFalse()
// WHEN the user is setup again
- userSetupRepository.setUserSetup(true)
+ userSetupRepository.setUserSetUp(true)
// THEN expand is enabled
assertThat(actual).isTrue()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index ee27c5c..64fd80d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -35,6 +35,7 @@
import com.android.systemui.plugins.PluginManager
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq
+import java.util.function.BiConsumer
import junit.framework.Assert.assertEquals
import junit.framework.Assert.fail
import kotlinx.coroutines.CoroutineDispatcher
@@ -100,10 +101,7 @@
override fun toString() = "Manager[$tag]"
override fun getPackage(): String = mComponentName.getPackageName()
override fun getComponentName(): ComponentName = mComponentName
-
- private var isDebug: Boolean = false
- override fun getIsDebug(): Boolean = isDebug
- override fun setIsDebug(value: Boolean) { isDebug = value }
+ override fun setLogFunc(func: BiConsumer<String, String>) { }
override fun loadPlugin() {
if (!mIsLoaded) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
index bc50c25..3defee9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
@@ -112,7 +112,7 @@
mPluginInstance = mPluginInstanceFactory.create(
mContext, mAppInfo, TEST_PLUGIN_COMPONENT_NAME,
TestPlugin.class, mPluginListener);
- mPluginInstance.setIsDebug(true);
+ mPluginInstance.setLogFunc((tag, msg) -> Log.d((String) tag, (String) msg));
mPluginContext = new WeakReference<>(mPluginInstance.getPluginContext());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
deleted file mode 100644
index e1d9282..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package com.android.systemui.statusbar;
-
-import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.spy;
-
-import android.app.ActivityManager;
-import android.app.Notification;
-import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.res.R;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.settings.FakeGlobalSettings;
-import com.android.systemui.util.time.FakeSystemClock;
-import com.android.systemui.util.time.SystemClock;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
[email protected]
-public class AlertingNotificationManagerTest extends SysuiTestCase {
- @Rule
- public MockitoRule rule = MockitoJUnit.rule();
-
- private static final String TEST_PACKAGE_NAME = "test";
- private static final int TEST_UID = 0;
-
- protected static final int TEST_MINIMUM_DISPLAY_TIME = 400;
- protected static final int TEST_AUTO_DISMISS_TIME = 600;
- protected static final int TEST_STICKY_AUTO_DISMISS_TIME = 800;
- // Number of notifications to use in tests requiring multiple notifications
- private static final int TEST_NUM_NOTIFICATIONS = 4;
-
- protected final FakeGlobalSettings mGlobalSettings = new FakeGlobalSettings();
- protected final FakeSystemClock mSystemClock = new FakeSystemClock();
- protected final FakeExecutor mExecutor = new FakeExecutor(mSystemClock);
-
- @Mock protected ExpandableNotificationRow mRow;
-
- static {
- assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME);
- assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME);
- }
-
- private static class TestableAlertingNotificationManager extends AlertingNotificationManager {
- private AlertEntry mLastCreatedEntry;
-
- private TestableAlertingNotificationManager(SystemClock systemClock,
- DelayableExecutor executor) {
- super(new HeadsUpManagerLogger(logcatLogBuffer()), systemClock, executor);
- mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
- mAutoDismissTime = TEST_AUTO_DISMISS_TIME;
- mStickyForSomeTimeAutoDismissTime = TEST_STICKY_AUTO_DISMISS_TIME;
- }
-
- @Override
- protected void onAlertEntryAdded(AlertEntry alertEntry) {}
-
- @Override
- protected void onAlertEntryRemoved(AlertEntry alertEntry) {}
-
- @Override
- protected AlertEntry createAlertEntry() {
- mLastCreatedEntry = spy(super.createAlertEntry());
- return mLastCreatedEntry;
- }
-
- @Override
- public int getContentFlag() {
- return FLAG_CONTENT_VIEW_CONTRACTED;
- }
- }
-
- protected AlertingNotificationManager createAlertingNotificationManager() {
- return new TestableAlertingNotificationManager(mSystemClock, mExecutor);
- }
-
- protected StatusBarNotification createSbn(int id, Notification n) {
- return new StatusBarNotification(
- TEST_PACKAGE_NAME /* pkg */,
- TEST_PACKAGE_NAME,
- id,
- null /* tag */,
- TEST_UID,
- 0 /* initialPid */,
- n,
- new UserHandle(ActivityManager.getCurrentUser()),
- null /* overrideGroupKey */,
- 0 /* postTime */);
- }
-
- protected StatusBarNotification createSbn(int id, Notification.Builder n) {
- return createSbn(id, n.build());
- }
-
- protected StatusBarNotification createSbn(int id) {
- final Notification.Builder b = new Notification.Builder(mContext, "")
- .setSmallIcon(R.drawable.ic_person)
- .setContentTitle("Title")
- .setContentText("Text");
- return createSbn(id, b);
- }
-
- protected NotificationEntry createEntry(int id, Notification n) {
- return new NotificationEntryBuilder().setSbn(createSbn(id, n)).build();
- }
-
- protected NotificationEntry createEntry(int id) {
- return new NotificationEntryBuilder().setSbn(createSbn(id)).build();
- }
-
-
- @Test
- public void testShowNotification_addsEntry() {
- final AlertingNotificationManager alm = createAlertingNotificationManager();
- final NotificationEntry entry = createEntry(/* id = */ 0);
-
- alm.showNotification(entry);
-
- assertTrue(alm.isAlerting(entry.getKey()));
- assertTrue(alm.hasNotifications());
- assertEquals(entry, alm.getEntry(entry.getKey()));
- }
-
- @Test
- public void testShowNotification_autoDismisses() {
- final AlertingNotificationManager alm = createAlertingNotificationManager();
- final NotificationEntry entry = createEntry(/* id = */ 0);
-
- alm.showNotification(entry);
- mSystemClock.advanceTime(TEST_AUTO_DISMISS_TIME * 3 / 2);
-
- assertFalse(alm.isAlerting(entry.getKey()));
- }
-
- @Test
- public void testRemoveNotification_removeDeferred() {
- final AlertingNotificationManager alm = createAlertingNotificationManager();
- final NotificationEntry entry = createEntry(/* id = */ 0);
-
- alm.showNotification(entry);
-
- final boolean removedImmediately = alm.removeNotification(
- entry.getKey(), /* releaseImmediately = */ false);
- assertFalse(removedImmediately);
- assertTrue(alm.isAlerting(entry.getKey()));
- }
-
- @Test
- public void testRemoveNotification_forceRemove() {
- final AlertingNotificationManager alm = createAlertingNotificationManager();
- final NotificationEntry entry = createEntry(/* id = */ 0);
-
- alm.showNotification(entry);
-
- final boolean removedImmediately = alm.removeNotification(
- entry.getKey(), /* releaseImmediately = */ true);
- assertTrue(removedImmediately);
- assertFalse(alm.isAlerting(entry.getKey()));
- }
-
- @Test
- public void testReleaseAllImmediately() {
- final AlertingNotificationManager alm = createAlertingNotificationManager();
- for (int i = 0; i < TEST_NUM_NOTIFICATIONS; i++) {
- final NotificationEntry entry = createEntry(i);
- entry.setRow(mRow);
- alm.showNotification(entry);
- }
-
- alm.releaseAllImmediately();
-
- assertEquals(0, alm.getAllEntries().count());
- }
-
- @Test
- public void testCanRemoveImmediately_notShownLongEnough() {
- final AlertingNotificationManager alm = createAlertingNotificationManager();
- final NotificationEntry entry = createEntry(/* id = */ 0);
-
- alm.showNotification(entry);
-
- // The entry has just been added so we should not remove immediately.
- assertFalse(alm.canRemoveImmediately(entry.getKey()));
- }
-}
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 aed6163..05e866e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -27,6 +27,7 @@
import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
@@ -36,13 +37,16 @@
import com.android.systemui.keyguard.data.repository.InWindowLauncherUnlockAnimationRepository
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.GlanceableHubTransitions
import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.data.repository.FakePowerRepository
import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
import com.android.systemui.shade.LargeScreenHeaderHelper
import com.android.systemui.shade.data.repository.FakeShadeRepository
@@ -51,9 +55,10 @@
import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl
import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
+import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import kotlinx.coroutines.flow.emptyFlow
import org.junit.Assert.assertEquals
@@ -77,8 +82,9 @@
@TestableLooper.RunWithLooper
class StatusBarStateControllerImplTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val testDispatcher = kosmos.testDispatcher
private lateinit var shadeInteractor: ShadeInteractor
private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
private lateinit var fromPrimaryBouncerTransitionInteractor:
@@ -128,7 +134,7 @@
FakeKeyguardBouncerRepository(),
ConfigurationInteractor(configurationRepository),
shadeRepository,
- utils::sceneInteractor
+ { kosmos.sceneInteractor },
)
val keyguardTransitionInteractor =
KeyguardTransitionInteractor(
@@ -138,15 +144,24 @@
{ fromLockscreenTransitionInteractor },
{ fromPrimaryBouncerTransitionInteractor }
)
+ val communalInteractor = CommunalInteractorFactory.create().communalInteractor
fromLockscreenTransitionInteractor =
FromLockscreenTransitionInteractor(
keyguardTransitionRepository,
keyguardTransitionInteractor,
testScope.backgroundScope,
+ testDispatcher,
+ testDispatcher,
keyguardInteractor,
featureFlags,
shadeRepository,
powerInteractor,
+ GlanceableHubTransitions(
+ testScope,
+ keyguardTransitionInteractor,
+ keyguardTransitionRepository,
+ communalInteractor
+ ),
{
InWindowLauncherUnlockAnimationInteractor(
InWindowLauncherUnlockAnimationRepository(),
@@ -162,6 +177,8 @@
keyguardTransitionRepository,
keyguardTransitionInteractor,
testScope.backgroundScope,
+ testDispatcher,
+ testDispatcher,
keyguardInteractor,
featureFlags,
mock(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
index 6059363..cd74410 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
@@ -73,7 +73,7 @@
}
private fun flagNotificationAsHun() {
- `when`(headsUpManager.isAlerting(notificationKey)).thenReturn(true)
+ `when`(headsUpManager.isHeadsUpEntry(notificationKey)).thenReturn(true)
}
@Test
@@ -151,8 +151,8 @@
.build()
assertSame(summary, notification.entry.parent?.summary)
- `when`(headsUpManager.isAlerting(notificationKey)).thenReturn(false)
- `when`(headsUpManager.isAlerting(summary.key)).thenReturn(true)
+ `when`(headsUpManager.isHeadsUpEntry(notificationKey)).thenReturn(false)
+ `when`(headsUpManager.isHeadsUpEntry(summary.key)).thenReturn(true)
assertNotSame(GROUP_ALERT_SUMMARY, summary.sbn.notification.groupAlertBehavior)
assertNotSame(GROUP_ALERT_SUMMARY, notification.entry.sbn.notification.groupAlertBehavior)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index a8be62b..cd75e08 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -148,7 +148,7 @@
verify(remoteInputManager).addActionPressListener(capture())
}
given(headsUpManager.allEntries).willAnswer { huns.stream() }
- given(headsUpManager.isAlerting(anyString())).willAnswer { invocation ->
+ given(headsUpManager.isHeadsUpEntry(anyString())).willAnswer { invocation ->
val key = invocation.getArgument<String>(0)
huns.any { entry -> entry.key == key }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index 2e74d11..ea5a6e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -140,7 +140,7 @@
.setSummary(mEntry)
.build();
- when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(false);
+ when(mHeadsUpManager.isHeadsUpEntry(mEntry.getKey())).thenReturn(false);
// Whenever we invalidate, the pipeline runs again, so we invalidate the state
doAnswer(i -> {
@@ -373,7 +373,7 @@
setSleepy(false);
// WHEN a notification is alerting and visible
- when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(true);
+ when(mHeadsUpManager.isHeadsUpEntry(mEntry.getKey())).thenReturn(true);
when(mVisibilityLocationProvider.isInVisibleLocation(any(NotificationEntry.class)))
.thenReturn(true);
@@ -389,7 +389,7 @@
setSleepy(false);
// WHEN a notification is alerting but not visible
- when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(true);
+ when(mHeadsUpManager.isHeadsUpEntry(mEntry.getKey())).thenReturn(true);
when(mVisibilityLocationProvider.isInVisibleLocation(any(NotificationEntry.class)))
.thenReturn(false);
@@ -537,7 +537,7 @@
assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
// GIVEN mEntry is a HUN
- when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(true);
+ when(mHeadsUpManager.isHeadsUpEntry(mEntry.getKey())).thenReturn(true);
// THEN group + section changes are allowed
assertTrue(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
index 4ab3cd4..9b641f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
@@ -50,6 +50,18 @@
DaggerActiveNotificationsInteractorTest_TestComponent.factory().create(test = this)
@Test
+ fun testAllNotificationsCount() =
+ testComponent.runTest {
+ val count by collectLastValue(underTest.allNotificationsCount)
+
+ activeNotificationListRepository.setActiveNotifs(5)
+ runCurrent()
+
+ assertThat(count).isEqualTo(5)
+ assertThat(underTest.allNotificationsCountValue).isEqualTo(5)
+ }
+
+ @Test
fun testAreAnyNotificationsPresent_isTrue() =
testComponent.runTest {
val areAnyNotificationsPresent by collectLastValue(underTest.areAnyNotificationsPresent)
@@ -74,6 +86,18 @@
}
@Test
+ fun testActiveNotificationRanks_sizeMatches() {
+ testComponent.runTest {
+ val activeNotificationRanks by collectLastValue(underTest.activeNotificationRanks)
+
+ activeNotificationListRepository.setActiveNotifs(5)
+ runCurrent()
+
+ assertThat(activeNotificationRanks!!.size).isEqualTo(5)
+ }
+ }
+
+ @Test
fun testHasClearableNotifications_whenHasClearableAlertingNotifs() =
testComponent.runTest {
val hasClearable by collectLastValue(underTest.hasClearableNotifications)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
index 6374d5e..334776c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
@@ -15,10 +15,12 @@
package com.android.systemui.statusbar.notification.domain.interactor
+import android.service.notification.StatusBarNotification
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.RankingBuilder
+import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.shared.byKey
@@ -49,22 +51,101 @@
testScope.runTest {
val notifs by collectLastValue(notifsInteractor.topLevelRepresentativeNotifications)
val keys = (1..50).shuffled().map { "$it" }
- val entries =
- keys.map {
- mock<ListEntry> {
- val mockRep =
- mock<NotificationEntry> {
- whenever(key).thenReturn(it)
- whenever(sbn).thenReturn(mock())
- whenever(icons).thenReturn(mock())
- }
- whenever(representativeEntry).thenReturn(mockRep)
- }
- }
+ val entries = keys.map { mockNotificationEntry(key = it) }
underTest.setRenderedList(entries)
assertThat(notifs)
.comparingElementsUsing(byKey)
.containsExactlyElementsIn(keys)
.inOrder()
}
+
+ @Test
+ fun setRenderList_flatMapsRankings() =
+ testScope.runTest {
+ val ranks by collectLastValue(notifsInteractor.activeNotificationRanks)
+
+ val single = mockNotificationEntry("single", 0)
+ val group =
+ mockGroupEntry(
+ key = "group",
+ summary = mockNotificationEntry("summary", 1),
+ children =
+ listOf(
+ mockNotificationEntry("child0", 2),
+ mockNotificationEntry("child1", 3),
+ ),
+ )
+
+ underTest.setRenderedList(listOf(single, group))
+
+ assertThat(ranks)
+ .containsExactlyEntriesIn(
+ mapOf(
+ "single" to 0,
+ "summary" to 1,
+ "child0" to 2,
+ "child1" to 3,
+ )
+ )
+ }
+
+ @Test
+ fun setRenderList_singleItems_mapsRankings() =
+ testScope.runTest {
+ val actual by collectLastValue(notifsInteractor.activeNotificationRanks)
+ val expected =
+ (0..10).shuffled().mapIndexed { index, value -> "$value" to index }.toMap()
+
+ val entries = expected.map { (key, rank) -> mockNotificationEntry(key, rank) }
+
+ underTest.setRenderedList(entries)
+
+ assertThat(actual).containsAtLeastEntriesIn(expected)
+ }
+
+ @Test
+ fun setRenderList_groupWithNoSummary_flatMapsRankings() =
+ testScope.runTest {
+ val actual by collectLastValue(notifsInteractor.activeNotificationRanks)
+ val expected =
+ (0..10).shuffled().mapIndexed { index, value -> "$value" to index }.toMap()
+
+ val group =
+ mockGroupEntry(
+ key = "group",
+ summary = null,
+ children = expected.map { (key, rank) -> mockNotificationEntry(key, rank) },
+ )
+
+ underTest.setRenderedList(listOf(group))
+
+ assertThat(actual).containsAtLeastEntriesIn(expected)
+ }
+}
+
+private fun mockGroupEntry(
+ key: String,
+ summary: NotificationEntry?,
+ children: List<NotificationEntry>,
+): GroupEntry {
+ return mock<GroupEntry> {
+ whenever(this.key).thenReturn(key)
+ whenever(this.summary).thenReturn(summary)
+ whenever(this.children).thenReturn(children)
+ }
+}
+
+private fun mockNotificationEntry(key: String, rank: Int = 0): NotificationEntry {
+ val mockSbn =
+ mock<StatusBarNotification>() {
+ whenever(notification).thenReturn(mock())
+ whenever(packageName).thenReturn("com.android")
+ }
+ return mock<NotificationEntry> {
+ whenever(this.key).thenReturn(key)
+ whenever(this.icons).thenReturn(mock())
+ whenever(this.representativeEntry).thenReturn(this)
+ whenever(this.ranking).thenReturn(RankingBuilder().setRank(rank).build())
+ whenever(this.sbn).thenReturn(mockSbn)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index 168e782..ff02ef3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -28,6 +28,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
+
import android.app.Notification;
import android.os.Handler;
import android.os.Looper;
@@ -56,6 +58,8 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
import com.android.systemui.statusbar.notification.logging.nano.Notifications;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -110,6 +114,11 @@
private final FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository();
private final PowerInteractor mPowerInteractor =
PowerInteractorFactory.create().getPowerInteractor();
+ private final ActiveNotificationListRepository mActiveNotificationListRepository =
+ new ActiveNotificationListRepository();
+ private final ActiveNotificationsInteractor mActiveNotificationsInteractor =
+ new ActiveNotificationsInteractor(mActiveNotificationListRepository,
+ StandardTestDispatcher(null, null));
private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
@@ -123,7 +132,8 @@
new WindowRootViewVisibilityRepository(mBarService, mUiBgExecutor),
mKeyguardRepository,
mHeadsUpManager,
- mPowerInteractor);
+ mPowerInteractor,
+ mActiveNotificationsInteractor);
mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
mEntry = new NotificationEntryBuilder()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java
index dae0aa2..d61fc05 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java
@@ -34,6 +34,11 @@
}
@Override
+ public void logPanelShown(boolean isLockscreen, Notifications.NotificationList proto) {
+ mCalls.add(new CallRecord(isLockscreen, proto));
+ }
+
+ @Override
public void logPanelShown(boolean isLockscreen,
List<NotificationEntry> visibleNotifications) {
mCalls.add(new CallRecord(isLockscreen,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
index 9547af1..8ac2a33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
@@ -40,12 +40,12 @@
import com.android.systemui.statusbar.notification.collection.render.FakeNodeController
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
-import com.android.systemui.statusbar.notification.logging.NotificationLogger
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController.BUBBLES_SETTING_URI
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationRowStatsLogger
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.SmartReplyConstants
@@ -92,7 +92,7 @@
private val groupMembershipManager: GroupMembershipManager = mock()
private val groupExpansionManager: GroupExpansionManager = mock()
private val rowContentBindStage: RowContentBindStage = mock()
- private val notifLogger: NotificationLogger = mock()
+ private val notifLogger: NotificationRowStatsLogger = mock()
private val headsUpManager: HeadsUpManager = mock()
private val onExpandClickListener: ExpandableNotificationRow.OnExpandClickListener = mock()
private val statusBarStateController: StatusBarStateController = mock()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
index 5549fee..91e4666 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
@@ -37,6 +37,7 @@
import com.android.systemui.statusbar.notification.FeedbackIcon
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import junit.framework.Assert.assertEquals
@@ -49,6 +50,8 @@
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
@@ -74,7 +77,8 @@
@Before
fun setup() {
initMocks(this)
- fakeParent = FrameLayout(mContext, /* attrs= */ null).also { it.visibility = View.GONE }
+ fakeParent =
+ spy(FrameLayout(mContext, /* attrs= */ null).also { it.visibility = View.GONE })
row =
spy(
ExpandableNotificationRow(mContext, /* attrs= */ null).apply {
@@ -558,6 +562,35 @@
verify(view.headsUpWrapper, never()).setAnimationsRunning(false)
}
+ @Test
+ fun notifySubtreeAccessibilityStateChanged_notifiesParent() {
+ // Given: a contentView is created
+ val view = createContentView()
+ clearInvocations(fakeParent)
+
+ // When: the contentView is notified for an A11y change
+ view.notifySubtreeAccessibilityStateChanged(view.contractedChild, view.contractedChild, 0)
+
+ // Then: the contentView propagates the event to its parent
+ verify(fakeParent).notifySubtreeAccessibilityStateChanged(any(), any(), anyInt())
+ }
+
+ @Test
+ fun notifySubtreeAccessibilityStateChanged_animatingContentView_dontNotifyParent() {
+ // Given: a collapsed contentView is created
+ val view = createContentView()
+ clearInvocations(fakeParent)
+
+ // And: it is animating to expanded
+ view.setAnimationStartVisibleType(NotificationContentView.VISIBLE_TYPE_EXPANDED)
+
+ // When: the contentView is notified for an A11y change
+ view.notifySubtreeAccessibilityStateChanged(view.contractedChild, view.contractedChild, 0)
+
+ // Then: the contentView DOESN'T propagates the event to its parent
+ verify(fakeParent, never()).notifySubtreeAccessibilityStateChanged(any(), any(), anyInt())
+ }
+
private fun createMockContainingNotification(notificationEntry: NotificationEntry) =
mock<ExpandableNotificationRow>().apply {
whenever(this.entry).thenReturn(notificationEntry)
@@ -597,7 +630,7 @@
}
private fun createContentView(
- isSystemExpanded: Boolean,
+ isSystemExpanded: Boolean = false,
contractedView: View = createViewWithHeight(contractedHeight),
expandedView: View = createViewWithHeight(expandedHeight),
headsUpView: View = createViewWithHeight(contractedHeight),
@@ -647,5 +680,5 @@
}
private fun NotificationContentView.clearInvocations() {
- Mockito.clearInvocations(contractedWrapper, expandedWrapper, headsUpWrapper)
+ clearInvocations(contractedWrapper, expandedWrapper, headsUpWrapper)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 8a730cf..71613ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -42,6 +42,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
+
import android.app.INotificationManager;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -85,6 +87,8 @@
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -156,6 +160,12 @@
@Mock private UserManager mUserManager;
+ private final ActiveNotificationListRepository mActiveNotificationListRepository =
+ new ActiveNotificationListRepository();
+ private final ActiveNotificationsInteractor mActiveNotificationsInteractor =
+ new ActiveNotificationsInteractor(mActiveNotificationListRepository,
+ StandardTestDispatcher(null, null));
+
private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
@Before
@@ -171,7 +181,8 @@
new WindowRootViewVisibilityRepository(mBarService, mExecutor),
new FakeKeyguardRepository(),
mHeadsUpManager,
- PowerInteractorFactory.create().getPowerInteractor());
+ PowerInteractorFactory.create().getPowerInteractor(),
+ mActiveNotificationsInteractor);
mGutsManager = new NotificationGutsManager(
mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/DisplaySwitchNotificationsHiderTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/DisplaySwitchNotificationsHiderTrackerTest.kt
new file mode 100644
index 0000000..1dfcb38
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/DisplaySwitchNotificationsHiderTrackerTest.kt
@@ -0,0 +1,163 @@
+/*
+ * 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.notification.stack
+
+import androidx.test.filters.SmallTest
+import com.android.internal.util.LatencyTracker
+import com.android.internal.util.LatencyTracker.ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE
+import com.android.internal.util.LatencyTracker.ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.launch
+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.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class DisplaySwitchNotificationsHiderTrackerTest : SysuiTestCase() {
+
+ private val testScope = TestScope()
+ private val shadeInteractor = mock<ShadeInteractor>()
+ private val latencyTracker = mock<LatencyTracker>()
+
+ private val shouldHideFlow = MutableStateFlow(false)
+ private val shadeExpandedFlow = MutableStateFlow(false)
+
+ private val tracker = DisplaySwitchNotificationsHiderTracker(shadeInteractor, latencyTracker)
+
+ @Before
+ fun setup() {
+ whenever(shadeInteractor.isAnyExpanded).thenReturn(shadeExpandedFlow)
+ }
+
+ @Test
+ fun notificationsBecomeHidden_tracksHideActionStart() = testScope.runTest {
+ startTracking()
+
+ shouldHideFlow.value = true
+ runCurrent()
+
+ verify(latencyTracker).onActionStart(HIDE_NOTIFICATIONS_ACTION)
+ }
+
+ @Test
+ fun notificationsBecomeVisibleAfterHidden_tracksHideActionEnd() = testScope.runTest {
+ startTracking()
+
+ shouldHideFlow.value = true
+ runCurrent()
+ clearInvocations(latencyTracker)
+ shouldHideFlow.value = false
+ runCurrent()
+
+ verify(latencyTracker).onActionEnd(HIDE_NOTIFICATIONS_ACTION)
+ }
+
+ @Test
+ fun notificationsBecomeHiddenWhenShadeIsClosed_doesNotTrackHideWhenVisibleActionStart() =
+ testScope.runTest {
+ shouldHideFlow.value = false
+ shadeExpandedFlow.value = false
+ startTracking()
+
+ shouldHideFlow.value = true
+ runCurrent()
+
+ verify(latencyTracker, never())
+ .onActionStart(HIDE_NOTIFICATIONS_WHEN_VISIBLE_ACTION)
+ }
+
+ @Test
+ fun notificationsBecomeHiddenWhenShadeIsOpen_tracksHideWhenVisibleActionStart() = testScope.runTest {
+ shouldHideFlow.value = false
+ shadeExpandedFlow.value = false
+ startTracking()
+
+ shouldHideFlow.value = true
+ shadeExpandedFlow.value = true
+ runCurrent()
+
+ verify(latencyTracker).onActionStart(HIDE_NOTIFICATIONS_WHEN_VISIBLE_ACTION)
+ }
+
+ @Test
+ fun shadeBecomesOpenWhenNotificationsHidden_tracksHideWhenVisibleActionStart() =
+ testScope.runTest {
+ shouldHideFlow.value = true
+ shadeExpandedFlow.value = false
+ startTracking()
+
+ shadeExpandedFlow.value = true
+ runCurrent()
+
+ verify(latencyTracker).onActionStart(HIDE_NOTIFICATIONS_WHEN_VISIBLE_ACTION)
+ }
+
+ @Test
+ fun notificationsBecomeVisibleWhenShadeIsOpen_tracksHideWhenVisibleActionEnd() = testScope.runTest {
+ shouldHideFlow.value = false
+ shadeExpandedFlow.value = false
+ startTracking()
+ shouldHideFlow.value = true
+ shadeExpandedFlow.value = true
+ runCurrent()
+ clearInvocations(latencyTracker)
+
+ shouldHideFlow.value = false
+ runCurrent()
+
+ verify(latencyTracker).onActionEnd(HIDE_NOTIFICATIONS_WHEN_VISIBLE_ACTION)
+ }
+
+ @Test
+ fun shadeBecomesClosedWhenNotificationsHidden_tracksHideWhenVisibleActionEnd() = testScope.runTest {
+ shouldHideFlow.value = false
+ shadeExpandedFlow.value = false
+ startTracking()
+ shouldHideFlow.value = true
+ shadeExpandedFlow.value = true
+ runCurrent()
+ clearInvocations(latencyTracker)
+
+ shadeExpandedFlow.value = false
+ runCurrent()
+
+ verify(latencyTracker).onActionEnd(HIDE_NOTIFICATIONS_WHEN_VISIBLE_ACTION)
+ }
+
+ private fun TestScope.startTracking() {
+ backgroundScope.launch { tracker.trackNotificationHideTime(shouldHideFlow) }
+ backgroundScope.launch { tracker.trackNotificationHideTimeWhenVisible(shouldHideFlow) }
+ runCurrent()
+ clearInvocations(latencyTracker)
+ }
+
+ private companion object {
+ const val HIDE_NOTIFICATIONS_ACTION = ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE
+ const val HIDE_NOTIFICATIONS_WHEN_VISIBLE_ACTION =
+ ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 88662b6..89f826b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -66,6 +66,8 @@
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.ui.view.WindowRootView;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -91,6 +93,7 @@
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent;
import com.android.systemui.statusbar.notification.stack.NotificationSwipeHelper.NotificationCallback;
+import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor;
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScrimController;
@@ -112,6 +115,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import javax.inject.Provider;
+
/**
* Tests for {@link NotificationStackScrollLayoutController}.
*/
@@ -153,6 +158,9 @@
@Mock private NotificationRemoteInputManager mRemoteInputManager;
@Mock private VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator;
@Mock private ShadeController mShadeController;
+ @Mock private SceneContainerFlags mSceneContainerFlags;
+ @Mock private Provider<WindowRootView> mWindowRootView;
+ @Mock private NotificationStackAppearanceInteractor mNotificationStackAppearanceInteractor;
@Mock private InteractionJankMonitor mJankMonitor;
private final StackStateLogger mStackLogger = new StackStateLogger(logcatLogBuffer(),
logcatLogBuffer());
@@ -724,6 +732,9 @@
mSeenNotificationsInteractor,
mViewBinder,
mShadeController,
+ mSceneContainerFlags,
+ mWindowRootView,
+ mNotificationStackAppearanceInteractor,
mJankMonitor,
mStackLogger,
mLogger,
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 83ba684..4afcc8c 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
@@ -20,6 +20,7 @@
import static android.view.WindowInsets.Type.ime;
import static com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION;
+import static com.android.systemui.Flags.FLAG_SCENE_CONTAINER;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.RUBBER_BAND_FACTOR_NORMAL;
@@ -37,6 +38,7 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
@@ -51,6 +53,7 @@
import android.graphics.Insets;
import android.graphics.Rect;
+import android.os.SystemClock;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
@@ -74,6 +77,7 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.EmptyShadeView;
@@ -93,11 +97,13 @@
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
import org.junit.Assert;
+import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -626,8 +632,6 @@
@Test
public void testClearNotifications_clearAllInProgress() {
- mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
-
ExpandableNotificationRow row = createClearableRow();
when(row.getEntry().hasFinishedInitialization()).thenReturn(true);
doReturn(true).when(mStackScroller).isVisible(row);
@@ -672,8 +676,6 @@
@Test
public void testAddNotificationUpdatesSpeedBumpIndex() {
- mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
-
// initial state calculated == 0
assertEquals(0, mStackScroller.getSpeedBumpIndex());
@@ -690,8 +692,6 @@
@Test
public void testAddAmbientNotificationNoSpeedBumpUpdate() {
- mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
-
// initial state calculated == 0
assertEquals(0, mStackScroller.getSpeedBumpIndex());
@@ -708,8 +708,6 @@
@Test
public void testRemoveNotificationUpdatesSpeedBump() {
- mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
-
// initial state calculated == 0
assertEquals(0, mStackScroller.getSpeedBumpIndex());
@@ -955,6 +953,78 @@
verify(runnable).run();
}
+ @Test
+ public void testDispatchTouchEvent_sceneContainerDisabled() {
+ Assume.assumeFalse(SceneContainerFlag.isEnabled());
+
+ MotionEvent event = MotionEvent.obtain(
+ SystemClock.uptimeMillis(),
+ SystemClock.uptimeMillis(),
+ MotionEvent.ACTION_MOVE,
+ 0,
+ 0,
+ 0
+ );
+
+ mStackScroller.dispatchTouchEvent(event);
+
+ verify(mStackScrollLayoutController, never()).sendTouchToSceneFramework(any());
+ }
+
+ @Test
+ public void testDispatchTouchEvent_sceneContainerEnabled() {
+ Assume.assumeTrue(SceneContainerFlag.isEnabled());
+ mStackScroller.setIsBeingDragged(true);
+
+ MotionEvent moveEvent = MotionEvent.obtain(
+ SystemClock.uptimeMillis(),
+ SystemClock.uptimeMillis(),
+ MotionEvent.ACTION_MOVE,
+ 0,
+ 0,
+ 0
+ );
+ MotionEvent syntheticDownEvent = moveEvent.copy();
+ syntheticDownEvent.setAction(MotionEvent.ACTION_DOWN);
+ mStackScroller.dispatchTouchEvent(moveEvent);
+
+ verify(mStackScrollLayoutController).sendTouchToSceneFramework(argThat(
+ new MotionEventMatcher(syntheticDownEvent)));
+
+ mStackScroller.dispatchTouchEvent(moveEvent);
+
+ verify(mStackScrollLayoutController).sendTouchToSceneFramework(moveEvent);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCENE_CONTAINER)
+ public void testDispatchTouchEvent_sceneContainerEnabled_actionUp() {
+ Assume.assumeTrue(SceneContainerFlag.isEnabled());
+ mStackScroller.setIsBeingDragged(true);
+
+ MotionEvent upEvent = MotionEvent.obtain(
+ SystemClock.uptimeMillis(),
+ SystemClock.uptimeMillis(),
+ MotionEvent.ACTION_UP,
+ 0,
+ 0,
+ 0
+ );
+ MotionEvent syntheticDownEvent = upEvent.copy();
+ syntheticDownEvent.setAction(MotionEvent.ACTION_DOWN);
+
+ mStackScroller.dispatchTouchEvent(upEvent);
+
+ verify(mStackScrollLayoutController, atLeastOnce()).sendTouchToSceneFramework(argThat(
+ new MotionEventMatcher(syntheticDownEvent)));
+
+ mStackScroller.dispatchTouchEvent(upEvent);
+
+ verify(mStackScrollLayoutController, atLeastOnce()).sendTouchToSceneFramework(argThat(
+ new MotionEventMatcher(upEvent)));
+ assertFalse(mStackScroller.getIsBeingDragged());
+ }
+
private void setBarStateForTest(int state) {
// Can't inject this through the listener or we end up on the actual implementation
// rather than the mock because the spy just coppied the anonymous inner /shruggie.
@@ -1001,4 +1071,21 @@
/* metaState= */0
);
}
+
+ private static class MotionEventMatcher implements ArgumentMatcher<MotionEvent> {
+ private final MotionEvent mLeftEvent;
+
+ MotionEventMatcher(MotionEvent leftEvent) {
+ mLeftEvent = leftEvent;
+ }
+
+ @Override
+ public boolean matches(MotionEvent right) {
+ return mLeftEvent.getActionMasked() == right.getActionMasked()
+ && mLeftEvent.getDownTime() == right.getDownTime()
+ && mLeftEvent.getEventTime() == right.getEventTime()
+ && mLeftEvent.getX() == right.getX()
+ && mLeftEvent.getY() == right.getY();
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerTest.kt
new file mode 100644
index 0000000..d7d1ffc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerTest.kt
@@ -0,0 +1,429 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.ui.view
+
+import android.service.notification.notificationListenerService
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.statusbar.NotificationVisibility
+import com.android.internal.statusbar.statusBarService
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
+import com.android.systemui.statusbar.notification.logging.nano.Notifications
+import com.android.systemui.statusbar.notification.logging.notificationPanelLogger
+import com.android.systemui.statusbar.notification.stack.ExpandableViewState
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Callable
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NotificationStatsLoggerTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private val testScope = kosmos.testScope
+ private val mockNotificationListenerService = kosmos.notificationListenerService
+ private val mockPanelLogger = kosmos.notificationPanelLogger
+ private val mockStatusBarService = kosmos.statusBarService
+
+ private val underTest = kosmos.notificationStatsLogger
+
+ private val visibilityArrayCaptor = argumentCaptor<Array<NotificationVisibility>>()
+ private val stringArrayCaptor = argumentCaptor<Array<String>>()
+ private val notificationListProtoCaptor = argumentCaptor<Notifications.NotificationList>()
+
+ @Test
+ fun onNotificationListUpdated_itemsAdded_logsNewlyVisibleItems() =
+ testScope.runTest {
+ // WHEN new Notifications are added
+ // AND they're visible
+ val (ranks, locations) = fakeNotificationMaps("key0", "key1")
+ val callable = Callable { locations }
+ underTest.onNotificationListUpdated(callable, ranks)
+ runCurrent()
+
+ // THEN visibility changes are reported
+ verify(mockStatusBarService)
+ .onNotificationVisibilityChanged(visibilityArrayCaptor.capture(), eq(emptyArray()))
+ verify(mockNotificationListenerService)
+ .setNotificationsShown(stringArrayCaptor.capture())
+ val loggedVisibilities = visibilityArrayCaptor.value
+ val loggedKeys = stringArrayCaptor.value
+ assertThat(loggedVisibilities).hasLength(2)
+ assertThat(loggedKeys).hasLength(2)
+ assertThat(loggedVisibilities[0]).apply {
+ isKeyEqualTo("key0")
+ isRankEqualTo(0)
+ isVisible()
+ isInMainArea()
+ isCountEqualTo(2)
+ }
+ assertThat(loggedVisibilities[1]).apply {
+ isKeyEqualTo("key1")
+ isRankEqualTo(1)
+ isVisible()
+ isInMainArea()
+ isCountEqualTo(2)
+ }
+ assertThat(loggedKeys[0]).isEqualTo("key0")
+ assertThat(loggedKeys[1]).isEqualTo("key1")
+ }
+
+ @Test
+ fun onNotificationListUpdated_itemsRemoved_logsNoLongerVisibleItems() =
+ testScope.runTest {
+ // GIVEN some visible Notifications are reported
+ val (ranks, locations) = fakeNotificationMaps("key0", "key1")
+ val callable = Callable { locations }
+ underTest.onNotificationListUpdated(callable, ranks)
+ runCurrent()
+ clearInvocations(mockStatusBarService, mockNotificationListenerService)
+
+ // WHEN the same Notifications are removed
+ val emptyCallable = Callable { emptyMap<String, Int>() }
+ underTest.onNotificationListUpdated(emptyCallable, emptyMap())
+ runCurrent()
+
+ // THEN visibility changes are reported
+ verify(mockStatusBarService)
+ .onNotificationVisibilityChanged(eq(emptyArray()), visibilityArrayCaptor.capture())
+ verifyZeroInteractions(mockNotificationListenerService)
+ val noLongerVisible = visibilityArrayCaptor.value
+ assertThat(noLongerVisible).hasLength(2)
+ assertThat(noLongerVisible[0]).apply {
+ isKeyEqualTo("key0")
+ isRankEqualTo(0)
+ notVisible()
+ isInMainArea()
+ isCountEqualTo(0)
+ }
+ assertThat(noLongerVisible[1]).apply {
+ isKeyEqualTo("key1")
+ isRankEqualTo(1)
+ notVisible()
+ isInMainArea()
+ isCountEqualTo(0)
+ }
+ }
+
+ @Test
+ fun onNotificationListUpdated_itemsBecomeInvisible_logsNoLongerVisibleItems() =
+ testScope.runTest {
+ // GIVEN some visible Notifications are reported
+ val (ranks, locations) = fakeNotificationMaps("key0", "key1")
+ val callable = Callable { locations }
+ underTest.onNotificationListUpdated(callable, ranks)
+ runCurrent()
+ clearInvocations(mockStatusBarService, mockNotificationListenerService)
+
+ // WHEN the same Notifications are becoming invisible
+ val emptyCallable = Callable { emptyMap<String, Int>() }
+ underTest.onNotificationListUpdated(emptyCallable, ranks)
+ runCurrent()
+
+ // THEN visibility changes are reported
+ verify(mockStatusBarService)
+ .onNotificationVisibilityChanged(eq(emptyArray()), visibilityArrayCaptor.capture())
+ verifyZeroInteractions(mockNotificationListenerService)
+ val noLongerVisible = visibilityArrayCaptor.value
+ assertThat(noLongerVisible).hasLength(2)
+ assertThat(noLongerVisible[0]).apply {
+ isKeyEqualTo("key0")
+ isRankEqualTo(0)
+ notVisible()
+ isInMainArea()
+ isCountEqualTo(2)
+ }
+ assertThat(noLongerVisible[1]).apply {
+ isKeyEqualTo("key1")
+ isRankEqualTo(1)
+ notVisible()
+ isInMainArea()
+ isCountEqualTo(2)
+ }
+ }
+
+ @Test
+ fun onNotificationListUpdated_itemsChangedPositions_nothingLogged() =
+ testScope.runTest {
+ // GIVEN some visible Notifications are reported
+ val (ranks, locations) = fakeNotificationMaps("key0", "key1")
+ underTest.onNotificationListUpdated({ locations }, ranks)
+ runCurrent()
+ clearInvocations(mockStatusBarService, mockNotificationListenerService)
+
+ // WHEN the reported Notifications are changing positions
+ val (newRanks, newLocations) = fakeNotificationMaps("key1", "key0")
+ underTest.onNotificationListUpdated({ newLocations }, newRanks)
+ runCurrent()
+
+ // THEN no visibility changes are reported
+ verifyZeroInteractions(mockStatusBarService, mockNotificationListenerService)
+ }
+
+ @Test
+ fun onNotificationListUpdated_calledTwice_usesTheNewCallable() =
+ testScope.runTest {
+ // GIVEN some visible Notifications are reported
+ val (ranks, locations) = fakeNotificationMaps("key0", "key1", "key2")
+ val callable = spy(Callable { locations })
+ underTest.onNotificationListUpdated(callable, ranks)
+ runCurrent()
+ clearInvocations(callable)
+
+ // WHEN a new update comes
+ val otherCallable = spy(Callable { locations })
+ underTest.onNotificationListUpdated(otherCallable, ranks)
+ runCurrent()
+
+ // THEN we call the new Callable
+ verifyZeroInteractions(callable)
+ verify(otherCallable).call()
+ }
+
+ @Test
+ fun onLockscreenOrShadeNotInteractive_logsNoLongerVisibleItems() =
+ testScope.runTest {
+ // GIVEN some visible Notifications are reported
+ val (ranks, locations) = fakeNotificationMaps("key0", "key1")
+ val callable = Callable { locations }
+ underTest.onNotificationListUpdated(callable, ranks)
+ runCurrent()
+ clearInvocations(mockStatusBarService, mockNotificationListenerService)
+
+ // WHEN the Shade becomes non interactive
+ underTest.onLockscreenOrShadeNotInteractive(emptyList())
+ runCurrent()
+
+ // THEN visibility changes are reported
+ verify(mockStatusBarService)
+ .onNotificationVisibilityChanged(eq(emptyArray()), visibilityArrayCaptor.capture())
+ verifyZeroInteractions(mockNotificationListenerService)
+ val noLongerVisible = visibilityArrayCaptor.value
+ assertThat(noLongerVisible).hasLength(2)
+ assertThat(noLongerVisible[0]).apply {
+ isKeyEqualTo("key0")
+ isRankEqualTo(0)
+ notVisible()
+ isInMainArea()
+ isCountEqualTo(0)
+ }
+ assertThat(noLongerVisible[1]).apply {
+ isKeyEqualTo("key1")
+ isRankEqualTo(1)
+ notVisible()
+ isInMainArea()
+ isCountEqualTo(0)
+ }
+ }
+
+ @Test
+ fun onLockscreenOrShadeInteractive_logsPanelShown() =
+ testScope.runTest {
+ // WHEN the Shade becomes interactive
+ underTest.onLockscreenOrShadeInteractive(
+ isOnLockScreen = true,
+ listOf(
+ activeNotificationModel(
+ key = "key0",
+ uid = 0,
+ packageName = "com.android.first"
+ ),
+ activeNotificationModel(
+ key = "key1",
+ uid = 1,
+ packageName = "com.android.second"
+ ),
+ )
+ )
+ runCurrent()
+
+ // THEN the Panel shown event is reported
+ verify(mockPanelLogger).logPanelShown(eq(true), notificationListProtoCaptor.capture())
+ val loggedNotifications = notificationListProtoCaptor.value.notifications
+ assertThat(loggedNotifications.size).isEqualTo(2)
+ with(loggedNotifications[0]) {
+ assertThat(uid).isEqualTo(0)
+ assertThat(packageName).isEqualTo("com.android.first")
+ }
+ with(loggedNotifications[1]) {
+ assertThat(uid).isEqualTo(1)
+ assertThat(packageName).isEqualTo("com.android.second")
+ }
+ }
+
+ @Test
+ fun onNotificationExpanded_visibleLocation_expansionLogged() =
+ testScope.runTest {
+ // WHEN a Notification is expanded
+ underTest.onNotificationExpansionChanged(
+ key = "key",
+ isExpanded = true,
+ location = ExpandableViewState.LOCATION_MAIN_AREA,
+ isUserAction = true
+ )
+ runCurrent()
+
+ // THEN the Expand event is reported
+ verify(mockStatusBarService)
+ .onNotificationExpansionChanged(
+ /* key = */ "key",
+ /* userAction = */ true,
+ /* expanded = */ true,
+ NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA.ordinal
+ )
+ }
+
+ @Test
+ fun onNotificationExpanded_notVisibleLocation_nothingLogged() =
+ testScope.runTest {
+ // WHEN a NOT visible Notification is expanded
+ underTest.onNotificationExpansionChanged(
+ key = "key",
+ isExpanded = true,
+ location = ExpandableViewState.LOCATION_BOTTOM_STACK_HIDDEN,
+ isUserAction = true
+ )
+ runCurrent()
+
+ // No events are reported
+ verifyZeroInteractions(mockStatusBarService)
+ }
+
+ @Test
+ fun onNotificationExpanded_notVisibleLocation_becomesVisible_expansionLogged() =
+ testScope.runTest {
+ // WHEN a NOT visible Notification is expanded
+ underTest.onNotificationExpansionChanged(
+ key = "key",
+ isExpanded = true,
+ location = ExpandableViewState.LOCATION_GONE,
+ isUserAction = true
+ )
+ runCurrent()
+
+ // AND it becomes visible
+ val (ranks, locations) = fakeNotificationMaps("key")
+ val callable = Callable { locations }
+ underTest.onNotificationListUpdated(callable, ranks)
+ runCurrent()
+
+ // THEN the Expand event is reported
+ verify(mockStatusBarService)
+ .onNotificationExpansionChanged(
+ /* key = */ "key",
+ /* userAction = */ true,
+ /* expanded = */ true,
+ NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA.ordinal
+ )
+ }
+
+ @Test
+ fun onNotificationCollapsed_isFirstInteraction_nothingLogged() =
+ testScope.runTest {
+ // WHEN a Notification is collapsed, and it is the first interaction
+ underTest.onNotificationExpansionChanged(
+ key = "key",
+ isExpanded = false,
+ location = ExpandableViewState.LOCATION_MAIN_AREA,
+ isUserAction = false
+ )
+ runCurrent()
+
+ // THEN no events are reported, because we consider the Notification initially
+ // collapsed, so only expanded is logged in the first time.
+ verifyZeroInteractions(mockStatusBarService)
+ }
+
+ @Test
+ fun onNotificationExpandedAndCollapsed_expansionChangesLogged() =
+ testScope.runTest {
+ // GIVEN a Notification is expanded
+ underTest.onNotificationExpansionChanged(
+ key = "key",
+ isExpanded = true,
+ location = ExpandableViewState.LOCATION_MAIN_AREA,
+ isUserAction = true
+ )
+ runCurrent()
+
+ // WHEN the Notification is collapsed
+ verify(mockStatusBarService)
+ .onNotificationExpansionChanged(
+ /* key = */ "key",
+ /* userAction = */ true,
+ /* expanded = */ true,
+ NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA.ordinal
+ )
+
+ // AND the Notification is expanded again
+ underTest.onNotificationExpansionChanged(
+ key = "key",
+ isExpanded = false,
+ location = ExpandableViewState.LOCATION_MAIN_AREA,
+ isUserAction = true
+ )
+ runCurrent()
+
+ // THEN the expansion changes are logged
+ verify(mockStatusBarService)
+ .onNotificationExpansionChanged(
+ /* key = */ "key",
+ /* userAction = */ true,
+ /* expanded = */ false,
+ NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA.ordinal
+ )
+ }
+
+ private fun fakeNotificationMaps(
+ vararg keys: String
+ ): Pair<Map<String, Int>, Map<String, Int>> {
+ val ranks: Map<String, Int> = keys.mapIndexed { index, key -> key to index }.toMap()
+ val locations: Map<String, Int> =
+ keys.associateWith { ExpandableViewState.LOCATION_MAIN_AREA }
+
+ return Pair(ranks, locations)
+ }
+
+ private fun assertThat(visibility: NotificationVisibility) =
+ NotificationVisibilitySubject(visibility)
+}
+
+private class NotificationVisibilitySubject(private val visibility: NotificationVisibility) {
+ fun isKeyEqualTo(key: String) = assertThat(visibility.key).isEqualTo(key)
+ fun isRankEqualTo(rank: Int) = assertThat(visibility.rank).isEqualTo(rank)
+ fun isCountEqualTo(count: Int) = assertThat(visibility.count).isEqualTo(count)
+ fun isVisible() = assertThat(this.visibility.visible).isTrue()
+ fun notVisible() = assertThat(this.visibility.visible).isFalse()
+ fun isInMainArea() =
+ assertThat(this.visibility.location)
+ .isEqualTo(NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index c17a8ef..4188c5d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -38,6 +38,7 @@
import com.android.systemui.runCurrent
import com.android.systemui.runTest
import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.statusbar.notification.dagger.NotificationStatsLoggerModule
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
@@ -71,6 +72,7 @@
FooterViewModelModule::class,
HeadlessSystemUserModeModule::class,
UnfoldTransitionModule.Bindings::class,
+ NotificationStatsLoggerModule::class,
]
)
interface TestComponent : SysUITestComponent<NotificationListViewModel> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt
new file mode 100644
index 0000000..e9d88cc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.kosmos.testScope
+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.scene.data.repository.windowRootViewVisibilityRepository
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
+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 NotificationLoggerViewModelTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private val testScope = kosmos.testScope
+ private val activeNotificationListRepository = kosmos.activeNotificationListRepository
+ private val keyguardRepository = kosmos.fakeKeyguardRepository
+ private val powerInteractor = kosmos.powerInteractor
+ private val windowRootViewVisibilityRepository = kosmos.windowRootViewVisibilityRepository
+
+ private val underTest = kosmos.notificationListLoggerViewModel
+
+ @Test
+ fun isLockscreenOrShadeInteractive_deviceActiveAndShadeIsInteractive_true() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ windowRootViewVisibilityRepository.setIsLockscreenOrShadeVisible(true)
+
+ val actual by collectLastValue(underTest.isLockscreenOrShadeInteractive)
+
+ assertThat(actual).isTrue()
+ }
+
+ @Test
+ fun isLockscreenOrShadeInteractive_deviceIsAsleepAndShadeIsInteractive_false() =
+ testScope.runTest {
+ powerInteractor.setAsleepForTest()
+ windowRootViewVisibilityRepository.setIsLockscreenOrShadeVisible(true)
+
+ val actual by collectLastValue(underTest.isLockscreenOrShadeInteractive)
+
+ assertThat(actual).isFalse()
+ }
+
+ @Test
+ fun isLockscreenOrShadeInteractive_deviceActiveAndShadeIsNotInteractive_false() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ windowRootViewVisibilityRepository.setIsLockscreenOrShadeVisible(false)
+
+ val actual by collectLastValue(underTest.isLockscreenOrShadeInteractive)
+
+ assertThat(actual).isFalse()
+ }
+
+ @Test
+ fun activeNotifications_hasNotifications() =
+ testScope.runTest {
+ activeNotificationListRepository.setActiveNotifs(5)
+
+ val notifs by collectLastValue(underTest.activeNotifications)
+
+ assertThat(notifs).hasSize(5)
+ requireNotNull(notifs).forEachIndexed { i, notif ->
+ assertThat(notif.key).isEqualTo("$i")
+ }
+ }
+
+ @Test
+ fun activeNotifications_isEmpty() =
+ testScope.runTest {
+ activeNotificationListRepository.setActiveNotifs(0)
+
+ val notifications by collectLastValue(underTest.activeNotifications)
+
+ assertThat(notifications).isEmpty()
+ }
+
+ @Test
+ fun activeNotificationRanks_hasNotifications() =
+ testScope.runTest {
+ val keys = (0..4).map { "$it" }
+ activeNotificationListRepository.setActiveNotifs(5)
+
+ val rankingsMap by collectLastValue(underTest.activeNotificationRanks)
+
+ assertThat(rankingsMap).hasSize(5)
+ keys.forEachIndexed { rank, key -> assertThat(rankingsMap).containsEntry(key, rank) }
+ }
+
+ @Test
+ fun activeNotificationRanks_isEmpty() =
+ testScope.runTest {
+ activeNotificationListRepository.setActiveNotifs(0)
+
+ val rankingsMap by collectLastValue(underTest.activeNotificationRanks)
+
+ assertThat(rankingsMap).isEmpty()
+ }
+
+ @Test
+ fun isOnLockScreen_true() =
+ testScope.runTest {
+ keyguardRepository.setKeyguardShowing(true)
+
+ val isOnLockScreen by collectLastValue(underTest.isOnLockScreen)
+
+ assertThat(isOnLockScreen).isTrue()
+ }
+ @Test
+ fun isOnLockScreen_false() =
+ testScope.runTest {
+ keyguardRepository.setKeyguardShowing(false)
+
+ val isOnLockScreen by collectLastValue(underTest.isOnLockScreen)
+
+ assertThat(isOnLockScreen).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 20020f2..a824bc4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -21,6 +21,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
@@ -39,8 +40,11 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.largeScreenHeaderHelper
+import com.android.systemui.shade.mockLargeScreenHeaderHelper
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
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
@@ -66,6 +70,7 @@
val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
val shadeRepository = kosmos.shadeRepository
val sharedNotificationContainerInteractor = kosmos.sharedNotificationContainerInteractor
+ val largeScreenHeaderHelper = kosmos.mockLargeScreenHeaderHelper
val underTest = kosmos.sharedNotificationContainerViewModel
@@ -101,8 +106,10 @@
}
@Test
- fun validatePaddingTopInSplitShade() =
+ fun validatePaddingTopInSplitShade_refactorFlagOff_usesLargeHeaderResource() =
testScope.runTest {
+ mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+ whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
overrideResource(R.bool.config_use_split_notification_shade, true)
overrideResource(R.dimen.large_screen_shade_header_height, 10)
overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
@@ -115,6 +122,22 @@
}
@Test
+ fun validatePaddingTopInSplitShade_refactorFlagOn_usesLargeHeaderHelper() =
+ testScope.runTest {
+ mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+ whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
+ overrideResource(R.bool.config_use_split_notification_shade, true)
+ overrideResource(R.dimen.large_screen_shade_header_height, 10)
+ overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
+
+ val dimens by collectLastValue(underTest.configurationBasedDimensions)
+
+ configurationRepository.onAnyConfigurationChange()
+
+ assertThat(dimens!!.paddingTop).isEqualTo(40)
+ }
+
+ @Test
fun validatePaddingTop() =
testScope.runTest {
overrideResource(R.bool.config_use_split_notification_shade, false)
@@ -153,17 +176,41 @@
}
@Test
- fun validateMarginTopWithLargeScreenHeader() =
+ fun validateMarginTopWithLargeScreenHeader_refactorFlagOff_usesResource() =
testScope.runTest {
+ mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+ val headerResourceHeight = 50
+ val headerHelperHeight = 100
+ whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+ .thenReturn(headerHelperHeight)
overrideResource(R.bool.config_use_large_screen_shade_header, true)
- overrideResource(R.dimen.large_screen_shade_header_height, 50)
+ overrideResource(R.dimen.large_screen_shade_header_height, headerResourceHeight)
overrideResource(R.dimen.notification_panel_margin_top, 0)
val dimens by collectLastValue(underTest.configurationBasedDimensions)
configurationRepository.onAnyConfigurationChange()
- assertThat(dimens!!.marginTop).isEqualTo(50)
+ assertThat(dimens!!.marginTop).isEqualTo(headerResourceHeight)
+ }
+
+ @Test
+ fun validateMarginTopWithLargeScreenHeader_refactorFlagOn_usesHelper() =
+ testScope.runTest {
+ mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+ val headerResourceHeight = 50
+ val headerHelperHeight = 100
+ whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+ .thenReturn(headerHelperHeight)
+ overrideResource(R.bool.config_use_large_screen_shade_header, true)
+ overrideResource(R.dimen.large_screen_shade_header_height, headerResourceHeight)
+ overrideResource(R.dimen.notification_panel_margin_top, 0)
+
+ val dimens by collectLastValue(underTest.configurationBasedDimensions)
+
+ configurationRepository.onAnyConfigurationChange()
+
+ assertThat(dimens!!.marginTop).isEqualTo(headerHelperHeight)
}
@Test
@@ -275,11 +322,13 @@
}
@Test
- fun boundsOnLockscreenInSplitShade() =
+ fun boundsOnLockscreenInSplitShade_refactorFlagOff_usesLargeHeaderResource() =
testScope.runTest {
+ mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
val bounds by collectLastValue(underTest.bounds)
// When in split shade
+ whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
overrideResource(R.bool.config_use_split_notification_shade, true)
overrideResource(R.dimen.large_screen_shade_header_height, 10)
overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
@@ -300,6 +349,33 @@
}
@Test
+ fun boundsOnLockscreenInSplitShade_refactorFlagOn_usesLargeHeaderHelper() =
+ testScope.runTest {
+ mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+ val bounds by collectLastValue(underTest.bounds)
+
+ // When in split shade
+ whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
+ overrideResource(R.bool.config_use_split_notification_shade, true)
+ overrideResource(R.dimen.large_screen_shade_header_height, 10)
+ overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
+
+ configurationRepository.onAnyConfigurationChange()
+ runCurrent()
+
+ // Start on lockscreen
+ showLockscreen()
+
+ keyguardInteractor.setNotificationContainerBounds(
+ NotificationContainerBounds(top = 1f, bottom = 2f)
+ )
+ runCurrent()
+
+ // Top should be equal to bounds (1) + padding adjustment (40)
+ assertThat(bounds).isEqualTo(NotificationContainerBounds(top = 41f, bottom = 2f))
+ }
+
+ @Test
fun boundsOnShade() =
testScope.runTest {
val bounds by collectLastValue(underTest.bounds)
@@ -408,6 +484,46 @@
}
@Test
+ fun translationYUpdatesOnKeyguard() =
+ testScope.runTest {
+ val translationY by collectLastValue(underTest.translationY)
+
+ configurationRepository.setDimensionPixelSize(
+ R.dimen.keyguard_translate_distance_on_swipe_up,
+ -100
+ )
+ configurationRepository.onAnyConfigurationChange()
+
+ // legacy expansion means the user is swiping up, usually for the bouncer
+ shadeRepository.setLegacyShadeExpansion(0.5f)
+
+ showLockscreen()
+
+ // The translation values are negative
+ assertThat(translationY).isLessThan(0f)
+ }
+
+ @Test
+ fun translationYDoesNotUpdateWhenShadeIsExpanded() =
+ testScope.runTest {
+ val translationY by collectLastValue(underTest.translationY)
+
+ configurationRepository.setDimensionPixelSize(
+ R.dimen.keyguard_translate_distance_on_swipe_up,
+ -100
+ )
+ configurationRepository.onAnyConfigurationChange()
+
+ // legacy expansion means the user is swiping up, usually for the bouncer but also for
+ // shade collapsing
+ shadeRepository.setLegacyShadeExpansion(0.5f)
+
+ showLockscreenWithShadeExpanded()
+
+ assertThat(translationY).isEqualTo(0f)
+ }
+
+ @Test
fun updateBounds_fromKeyguardRoot() =
testScope.runTest {
val bounds by collectLastValue(underTest.bounds)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index 2b1f5fc..c350de2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -35,13 +35,12 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
-import com.android.systemui.statusbar.AlertingNotificationManager;
-import com.android.systemui.statusbar.AlertingNotificationManagerTest;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
+import com.android.systemui.statusbar.policy.BaseHeadsUpManagerTest;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
@@ -64,7 +63,7 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest {
+public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest {
@Rule public MockitoRule rule = MockitoJUnit.rule();
private final HeadsUpManagerLogger mHeadsUpManagerLogger = new HeadsUpManagerLogger(
@@ -137,11 +136,6 @@
);
}
- @Override
- protected AlertingNotificationManager createAlertingNotificationManager() {
- return createHeadsUpManagerPhone();
- }
-
@Before
public void setUp() {
when(mShadeInteractor.isAnyExpanded()).thenReturn(StateFlowKt.MutableStateFlow(false));
@@ -179,7 +173,7 @@
/* releaseImmediately = */ false);
assertTrue(removedImmediately);
- assertFalse(hmp.isAlerting(entry.getKey()));
+ assertFalse(hmp.isHeadsUpEntry(entry.getKey()));
}
@Test
@@ -218,6 +212,6 @@
hmp.extendHeadsUp();
mSystemClock.advanceTime(TEST_AUTO_DISMISS_TIME + hmp.mExtensionTime / 2);
- assertTrue(hmp.isAlerting(entry.getKey()));
+ assertTrue(hmp.isHeadsUpEntry(entry.getKey()));
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
index 91cbc32..7362e34 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
@@ -24,9 +24,9 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.policy.DevicePostureController
@@ -35,6 +35,7 @@
import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED
import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
import com.android.systemui.tuner.TunerService
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -60,8 +61,8 @@
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class KeyguardBypassControllerTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private val featureFlags = FakeFeatureFlags()
private val shadeRepository = FakeShadeRepository()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index 4dc4798..bbf9a6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -30,11 +30,13 @@
import androidx.test.filters.SmallTest;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.doze.util.BurnInHelperKt;
import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.core.FakeLogBuffer;
import com.android.systemui.res.R;
+import com.android.systemui.shade.LargeScreenHeaderHelper;
import org.junit.After;
import org.junit.Before;
@@ -79,6 +81,7 @@
MockitoAnnotations.initMocks(this);
mStaticMockSession = mockitoSession()
.mockStatic(BurnInHelperKt.class)
+ .mockStatic(LargeScreenHeaderHelper.class)
.startMocking();
LogBuffer logBuffer = FakeLogBuffer.Factory.Companion.create();
@@ -292,18 +295,44 @@
}
@Test
- public void notifPaddingMakesUpToFullMarginInSplitShade() {
+ public void notifPaddingMakesUpToFullMarginInSplitShade_refactorFlagOff_usesResource() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR);
+ int keyguardSplitShadeTopMargin = 100;
+ int largeScreenHeaderHeightResource = 70;
when(mResources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin))
- .thenReturn(100);
+ .thenReturn(keyguardSplitShadeTopMargin);
when(mResources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height))
- .thenReturn(70);
+ .thenReturn(largeScreenHeaderHeightResource);
mClockPositionAlgorithm.loadDimens(mContext, mResources);
givenLockScreen();
mIsSplitShade = true;
// WHEN the position algorithm is run
positionClock();
- // THEN the notif padding makes up lacking margin (margin - header height = 30).
- assertThat(mClockPosition.stackScrollerPadding).isEqualTo(30);
+ // THEN the notif padding makes up lacking margin (margin - header height).
+ int expectedPadding = keyguardSplitShadeTopMargin - largeScreenHeaderHeightResource;
+ assertThat(mClockPosition.stackScrollerPadding).isEqualTo(expectedPadding);
+ }
+
+ @Test
+ public void notifPaddingMakesUpToFullMarginInSplitShade_refactorFlagOn_usesHelper() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR);
+ int keyguardSplitShadeTopMargin = 100;
+ int largeScreenHeaderHeightHelper = 50;
+ int largeScreenHeaderHeightResource = 70;
+ when(LargeScreenHeaderHelper.getLargeScreenHeaderHeight(mContext))
+ .thenReturn(largeScreenHeaderHeightHelper);
+ when(mResources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin))
+ .thenReturn(keyguardSplitShadeTopMargin);
+ when(mResources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height))
+ .thenReturn(largeScreenHeaderHeightResource);
+ mClockPositionAlgorithm.loadDimens(mContext, mResources);
+ givenLockScreen();
+ mIsSplitShade = true;
+ // WHEN the position algorithm is run
+ positionClock();
+ // THEN the notif padding makes up lacking margin (margin - header height).
+ int expectedPadding = keyguardSplitShadeTopMargin - largeScreenHeaderHeightHelper;
+ assertThat(mClockPosition.stackScrollerPadding).isEqualTo(expectedPadding);
}
@Test
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 5102b4f..2d120cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -60,10 +60,10 @@
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
import com.android.systemui.res.R;
-import com.android.systemui.scene.SceneTestUtils;
import com.android.systemui.shade.ShadeViewStateProvider;
import com.android.systemui.shade.data.repository.FakeShadeRepository;
import com.android.systemui.statusbar.CommandQueue;
@@ -149,7 +149,7 @@
private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
private final TestScope mTestScope = TestScopeProvider.getTestScope();
private final FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository();
- private final SceneTestUtils mSceneTestUtils = new SceneTestUtils(this);
+ private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
private KeyguardInteractor mKeyguardInteractor;
private KeyguardStatusBarViewModel mViewModel;
@@ -166,11 +166,11 @@
mKeyguardRepository,
mCommandQueue,
PowerInteractorFactory.create().getPowerInteractor(),
- mSceneTestUtils.getSceneContainerFlags(),
+ mKosmos.getFakeSceneContainerFlags(),
new FakeKeyguardBouncerRepository(),
new ConfigurationInteractor(new FakeConfigurationRepository()),
new FakeShadeRepository(),
- () -> mSceneTestUtils.sceneInteractor());
+ () -> mKosmos.getSceneInteractor());
mViewModel =
new KeyguardStatusBarViewModel(
mTestScope.getBackgroundScope(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryTest.kt
deleted file mode 100644
index 91c233a..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryTest.kt
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline.mobile.data.repository
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-class UserSetupRepositoryTest : SysuiTestCase() {
- private lateinit var underTest: UserSetupRepository
- @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
- private val scope = CoroutineScope(IMMEDIATE)
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- underTest =
- UserSetupRepositoryImpl(
- deviceProvisionedController,
- IMMEDIATE,
- scope,
- )
- }
-
- @After
- fun tearDown() {
- scope.cancel()
- }
-
- @Test
- fun testUserSetup_defaultFalse() =
- runBlocking(IMMEDIATE) {
- var latest: Boolean? = null
-
- val job = underTest.isUserSetupFlow.onEach { latest = it }.launchIn(this)
-
- assertThat(latest).isFalse()
-
- job.cancel()
- }
-
- @Test
- fun testUserSetup_updatesOnChange() =
- runBlocking(IMMEDIATE) {
- var latest: Boolean? = null
-
- val job = underTest.isUserSetupFlow.onEach { latest = it }.launchIn(this)
-
- whenever(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
- val callback = getDeviceProvisionedListener()
- callback.onUserSetupChanged()
-
- assertThat(latest).isTrue()
-
- job.cancel()
- }
-
- private fun getDeviceProvisionedListener(): DeviceProvisionedListener {
- val captor = argumentCaptor<DeviceProvisionedListener>()
- verify(deviceProvisionedController).addCallback(captor.capture())
- return captor.value!!
- }
-
- companion object {
- private val IMMEDIATE = Dispatchers.Main.immediate
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index 2060288..0b14be1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -31,10 +31,10 @@
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
import com.android.systemui.util.CarrierConfigTracker
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
index 52fc258..889130f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
@@ -30,7 +30,6 @@
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractorImpl
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
@@ -39,6 +38,7 @@
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
import com.android.systemui.util.CarrierConfigTracker
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index 44fa132..147efcb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -42,7 +42,6 @@
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractorImpl
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
@@ -51,6 +50,7 @@
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
import com.android.systemui.util.CarrierConfigTracker
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
index a906a89..02e6fd5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
@@ -31,7 +31,7 @@
import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE
import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN
import android.telephony.satellite.SatelliteManager.SatelliteException
-import android.telephony.satellite.SatelliteStateCallback
+import android.telephony.satellite.SatelliteModemStateCallback
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
@@ -106,7 +106,7 @@
val latest by collectLastValue(underTest.connectionState)
runCurrent()
val callback =
- withArgCaptor<SatelliteStateCallback> {
+ withArgCaptor<SatelliteModemStateCallback> {
verify(satelliteManager).registerForSatelliteModemStateChanged(any(), capture())
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
index 89842d6..f63f79f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.collectLastValue
import com.android.systemui.collectValues
+import com.android.systemui.communal.dagger.CommunalModule
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -58,6 +59,7 @@
modules =
[
SysUITestModule::class,
+ CommunalModule::class,
]
)
interface TestComponent : SysUITestComponent<CollapsedStatusBarViewModelImpl> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
index 1bdf644..0cb3329 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
@@ -35,7 +35,6 @@
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
@@ -48,6 +47,7 @@
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
+import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
import com.android.systemui.util.CarrierConfigTracker
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
index 6a3b2c3..4c824c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
@@ -19,6 +19,7 @@
import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
import static com.android.systemui.util.concurrency.MockExecutorHandlerKt.mockExecutorHandler;
import static com.google.common.truth.Truth.assertThat;
@@ -36,12 +37,15 @@
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.Context;
import android.content.Intent;
import android.graphics.Region;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -51,13 +55,16 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.testing.UiEventLoggerFake;
+import com.android.systemui.SysuiTestCase;
import com.android.systemui.res.R;
-import com.android.systemui.statusbar.AlertingNotificationManager;
-import com.android.systemui.statusbar.AlertingNotificationManagerTest;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.settings.FakeGlobalSettings;
import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.util.time.SystemClock;
import org.junit.Rule;
@@ -70,10 +77,12 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest {
+public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Rule
public MockitoRule rule = MockitoJUnit.rule();
+ private static final String TEST_PACKAGE_NAME = "BaseHeadsUpManagerTest";
+
private static final int TEST_TOUCH_ACCEPTANCE_TIME = 200;
private static final int TEST_A11Y_AUTO_DISMISS_TIME = 1_000;
@@ -81,6 +90,20 @@
private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
@Mock private AccessibilityManagerWrapper mAccessibilityMgr;
+ private static final int TEST_UID = 0;
+
+ protected static final int TEST_MINIMUM_DISPLAY_TIME = 400;
+ protected static final int TEST_AUTO_DISMISS_TIME = 600;
+ protected static final int TEST_STICKY_AUTO_DISMISS_TIME = 800;
+ // Number of notifications to use in tests requiring multiple notifications
+ private static final int TEST_NUM_NOTIFICATIONS = 4;
+
+ protected final FakeGlobalSettings mGlobalSettings = new FakeGlobalSettings();
+ protected final FakeSystemClock mSystemClock = new FakeSystemClock();
+ protected final FakeExecutor mExecutor = new FakeExecutor(mSystemClock);
+
+ @Mock protected ExpandableNotificationRow mRow;
+
static {
assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME);
assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME);
@@ -88,6 +111,9 @@
}
private final class TestableHeadsUpManager extends BaseHeadsUpManager {
+
+ private HeadsUpEntry mLastCreatedEntry;
+
TestableHeadsUpManager(Context context,
HeadsUpManagerLogger logger,
DelayableExecutor executor,
@@ -97,10 +123,23 @@
UiEventLogger uiEventLogger) {
super(context, logger, mockExecutorHandler(executor), globalSettings, systemClock,
executor, accessibilityManagerWrapper, uiEventLogger);
+
mTouchAcceptanceDelay = TEST_TOUCH_ACCEPTANCE_TIME;
mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
mAutoDismissTime = TEST_AUTO_DISMISS_TIME;
mStickyForSomeTimeAutoDismissTime = TEST_STICKY_AUTO_DISMISS_TIME;
+
+ }
+
+ @Override
+ protected HeadsUpEntry createHeadsUpEntry() {
+ mLastCreatedEntry = spy(super.createHeadsUpEntry());
+ return mLastCreatedEntry;
+ }
+
+ @Override
+ public int getContentFlag() {
+ return FLAG_CONTENT_VIEW_CONTRACTED;
}
// The following are only implemented by HeadsUpManagerPhone. If you need them, use that.
@@ -173,16 +212,46 @@
}
}
+ protected StatusBarNotification createSbn(int id, Notification n) {
+ return new StatusBarNotification(
+ TEST_PACKAGE_NAME /* pkg */,
+ TEST_PACKAGE_NAME,
+ id,
+ null /* tag */,
+ TEST_UID,
+ 0 /* initialPid */,
+ n,
+ new UserHandle(ActivityManager.getCurrentUser()),
+ null /* overrideGroupKey */,
+ 0 /* postTime */);
+ }
+
+ protected StatusBarNotification createSbn(int id, Notification.Builder n) {
+ return createSbn(id, n.build());
+ }
+
+ protected StatusBarNotification createSbn(int id) {
+ final Notification.Builder b = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setContentTitle("Title")
+ .setContentText("Text");
+ return createSbn(id, b);
+ }
+
+ protected NotificationEntry createEntry(int id, Notification n) {
+ return new NotificationEntryBuilder().setSbn(createSbn(id, n)).build();
+ }
+
+ protected NotificationEntry createEntry(int id) {
+ return new NotificationEntryBuilder().setSbn(createSbn(id)).build();
+ }
+
+
private BaseHeadsUpManager createHeadsUpManager() {
return new TestableHeadsUpManager(mContext, mLogger, mExecutor, mGlobalSettings,
mSystemClock, mAccessibilityMgr, mUiEventLoggerFake);
}
- @Override
- protected AlertingNotificationManager createAlertingNotificationManager() {
- return createHeadsUpManager();
- }
-
private NotificationEntry createStickyEntry(int id) {
final Notification notif = new Notification.Builder(mContext, "")
.setSmallIcon(R.drawable.ic_person)
@@ -224,6 +293,79 @@
}
}
+ @Test
+ public void testShowNotification_addsEntry() {
+ final BaseHeadsUpManager alm = createHeadsUpManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+
+ alm.showNotification(entry);
+
+ assertTrue(alm.isHeadsUpEntry(entry.getKey()));
+ assertTrue(alm.hasNotifications());
+ assertEquals(entry, alm.getEntry(entry.getKey()));
+ }
+
+ @Test
+ public void testShowNotification_autoDismisses() {
+ final BaseHeadsUpManager alm = createHeadsUpManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+
+ alm.showNotification(entry);
+ mSystemClock.advanceTime(TEST_AUTO_DISMISS_TIME * 3 / 2);
+
+ assertFalse(alm.isHeadsUpEntry(entry.getKey()));
+ }
+
+ @Test
+ public void testRemoveNotification_removeDeferred() {
+ final BaseHeadsUpManager alm = createHeadsUpManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+
+ alm.showNotification(entry);
+
+ final boolean removedImmediately = alm.removeNotification(
+ entry.getKey(), /* releaseImmediately = */ false);
+ assertFalse(removedImmediately);
+ assertTrue(alm.isHeadsUpEntry(entry.getKey()));
+ }
+
+ @Test
+ public void testRemoveNotification_forceRemove() {
+ final BaseHeadsUpManager alm = createHeadsUpManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+
+ alm.showNotification(entry);
+
+ final boolean removedImmediately = alm.removeNotification(
+ entry.getKey(), /* releaseImmediately = */ true);
+ assertTrue(removedImmediately);
+ assertFalse(alm.isHeadsUpEntry(entry.getKey()));
+ }
+
+ @Test
+ public void testReleaseAllImmediately() {
+ final BaseHeadsUpManager alm = createHeadsUpManager();
+ for (int i = 0; i < TEST_NUM_NOTIFICATIONS; i++) {
+ final NotificationEntry entry = createEntry(i);
+ entry.setRow(mRow);
+ alm.showNotification(entry);
+ }
+
+ alm.releaseAllImmediately();
+
+ assertEquals(0, alm.getAllEntries().count());
+ }
+
+ @Test
+ public void testCanRemoveImmediately_notShownLongEnough() {
+ final BaseHeadsUpManager alm = createHeadsUpManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+
+ alm.showNotification(entry);
+
+ // The entry has just been added so we should not remove immediately.
+ assertFalse(alm.canRemoveImmediately(entry.getKey()));
+ }
@Test
public void testHunRemovedLogging() {
@@ -233,7 +375,7 @@
BaseHeadsUpManager.HeadsUpEntry.class);
headsUpEntry.mEntry = notifEntry;
- hum.onAlertEntryRemoved(headsUpEntry);
+ hum.onEntryRemoved(headsUpEntry);
verify(mLogger, times(1)).logNotificationActuallyRemoved(eq(notifEntry));
}
@@ -286,7 +428,7 @@
hum.showNotification(entry);
mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME / 2 + TEST_AUTO_DISMISS_TIME);
- assertTrue(hum.isAlerting(entry.getKey()));
+ assertTrue(hum.isHeadsUpEntry(entry.getKey()));
}
@@ -300,7 +442,7 @@
mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME
+ (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2);
- assertFalse(hum.isAlerting(entry.getKey()));
+ assertFalse(hum.isHeadsUpEntry(entry.getKey()));
}
@@ -314,7 +456,7 @@
mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME
+ (TEST_AUTO_DISMISS_TIME + TEST_STICKY_AUTO_DISMISS_TIME) / 2);
- assertTrue(hum.isAlerting(entry.getKey()));
+ assertTrue(hum.isHeadsUpEntry(entry.getKey()));
}
@@ -327,7 +469,7 @@
hum.showNotification(entry);
mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME + 2 * TEST_A11Y_AUTO_DISMISS_TIME);
- assertTrue(hum.isAlerting(entry.getKey()));
+ assertTrue(hum.isHeadsUpEntry(entry.getKey()));
}
@@ -341,7 +483,7 @@
mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME
+ (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2);
- assertTrue(hum.isAlerting(entry.getKey()));
+ assertTrue(hum.isHeadsUpEntry(entry.getKey()));
}
@@ -355,7 +497,7 @@
mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME
+ (TEST_STICKY_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2);
- assertTrue(hum.isAlerting(entry.getKey()));
+ assertTrue(hum.isHeadsUpEntry(entry.getKey()));
}
@@ -370,11 +512,11 @@
final boolean removedImmediately = hum.removeNotification(
entry.getKey(), /* releaseImmediately = */ false);
assertFalse(removedImmediately);
- assertTrue(hum.isAlerting(entry.getKey()));
+ assertTrue(hum.isHeadsUpEntry(entry.getKey()));
mSystemClock.advanceTime((TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2);
- assertFalse(hum.isAlerting(entry.getKey()));
+ assertFalse(hum.isHeadsUpEntry(entry.getKey()));
}
@@ -387,12 +529,12 @@
hum.showNotification(entry);
mSystemClock.advanceTime((TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2);
- assertTrue(hum.isAlerting(entry.getKey()));
+ assertTrue(hum.isHeadsUpEntry(entry.getKey()));
final boolean removedImmediately = hum.removeNotification(
entry.getKey(), /* releaseImmediately = */ false);
assertTrue(removedImmediately);
- assertFalse(hum.isAlerting(entry.getKey()));
+ assertFalse(hum.isHeadsUpEntry(entry.getKey()));
}
@@ -406,7 +548,7 @@
final boolean removedImmediately = hum.removeNotification(
entry.getKey(), /* releaseImmediately = */ true);
assertTrue(removedImmediately);
- assertFalse(hum.isAlerting(entry.getKey()));
+ assertFalse(hum.isHeadsUpEntry(entry.getKey()));
}
@@ -560,7 +702,7 @@
// the notification and then updates it; in order to not log twice, the entry needs
// to have a functional ExpandableNotificationRow that can keep track of whether it's
// pinned or not (via isRowPinned()). That feels like a lot to pull in to test this one bit.
- hum.onAlertEntryAdded(entryToPin);
+ hum.onEntryAdded(entryToPin);
assertEquals(1, mUiEventLoggerFake.numLogs());
assertEquals(BaseHeadsUpManager.NotificationPeekEvent.NOTIFICATION_PEEK.getId(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
index 1dab84e..cb6ce68 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.policy;
import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -214,7 +215,8 @@
public void testNetworkRequest() {
verify(mConnectivityManager, times(1)).registerNetworkCallback(argThat(
(NetworkRequest request) ->
- request.equals(new NetworkRequest.Builder().clearCapabilities().build())
+ request.equals(new NetworkRequest.Builder()
+ .clearCapabilities().addTransportType(TRANSPORT_VPN).build())
), any(NetworkCallback.class));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
index 9419d63..ca0e526 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
@@ -25,20 +25,22 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.data.repository.FakeKeyguardStatusBarRepository
import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor
import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -47,20 +49,20 @@
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
class KeyguardStatusBarViewModelTest : SysuiTestCase() {
- private val testScope = TestScope()
- private val sceneTestUtils = SceneTestUtils(this)
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private val keyguardRepository = FakeKeyguardRepository()
private val keyguardInteractor =
KeyguardInteractor(
keyguardRepository,
mock<CommandQueue>(),
PowerInteractorFactory.create().powerInteractor,
- sceneTestUtils.sceneContainerFlags,
+ kosmos.fakeSceneContainerFlags,
FakeKeyguardBouncerRepository(),
ConfigurationInteractor(FakeConfigurationRepository()),
FakeShadeRepository(),
) {
- sceneTestUtils.sceneInteractor()
+ kosmos.sceneInteractor
}
private val keyguardStatusBarInteractor =
KeyguardStatusBarInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt
index 5b4f4d3..95c934e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt
@@ -88,6 +88,30 @@
}
@Test
+ fun setReadyToHandleTransition_whileTransitionRunning_fromBgThread_propagatesCallbacks() =
+ testScope.runTest {
+ runBlockingInBg { rootProvider.onTransitionStarted() }
+
+ runBlockingInBg {
+ // This causes the transition started callback to be propagated immediately, without
+ // the need to switch thread (as we're already in the correct one). We don't need a
+ // sync barrier on the bg thread as in
+ // setReadyToHandleTransition_whileTransitionRunning_propagatesCallbacks here.
+ scopedProvider.setReadyToHandleTransition(true)
+ }
+
+ listener.assertStarted()
+
+ runBlockingInBg { rootProvider.onTransitionProgress(1f) }
+
+ listener.assertLastProgress(1f)
+
+ runBlockingInBg { rootProvider.onTransitionFinished() }
+
+ listener.assertNotStarted()
+ }
+
+ @Test
fun setReadyToHandleTransition_beforeAnyCallback_doesNotCrash() {
testScope.runTest { scopedProvider.setReadyToHandleTransition(true) }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
index fb5375a..b6a033a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
@@ -40,13 +40,18 @@
import com.android.systemui.common.shared.model.Text
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.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
+import com.android.systemui.telephony.domain.interactor.telephonyInteractor
+import com.android.systemui.testKosmos
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.source.UserRecord
@@ -98,8 +103,8 @@
@Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private lateinit var spyContext: Context
private lateinit var userRepository: FakeUserRepository
private lateinit var keyguardReply: KeyguardInteractorFactory.WithDependencies
@@ -121,16 +126,17 @@
SUPERVISED_USER_CREATION_APP_PACKAGE,
)
- utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+ kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
mSetFlagsRule.enableFlags(AConfigFlags.FLAG_SWITCH_USER_ON_BG)
spyContext = spy(context)
- keyguardReply = KeyguardInteractorFactory.create(featureFlags = utils.featureFlags)
+ keyguardReply =
+ KeyguardInteractorFactory.create(featureFlags = kosmos.fakeFeatureFlagsClassic)
keyguardRepository = keyguardReply.repository
userRepository = FakeUserRepository()
refreshUsersScheduler =
RefreshUsersScheduler(
applicationScope = testScope.backgroundScope,
- mainDispatcher = utils.testDispatcher,
+ mainDispatcher = kosmos.testDispatcher,
repository = userRepository,
)
}
@@ -363,7 +369,7 @@
fun actions_deviceUnlocked_fullScreen() {
createUserInteractor()
testScope.runTest {
- utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+ kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
@@ -447,7 +453,7 @@
fun actions_deviceLockedAddFromLockscreenSet_fullList_fullScreen() {
createUserInteractor()
testScope.runTest {
- utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+ kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
@@ -640,7 +646,7 @@
val refreshUsersCallCount = userRepository.refreshUsersCallCount
- utils.telephonyRepository.setCallState(1)
+ kosmos.fakeTelephonyRepository.setCallState(1)
runCurrent()
assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
@@ -792,7 +798,7 @@
fun userRecordsFullScreen() {
createUserInteractor()
testScope.runTest {
- utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+ kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
val userInfos = createUserInfos(count = 3, includeGuest = false)
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
userRepository.setUserInfos(userInfos)
@@ -901,7 +907,7 @@
fun showUserSwitcher_fullScreenEnabled_launchesFullScreenDialog() {
createUserInteractor()
testScope.runTest {
- utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+ kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
val expandable = mock<Expandable>()
underTest.showUserSwitcher(expandable)
@@ -1116,19 +1122,19 @@
manager = manager,
headlessSystemUserMode = headlessSystemUserMode,
applicationScope = testScope.backgroundScope,
- telephonyInteractor = utils.telephonyInteractor(),
+ telephonyInteractor = kosmos.telephonyInteractor,
broadcastDispatcher = fakeBroadcastDispatcher,
keyguardUpdateMonitor = keyguardUpdateMonitor,
- backgroundDispatcher = utils.testDispatcher,
- mainDispatcher = utils.testDispatcher,
+ backgroundDispatcher = kosmos.testDispatcher,
+ mainDispatcher = kosmos.testDispatcher,
activityManager = activityManager,
refreshUsersScheduler = refreshUsersScheduler,
guestUserInteractor =
GuestUserInteractor(
applicationContext = spyContext,
applicationScope = testScope.backgroundScope,
- mainDispatcher = utils.testDispatcher,
- backgroundDispatcher = utils.testDispatcher,
+ mainDispatcher = kosmos.testDispatcher,
+ backgroundDispatcher = kosmos.testDispatcher,
manager = manager,
repository = userRepository,
deviceProvisionedController = deviceProvisionedController,
@@ -1139,7 +1145,7 @@
resetOrExitSessionReceiver = resetOrExitSessionReceiver,
),
uiEventLogger = uiEventLogger,
- featureFlags = utils.featureFlags,
+ featureFlags = kosmos.fakeFeatureFlagsClassic,
userRestrictionChecker = mock(),
)
}
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 b7529a8..589f7c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -99,6 +99,8 @@
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
+import com.android.systemui.communal.domain.interactor.CommunalInteractor;
+import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
@@ -111,14 +113,15 @@
import com.android.systemui.keyguard.data.repository.InWindowLauncherUnlockAnimationRepository;
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
+import com.android.systemui.keyguard.domain.interactor.GlanceableHubTransitions;
import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.scene.FakeWindowRootViewComponent;
-import com.android.systemui.scene.SceneTestUtils;
import com.android.systemui.scene.data.repository.SceneContainerRepository;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
@@ -160,7 +163,6 @@
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -169,6 +171,7 @@
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
+import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
import com.android.systemui.util.FakeEventLog;
@@ -353,8 +356,8 @@
@Mock
private LargeScreenHeaderHelper mLargeScreenHeaderHelper;
- private final SceneTestUtils mUtils = new SceneTestUtils(this);
- private final TestScope mTestScope = mUtils.getTestScope();
+ private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+ private final TestScope mTestScope = mKosmos.getTestScope();
private ShadeInteractor mShadeInteractor;
private ShellTaskOrganizer mShellTaskOrganizer;
private TaskViewTransitions mTaskViewTransitions;
@@ -405,8 +408,8 @@
FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository();
PowerInteractor powerInteractor = new PowerInteractor(
- mUtils.getPowerRepository(),
- mUtils.falsingCollector(),
+ mKosmos.getPowerRepository(),
+ mKosmos.getFalsingCollector(),
mock(ScreenOffAnimationController.class),
mStatusBarStateController);
@@ -414,9 +417,10 @@
mTestScope.getBackgroundScope(),
new SceneContainerRepository(
mTestScope.getBackgroundScope(),
- mUtils.fakeSceneContainerConfig()),
+ mKosmos.getFakeSceneContainerConfig()),
powerInteractor,
- mock(SceneLogger.class));
+ mock(SceneLogger.class),
+ mKosmos.getDeviceUnlockedInteractor());
FakeSceneContainerFlags sceneContainerFlags = new FakeSceneContainerFlags();
KeyguardInteractor keyguardInteractor = new KeyguardInteractor(
@@ -439,15 +443,25 @@
() -> keyguardInteractor,
() -> mFromLockscreenTransitionInteractor,
() -> mFromPrimaryBouncerTransitionInteractor);
+ CommunalInteractor communalInteractor =
+ CommunalInteractorFactory.create().getCommunalInteractor();
mFromLockscreenTransitionInteractor = new FromLockscreenTransitionInteractor(
keyguardTransitionRepository,
keyguardTransitionInteractor,
mTestScope.getBackgroundScope(),
+ mKosmos.getTestDispatcher(),
+ mKosmos.getTestDispatcher(),
keyguardInteractor,
featureFlags,
shadeRepository,
powerInteractor,
+ new GlanceableHubTransitions(
+ mTestScope,
+ keyguardTransitionInteractor,
+ keyguardTransitionRepository,
+ communalInteractor
+ ),
() ->
new InWindowLauncherUnlockAnimationInteractor(
new InWindowLauncherUnlockAnimationRepository(),
@@ -462,6 +476,8 @@
keyguardTransitionRepository,
keyguardTransitionInteractor,
mTestScope.getBackgroundScope(),
+ mKosmos.getTestDispatcher(),
+ mKosmos.getTestDispatcher(),
keyguardInteractor,
featureFlags,
mock(KeyguardSecurityModel.class),
diff --git a/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt b/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt
index f96c508..5e254bf 100644
--- a/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt
@@ -16,9 +16,7 @@
package android.content
-import com.android.systemui.SysuiTestableContext
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testCase
-val Kosmos.testableContext: SysuiTestableContext by Kosmos.Fixture { testCase.context }
-var Kosmos.applicationContext: Context by Kosmos.Fixture { testableContext }
+var Kosmos.applicationContext: Context by Kosmos.Fixture { testCase.context }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/android/service/notification/NotificationListenerServiceKosmos.kt
similarity index 77%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
copy to packages/SystemUI/tests/utils/src/android/service/notification/NotificationListenerServiceKosmos.kt
index d98f496..bff0d0e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/service/notification/NotificationListenerServiceKosmos.kt
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+package android.service.notification
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.collection.NotifCollection
import com.android.systemui.util.mockito.mock
-var Kosmos.notifCollection by Fixture { mock<NotifCollection>() }
+val Kosmos.notificationListenerService by Fixture { mockNotificationListenerService }
+val Kosmos.mockNotificationListenerService by Fixture { mock<NotificationListenerService>() }
diff --git a/packages/SystemUI/tests/utils/src/android/telephony/TelephonyManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/telephony/TelephonyManagerKosmos.kt
new file mode 100644
index 0000000..a4ee702
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/telephony/TelephonyManagerKosmos.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.telephony
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+
+val Kosmos.telephonyManager by Fixture {
+ mock<TelephonyManager> {
+ whenever(createForSubscriptionId(anyInt())).thenReturn(this)
+ whenever(supplyIccLockPin(anyString()))
+ .thenReturn(PinResult(PinResult.PIN_RESULT_TYPE_SUCCESS, 3))
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/android/view/accessibility/AccessibilityManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/view/accessibility/AccessibilityManagerKosmos.kt
index a11bf6a..d095f42 100644
--- a/packages/SystemUI/tests/utils/src/android/view/accessibility/AccessibilityManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/view/accessibility/AccessibilityManagerKosmos.kt
@@ -18,6 +18,11 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
import com.android.systemui.util.mockito.mock
var Kosmos.accessibilityManager by Fixture { mock<AccessibilityManager>() }
+
+var Kosmos.accessibilityManagerWrapper by Fixture {
+ AccessibilityManagerWrapper(accessibilityManager)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/app/ActivityTaskManagerKosmos.kt
similarity index 71%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/app/ActivityTaskManagerKosmos.kt
index d98f496..fa3e8f9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/app/ActivityTaskManagerKosmos.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.
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+package com.android.app
+import android.app.ActivityTaskManager
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.collection.NotifCollection
import com.android.systemui.util.mockito.mock
-var Kosmos.notifCollection by Fixture { mock<NotifCollection>() }
+val Kosmos.activityTaskManager by Fixture { mock<ActivityTaskManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/internal/logging/MetricsLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/internal/logging/MetricsLoggerKosmos.kt
index a1815c5..22dff0a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/internal/logging/MetricsLoggerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/internal/logging/MetricsLoggerKosmos.kt
@@ -16,8 +16,12 @@
package com.android.internal.logging
+import com.android.internal.logging.testing.FakeMetricsLogger
+import com.android.internal.util.LatencyTracker
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.util.mockito.mock
-var Kosmos.metricsLogger by Fixture { mock<MetricsLogger>() }
+val Kosmos.fakeMetricsLogger by Fixture { FakeMetricsLogger() }
+val Kosmos.metricsLogger by Fixture<MetricsLogger> { fakeMetricsLogger }
+val Kosmos.latencyTracker by Fixture { mock<LatencyTracker>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/internal/util/EmergencyAffordanceManagerKosmos.kt
similarity index 71%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/internal/util/EmergencyAffordanceManagerKosmos.kt
index d98f496..3133437 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/internal/util/EmergencyAffordanceManagerKosmos.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.
@@ -14,11 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+package com.android.internal.util
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.collection.NotifCollection
import com.android.systemui.util.mockito.mock
-var Kosmos.notifCollection by Fixture { mock<NotifCollection>() }
+val Kosmos.emergencyAffordanceManager by Fixture { mock<EmergencyAffordanceManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/CoroutineTestScopeModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/CoroutineTestScopeModule.kt
index e24ba26..c2dc673 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/CoroutineTestScopeModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/CoroutineTestScopeModule.kt
@@ -48,6 +48,9 @@
@get:[Provides Application]
val appScope: CoroutineScope = scope.backgroundScope
+ @get:[Provides Background]
+ val bgScope: CoroutineScope = scope.backgroundScope
+
@Module
interface Bindings {
@Binds @Main fun bindMainContext(dispatcher: TestDispatcher): CoroutineContext
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index d23dae9..af7f4c8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -39,6 +39,7 @@
import androidx.test.uiautomator.UiDevice;
import com.android.systemui.broadcast.FakeBroadcastDispatcher;
+import com.android.systemui.flags.SceneContainerRule;
import org.junit.After;
import org.junit.AfterClass;
@@ -68,6 +69,9 @@
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+ @Rule(order = 10)
+ public final SceneContainerRule mSceneContainerRule = new SceneContainerRule();
+
@Rule
public SysuiTestableContext mContext = createTestableContext();
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 b28af46..b18859d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
@@ -18,13 +18,16 @@
import android.app.ActivityManager
import android.app.admin.DevicePolicyManager
import android.os.UserManager
+import android.service.notification.NotificationListenerService
import android.util.DisplayMetrics
import android.view.LayoutInflater
import com.android.internal.logging.MetricsLogger
+import com.android.internal.statusbar.IStatusBarService
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardViewController
import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.demomode.DemoModeController
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.ScreenLifecycle
@@ -48,9 +51,11 @@
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
import com.android.systemui.statusbar.notification.collection.NotifCollection
+import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger
import com.android.systemui.statusbar.notification.stack.AmbientState
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationStatsLogger
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.phone.LSShadeTransitionLogger
@@ -58,6 +63,7 @@
import com.android.systemui.statusbar.phone.ScrimController
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.ZenModeController
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
@@ -79,6 +85,7 @@
@get:Provides val deviceProvisionedController: DeviceProvisionedController = mock(),
@get:Provides val dozeParameters: DozeParameters = mock(),
@get:Provides val dumpManager: DumpManager = mock(),
+ @get:Provides val headsUpManager: HeadsUpManager = mock(),
@get:Provides val guestResumeSessionReceiver: GuestResumeSessionReceiver = mock(),
@get:Provides val keyguardBypassController: KeyguardBypassController = mock(),
@get:Provides val keyguardSecurityModel: KeyguardSecurityModel = mock(),
@@ -88,8 +95,10 @@
val lockscreenShadeTransitionController: LockscreenShadeTransitionController = mock(),
@get:Provides val mediaHierarchyManager: MediaHierarchyManager = mock(),
@get:Provides val notifCollection: NotifCollection = mock(),
+ @get:Provides val notificationListLogger: NotificationStatsLogger = mock(),
@get:Provides val notificationListener: NotificationListener = mock(),
@get:Provides val notificationLockscreenUserManager: NotificationLockscreenUserManager = mock(),
+ @get:Provides val notificationPanelLogger: NotificationPanelLogger = mock(),
@get:Provides val notificationMediaManager: NotificationMediaManager = mock(),
@get:Provides val notificationShadeDepthController: NotificationShadeDepthController = mock(),
@get:Provides
@@ -111,6 +120,7 @@
@get:Provides val zenModeController: ZenModeController = mock(),
@get:Provides val systemUIDialogManager: SystemUIDialogManager = mock(),
@get:Provides val deviceEntryIconTransitions: Set<DeviceEntryIconTransition> = emptySet(),
+ @get:Provides val communalInteractor: CommunalInteractor = mock(),
// log buffers
@get:[Provides BroadcastDispatcherLog]
@@ -127,6 +137,10 @@
@get:Provides val displayMetrics: DisplayMetrics = mock(),
@get:Provides val metricsLogger: MetricsLogger = mock(),
@get:Provides val userManager: UserManager = mock(),
+
+ // system server mocks
+ @get:Provides val mockStatusBarService: IStatusBarService = mock(),
+ @get:Provides val mockNotificationListenerService: NotificationListenerService = mock(),
) {
@Module
interface Bindings {
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 42ec8fed..f192de2 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
@@ -26,12 +26,24 @@
private val _isConfirmationRequired = MutableStateFlow(false)
override val isConfirmationRequired = _isConfirmationRequired.asStateFlow()
+ private val _opPackageName: MutableStateFlow<String?> = MutableStateFlow(null)
+ override val opPackageName = _opPackageName.asStateFlow()
+
override fun setPrompt(
promptInfo: PromptInfo,
userId: Int,
gatekeeperChallenge: Long?,
kind: PromptKind,
- ) = setPrompt(promptInfo, userId, gatekeeperChallenge, kind, forceConfirmation = false)
+ opPackageName: String,
+ ) =
+ setPrompt(
+ promptInfo,
+ userId,
+ gatekeeperChallenge,
+ kind,
+ forceConfirmation = false,
+ opPackageName = opPackageName
+ )
fun setPrompt(
promptInfo: PromptInfo,
@@ -39,12 +51,14 @@
gatekeeperChallenge: Long?,
kind: PromptKind,
forceConfirmation: Boolean = false,
+ opPackageName: String? = null,
) {
_promptInfo.value = promptInfo
_userId.value = userId
_challenge.value = gatekeeperChallenge
_kind.value = kind
_isConfirmationRequired.value = promptInfo.isConfirmationRequested || forceConfirmation
+ _opPackageName.value = opPackageName
}
override fun unsetPrompt() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorKosmos.kt
new file mode 100644
index 0000000..4a089d3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorKosmos.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.biometrics.domain.interactor
+
+import android.content.applicationContext
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.display.data.repository.displayStateRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.util.mockito.mock
+import java.util.concurrent.Executor
+
+val Kosmos.displayStateInteractor by Fixture {
+ DisplayStateInteractorImpl(
+ applicationScope = applicationCoroutineScope,
+ context = applicationContext,
+ mainExecutor = mock<Executor>(),
+ displayStateRepository = displayStateRepository,
+ displayRepository = displayRepository,
+ )
+}
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
new file mode 100644
index 0000000..e262066
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorKosmos.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.biometrics.domain.interactor
+
+import android.content.applicationContext
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.fingerprintPropertyInteractor by Fixture {
+ FingerprintPropertyInteractor(
+ context = applicationContext,
+ repository = fingerprintPropertyRepository,
+ configurationInteractor = configurationInteractor,
+ displayStateInteractor = displayStateInteractor,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/BouncerRepositoryKosmos.kt
similarity index 67%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/BouncerRepositoryKosmos.kt
index d98f496..c0f8638 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/BouncerRepositoryKosmos.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.
@@ -14,11 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+package com.android.systemui.bouncer.data.repository
+import com.android.systemui.flags.featureFlagsClassic
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.collection.NotifCollection
-import com.android.systemui.util.mockito.mock
-var Kosmos.notifCollection by Fixture { mock<NotifCollection>() }
+val Kosmos.bouncerRepository by Fixture {
+ BouncerRepository(
+ flags = featureFlagsClassic,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryKosmos.kt
new file mode 100644
index 0000000..8851709
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.data.repository
+
+import android.content.res.mainResources
+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.testScope
+
+val Kosmos.emergencyServicesRepository by Fixture {
+ EmergencyServicesRepository(
+ applicationScope = testScope.backgroundScope,
+ resources = mainResources,
+ configurationRepository = configurationRepository,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt
index ff5179a..8010261 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt
@@ -2,6 +2,7 @@
import com.android.systemui.biometrics.shared.SideFpsControllerRefactor
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
+import com.android.systemui.bouncer.shared.model.BouncerDismissActionModel
import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel
import com.android.systemui.dagger.SysUISingleton
import dagger.Binds
@@ -53,6 +54,7 @@
override val alternateBouncerUIAvailable = _isAlternateBouncerUIAvailable.asStateFlow()
private val _sideFpsShowing: MutableStateFlow<Boolean> = MutableStateFlow(false)
override val sideFpsShowing: StateFlow<Boolean> = _sideFpsShowing.asStateFlow()
+ override var bouncerDismissActionModel: BouncerDismissActionModel? = null
override fun setPrimaryScrimmed(isScrimmed: Boolean) {
_primaryBouncerScrimmed.value = isScrimmed
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/SimBouncerRepositoryKosmos.kt
similarity index 67%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/SimBouncerRepositoryKosmos.kt
index d98f496..7af39df 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/SimBouncerRepositoryKosmos.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.
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+package com.android.systemui.bouncer.data.repository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.collection.NotifCollection
-import com.android.systemui.util.mockito.mock
-var Kosmos.notifCollection by Fixture { mock<NotifCollection>() }
+val Kosmos.fakeSimBouncerRepository by Fixture { FakeSimBouncerRepository() }
+
+val Kosmos.simBouncerRepository by Fixture<SimBouncerRepository> { fakeSimBouncerRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt
index 86a4509..c4fc30d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt
@@ -25,7 +25,7 @@
import com.android.systemui.plugins.statusbar.statusBarStateController
import com.android.systemui.statusbar.policy.KeyguardStateControllerImpl
import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.time.fakeSystemClock
+import com.android.systemui.util.time.systemClock
var Kosmos.alternateBouncerInteractor by
Kosmos.Fixture {
@@ -35,7 +35,7 @@
bouncerRepository = keyguardBouncerRepository,
fingerprintPropertyRepository = fingerprintPropertyRepository,
biometricSettingsRepository = biometricSettingsRepository,
- systemClock = fakeSystemClock,
+ systemClock = systemClock,
keyguardUpdateMonitor = keyguardUpdateMonitor,
scope = testScope.backgroundScope,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorKosmos.kt
new file mode 100644
index 0000000..5ced578
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorKosmos.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.domain.interactor
+
+import android.content.Intent
+import android.content.applicationContext
+import com.android.app.activityTaskManager
+import com.android.internal.logging.metricsLogger
+import com.android.internal.util.emergencyAffordanceManager
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
+import com.android.systemui.bouncer.data.repository.emergencyServicesRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository
+import com.android.systemui.telephony.domain.interactor.telephonyInteractor
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
+import com.android.systemui.util.mockito.mock
+import com.android.telecom.telecomManager
+
+val Kosmos.bouncerActionButtonInteractor by Fixture {
+ BouncerActionButtonInteractor(
+ applicationContext = applicationContext,
+ backgroundDispatcher = testDispatcher,
+ repository = emergencyServicesRepository,
+ mobileConnectionsRepository = mobileConnectionsRepository,
+ telephonyInteractor = telephonyInteractor,
+ authenticationInteractor = authenticationInteractor,
+ selectedUserInteractor = selectedUserInteractor,
+ activityTaskManager = activityTaskManager,
+ telecomManager = telecomManager,
+ emergencyAffordanceManager = emergencyAffordanceManager,
+ emergencyDialerIntentFactory =
+ object : EmergencyDialerIntentFactory {
+ override fun invoke(): Intent = Intent()
+ },
+ metricsLogger = metricsLogger,
+ dozeLogger = mock(),
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
new file mode 100644
index 0000000..27803b2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.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.systemui.bouncer.domain.interactor
+
+import android.content.applicationContext
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
+import com.android.systemui.bouncer.data.repository.bouncerRepository
+import com.android.systemui.classifier.domain.interactor.falsingInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.powerInteractor
+
+val Kosmos.bouncerInteractor by Fixture {
+ BouncerInteractor(
+ applicationScope = testScope.backgroundScope,
+ applicationContext = applicationContext,
+ repository = bouncerRepository,
+ authenticationInteractor = authenticationInteractor,
+ deviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor,
+ falsingInteractor = falsingInteractor,
+ powerInteractor = powerInteractor,
+ simBouncerInteractor = simBouncerInteractor,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorKosmos.kt
new file mode 100644
index 0000000..8ed9f45
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorKosmos.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.domain.interactor
+
+import android.content.Context
+import android.content.applicationContext
+import android.content.res.mainResources
+import android.telephony.euicc.EuiccManager
+import android.telephony.telephonyManager
+import com.android.keyguard.keyguardUpdateMonitor
+import com.android.systemui.bouncer.data.repository.simBouncerRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository
+
+val Kosmos.simBouncerInteractor by Fixture {
+ SimBouncerInteractor(
+ applicationContext = applicationContext,
+ backgroundDispatcher = testDispatcher,
+ applicationScope = testScope.backgroundScope,
+ repository = simBouncerRepository,
+ telephonyManager = telephonyManager,
+ resources = mainResources,
+ keyguardUpdateMonitor = keyguardUpdateMonitor,
+ euiccManager = applicationContext.getSystemService(Context.EUICC_SERVICE) as EuiccManager,
+ mobileConnectionsRepository = mobileConnectionsRepository,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
new file mode 100644
index 0000000..d91c597
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.ui.viewmodel
+
+import android.content.applicationContext
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
+import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
+import com.android.systemui.user.ui.viewmodel.userSwitcherViewModel
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.time.systemClock
+
+val Kosmos.bouncerViewModel by Fixture {
+ BouncerViewModel(
+ applicationContext = applicationContext,
+ applicationScope = testScope.backgroundScope,
+ mainDispatcher = testDispatcher,
+ bouncerInteractor = bouncerInteractor,
+ simBouncerInteractor = simBouncerInteractor,
+ authenticationInteractor = authenticationInteractor,
+ flags = sceneContainerFlags,
+ selectedUser = userSwitcherViewModel.selectedUser,
+ users = userSwitcherViewModel.users,
+ userSwitcherMenu = userSwitcherViewModel.menu,
+ actionButton = bouncerActionButtonInteractor.actionButton,
+ clock = systemClock,
+ devicePolicyManager = mock(),
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/domain/interactor/FalsingInteractorKosmos.kt
similarity index 67%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/classifier/domain/interactor/FalsingInteractorKosmos.kt
index d98f496..8fee5b2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/domain/interactor/FalsingInteractorKosmos.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.
@@ -14,11 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+package com.android.systemui.classifier.domain.interactor
+import com.android.systemui.classifier.falsingCollector
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.collection.NotifCollection
-import com.android.systemui.util.mockito.mock
-var Kosmos.notifCollection by Fixture { mock<NotifCollection>() }
+val Kosmos.falsingInteractor by Fixture {
+ FalsingInteractor(
+ collector = falsingCollector,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryKosmos.kt
similarity index 65%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryKosmos.kt
index 7b9634a..0768106 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryKosmos.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.
@@ -14,9 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.mobile.data.repository
+package com.android.systemui.communal.data.repository
import com.android.systemui.kosmos.Kosmos
-var Kosmos.userSetupRepository: UserSetupRepository by Kosmos.Fixture { fakeUserSetupRepository }
-val Kosmos.fakeUserSetupRepository by Kosmos.Fixture { FakeUserSetupRepository() }
+val Kosmos.fakeCommunalMediaRepository by Kosmos.Fixture { FakeCommunalMediaRepository() }
+
+val Kosmos.communalMediaRepository by
+ Kosmos.Fixture<CommunalMediaRepository> { fakeCommunalMediaRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryKosmos.kt
similarity index 65%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryKosmos.kt
index 7b9634a..79107cc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryKosmos.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.
@@ -12,11 +12,13 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
+ *
*/
-package com.android.systemui.statusbar.pipeline.mobile.data.repository
+package com.android.systemui.communal.data.repository
import com.android.systemui.kosmos.Kosmos
-var Kosmos.userSetupRepository: UserSetupRepository by Kosmos.Fixture { fakeUserSetupRepository }
-val Kosmos.fakeUserSetupRepository by Kosmos.Fixture { FakeUserSetupRepository() }
+var Kosmos.communalPrefsRepository: CommunalPrefsRepository by
+ Kosmos.Fixture { fakeCommunalPrefsRepository }
+val Kosmos.fakeCommunalPrefsRepository by Kosmos.Fixture { FakeCommunalPrefsRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalRepositoryKosmos.kt
similarity index 63%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalRepositoryKosmos.kt
index d98f496..482d60ce 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalRepositoryKosmos.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.
@@ -14,11 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+package com.android.systemui.communal.data.repository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.collection.NotifCollection
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.kosmos.applicationCoroutineScope
-var Kosmos.notifCollection by Fixture { mock<NotifCollection>() }
+val Kosmos.fakeCommunalRepository by Fixture {
+ FakeCommunalRepository(applicationScope = applicationCoroutineScope)
+}
+
+val Kosmos.communalRepository by Fixture<CommunalRepository> { fakeCommunalRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryKosmos.kt
similarity index 64%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryKosmos.kt
index 7b9634a..f7665fb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryKosmos.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.
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.mobile.data.repository
+package com.android.systemui.communal.data.repository
import com.android.systemui.kosmos.Kosmos
-var Kosmos.userSetupRepository: UserSetupRepository by Kosmos.Fixture { fakeUserSetupRepository }
-val Kosmos.fakeUserSetupRepository by Kosmos.Fixture { FakeUserSetupRepository() }
+val Kosmos.communalTutorialRepository by
+ Kosmos.Fixture<CommunalTutorialRepository> { fakeCommunalTutorialRepository }
+val Kosmos.fakeCommunalTutorialRepository by Kosmos.Fixture { FakeCommunalTutorialRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryKosmos.kt
similarity index 60%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryKosmos.kt
index d98f496..c225e3c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryKosmos.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.
@@ -14,11 +14,17 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+package com.android.systemui.communal.data.repository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.collection.NotifCollection
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.kosmos.applicationCoroutineScope
-var Kosmos.notifCollection by Fixture { mock<NotifCollection>() }
+val Kosmos.fakeCommunalWidgetRepository by Fixture {
+ FakeCommunalWidgetRepository(
+ coroutineScope = applicationCoroutineScope,
+ )
+}
+
+val Kosmos.communalWidgetRepository by
+ Fixture<CommunalWidgetRepository> { fakeCommunalWidgetRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt
new file mode 100644
index 0000000..d3ed58b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.data.repository
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Fake implementation of [CommunalPrefsRepository] */
+class FakeCommunalPrefsRepository : CommunalPrefsRepository {
+ private val _isCtaDismissed = MutableStateFlow(false)
+ override val isCtaDismissed: Flow<Boolean> = _isCtaDismissed.asStateFlow()
+
+ override suspend fun setCtaDismissedForCurrentUser() {
+ _isCtaDismissed.value = true
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
index e82cae4..20fa545 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
@@ -2,24 +2,21 @@
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
-import com.android.systemui.dagger.qualifiers.Background
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.test.TestScope
/** Fake implementation of [CommunalRepository]. */
@OptIn(ExperimentalCoroutinesApi::class)
class FakeCommunalRepository(
- @Background applicationScope: CoroutineScope = TestScope(),
- override var isCommunalEnabled: Boolean = false,
+ applicationScope: CoroutineScope,
+ override var isCommunalEnabled: Boolean = true,
override val desiredScene: MutableStateFlow<CommunalSceneKey> =
MutableStateFlow(CommunalSceneKey.DEFAULT),
) : CommunalRepository {
@@ -53,12 +50,4 @@
fun setIsCommunalHubShowing(isCommunalHubShowing: Boolean) {
_isCommunalHubShowing.value = isCommunalHubShowing
}
-
- private val _isCtaTileInViewModeVisible: MutableStateFlow<Boolean> = MutableStateFlow(true)
- override val isCtaTileInViewModeVisible: Flow<Boolean> =
- _isCtaTileInViewModeVisible.asStateFlow()
-
- override fun setCtaTileInViewModeVisibility(isVisible: Boolean) {
- _isCtaTileInViewModeVisible.value = isVisible
- }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index 397dc1a..d0c2d4b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -35,4 +35,13 @@
}
}
}
+
+ private var isHostActive = false
+ override fun updateAppWidgetHostActive(active: Boolean) {
+ isHostActive = active
+ }
+
+ fun isHostActive(): Boolean {
+ return isHostActive
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt
index eb287ee..1753ca0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt
@@ -17,11 +17,12 @@
package com.android.systemui.communal.domain.interactor
-import android.appwidget.AppWidgetHost
import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository
import com.android.systemui.communal.data.repository.FakeCommunalRepository
import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -35,13 +36,15 @@
@JvmStatic
fun create(
testScope: TestScope = TestScope(),
- communalRepository: FakeCommunalRepository = FakeCommunalRepository(),
+ communalRepository: FakeCommunalRepository =
+ FakeCommunalRepository(testScope.backgroundScope),
widgetRepository: FakeCommunalWidgetRepository =
FakeCommunalWidgetRepository(testScope.backgroundScope),
mediaRepository: FakeCommunalMediaRepository = FakeCommunalMediaRepository(),
smartspaceRepository: FakeSmartspaceRepository = FakeSmartspaceRepository(),
tutorialRepository: FakeCommunalTutorialRepository = FakeCommunalTutorialRepository(),
- appWidgetHost: AppWidgetHost = mock(),
+ communalPrefsRepository: FakeCommunalPrefsRepository = FakeCommunalPrefsRepository(),
+ appWidgetHost: CommunalAppWidgetHost = mock(),
editWidgetsActivityStarter: EditWidgetsActivityStarter = mock(),
): WithDependencies {
val withDeps =
@@ -51,8 +54,10 @@
communalRepository = communalRepository,
)
return WithDependencies(
+ testScope,
communalRepository,
widgetRepository,
+ communalPrefsRepository,
mediaRepository,
smartspaceRepository,
tutorialRepository,
@@ -62,8 +67,10 @@
appWidgetHost,
editWidgetsActivityStarter,
CommunalInteractor(
+ testScope.backgroundScope,
communalRepository,
widgetRepository,
+ communalPrefsRepository,
mediaRepository,
smartspaceRepository,
withDeps.keyguardInteractor,
@@ -74,15 +81,17 @@
}
data class WithDependencies(
+ val testScope: TestScope,
val communalRepository: FakeCommunalRepository,
val widgetRepository: FakeCommunalWidgetRepository,
+ val communalPrefsRepository: FakeCommunalPrefsRepository,
val mediaRepository: FakeCommunalMediaRepository,
val smartspaceRepository: FakeSmartspaceRepository,
val tutorialRepository: FakeCommunalTutorialRepository,
val keyguardRepository: FakeKeyguardRepository,
val keyguardInteractor: KeyguardInteractor,
val tutorialInteractor: CommunalTutorialInteractor,
- val appWidgetHost: AppWidgetHost,
+ val appWidgetHost: CommunalAppWidgetHost,
val editWidgetsActivityStarter: EditWidgetsActivityStarter,
val communalInteractor: CommunalInteractor,
)
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
new file mode 100644
index 0000000..65579a6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.domain.interactor
+
+import com.android.systemui.communal.data.repository.communalMediaRepository
+import com.android.systemui.communal.data.repository.communalPrefsRepository
+import com.android.systemui.communal.data.repository.communalRepository
+import com.android.systemui.communal.data.repository.communalWidgetRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.smartspace.data.repository.smartspaceRepository
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.communalInteractor by Fixture {
+ CommunalInteractor(
+ applicationScope = applicationCoroutineScope,
+ communalRepository = communalRepository,
+ widgetRepository = communalWidgetRepository,
+ mediaRepository = communalMediaRepository,
+ communalPrefsRepository = communalPrefsRepository,
+ smartspaceRepository = smartspaceRepository,
+ appWidgetHost = mock(),
+ keyguardInteractor = keyguardInteractor,
+ editWidgetsActivityStarter = mock(),
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorFactory.kt
index e5cadab..3ff2a3e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorFactory.kt
@@ -32,7 +32,7 @@
communalTutorialRepository: FakeCommunalTutorialRepository =
FakeCommunalTutorialRepository(),
communalRepository: FakeCommunalRepository =
- FakeCommunalRepository(isCommunalEnabled = true),
+ FakeCommunalRepository(applicationScope = testScope.backgroundScope),
keyguardRepository: FakeKeyguardRepository = FakeKeyguardRepository(),
keyguardInteractor: KeyguardInteractor =
KeyguardInteractorFactory.create(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
index 6bf527d..de58ae5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
@@ -24,7 +24,7 @@
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.power.domain.interactor.powerInteractor
-import com.android.systemui.util.time.fakeSystemClock
+import com.android.systemui.util.time.systemClock
import kotlinx.coroutines.ExperimentalCoroutinesApi
val Kosmos.deviceEntryHapticsInteractor by
@@ -37,7 +37,7 @@
biometricSettingsRepository = biometricSettingsRepository,
keyEventInteractor = keyEventInteractor,
powerInteractor = powerInteractor,
- systemClock = fakeSystemClock,
+ systemClock = systemClock,
logger = biometricUnlockLogger,
)
}
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 b600b50..8dcdd3a 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
@@ -38,5 +38,6 @@
deviceEntryFaceAuthRepository = deviceEntryFaceAuthRepository,
trustRepository = trustRepository,
flags = sceneContainerFlags,
+ deviceUnlockedInteractor = deviceUnlockedInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
new file mode 100644
index 0000000..df1cdc2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * 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 com.android.systemui.authentication.domain.interactor.authenticationInteractor
+import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
+
+val Kosmos.deviceUnlockedInteractor by Fixture {
+ DeviceUnlockedInteractor(
+ applicationScope = applicationCoroutineScope,
+ authenticationInteractor = authenticationInteractor,
+ deviceEntryRepository = deviceEntryRepository,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneContainerConfigKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayRepositoryKosmos.kt
similarity index 72%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneContainerConfigKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayRepositoryKosmos.kt
index f9cdc1b..048ea3c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneContainerConfigKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayRepositoryKosmos.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.
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.scene.shared.model
+package com.android.systemui.display.data.repository
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
-val Kosmos.sceneContainerConfig by
- Kosmos.Fixture { FakeSceneContainerConfigModule().sceneContainerConfig }
+val Kosmos.displayRepository by Fixture { FakeDisplayRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayStateRepositoryKosmos.kt
similarity index 67%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayStateRepositoryKosmos.kt
index d98f496..4a71a09 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayStateRepositoryKosmos.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.
@@ -14,11 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+package com.android.systemui.display.data.repository
+import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.collection.NotifCollection
-import com.android.systemui.util.mockito.mock
-var Kosmos.notifCollection by Fixture { mock<NotifCollection>() }
+val Kosmos.displayStateRepository by Fixture { FakeDisplayStateRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
new file mode 100644
index 0000000..97f84c6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
@@ -0,0 +1,37 @@
+/*
+ * 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 android.platform.test.annotations.EnableFlags
+import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
+import com.android.systemui.Flags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL
+import com.android.systemui.Flags.FLAG_MEDIA_IN_SCENE_CONTAINER
+import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
+
+/**
+ * This includes @[EnableFlags] to work with [SetFlagsRule] to enable all aconfig flags required by
+ * that feature. It is also picked up by [SceneContainerRule] to set non-aconfig prerequisites.
+ */
+@EnableFlags(
+ FLAG_SCENE_CONTAINER,
+ FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
+ FLAG_KEYGUARD_SHADE_MIGRATION_NSSL,
+ FLAG_MEDIA_IN_SCENE_CONTAINER,
+)
+@Retention(AnnotationRetention.RUNTIME)
+@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
+annotation class EnableSceneContainer
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 abadaf7..7b36a29 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
@@ -30,7 +30,13 @@
* Fixture supplying a shared [FakeFeatureFlagsClassic] instance. Can be accessed in tests in order
* to override flag values.
*/
-val Kosmos.fakeFeatureFlagsClassic by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+val Kosmos.fakeFeatureFlagsClassic by
+ Kosmos.Fixture {
+ FakeFeatureFlagsClassic().apply {
+ set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+ set(Flags.NSSL_DEBUG_LINES, false)
+ }
+ }
/**
* Fixture supplying a real [FeatureFlagsClassicRelease] instance, for use by tests that want to
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerRule.kt
new file mode 100644
index 0000000..3faa6eb
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerRule.kt
@@ -0,0 +1,89 @@
+/*
+ * 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 android.util.Log
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import org.junit.Assert
+import org.junit.Assume
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * Should always be used with [SetFlagsRule] and should be ordered after it.
+ *
+ * Used to ensure tests annotated with [EnableSceneContainer] can actually get `true` from
+ * [SceneContainerFlag.isEnabled].
+ */
+class SceneContainerRule : TestRule {
+ override fun apply(base: Statement?, description: Description?): Statement {
+ return object : Statement() {
+ @Throws(Throwable::class)
+ override fun evaluate() {
+ val initialEnabledValue = Flags.SCENE_CONTAINER_ENABLED
+ val hasAnnotation =
+ description?.testClass?.getAnnotation(EnableSceneContainer::class.java) !=
+ null || description?.getAnnotation(EnableSceneContainer::class.java) != null
+ if (hasAnnotation) {
+ Assume.assumeTrue(
+ "Compose must be available for @EnableSceneContainer test",
+ ComposeFacade.isComposeAvailable()
+ )
+ Assume.assumeTrue(
+ "Couldn't set Flags.SCENE_CONTAINER_ENABLED for @EnableSceneContainer test",
+ trySetSceneContainerEnabled(true)
+ )
+ Assert.assertTrue(
+ "SceneContainerFlag.isEnabled is false:" +
+ "\n * Did you forget to add a new aconfig flag dependency in" +
+ " @EnableSceneContainer?" +
+ "\n * Did you forget to use SetFlagsRule with an earlier order?",
+ SceneContainerFlag.isEnabled
+ )
+ }
+ try {
+ base?.evaluate()
+ } finally {
+ if (hasAnnotation) {
+ trySetSceneContainerEnabled(initialEnabledValue)
+ }
+ }
+ }
+ }
+ }
+
+ companion object {
+ fun trySetSceneContainerEnabled(enabled: Boolean): Boolean {
+ if (Flags.SCENE_CONTAINER_ENABLED == enabled) {
+ return true
+ }
+ return try {
+ // TODO(b/283300105): remove this reflection setting once the hard-coded
+ // Flags.SCENE_CONTAINER_ENABLED is no longer needed.
+ val field = Flags::class.java.getField("SCENE_CONTAINER_ENABLED")
+ field.isAccessible = true
+ field.set(null, enabled) // note: this does not work with multivalent tests
+ true
+ } catch (t: Throwable) {
+ Log.e("SceneContainerRule", "Unable to set SCENE_CONTAINER_ENABLED=$enabled", t)
+ false
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/jank/InteractionJankMonitorKosmos.kt
similarity index 71%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/jank/InteractionJankMonitorKosmos.kt
index d98f496..5c5016d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/jank/InteractionJankMonitorKosmos.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.
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+package com.android.systemui.jank
+import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.collection.NotifCollection
import com.android.systemui.util.mockito.mock
-var Kosmos.notifCollection by Fixture { mock<NotifCollection>() }
+val Kosmos.interactionJankMonitor by Fixture<InteractionJankMonitor> { mock() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
index 1d44929..93e0b41 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
@@ -62,6 +62,10 @@
fun setAuthenticationStatus(status: FingerprintAuthenticationStatus) {
_authenticationStatus.value = status
}
+
+ fun setShouldUpdateIndicatorVisibility(shouldUpdateIndicatorVisibility: Boolean) {
+ _shouldUpdateIndicatorVisibility.value = shouldUpdateIndicatorVisibility
+ }
}
@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 975db3b..59f56dd 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
@@ -127,6 +127,9 @@
override val ambientIndicationVisible: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ private val _isEncryptedOrLockdown = MutableStateFlow(true)
+ override val isEncryptedOrLockdown: Flow<Boolean> = _isEncryptedOrLockdown
+
override fun setQuickSettingsVisible(isVisible: Boolean) {
_isQuickSettingsVisible.value = isVisible
}
@@ -247,6 +250,10 @@
override fun setKeyguardAlpha(alpha: Float) {
_keyguardAlpha.value = alpha
}
+
+ fun setIsEncryptedOrLockdown(value: Boolean) {
+ _isEncryptedOrLockdown.value = value
+ }
}
@Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
index a94ca29..0c1dbfe 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
@@ -147,7 +147,6 @@
)
}
}
-
_transitions.emit(step)
}
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
new file mode 100644
index 0000000..19cd950
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.data.repository
+
+import com.android.systemui.common.ui.data.repository.configurationRepository
+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.kosmos.Kosmos
+
+val Kosmos.keyguardBlueprintRepository by
+ Kosmos.Fixture {
+ KeyguardBlueprintRepository(
+ configurationRepository = configurationRepository,
+ blueprints = setOf(defaultBlueprint),
+ )
+ }
+
+private val defaultBlueprint =
+ object : KeyguardBlueprint {
+ override val id: String
+ get() = DEFAULT
+ override val sections: List<KeyguardSection>
+ get() = listOf()
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt
new file mode 100644
index 0000000..d8f0cec
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.communal.data.repository.communalMediaRepository
+import com.android.systemui.communal.data.repository.communalPrefsRepository
+import com.android.systemui.communal.data.repository.communalRepository
+import com.android.systemui.communal.data.repository.communalWidgetRepository
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
+import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.smartspace.data.repository.smartspaceRepository
+import org.mockito.Mockito.mock
+
+val Kosmos.communalInteractor by
+ Kosmos.Fixture {
+ CommunalInteractor(
+ applicationScope = testScope.backgroundScope,
+ communalRepository = communalRepository,
+ widgetRepository = communalWidgetRepository,
+ mediaRepository = communalMediaRepository,
+ communalPrefsRepository = communalPrefsRepository,
+ smartspaceRepository = smartspaceRepository,
+ keyguardInteractor = keyguardInteractor,
+ appWidgetHost = mock(CommunalAppWidgetHost::class.java),
+ editWidgetsActivityStarter = editWidgetsActivityStarter,
+ )
+ }
+
+val Kosmos.editWidgetsActivityStarter by
+ Kosmos.Fixture<EditWidgetsActivityStarter> { mock(EditWidgetsActivityStarter::class.java) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
index b03d0b8..3b38342 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
@@ -20,6 +20,7 @@
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.shade.data.repository.shadeRepository
import dagger.Lazy
@@ -30,10 +31,13 @@
transitionRepository = keyguardTransitionRepository,
transitionInteractor = keyguardTransitionInteractor,
scope = applicationCoroutineScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
keyguardInteractor = keyguardInteractor,
flags = featureFlagsClassic,
shadeRepository = shadeRepository,
powerInteractor = powerInteractor,
+ glanceableHubTransitions = glanceableHubTransitions,
inWindowLauncherUnlockAnimationInteractor =
Lazy { inWindowLauncherUnlockAnimationInteractor },
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
index ade3e1a..97536e2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
@@ -21,6 +21,7 @@
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.user.domain.interactor.selectedUserInteractor
@@ -30,6 +31,8 @@
transitionRepository = keyguardTransitionRepository,
transitionInteractor = keyguardTransitionInteractor,
scope = applicationCoroutineScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
keyguardInteractor = keyguardInteractor,
flags = featureFlagsClassic,
keyguardSecurityModel = keyguardSecurityModel,
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
new file mode 100644
index 0000000..294b5ba
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+
+val Kosmos.glanceableHubTransitions by
+ Kosmos.Fixture {
+ GlanceableHubTransitions(
+ scope = applicationCoroutineScope,
+ 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
new file mode 100644
index 0000000..d9a3192
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.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.domain.interactor
+
+import android.content.applicationContext
+import com.android.systemui.keyguard.data.repository.keyguardBlueprintRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.statusbar.policy.splitShadeStateController
+
+val Kosmos.keyguardBlueprintInteractor by
+ Kosmos.Fixture {
+ KeyguardBlueprintInteractor(
+ keyguardBlueprintRepository = keyguardBlueprintRepository,
+ applicationScope = applicationCoroutineScope,
+ context = applicationContext,
+ splitShadeStateController = splitShadeStateController,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
new file mode 100644
index 0000000..638a6a3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.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.systemui.keyguard.domain.interactor
+
+import android.content.applicationContext
+import android.view.accessibility.accessibilityManagerWrapper
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.qs.uiEventLogger
+
+val Kosmos.keyguardLongPressInteractor by
+ Kosmos.Fixture {
+ KeyguardLongPressInteractor(
+ appContext = applicationContext,
+ scope = applicationCoroutineScope,
+ transitionInteractor = keyguardTransitionInteractor,
+ repository = keyguardRepository,
+ logger = uiEventLogger,
+ featureFlags = featureFlagsClassic,
+ broadcastDispatcher = broadcastDispatcher,
+ accessibilityManager = accessibilityManagerWrapper,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowKosmos.kt
index 8d6529a..dad1887 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowKosmos.kt
@@ -19,6 +19,7 @@
package com.android.systemui.keyguard.ui
import com.android.keyguard.logging.keyguardTransitionAnimationLogger
+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
@@ -27,6 +28,7 @@
val Kosmos.keyguardTransitionAnimationFlow by Fixture {
KeyguardTransitionAnimationFlow(
scope = applicationCoroutineScope,
+ transitionInteractor = keyguardTransitionInteractor,
logger = keyguardTransitionAnimationLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelKosmos.kt
index d9c6e4f..3ed9392 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelKosmos.kt
@@ -19,7 +19,6 @@
package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -27,7 +26,6 @@
val Kosmos.alternateBouncerToAodTransitionViewModel by Fixture {
AlternateBouncerToAodTransitionViewModel(
- interactor = keyguardTransitionInteractor,
deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
animationFlow = keyguardTransitionAnimationFlow,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelKosmos.kt
index e4821b0..c909dd6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelKosmos.kt
@@ -18,7 +18,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 @@
val Kosmos.alternateBouncerToGoneTransitionViewModel by Fixture {
AlternateBouncerToGoneTransitionViewModel(
- interactor = keyguardTransitionInteractor,
bouncerToGoneFlows = bouncerToGoneFlows,
animationFlow = keyguardTransitionAnimationFlow,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelKosmos.kt
similarity index 62%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelKosmos.kt
index d98f496..2d1f836 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelKosmos.kt
@@ -14,11 +14,17 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+@file:OptIn(ExperimentalCoroutinesApi::class)
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.collection.NotifCollection
-import com.android.systemui.util.mockito.mock
+import kotlinx.coroutines.ExperimentalCoroutinesApi
-var Kosmos.notifCollection by Fixture { mock<NotifCollection>() }
+val Kosmos.alternateBouncerToPrimaryBouncerTransitionViewModel by Fixture {
+ AlternateBouncerToPrimaryBouncerTransitionViewModel(
+ animationFlow = keyguardTransitionAnimationFlow,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt
index 9f0466d..b4f1218 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt
@@ -18,7 +18,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
@@ -28,7 +27,6 @@
val Kosmos.alternateBouncerViewModel by Fixture {
AlternateBouncerViewModel(
statusBarKeyguardViewManager = statusBarKeyguardViewManager,
- transitionInteractor = keyguardTransitionInteractor,
animationFlow = keyguardTransitionAnimationFlow,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelKosmos.kt
index 44e5426..b6f278c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelKosmos.kt
@@ -18,7 +18,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 @@
val Kosmos.aodToGoneTransitionViewModel by Fixture {
AodToGoneTransitionViewModel(
- interactor = keyguardTransitionInteractor,
animationFlow = keyguardTransitionAnimationFlow,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt
index b5a5f03..733340c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt
@@ -19,7 +19,6 @@
package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -27,7 +26,6 @@
val Kosmos.aodToLockscreenTransitionViewModel by Fixture {
AodToLockscreenTransitionViewModel(
- interactor = keyguardTransitionInteractor,
deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
animationFlow = keyguardTransitionAnimationFlow,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelKosmos.kt
index 27ad0f0..8d066fc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelKosmos.kt
@@ -18,7 +18,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 @@
val Kosmos.aodToOccludedTransitionViewModel by Fixture {
AodToOccludedTransitionViewModel(
- interactor = keyguardTransitionInteractor,
animationFlow = keyguardTransitionAnimationFlow,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsKosmos.kt
index 6ffcc9a..c71c1c3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsKosmos.kt
@@ -20,7 +20,6 @@
import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
import com.android.systemui.flags.featureFlagsClassic
-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
@@ -31,7 +30,6 @@
val Kosmos.bouncerToGoneFlows by Fixture {
BouncerToGoneFlows(
- interactor = keyguardTransitionInteractor,
statusBarStateController = sysuiStatusBarStateController,
primaryBouncerInteractor = primaryBouncerInteractor,
keyguardDismissActionInteractor = mock(),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryFgIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryFgIconViewModelKosmos.kt
index 4bfe4f5..4f638d0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryFgIconViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryFgIconViewModelKosmos.kt
@@ -18,7 +18,7 @@
import android.content.applicationContext
import com.android.systemui.biometrics.domain.interactor.udfpsOverlayInteractor
-import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
@@ -29,7 +29,7 @@
val Kosmos.deviceEntryForegroundIconViewModel by Fixture {
DeviceEntryForegroundViewModel(
context = applicationContext,
- configurationRepository = configurationRepository,
+ configurationInteractor = configurationInteractor,
deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
transitionInteractor = keyguardTransitionInteractor,
deviceEntryIconViewModel = deviceEntryIconViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelKosmos.kt
new file mode 100644
index 0000000..400a0d8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelKosmos.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.dozingToLockscreenTransitionViewModel by Fixture {
+ DozingToLockscreenTransitionViewModel(
+ animationFlow = keyguardTransitionAnimationFlow,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelKosmos.kt
new file mode 100644
index 0000000..28fce77
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@OptIn(ExperimentalCoroutinesApi::class)
+val Kosmos.glanceableHubToLockscreenTransitionViewModel by Fixture {
+ GlanceableHubToLockscreenTransitionViewModel(
+ animationFlow = keyguardTransitionAnimationFlow,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt
index 00ece14..19e4241 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt
@@ -19,7 +19,6 @@
package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -27,7 +26,6 @@
var Kosmos.goneToAodTransitionViewModel by Fixture {
GoneToAodTransitionViewModel(
- interactor = keyguardTransitionInteractor,
deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
animationFlow = keyguardTransitionAnimationFlow,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelKosmos.kt
index 073b34b..b267a96 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelKosmos.kt
@@ -18,7 +18,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 @@
val Kosmos.goneToDreamingTransitionViewModel by Fixture {
GoneToDreamingTransitionViewModel(
- interactor = keyguardTransitionInteractor,
animationFlow = keyguardTransitionAnimationFlow,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModelKosmos.kt
similarity index 62%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModelKosmos.kt
index 7b9634a..3c9846a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModelKosmos.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.
@@ -14,9 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.mobile.data.repository
+package com.android.systemui.keyguard.ui.viewmodel
+import com.android.systemui.keyguard.domain.interactor.keyguardLongPressInteractor
import com.android.systemui.kosmos.Kosmos
-var Kosmos.userSetupRepository: UserSetupRepository by Kosmos.Fixture { fakeUserSetupRepository }
-val Kosmos.fakeUserSetupRepository by Kosmos.Fixture { FakeUserSetupRepository() }
+val Kosmos.keyguardLongPressViewModel by
+ Kosmos.Fixture {
+ KeyguardLongPressViewModel(
+ interactor = keyguardLongPressInteractor,
+ )
+ }
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 933f50c3..5564767 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
@@ -39,5 +39,7 @@
screenOffAnimationController = screenOffAnimationController,
aodBurnInViewModel = aodBurnInViewModel,
aodAlphaViewModel = aodAlphaViewModel,
+ lockscreenToGlanceableHubTransitionViewModel = lockscreenToGlanceableHubTransitionViewModel,
+ glanceableHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
new file mode 100644
index 0000000..96de4ba
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.biometrics.authController
+import com.android.systemui.keyguard.domain.interactor.keyguardBlueprintInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.lockscreenContentViewModel by
+ Kosmos.Fixture {
+ LockscreenContentViewModel(
+ clockInteractor = keyguardClockInteractor,
+ interactor = keyguardBlueprintInteractor,
+ authController = authController,
+ longPress = keyguardLongPressViewModel,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelKosmos.kt
index 7865f71..07b4cd4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelKosmos.kt
@@ -19,7 +19,6 @@
package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -27,7 +26,6 @@
val Kosmos.lockscreenToAodTransitionViewModel by Fixture {
LockscreenToAodTransitionViewModel(
- interactor = keyguardTransitionInteractor,
deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
shadeDependentFlows = shadeDependentFlows,
animationFlow = keyguardTransitionAnimationFlow,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelKosmos.kt
index b9f4b71..56d5ff6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelKosmos.kt
@@ -18,7 +18,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 @@
val Kosmos.lockscreenToDreamingTransitionViewModel by Fixture {
LockscreenToDreamingTransitionViewModel(
- interactor = keyguardTransitionInteractor,
shadeDependentFlows = shadeDependentFlows,
animationFlow = keyguardTransitionAnimationFlow,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelKosmos.kt
new file mode 100644
index 0000000..9fe4ea3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelKosmos.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.lockscreenToGlanceableHubTransitionViewModel by Fixture {
+ LockscreenToGlanceableHubTransitionViewModel(
+ animationFlow = keyguardTransitionAnimationFlow,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelKosmos.kt
index 475aa2d..1b2337f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelKosmos.kt
@@ -18,7 +18,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 @@
val Kosmos.lockscreenToGoneTransitionViewModel by Fixture {
LockscreenToGoneTransitionViewModel(
- interactor = keyguardTransitionInteractor,
animationFlow = keyguardTransitionAnimationFlow,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelKosmos.kt
index 8541a4f..9953d39 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelKosmos.kt
@@ -19,7 +19,6 @@
package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.common.ui.domain.interactor.configurationInteractor
-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
@@ -27,7 +26,6 @@
val Kosmos.lockscreenToOccludedTransitionViewModel by Fixture {
LockscreenToOccludedTransitionViewModel(
- interactor = keyguardTransitionInteractor,
shadeDependentFlows = shadeDependentFlows,
configurationInteractor = configurationInteractor,
animationFlow = keyguardTransitionAnimationFlow,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelKosmos.kt
index 65c47fc..f094f22 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelKosmos.kt
@@ -18,7 +18,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 @@
val Kosmos.lockscreenToPrimaryBouncerTransitionViewModel by Fixture {
LockscreenToPrimaryBouncerTransitionViewModel(
- interactor = keyguardTransitionInteractor,
shadeDependentFlows = shadeDependentFlows,
animationFlow = keyguardTransitionAnimationFlow,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelKosmos.kt
index ddde549..b7867b6c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelKosmos.kt
@@ -19,7 +19,6 @@
package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -27,7 +26,6 @@
val Kosmos.occludedToAodTransitionViewModel by Fixture {
OccludedToAodTransitionViewModel(
- interactor = keyguardTransitionInteractor,
deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
animationFlow = keyguardTransitionAnimationFlow,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt
index 93ecb79..e6651a4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt
@@ -20,7 +20,6 @@
import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
-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
@@ -28,7 +27,6 @@
var Kosmos.occludedToLockscreenTransitionViewModel by Fixture {
OccludedToLockscreenTransitionViewModel(
- interactor = keyguardTransitionInteractor,
deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
configurationInteractor = configurationInteractor,
animationFlow = keyguardTransitionAnimationFlow,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelKosmos.kt
index a7f29d6..8d88730 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelKosmos.kt
@@ -19,7 +19,6 @@
package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -27,7 +26,6 @@
val Kosmos.primaryBouncerToAodTransitionViewModel by Fixture {
PrimaryBouncerToAodTransitionViewModel(
- interactor = keyguardTransitionInteractor,
deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
animationFlow = keyguardTransitionAnimationFlow,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelKosmos.kt
index ace6ae3..ab28d0d6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelKosmos.kt
@@ -20,7 +20,6 @@
import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
import com.android.systemui.flags.featureFlagsClassic
-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
@@ -30,7 +29,6 @@
val Kosmos.primaryBouncerToGoneTransitionViewModel by Fixture {
PrimaryBouncerToGoneTransitionViewModel(
- interactor = keyguardTransitionInteractor,
statusBarStateController = sysuiStatusBarStateController,
primaryBouncerInteractor = primaryBouncerInteractor,
keyguardDismissActionInteractor = mock(),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelKosmos.kt
index 3bbabf7..8566251 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelKosmos.kt
@@ -19,7 +19,6 @@
package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -27,7 +26,6 @@
val Kosmos.primaryBouncerToLockscreenTransitionViewModel by Fixture {
PrimaryBouncerToLockscreenTransitionViewModel(
- interactor = keyguardTransitionInteractor,
deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
animationFlow = keyguardTransitionAnimationFlow,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
new file mode 100644
index 0000000..cc0449d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -0,0 +1,80 @@
+/*
+ * 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.kosmos
+
+import android.content.applicationContext
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.bouncerRepository
+import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
+import com.android.systemui.classifier.falsingCollector
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.jank.interactionJankMonitor
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.sceneContainerConfig
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.statusbar.phone.screenOffAnimationController
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.util.time.systemClock
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** Helper for using [Kosmos] from Java. */
+@Deprecated("Please convert your test to Kotlin and use [Kosmos] directly.")
+class KosmosJavaAdapter(
+ testCase: SysuiTestCase,
+) {
+
+ private val kosmos = Kosmos()
+
+ val testDispatcher by lazy { kosmos.testDispatcher }
+ val testScope by lazy { kosmos.testScope }
+ val fakeFeatureFlags by lazy { kosmos.fakeFeatureFlagsClassic }
+ val fakeSceneContainerFlags by lazy { kosmos.fakeSceneContainerFlags }
+ val configurationRepository by lazy { kosmos.fakeConfigurationRepository }
+ val configurationInteractor by lazy { kosmos.configurationInteractor }
+ val bouncerRepository by lazy { kosmos.bouncerRepository }
+ val communalRepository by lazy { kosmos.fakeCommunalRepository }
+ val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
+ val powerRepository by lazy { kosmos.fakePowerRepository }
+ val clock by lazy { kosmos.systemClock }
+ val mobileConnectionsRepository by lazy { kosmos.fakeMobileConnectionsRepository }
+ val simBouncerInteractor by lazy { kosmos.simBouncerInteractor }
+ val statusBarStateController by lazy { kosmos.statusBarStateController }
+ val interactionJankMonitor by lazy { kosmos.interactionJankMonitor }
+ val screenOffAnimationController by lazy { kosmos.screenOffAnimationController }
+ val fakeSceneContainerConfig by lazy { kosmos.sceneContainerConfig }
+ val sceneInteractor by lazy { kosmos.sceneInteractor }
+ val falsingCollector by lazy { kosmos.falsingCollector }
+ val powerInteractor by lazy { kosmos.powerInteractor }
+ val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor }
+ val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor }
+
+ init {
+ kosmos.applicationContext = testCase.context
+ kosmos.testCase = testCase
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
index cac2646..73b7c50 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
@@ -16,7 +16,20 @@
package com.android.systemui.plugins.statusbar
+import com.android.systemui.jank.interactionJankMonitor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.uiEventLogger
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.StatusBarStateControllerImpl
import com.android.systemui.util.mockito.mock
-var Kosmos.statusBarStateController by Kosmos.Fixture { mock<StatusBarStateController>() }
+var Kosmos.statusBarStateController by
+ Kosmos.Fixture {
+ StatusBarStateControllerImpl(
+ uiEventLogger,
+ interactionJankMonitor,
+ mock(),
+ ) {
+ shadeInteractor
+ }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt
index 1185f2e..0307c41 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt
@@ -35,14 +35,21 @@
mutableInputs.add(Input.Intent(view, intent))
}
- override fun handle(view: View?, pendingIntent: PendingIntent) {
- mutableInputs.add(Input.PendingIntent(view, pendingIntent))
+ override fun handle(
+ view: View?,
+ pendingIntent: PendingIntent,
+ requestLaunchingDefaultActivity: Boolean
+ ) {
+ mutableInputs.add(Input.PendingIntent(view, pendingIntent, requestLaunchingDefaultActivity))
}
sealed interface Input {
data class Intent(val view: View?, val intent: android.content.Intent) : Input
- data class PendingIntent(val view: View?, val pendingIntent: android.app.PendingIntent) :
- Input
+ data class PendingIntent(
+ val view: View?,
+ val pendingIntent: android.app.PendingIntent,
+ val requestLaunchingDefaultActivity: Boolean
+ ) : Input
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
deleted file mode 100644
index 09ab655..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ /dev/null
@@ -1,423 +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.scene
-
-import android.app.ActivityTaskManager
-import android.app.admin.DevicePolicyManager
-import android.content.Context
-import android.content.Intent
-import android.content.pm.UserInfo
-import android.graphics.Bitmap
-import android.graphics.drawable.BitmapDrawable
-import android.telecom.TelecomManager
-import android.telephony.PinResult
-import android.telephony.PinResult.PIN_RESULT_TYPE_SUCCESS
-import android.telephony.TelephonyManager
-import android.telephony.euicc.EuiccManager
-import com.android.internal.logging.MetricsLogger
-import com.android.internal.util.EmergencyAffordanceManager
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.data.repository.AuthenticationRepository
-import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
-import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
-import com.android.systemui.bouncer.data.repository.BouncerRepository
-import com.android.systemui.bouncer.data.repository.EmergencyServicesRepository
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
-import com.android.systemui.bouncer.data.repository.FakeSimBouncerRepository
-import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
-import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.EmergencyDialerIntentFactory
-import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor
-import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.classifier.FalsingCollectorFake
-import com.android.systemui.classifier.domain.interactor.FalsingInteractor
-import com.android.systemui.common.shared.model.Text
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
-import com.android.systemui.communal.data.repository.FakeCommunalRepository
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
-import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
-import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
-import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
-import com.android.systemui.doze.DozeLogger
-import com.android.systemui.flags.FakeFeatureFlagsClassic
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeCommandQueue
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import com.android.systemui.keyguard.data.repository.TrustRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testDispatcher
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.power.data.repository.FakePowerRepository
-import com.android.systemui.power.data.repository.PowerRepository
-import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
-import com.android.systemui.scene.data.repository.SceneContainerRepository
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
-import com.android.systemui.scene.shared.model.SceneContainerConfig
-import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.statusbar.notification.stack.data.repository.NotificationStackAppearanceRepository
-import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
-import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
-import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
-import com.android.systemui.telephony.data.repository.TelephonyRepository
-import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
-import com.android.systemui.user.ui.viewmodel.UserActionViewModel
-import com.android.systemui.user.ui.viewmodel.UserViewModel
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.time.SystemClock
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.currentTime
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.ArgumentMatchers.anyString
-
-/**
- * Utilities for creating scene container framework related repositories, interactors, and
- * view-models for tests.
- */
-@OptIn(ExperimentalCoroutinesApi::class)
-class SceneTestUtils(
- private val context: Context,
-) {
- constructor(test: SysuiTestCase) : this(context = test.context)
-
- val kosmos = Kosmos()
- val testDispatcher = kosmos.testDispatcher
- val testScope = kosmos.testScope
- val featureFlags =
- FakeFeatureFlagsClassic().apply {
- set(Flags.FULL_SCREEN_USER_SWITCHER, false)
- set(Flags.NSSL_DEBUG_LINES, false)
- }
- val sceneContainerFlags = FakeSceneContainerFlags().apply { enabled = true }
- val deviceEntryRepository: FakeDeviceEntryRepository by lazy { FakeDeviceEntryRepository() }
- val authenticationRepository: FakeAuthenticationRepository by lazy {
- FakeAuthenticationRepository(
- currentTime = { testScope.currentTime },
- )
- }
- val configurationRepository: FakeConfigurationRepository by lazy {
- FakeConfigurationRepository()
- }
- val configurationInteractor: ConfigurationInteractor by lazy {
- ConfigurationInteractor(configurationRepository)
- }
- private val emergencyServicesRepository: EmergencyServicesRepository by lazy {
- EmergencyServicesRepository(
- applicationScope = applicationScope(),
- resources = context.resources,
- configurationRepository = configurationRepository,
- )
- }
- val telephonyRepository: FakeTelephonyRepository by lazy { FakeTelephonyRepository() }
- val bouncerRepository = BouncerRepository(featureFlags)
- val communalRepository: FakeCommunalRepository by lazy { FakeCommunalRepository() }
- val keyguardRepository: FakeKeyguardRepository by lazy { FakeKeyguardRepository() }
- val powerRepository: FakePowerRepository by lazy { FakePowerRepository() }
- val simBouncerRepository: FakeSimBouncerRepository by lazy { FakeSimBouncerRepository() }
-
- val clock: SystemClock = mock {
- whenever(elapsedRealtime()).thenAnswer { testScope.currentTime }
- }
- val telephonyManager: TelephonyManager = mock {
- whenever(createForSubscriptionId(anyInt())).thenReturn(this)
- whenever(supplyIccLockPin(anyString())).thenReturn(PinResult(PIN_RESULT_TYPE_SUCCESS, 3))
- }
- val devicePolicyManager: DevicePolicyManager = mock {}
- val mobileConnectionsRepository: FakeMobileConnectionsRepository by lazy {
- FakeMobileConnectionsRepository(mock(), mock())
- }
-
- val simBouncerInteractor =
- SimBouncerInteractor(
- applicationContext = context,
- backgroundDispatcher = testDispatcher,
- applicationScope = applicationScope(),
- repository = simBouncerRepository,
- telephonyManager = telephonyManager,
- resources = context.resources,
- keyguardUpdateMonitor = mock(),
- euiccManager = context.getSystemService(Context.EUICC_SERVICE) as EuiccManager,
- mobileConnectionsRepository = mobileConnectionsRepository,
- )
-
- val userRepository: FakeUserRepository by lazy {
- FakeUserRepository().apply {
- val users = listOf(UserInfo(/* id= */ 0, "name", /* flags= */ 0))
- setUserInfos(users)
- runBlocking { setSelectedUserInfo(users.first()) }
- }
- }
-
- private val falsingCollectorFake: FalsingCollector by lazy { FalsingCollectorFake() }
- private var falsingInteractor: FalsingInteractor? = null
- private var powerInteractor: PowerInteractor? = null
-
- fun fakeSceneContainerRepository(
- containerConfig: SceneContainerConfig = fakeSceneContainerConfig(),
- ): SceneContainerRepository {
- return SceneContainerRepository(applicationScope(), containerConfig)
- }
-
- fun fakeSceneKeys(): List<SceneKey> {
- return kosmos.sceneKeys
- }
-
- fun fakeSceneContainerConfig(): SceneContainerConfig {
- return kosmos.sceneContainerConfig
- }
-
- @JvmOverloads
- fun sceneInteractor(
- repository: SceneContainerRepository = fakeSceneContainerRepository()
- ): SceneInteractor {
- return SceneInteractor(
- applicationScope = applicationScope(),
- repository = repository,
- powerInteractor = powerInteractor(),
- logger = mock(),
- )
- }
-
- fun deviceEntryInteractor(
- repository: DeviceEntryRepository = deviceEntryRepository,
- authenticationInteractor: AuthenticationInteractor,
- sceneInteractor: SceneInteractor,
- faceAuthRepository: DeviceEntryFaceAuthRepository = FakeDeviceEntryFaceAuthRepository(),
- trustRepository: TrustRepository = FakeTrustRepository(),
- ): DeviceEntryInteractor {
- return DeviceEntryInteractor(
- applicationScope = applicationScope(),
- repository = repository,
- authenticationInteractor = authenticationInteractor,
- sceneInteractor = sceneInteractor,
- deviceEntryFaceAuthRepository = faceAuthRepository,
- trustRepository = trustRepository,
- flags = FakeSceneContainerFlags(enabled = true)
- )
- }
-
- fun authenticationInteractor(
- repository: AuthenticationRepository = authenticationRepository,
- ): AuthenticationInteractor {
- return AuthenticationInteractor(
- applicationScope = applicationScope(),
- repository = repository,
- selectedUserInteractor = selectedUserInteractor(),
- )
- }
-
- fun keyguardInteractor(
- repository: KeyguardRepository = keyguardRepository
- ): KeyguardInteractor {
- return KeyguardInteractor(
- repository = repository,
- commandQueue = FakeCommandQueue(),
- sceneContainerFlags = sceneContainerFlags,
- bouncerRepository = FakeKeyguardBouncerRepository(),
- configurationInteractor = configurationInteractor,
- shadeRepository = FakeShadeRepository(),
- sceneInteractorProvider = { sceneInteractor() },
- powerInteractor = PowerInteractorFactory.create().powerInteractor,
- )
- }
-
- fun communalInteractor(): CommunalInteractor {
- return CommunalInteractorFactory.create(
- communalRepository = communalRepository,
- )
- .communalInteractor
- }
-
- fun bouncerInteractor(
- authenticationInteractor: AuthenticationInteractor,
- deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor = mock(),
- ): BouncerInteractor {
- return BouncerInteractor(
- applicationScope = applicationScope(),
- applicationContext = context,
- repository = bouncerRepository,
- authenticationInteractor = authenticationInteractor,
- deviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor,
- falsingInteractor = falsingInteractor(),
- powerInteractor = powerInteractor(),
- simBouncerInteractor = simBouncerInteractor,
- )
- }
-
- fun keyguardRootViewModel(): KeyguardRootViewModel = mock()
-
- fun notificationsPlaceholderViewModel(): NotificationsPlaceholderViewModel {
- return NotificationsPlaceholderViewModel(
- interactor =
- NotificationStackAppearanceInteractor(
- repository = NotificationStackAppearanceRepository(),
- ),
- flags = sceneContainerFlags,
- featureFlags = featureFlags,
- )
- }
-
- fun bouncerViewModel(
- bouncerInteractor: BouncerInteractor,
- authenticationInteractor: AuthenticationInteractor,
- actionButtonInteractor: BouncerActionButtonInteractor = bouncerActionButtonInteractor(),
- users: List<UserViewModel> = createUsers(),
- ): BouncerViewModel {
- return BouncerViewModel(
- applicationContext = context,
- applicationScope = applicationScope(),
- mainDispatcher = testDispatcher,
- bouncerInteractor = bouncerInteractor,
- simBouncerInteractor = simBouncerInteractor,
- authenticationInteractor = authenticationInteractor,
- flags = sceneContainerFlags,
- selectedUser = flowOf(users.first { it.isSelectionMarkerVisible }),
- users = flowOf(users),
- userSwitcherMenu = flowOf(createMenuActions()),
- actionButton = actionButtonInteractor.actionButton,
- clock = clock,
- devicePolicyManager = devicePolicyManager,
- )
- }
-
- fun telephonyInteractor(
- repository: TelephonyRepository = telephonyRepository,
- ): TelephonyInteractor {
- return TelephonyInteractor(repository = repository)
- }
-
- fun falsingInteractor(collector: FalsingCollector = falsingCollector()): FalsingInteractor {
- return falsingInteractor ?: FalsingInteractor(collector).also { falsingInteractor = it }
- }
-
- fun falsingCollector(): FalsingCollector {
- return falsingCollectorFake
- }
-
- fun powerInteractor(
- repository: PowerRepository = powerRepository,
- falsingCollector: FalsingCollector = falsingCollector(),
- screenOffAnimationController: ScreenOffAnimationController = mock(),
- statusBarStateController: StatusBarStateController = mock(),
- ): PowerInteractor {
- return powerInteractor
- ?: PowerInteractor(
- repository = repository,
- falsingCollector = falsingCollector,
- screenOffAnimationController = screenOffAnimationController,
- statusBarStateController = statusBarStateController,
- )
- .also { powerInteractor = it }
- }
-
- private fun applicationScope(): CoroutineScope {
- return testScope.backgroundScope
- }
-
- private fun createUsers(
- count: Int = 3,
- selectedIndex: Int = 0,
- ): List<UserViewModel> {
- check(selectedIndex in 0 until count)
-
- return buildList {
- repeat(count) { index ->
- add(
- UserViewModel(
- viewKey = index,
- name = Text.Loaded("name_$index"),
- image = BitmapDrawable(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)),
- isSelectionMarkerVisible = index == selectedIndex,
- alpha = 1f,
- onClicked = {},
- )
- )
- }
- }
- }
-
- private fun createMenuActions(): List<UserActionViewModel> {
- return buildList {
- repeat(3) { index ->
- add(
- UserActionViewModel(
- viewKey = index.toLong(),
- iconResourceId = 0,
- textResourceId = 0,
- onClicked = {},
- )
- )
- }
- }
- }
-
- fun selectedUserInteractor(): SelectedUserInteractor {
- return SelectedUserInteractor(userRepository)
- }
-
- fun bouncerActionButtonInteractor(
- mobileConnectionsRepository: MobileConnectionsRepository = mock(),
- activityTaskManager: ActivityTaskManager = mock(),
- telecomManager: TelecomManager? = null,
- emergencyAffordanceManager: EmergencyAffordanceManager =
- EmergencyAffordanceManager(context),
- emergencyDialerIntentFactory: EmergencyDialerIntentFactory =
- object : EmergencyDialerIntentFactory {
- override fun invoke(): Intent = Intent()
- },
- metricsLogger: MetricsLogger = mock(),
- dozeLogger: DozeLogger = mock(),
- ): BouncerActionButtonInteractor {
- return BouncerActionButtonInteractor(
- applicationContext = context,
- backgroundDispatcher = testDispatcher,
- repository = emergencyServicesRepository,
- mobileConnectionsRepository = mobileConnectionsRepository,
- telephonyInteractor = telephonyInteractor(),
- authenticationInteractor = authenticationInteractor(),
- selectedUserInteractor = selectedUserInteractor(),
- activityTaskManager = activityTaskManager,
- telecomManager = telecomManager,
- emergencyAffordanceManager = emergencyAffordanceManager,
- emergencyDialerIntentFactory = emergencyDialerIntentFactory,
- metricsLogger = metricsLogger,
- dozeLogger = dozeLogger,
- )
- }
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryKosmos.kt
index 7c4e160..e19941c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryKosmos.kt
@@ -18,7 +18,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.scene.shared.model.sceneContainerConfig
+import com.android.systemui.scene.sceneContainerConfig
val Kosmos.sceneContainerRepository by
Kosmos.Fixture { SceneContainerRepository(applicationCoroutineScope, sceneContainerConfig) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
index 9989876..fc02375 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.scene.domain.interactor
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.power.domain.interactor.powerInteractor
@@ -29,5 +30,6 @@
repository = sceneContainerRepository,
powerInteractor = powerInteractor,
logger = sceneLogger,
+ deviceUnlockedInteractor = deviceUnlockedInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
index c2cdbed..979d8e7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
@@ -18,4 +18,5 @@
import com.android.systemui.kosmos.Kosmos
-var Kosmos.sceneContainerFlags by Kosmos.Fixture { FakeSceneContainerFlags() }
+var Kosmos.fakeSceneContainerFlags by Kosmos.Fixture { FakeSceneContainerFlags() }
+val Kosmos.sceneContainerFlags by Kosmos.Fixture<SceneContainerFlags> { fakeSceneContainerFlags }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneContainerConfigModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneContainerConfigModule.kt
index b4fc948..8811b8d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneContainerConfigModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneContainerConfigModule.kt
@@ -30,6 +30,7 @@
SceneKey.Lockscreen,
SceneKey.Bouncer,
SceneKey.Gone,
+ SceneKey.Communal,
),
initialSceneKey = SceneKey.Lockscreen,
),
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 7da57f0..afd37b3 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
@@ -29,8 +29,8 @@
import com.android.systemui.statusbar.disableflags.data.repository.disableFlagsRepository
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
import com.android.systemui.statusbar.phone.dozeParameters
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.userSetupRepository
import com.android.systemui.statusbar.policy.data.repository.deviceProvisioningRepository
+import com.android.systemui.statusbar.policy.data.repository.userSetupRepository
import com.android.systemui.user.domain.interactor.userSwitcherInteractor
var Kosmos.baseShadeInteractor: BaseShadeInteractor by
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/SmartspaceRepositoryKosmos.kt
similarity index 67%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/SmartspaceRepositoryKosmos.kt
index d98f496..0e4c923 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/SmartspaceRepositoryKosmos.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.
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+package com.android.systemui.smartspace.data.repository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.collection.NotifCollection
-import com.android.systemui.util.mockito.mock
-var Kosmos.notifCollection by Fixture { mock<NotifCollection>() }
+val Kosmos.fakeSmartspaceRepository by Fixture { FakeSmartspaceRepository() }
+
+val Kosmos.smartspaceRepository by Fixture<SmartspaceRepository> { fakeSmartspaceRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeScrimTransitionControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeScrimTransitionControllerKosmos.kt
index 93a7adf..8385403 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeScrimTransitionControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeScrimTransitionControllerKosmos.kt
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar
-import android.content.testableContext
+import android.content.applicationContext
import com.android.systemui.dump.dumpManager
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -27,7 +27,7 @@
val Kosmos.lockscreenShadeScrimTransitionController by Fixture {
LockscreenShadeScrimTransitionController(
scrimController = scrimController,
- context = testableContext,
+ context = applicationContext,
configurationController = configurationController,
dumpManager = dumpManager,
splitShadeStateController = splitShadeStateController,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt
index 2752cc2..1c6ce79 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar
-import android.content.testableContext
+import android.content.applicationContext
import com.android.systemui.classifier.falsingCollector
import com.android.systemui.classifier.falsingManager
import com.android.systemui.dump.dumpManager
@@ -47,7 +47,7 @@
scrimTransitionController = lockscreenShadeScrimTransitionController,
keyguardTransitionControllerFactory = lockscreenShadeKeyguardTransitionControllerFactory,
depthController = notificationShadeDepthController,
- context = testableContext,
+ context = applicationContext,
splitShadeOverScrollerFactory = splitShadeLockScreenOverScrollerFactory,
singleShadeOverScrollerFactory = singleShadeLockScreenOverScrollerFactory,
activityStarter = activityStarter,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeKeyguardStatusBarRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeKeyguardStatusBarRepository.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeKeyguardStatusBarRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeKeyguardStatusBarRepository.kt
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt
similarity index 72%
copy from core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt
index ce92b6d..c416ea1 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt
@@ -13,10 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.companion.virtual.camera;
-/**
- * The configuration of a single virtual camera stream.
- * @hide
- */
-parcelable VirtualCameraStreamConfig;
\ No newline at end of file
+package com.android.systemui.statusbar.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeRemoteInputRepository : RemoteInputRepository {
+ override val isRemoteInputActive = MutableStateFlow(false)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryKosmos.kt
similarity index 72%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryKosmos.kt
index 7b9634a..1684efb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryKosmos.kt
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.mobile.data.repository
+package com.android.systemui.statusbar.data.repository
import com.android.systemui.kosmos.Kosmos
-var Kosmos.userSetupRepository: UserSetupRepository by Kosmos.Fixture { fakeUserSetupRepository }
-val Kosmos.fakeUserSetupRepository by Kosmos.Fixture { FakeUserSetupRepository() }
+var Kosmos.remoteInputRepository: RemoteInputRepository by
+ Kosmos.Fixture { fakeRemoteInputRepository }
+val Kosmos.fakeRemoteInputRepository by Kosmos.Fixture { FakeRemoteInputRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneContainerConfigKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractorKosmos.kt
similarity index 73%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneContainerConfigKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractorKosmos.kt
index f9cdc1b..07b39dc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneContainerConfigKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractorKosmos.kt
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.scene.shared.model
+package com.android.systemui.statusbar.domain.interactor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.data.repository.remoteInputRepository
-val Kosmos.sceneContainerConfig by
- Kosmos.Fixture { FakeSceneContainerConfigModule().sceneContainerConfig }
+val Kosmos.remoteInputInteractor by Kosmos.Fixture { RemoteInputInteractor(remoteInputRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotifCollectionKosmos.kt
similarity index 77%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotifCollectionKosmos.kt
index d98f496..9b27a9f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotifCollectionKosmos.kt
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+package com.android.systemui.statusbar.notification.collection
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.collection.NotifCollection
import com.android.systemui.util.mockito.mock
-var Kosmos.notifCollection by Fixture { mock<NotifCollection>() }
+val Kosmos.notifCollection by Fixture { mockNotifCollection }
+val Kosmos.mockNotifCollection by Fixture { mock<NotifCollection>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
index 9851b0e..9c5c486 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
@@ -18,6 +18,7 @@
import android.graphics.drawable.Icon
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import com.android.systemui.statusbar.notification.stack.BUCKET_UNKNOWN
/** Simple ActiveNotificationModel builder for use in tests. */
fun activeNotificationModel(
@@ -32,6 +33,11 @@
aodIcon: Icon? = null,
shelfIcon: Icon? = null,
statusBarIcon: Icon? = null,
+ uid: Int = 0,
+ instanceId: Int? = null,
+ isGroupSummary: Boolean = false,
+ packageName: String = "pkg",
+ bucket: Int = BUCKET_UNKNOWN,
) =
ActiveNotificationModel(
key = key,
@@ -45,4 +51,9 @@
aodIcon = aodIcon,
shelfIcon = shelfIcon,
statusBarIcon = statusBarIcon,
+ uid = uid,
+ packageName = packageName,
+ instanceId = instanceId,
+ isGroupSummary = isGroupSummary,
+ bucket = bucket,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepositoryExt.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepositoryExt.kt
index cb1ba20..b40e1e7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepositoryExt.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepositoryExt.kt
@@ -20,11 +20,20 @@
/**
* Make the repository hold [count] active notifications for testing. The keys of the notifications
- * are "0", "1", ..., (count - 1).toString().
+ * are "0", "1", ..., (count - 1).toString(). The ranks are the same values in Int.
*/
fun ActiveNotificationListRepository.setActiveNotifs(count: Int) {
this.activeNotifications.value =
ActiveNotificationsStore.Builder()
- .apply { repeat(count) { i -> addEntry(activeNotificationModel(key = i.toString())) } }
+ .apply {
+ val rankingsMap = mutableMapOf<String, Int>()
+ repeat(count) { i ->
+ val key = "$i"
+ addEntry(activeNotificationModel(key = key))
+ rankingsMap[key] = i
+ }
+
+ setRankingsMap(rankingsMap)
+ }
.build()
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinderKosmos.kt
index 67fecb4..acc455f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinderKosmos.kt
@@ -19,8 +19,8 @@
import com.android.systemui.common.ui.configurationState
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.notification.collection.notifCollection
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.notificationIconContainerShelfViewModel
-import com.android.systemui.statusbar.notification.stack.ui.viewbinder.notifCollection
import com.android.systemui.statusbar.ui.systemBarUtilsState
val Kosmos.notificationIconContainerShelfViewBinder by Fixture {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.kt
similarity index 76%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.kt
index d98f496..30fc2f3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.kt
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+package com.android.systemui.statusbar.notification.logging
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.collection.NotifCollection
import com.android.systemui.util.mockito.mock
-var Kosmos.notifCollection by Fixture { mock<NotifCollection>() }
+val Kosmos.notificationPanelLogger by Fixture { mockNotificationPanelLogger }
+val Kosmos.mockNotificationPanelLogger by Fixture { mock<NotificationPanelLogger>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/AmbientStateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/AmbientStateKosmos.kt
index 83ac330..7f6f698 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/AmbientStateKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/AmbientStateKosmos.kt
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.notification.stack
-import android.content.testableContext
+import android.content.applicationContext
import com.android.systemui.dump.dumpManager
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -27,7 +27,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
val Kosmos.ambientState by Fixture {
AmbientState(
- /*context=*/ testableContext,
+ /*context=*/ applicationContext,
/*dumpManager=*/ dumpManager,
/*sectionProvider=*/ stackScrollAlgorithmSectionProvider,
/*bypassController=*/ stackScrollAlgorithmBypassController,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/DisplaySwitchNotficationsHiderTrackerKosmos.kt
similarity index 69%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/DisplaySwitchNotficationsHiderTrackerKosmos.kt
index d98f496..b12f5af 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/DisplaySwitchNotficationsHiderTrackerKosmos.kt
@@ -14,11 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+package com.android.systemui.statusbar.notification.stack
+import com.android.internal.logging.latencyTracker
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.collection.NotifCollection
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.shade.domain.interactor.shadeInteractor
-var Kosmos.notifCollection by Fixture { mock<NotifCollection>() }
+val Kosmos.displaySwitchNotificationsHiderTracker by Fixture {
+ DisplaySwitchNotificationsHiderTracker(shadeInteractor, latencyTracker)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerKosmos.kt
new file mode 100644
index 0000000..de52155
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.ui.view
+
+import android.service.notification.notificationListenerService
+import com.android.internal.statusbar.statusBarService
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.notification.logging.notificationPanelLogger
+
+val Kosmos.notificationStatsLogger by Fixture {
+ NotificationStatsLoggerImpl(
+ applicationScope = testScope,
+ bgDispatcher = testDispatcher,
+ statusBarService = statusBarService,
+ notificationListenerService = notificationListenerService,
+ notificationPanelLogger = notificationPanelLogger,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
index 04716b9..748d04d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
@@ -23,8 +23,11 @@
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.notificationIconContainerShelfViewBinder
+import com.android.systemui.statusbar.notification.stack.ui.view.notificationStatsLogger
+import com.android.systemui.statusbar.notification.stack.displaySwitchNotificationsHiderTracker
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationListViewModel
import com.android.systemui.statusbar.phone.notificationIconAreaController
+import java.util.Optional
val Kosmos.notificationListViewBinder by Fixture {
NotificationListViewBinder(
@@ -34,6 +37,8 @@
falsingManager = falsingManager,
iconAreaController = notificationIconAreaController,
metricsLogger = metricsLogger,
+ hiderTracker = displaySwitchNotificationsHiderTracker,
nicBinder = notificationIconContainerShelfViewBinder,
+ loggerOptional = Optional.of(notificationStatsLogger),
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListLoggerViewModelKosmos.kt
similarity index 61%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListLoggerViewModelKosmos.kt
index d98f496..08bda46 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListLoggerViewModelKosmos.kt
@@ -14,11 +14,17 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.collection.NotifCollection
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
-var Kosmos.notifCollection by Fixture { mock<NotifCollection>() }
+val Kosmos.notificationListLoggerViewModel by Fixture {
+ NotificationLoggerViewModel(
+ keyguardInteractor = keyguardInteractor,
+ windowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor,
+ activeNotificationsInteractor = activeNotificationsInteractor,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
index 44f3134..998e579 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
@@ -24,6 +24,7 @@
import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
import com.android.systemui.statusbar.notification.footer.ui.viewmodel.footerViewModel
import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.notificationShelfViewModel
+import com.android.systemui.statusbar.policy.domain.interactor.userSetupInteractor
import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
import java.util.Optional
@@ -32,6 +33,7 @@
shelf = notificationShelfViewModel,
hideListViewModel = hideListViewModel,
footer = Optional.of(footerViewModel),
+ logger = Optional.of(notificationListLoggerViewModel),
activeNotificationsInteractor = activeNotificationsInteractor,
keyguardTransitionInteractor = keyguardTransitionInteractor,
seenNotificationsInteractor = seenNotificationsInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
index 0dbade7..d7e948e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
@@ -20,11 +20,13 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.scene.shared.flag.sceneContainerFlags
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
val Kosmos.notificationsPlaceholderViewModel by Fixture {
NotificationsPlaceholderViewModel(
interactor = notificationStackAppearanceInteractor,
+ shadeInteractor = shadeInteractor,
flags = sceneContainerFlags,
featureFlags = featureFlagsClassic,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
index e4313bb1..d80ee75 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
@@ -19,18 +19,20 @@
import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.data.repository.windowRootViewVisibilityRepository
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.policy.headsUpManager
val Kosmos.windowRootViewVisibilityInteractor by Fixture {
WindowRootViewVisibilityInteractor(
- scope = testScope,
+ scope = applicationCoroutineScope,
windowRootViewVisibilityRepository = windowRootViewVisibilityRepository,
keyguardRepository = keyguardRepository,
headsUpManager = headsUpManager,
powerInteractor = powerInteractor,
+ activeNotificationsInteractor = activeNotificationsInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/FakeStatusBarPipelineMobileDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/FakeStatusBarPipelineMobileDataLayerModule.kt
index 549929c..6e2d12a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/FakeStatusBarPipelineMobileDataLayerModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/FakeStatusBarPipelineMobileDataLayerModule.kt
@@ -15,7 +15,7 @@
*/
package com.android.systemui.statusbar.pipeline.mobile.data
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepositoryModule
+import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepositoryModule
import dagger.Module
@Module(includes = [FakeUserSetupRepositoryModule::class])
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKosmos.kt
similarity index 65%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKosmos.kt
index d98f496..9d62ea5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKosmos.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.
@@ -14,11 +14,15 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+package com.android.systemui.statusbar.pipeline.mobile.data.repository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.collection.NotifCollection
import com.android.systemui.util.mockito.mock
-var Kosmos.notifCollection by Fixture { mock<NotifCollection>() }
+val Kosmos.fakeMobileConnectionsRepository by Fixture {
+ FakeMobileConnectionsRepository(tableLogBuffer = mock())
+}
+
+val Kosmos.mobileConnectionsRepository by
+ Fixture<MobileConnectionsRepository> { fakeMobileConnectionsRepository }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
similarity index 97%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
index a9ee405..de6c87c2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
@@ -76,8 +76,8 @@
private val _defaultMobileIconGroup = MutableStateFlow(DEFAULT_ICON)
override val defaultMobileIconGroup = _defaultMobileIconGroup
- private val _isUserSetup = MutableStateFlow(true)
- override val isUserSetup = _isUserSetup
+ private val _isUserSetUp = MutableStateFlow(true)
+ override val isUserSetUp = _isUserSetUp
override val isForceHidden = MutableStateFlow(false)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeSecurityController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeSecurityController.kt
index 021e7df..ac90a45 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeSecurityController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeSecurityController.kt
@@ -77,6 +77,8 @@
override fun isVpnBranded(): Boolean = fakeState.isVpnBranded
+ override fun isVpnValidated(): Boolean = fakeState.isVpnValidated
+
override fun getPrimaryVpnName(): String? = fakeState.primaryVpnName
override fun getWorkProfileVpnName(): String? = fakeState.workProfileVpnName
@@ -110,6 +112,7 @@
var isVpnEnabled: Boolean = false,
var isVpnRestricted: Boolean = false,
var isVpnBranded: Boolean = false,
+ var isVpnValidated: Boolean = false,
var primaryVpnName: String? = null,
var workProfileVpnName: String? = null,
var hasCACertInCurrentUser: Boolean = false,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeUserSetupRepository.kt
similarity index 85%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeUserSetupRepository.kt
index 55e81bb..76a9861 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeUserSetupRepository.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.mobile.data.repository
+package com.android.systemui.statusbar.policy.data.repository
import com.android.systemui.dagger.SysUISingleton
import dagger.Binds
@@ -26,10 +26,10 @@
@SysUISingleton
class FakeUserSetupRepository @Inject constructor() : UserSetupRepository {
private val _isUserSetup: MutableStateFlow<Boolean> = MutableStateFlow(true)
- override val isUserSetupFlow = _isUserSetup
+ override val isUserSetUp = _isUserSetup
- fun setUserSetup(setup: Boolean) {
- _isUserSetup.value = setup
+ fun setUserSetUp(isSetUp: Boolean) {
+ _isUserSetup.value = isSetUp
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/UserSetupRepositoryKosmos.kt
similarity index 92%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/UserSetupRepositoryKosmos.kt
index 7b9634a..a1c5b9a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/UserSetupRepositoryKosmos.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.mobile.data.repository
+package com.android.systemui.statusbar.policy.data.repository
import com.android.systemui.kosmos.Kosmos
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneContainerConfigKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/UserSetupInteractorKosmos.kt
similarity index 73%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneContainerConfigKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/UserSetupInteractorKosmos.kt
index f9cdc1b..83f4939 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneContainerConfigKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/UserSetupInteractorKosmos.kt
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.scene.shared.model
+package com.android.systemui.statusbar.policy.domain.interactor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.policy.data.repository.userSetupRepository
-val Kosmos.sceneContainerConfig by
- Kosmos.Fixture { FakeSceneContainerConfigModule().sceneContainerConfig }
+val Kosmos.userSetupInteractor by Kosmos.Fixture { UserSetupInteractor(userSetupRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelKosmos.kt
similarity index 60%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelKosmos.kt
index d98f496..0b9f897 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelKosmos.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.
@@ -14,11 +14,16 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+package com.android.systemui.user.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.collection.NotifCollection
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.user.domain.interactor.guestUserInteractor
+import com.android.systemui.user.domain.interactor.userSwitcherInteractor
-var Kosmos.notifCollection by Fixture { mock<NotifCollection>() }
+val Kosmos.userSwitcherViewModel by Fixture {
+ UserSwitcherViewModel(
+ userSwitcherInteractor = userSwitcherInteractor,
+ guestUserInteractor = guestUserInteractor,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeExecutorModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeExecutorModule.kt
index 1f48d94..11c09ee 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeExecutorModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeExecutorModule.kt
@@ -17,6 +17,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dagger.qualifiers.UiBackground
import com.android.systemui.util.time.FakeSystemClock
import dagger.Binds
import dagger.Module
@@ -27,8 +28,9 @@
interface FakeExecutorModule {
@Binds @Main @SysUISingleton fun bindMainExecutor(executor: FakeExecutor): Executor
+ @Binds @UiBackground @SysUISingleton fun bindUiBgExecutor(executor: FakeExecutor): Executor
+
companion object {
- @Provides
- fun provideFake(clock: FakeSystemClock) = FakeExecutor(clock)
+ @Provides fun provideFake(clock: FakeSystemClock) = FakeExecutor(clock)
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt
index 914e654..f3a8b14 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt
@@ -17,5 +17,19 @@
package com.android.systemui.util.time
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.currentTime
-var Kosmos.fakeSystemClock by Kosmos.Fixture { FakeSystemClock() }
+@OptIn(ExperimentalCoroutinesApi::class)
+val Kosmos.systemClock by
+ Kosmos.Fixture<SystemClock> {
+ mock {
+ whenever(elapsedRealtime()).thenAnswer { testScope.currentTime }
+ whenever(uptimeMillis()).thenAnswer { testScope.currentTime }
+ }
+ }
+
+val Kosmos.fakeSystemClock by Kosmos.Fixture { FakeSystemClock() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeSecurityController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeSecurityController.java
index 76199e3..791165d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeSecurityController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeSecurityController.java
@@ -109,6 +109,11 @@
}
@Override
+ public boolean isVpnValidated() {
+ return false;
+ }
+
+ @Override
public String getPrimaryVpnName() {
return null;
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/telecom/TelecomManagerKosmos.kt
similarity index 71%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/telecom/TelecomManagerKosmos.kt
index d98f496..4e0c0883 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/telecom/TelecomManagerKosmos.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.
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+package com.android.telecom
+import android.telecom.TelecomManager
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.collection.NotifCollection
import com.android.systemui.util.mockito.mock
-var Kosmos.notifCollection by Fixture { mock<NotifCollection>() }
+var Kosmos.telecomManager by Fixture<TelecomManager?> { mock() }
diff --git a/packages/SystemUI/unfold/Android.bp b/packages/SystemUI/unfold/Android.bp
index e52cefb..81fd8ce 100644
--- a/packages/SystemUI/unfold/Android.bp
+++ b/packages/SystemUI/unfold/Android.bp
@@ -39,7 +39,4 @@
sdk_version: "current",
min_sdk_version: "current",
plugins: ["dagger2-compiler"],
- lint: {
- baseline_filename: "lint-baseline.xml",
- },
}
diff --git a/packages/SystemUI/unfold/lint-baseline.xml b/packages/SystemUI/unfold/lint-baseline.xml
deleted file mode 100644
index 449ed2e..0000000
--- a/packages/SystemUI/unfold/lint-baseline.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 7.1.0-dev" type="baseline" client="" name="" variant="all" version="7.1.0-dev">
-</issues>
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
index 2bca272..24b22f8 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
@@ -17,6 +17,7 @@
import android.os.Handler
import android.os.Looper
+import androidx.annotation.GuardedBy
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import java.util.concurrent.CopyOnWriteArrayList
@@ -41,8 +42,12 @@
private val listeners = CopyOnWriteArrayList<TransitionProgressListener>()
- @Volatile private var isReadyToHandleTransition = false
- @Volatile private var isTransitionRunning = false
+ private val lock = Object()
+
+ @GuardedBy("lock") private var isReadyToHandleTransition = false
+ // Accessed only from progress thread
+ private var isTransitionRunning = false
+ // Accessed only from progress thread
private var lastTransitionProgress = PROGRESS_UNSET
init {
@@ -72,23 +77,44 @@
* the transition progress events.
*
* Call it with readyToHandleTransition = false when listeners can't process the events.
+ *
+ * Note that this could be called by any thread.
*/
fun setReadyToHandleTransition(isReadyToHandleTransition: Boolean) {
- val progressHandler = this.progressHandler
- if (isTransitionRunning && progressHandler != null) {
- progressHandler.post {
- if (isReadyToHandleTransition) {
- listeners.forEach { it.onTransitionStarted() }
- if (lastTransitionProgress != PROGRESS_UNSET) {
- listeners.forEach { it.onTransitionProgress(lastTransitionProgress) }
- }
- } else {
- isTransitionRunning = false
- listeners.forEach { it.onTransitionFinished() }
- }
+ synchronized(lock) {
+ this.isReadyToHandleTransition = isReadyToHandleTransition
+ val progressHandlerLocal = this.progressHandler
+ if (progressHandlerLocal != null) {
+ ensureInHandler(progressHandlerLocal) { reportLastProgressIfNeeded() }
}
}
- this.isReadyToHandleTransition = isReadyToHandleTransition
+ }
+
+ /** Runs directly if called from the handler thread. Posts otherwise. */
+ private fun ensureInHandler(handler: Handler, f: () -> Unit) {
+ if (handler.looper.isCurrentThread) {
+ f()
+ } else {
+ handler.post(f)
+ }
+ }
+
+ private fun reportLastProgressIfNeeded() {
+ assertInProgressThread()
+ synchronized(lock) {
+ if (!isTransitionRunning) {
+ return
+ }
+ if (isReadyToHandleTransition) {
+ listeners.forEach { it.onTransitionStarted() }
+ if (lastTransitionProgress != PROGRESS_UNSET) {
+ listeners.forEach { it.onTransitionProgress(lastTransitionProgress) }
+ }
+ } else {
+ isTransitionRunning = false
+ listeners.forEach { it.onTransitionFinished() }
+ }
+ }
}
override fun addCallback(listener: TransitionProgressListener) {
@@ -106,34 +132,42 @@
override fun onTransitionStarted() {
assertInProgressThread()
- isTransitionRunning = true
- if (isReadyToHandleTransition) {
- listeners.forEach { it.onTransitionStarted() }
+ synchronized(lock) {
+ isTransitionRunning = true
+ if (isReadyToHandleTransition) {
+ listeners.forEach { it.onTransitionStarted() }
+ }
}
}
override fun onTransitionProgress(progress: Float) {
assertInProgressThread()
- if (isReadyToHandleTransition) {
- listeners.forEach { it.onTransitionProgress(progress) }
+ synchronized(lock) {
+ if (isReadyToHandleTransition) {
+ listeners.forEach { it.onTransitionProgress(progress) }
+ }
+ lastTransitionProgress = progress
}
- lastTransitionProgress = progress
}
override fun onTransitionFinishing() {
assertInProgressThread()
- if (isReadyToHandleTransition) {
- listeners.forEach { it.onTransitionFinishing() }
+ synchronized(lock) {
+ if (isReadyToHandleTransition) {
+ listeners.forEach { it.onTransitionFinishing() }
+ }
}
}
override fun onTransitionFinished() {
assertInProgressThread()
- if (isReadyToHandleTransition) {
- listeners.forEach { it.onTransitionFinished() }
+ synchronized(lock) {
+ if (isReadyToHandleTransition) {
+ listeners.forEach { it.onTransitionFinished() }
+ }
+ isTransitionRunning = false
+ lastTransitionProgress = PROGRESS_UNSET
}
- isTransitionRunning = false
- lastTransitionProgress = PROGRESS_UNSET
}
private fun assertInProgressThread() {
@@ -151,7 +185,7 @@
}
}
- companion object {
- private const val PROGRESS_UNSET = -1f
+ private companion object {
+ const val PROGRESS_UNSET = -1f
}
}
diff --git a/packages/WallpaperBackup/AndroidManifest.xml b/packages/WallpaperBackup/AndroidManifest.xml
index c548101..3ce97cd 100644
--- a/packages/WallpaperBackup/AndroidManifest.xml
+++ b/packages/WallpaperBackup/AndroidManifest.xml
@@ -17,11 +17,19 @@
*/
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.wallpaperbackup"
- android:sharedUserId="android.uid.system" >
+ package="com.android.wallpaperbackup" >
+
+ <uses-permission android:name="android.permission.READ_WALLPAPER_INTERNAL" />
+ <uses-permission android:name="android.permission.SET_WALLPAPER_COMPONENT" />
+ <uses-permission android:name="android.permission.SET_WALLPAPER" />
+
+ <queries>
+ <intent>
+ <action android:name="android.service.wallpaper.WallpaperService" />
+ </intent>
+ </queries>
<application android:allowClearUserData="false"
- android:process="system"
android:killAfterRestore="false"
android:allowBackup="true"
android:backupInForeground="true"
diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
index 98421a9..f31eb44 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -18,11 +18,13 @@
import static android.app.WallpaperManager.FLAG_LOCK;
import static android.app.WallpaperManager.FLAG_SYSTEM;
+import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_INELIGIBLE;
import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_NO_METADATA;
import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_NO_WALLPAPER;
import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_QUOTA_EXCEEDED;
+import static com.android.window.flags.Flags.multiCrop;
import android.app.AppGlobals;
import android.app.WallpaperManager;
@@ -43,7 +45,9 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
+import android.util.Pair;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
@@ -55,6 +59,7 @@
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.util.List;
/**
* Backs up and restores wallpaper and metadata related to it.
@@ -432,6 +437,27 @@
private void restoreFromStage(File stage, File info, String hintTag, int which)
throws IOException {
if (stage.exists()) {
+ if (multiCrop()) {
+ SparseArray<Rect> cropHints = parseCropHints(info, hintTag);
+ if (cropHints != null) {
+ Slog.i(TAG, "Got restored wallpaper; applying which=" + which
+ + "; cropHints = " + cropHints);
+ try (FileInputStream in = new FileInputStream(stage)) {
+ mWallpaperManager.setStreamWithCrops(in, cropHints, true, which);
+ }
+ // And log the success
+ if ((which & FLAG_SYSTEM) > 0) {
+ mEventLogger.onSystemImageWallpaperRestored();
+ }
+ if ((which & FLAG_LOCK) > 0) {
+ mEventLogger.onLockImageWallpaperRestored();
+ }
+ } else {
+ logRestoreError(which, ERROR_NO_METADATA);
+ }
+ return;
+ }
+
// Parse the restored info file to find the crop hint. Note that this currently
// relies on a priori knowledge of the wallpaper info file schema.
Rect cropHint = parseCropHint(info, hintTag);
@@ -501,6 +527,47 @@
return cropHint;
}
+ private SparseArray<Rect> parseCropHints(File wallpaperInfo, String sectionTag) {
+ SparseArray<Rect> cropHints = new SparseArray<>();
+ try (FileInputStream stream = new FileInputStream(wallpaperInfo)) {
+ XmlPullParser parser = Xml.resolvePullParser(stream);
+ int type;
+ do {
+ type = parser.next();
+ if (type != XmlPullParser.START_TAG) continue;
+ String tag = parser.getName();
+ if (!sectionTag.equals(tag)) continue;
+ for (Pair<Integer, String> pair: List.of(
+ new Pair<>(WallpaperManager.PORTRAIT, "Portrait"),
+ new Pair<>(WallpaperManager.LANDSCAPE, "Landscape"),
+ new Pair<>(WallpaperManager.SQUARE_PORTRAIT, "SquarePortrait"),
+ new Pair<>(WallpaperManager.SQUARE_LANDSCAPE, "SquareLandscape"))) {
+ Rect cropHint = new Rect(
+ getAttributeInt(parser, "cropLeft" + pair.second, 0),
+ getAttributeInt(parser, "cropTop" + pair.second, 0),
+ getAttributeInt(parser, "cropRight" + pair.second, 0),
+ getAttributeInt(parser, "cropBottom" + pair.second, 0));
+ if (!cropHint.isEmpty()) cropHints.put(pair.first, cropHint);
+ }
+ if (cropHints.size() == 0) {
+ // migration case: the crops per screen orientation are not specified.
+ // use the old attributes to restore the crop for one screen orientation.
+ Rect cropHint = new Rect(
+ getAttributeInt(parser, "cropLeft", 0),
+ getAttributeInt(parser, "cropTop", 0),
+ getAttributeInt(parser, "cropRight", 0),
+ getAttributeInt(parser, "cropBottom", 0));
+ if (!cropHint.isEmpty()) cropHints.put(ORIENTATION_UNKNOWN, cropHint);
+ }
+ } while (type != XmlPullParser.END_DOCUMENT);
+ } catch (Exception e) {
+ // Whoops; can't process the info file at all. Report failure.
+ Slog.w(TAG, "Failed to parse restored crops: " + e.getMessage());
+ return null;
+ }
+ return cropHints;
+ }
+
private ComponentName parseWallpaperComponent(File wallpaperInfo, String sectionTag) {
ComponentName name = null;
try (FileInputStream stream = new FileInputStream(wallpaperInfo)) {
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
index fb521e1..053ed77 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
@@ -31,6 +31,7 @@
import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_IMG_SYSTEM;
import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_LIVE_LOCK;
import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_LIVE_SYSTEM;
+import static com.android.window.flags.Flags.multiCrop;
import static com.google.common.truth.Truth.assertThat;
@@ -60,6 +61,7 @@
import android.os.ParcelFileDescriptor;
import android.os.UserHandle;
import android.service.wallpaper.WallpaperService;
+import android.util.SparseArray;
import android.util.Xml;
import androidx.test.InstrumentationRegistry;
@@ -711,8 +713,13 @@
@Test
public void testOnRestore_throwsException_logsErrors() throws Exception {
- when(mWallpaperManager.setStream(any(), any(), anyBoolean(), anyInt())).thenThrow(
- new RuntimeException());
+ if (!multiCrop()) {
+ when(mWallpaperManager.setStream(any(), any(), anyBoolean(), anyInt()))
+ .thenThrow(new RuntimeException());
+ } else {
+ when(mWallpaperManager.setStreamWithCrops(any(), any(SparseArray.class), anyBoolean(),
+ anyInt())).thenThrow(new RuntimeException());
+ }
mockStagedWallpaperFile(SYSTEM_WALLPAPER_STAGE);
mockStagedWallpaperFile(WALLPAPER_INFO_STAGE);
mWallpaperBackupAgent.onCreate(USER_HANDLE, BackupAnnotations.BackupDestination.CLOUD,
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index b403a7f..7f542d1 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -408,5 +408,9 @@
// Notify the user about external display events related to screenshot.
// Package: com.android.systemui
NOTE_GLOBAL_SCREENSHOT_EXTERNAL_DISPLAY = 1008;
+
+ // Notify the user that accessibility floating menu is hidden.
+ // Package: com.android.systemui
+ NOTE_A11Y_FLOATING_MENU_HIDDEN = 1009;
}
}
diff --git a/ravenwood/junit-src/android/platform/test/annotations/ExcludeUnderRavenwood.java b/ravenwood/junit-src/android/platform/test/annotations/ExcludeUnderRavenwood.java
new file mode 100644
index 0000000..2ef381e
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/annotations/ExcludeUnderRavenwood.java
@@ -0,0 +1,44 @@
+/*
+ * 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.platform.test.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Tests marked with this annotation are excluded when running under a Ravenwood test environment.
+ *
+ * A more specific method-level annotation always takes precedence over any class-level
+ * annotation, and an {@link IncludeUnderRavenwood} annotation always takes precedence over
+ * an {@link ExcludeUnderRavenwood} annotation.
+ *
+ * This annotation only takes effect when the containing class has a {@code
+ * RavenwoodRule} configured. Ignoring is accomplished by throwing an {@code org.junit
+ * .AssumptionViolatedException} which test infrastructure treats as being ignored.
+ *
+ * This annotation has no effect on any other non-Ravenwood test environments.
+ *
+ * @hide
+ */
+@Inherited
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ExcludeUnderRavenwood {
+}
diff --git a/ravenwood/junit-src/android/platform/test/annotations/IncludeUnderRavenwood.java b/ravenwood/junit-src/android/platform/test/annotations/IncludeUnderRavenwood.java
new file mode 100644
index 0000000..0b2e32f
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/annotations/IncludeUnderRavenwood.java
@@ -0,0 +1,44 @@
+/*
+ * 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.platform.test.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Tests marked with this annotation are included when running under a Ravenwood test environment.
+ *
+ * A more specific method-level annotation always takes precedence over any class-level
+ * annotation, and an {@link IncludeUnderRavenwood} annotation always takes precedence over
+ * an {@link ExcludeUnderRavenwood} annotation.
+ *
+ * This annotation only takes effect when the containing class has a {@code
+ * RavenwoodRule} configured. Ignoring is accomplished by throwing an {@code org.junit
+ * .AssumptionViolatedException} which test infrastructure treats as being ignored.
+ *
+ * This annotation has no effect on any other non-Ravenwood test environments.
+ *
+ * @hide
+ */
+@Inherited
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface IncludeUnderRavenwood {
+}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index d967874..53da8ba 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -18,7 +18,9 @@
import static org.junit.Assert.fail;
+import android.platform.test.annotations.ExcludeUnderRavenwood;
import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.annotations.IncludeUnderRavenwood;
import org.junit.Assume;
import org.junit.rules.TestRule;
@@ -109,21 +111,52 @@
}
/**
- * Test if the given {@link Description} has been marked with an {@link IgnoreUnderRavenwood}
- * annotation, either at the method or class level.
+ * Determine if the given {@link Description} should be included when running under the
+ * Ravenwood test environment.
+ *
+ * A more specific method-level annotation always takes precedence over any class-level
+ * annotation, and an {@link IncludeUnderRavenwood} annotation always takes precedence over
+ * an {@link ExcludeUnderRavenwood} annotation.
*/
- private static boolean hasIgnoreUnderRavenwoodAnnotation(Description description) {
- if (description.getTestClass().getAnnotation(IgnoreUnderRavenwood.class) != null) {
- return true;
- } else if (description.getAnnotation(IgnoreUnderRavenwood.class) != null) {
- return true;
- } else {
+ private boolean shouldIncludeUnderRavenwood(Description description) {
+ // Stopgap for http://g/ravenwood/EPAD-N5ntxM
+ if (description.getMethodName().endsWith("$noRavenwood")) {
return false;
}
+
+ // First, consult any method-level annotations
+ if (description.getAnnotation(IncludeUnderRavenwood.class) != null) {
+ return true;
+ }
+ if (description.getAnnotation(ExcludeUnderRavenwood.class) != null) {
+ return false;
+ }
+ if (description.getAnnotation(IgnoreUnderRavenwood.class) != null) {
+ return false;
+ }
+
+ // Otherwise, consult any class-level annotations
+ if (description.getTestClass().getAnnotation(IncludeUnderRavenwood.class) != null) {
+ return true;
+ }
+ if (description.getTestClass().getAnnotation(ExcludeUnderRavenwood.class) != null) {
+ return false;
+ }
+ if (description.getTestClass().getAnnotation(IgnoreUnderRavenwood.class) != null) {
+ return false;
+ }
+
+ // When no annotations have been requested, assume test should be included
+ return true;
}
@Override
public Statement apply(Statement base, Description description) {
+ // No special treatment when running outside Ravenwood; run tests as-is
+ if (!IS_UNDER_RAVENWOOD) {
+ return base;
+ }
+
if (ENABLE_PROBE_IGNORED) {
return applyProbeIgnored(base, description);
} else {
@@ -138,14 +171,7 @@
return new Statement() {
@Override
public void evaluate() throws Throwable {
- if (hasIgnoreUnderRavenwoodAnnotation(description)) {
- Assume.assumeFalse(IS_UNDER_RAVENWOOD);
- }
-
- // Stopgap for http://g/ravenwood/EPAD-N5ntxM
- if (description.getMethodName().endsWith("$noRavenwood")) {
- Assume.assumeFalse(IS_UNDER_RAVENWOOD);
- }
+ Assume.assumeTrue(shouldIncludeUnderRavenwood(description));
RavenwoodRuleImpl.init(RavenwoodRule.this);
try {
@@ -170,19 +196,17 @@
try {
base.evaluate();
} catch (Throwable t) {
- if (hasIgnoreUnderRavenwoodAnnotation(description)) {
- // This failure is expected, so eat the exception and report the
- // assumption failure that test authors expect
- Assume.assumeFalse(IS_UNDER_RAVENWOOD);
- }
+ // If the test isn't included, eat the exception and report the
+ // assumption failure that test authors expect; otherwise throw
+ Assume.assumeTrue(shouldIncludeUnderRavenwood(description));
throw t;
} finally {
RavenwoodRuleImpl.reset(RavenwoodRule.this);
}
- if (hasIgnoreUnderRavenwoodAnnotation(description) && IS_UNDER_RAVENWOOD) {
- fail("Test was annotated with IgnoreUnderRavenwood, but it actually "
- + "passed under Ravenwood; consider removing the annotation");
+ if (!shouldIncludeUnderRavenwood(description)) {
+ fail("Test wasn't included under Ravenwood, but it actually "
+ + "passed under Ravenwood; consider updating annotations");
}
}
};
diff --git a/services/Android.bp b/services/Android.bp
index 0b484f4..7e8333c 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -148,9 +148,6 @@
java_library {
name: "Slogf",
srcs: ["core/java/com/android/server/utils/Slogf.java"],
- lint: {
- baseline_filename: "lint-baseline.xml",
- },
}
// merge all required services into one jar
diff --git a/services/accessibility/Android.bp b/services/accessibility/Android.bp
index a354671..69cc68a 100644
--- a/services/accessibility/Android.bp
+++ b/services/accessibility/Android.bp
@@ -22,6 +22,7 @@
lint: {
error_checks: ["MissingPermissionAnnotation"],
baseline_filename: "lint-baseline.xml",
+
},
srcs: [
":services.accessibility-sources",
@@ -50,9 +51,6 @@
libs: [
"androidx.annotation_annotation",
],
- lint: {
- baseline_filename: "lint-baseline.xml",
- },
}
aconfig_declarations {
diff --git a/services/accessibility/lint-baseline.xml b/services/accessibility/lint-baseline.xml
index 6bec8cf..b808219 100644
--- a/services/accessibility/lint-baseline.xml
+++ b/services/accessibility/lint-baseline.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="8.1.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
<issue
id="SimpleManualPermissionEnforcement"
@@ -23,4 +23,4 @@
column="9"/>
</issue>
-</issues>
+</issues>
\ No newline at end of file
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index cab2d74..5407af7 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -362,6 +362,7 @@
packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ packageFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
packageFilter.addDataScheme("package");
mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
packageFilter, null, mCallbackHandler);
@@ -402,6 +403,7 @@
boolean added = false;
boolean changed = false;
boolean componentsModified = false;
+ int clearedUid = -1;
final String pkgList[];
switch (action) {
@@ -416,6 +418,10 @@
case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
break;
+ case Intent.ACTION_PACKAGE_DATA_CLEARED:
+ pkgList = null;
+ clearedUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+ break;
default: {
Uri uri = intent.getData();
if (uri == null) {
@@ -430,7 +436,7 @@
changed = Intent.ACTION_PACKAGE_CHANGED.equals(action);
}
}
- if (pkgList == null || pkgList.length == 0) {
+ if ((pkgList == null || pkgList.length == 0) && clearedUid == -1) {
return;
}
@@ -461,6 +467,8 @@
}
}
}
+ } else if (clearedUid != -1) {
+ componentsModified |= clearPreviewsForUidLocked(clearedUid);
} else {
// If the package is being updated, we'll receive a PACKAGE_ADDED
// shortly, otherwise it is removed permanently.
@@ -486,6 +494,19 @@
}
}
+ @GuardedBy("mLock")
+ private boolean clearPreviewsForUidLocked(int clearedUid) {
+ boolean changed = false;
+ final int providerCount = mProviders.size();
+ for (int i = 0; i < providerCount; i++) {
+ Provider provider = mProviders.get(i);
+ if (provider.id.uid == clearedUid) {
+ changed |= provider.clearGeneratedPreviewsLocked();
+ }
+ }
+ return changed;
+ }
+
/**
* Reload all widgets' masked state for the given user and its associated profiles, including
* due to user not being available and package suspension.
@@ -3904,6 +3925,124 @@
}
}
+ @Override
+ @Nullable
+ public RemoteViews getWidgetPreview(@NonNull String callingPackage,
+ @NonNull ComponentName providerComponent, int profileId,
+ @AppWidgetProviderInfo.CategoryFlags int widgetCategory) {
+ final int callingUserId = UserHandle.getCallingUserId();
+ if (DEBUG) {
+ Slog.i(TAG, "getWidgetPreview() " + callingUserId);
+ }
+ mSecurityPolicy.enforceCallFromPackage(callingPackage);
+ ensureWidgetCategoryCombinationIsValid(widgetCategory);
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(profileId);
+ final int providerCount = mProviders.size();
+ for (int i = 0; i < providerCount; i++) {
+ Provider provider = mProviders.get(i);
+ final ComponentName componentName = provider.id.componentName;
+ if (provider.zombie || !providerComponent.equals(componentName)) {
+ continue;
+ }
+
+ final AppWidgetProviderInfo info = provider.getInfoLocked(mContext);
+ final int providerProfileId = info.getProfile().getIdentifier();
+ if (providerProfileId != profileId) {
+ continue;
+ }
+
+ // Allow access to this provider if it is from the calling package or the caller has
+ // BIND_APPWIDGET permission.
+ final int callingUid = Binder.getCallingUid();
+ final String providerPackageName = componentName.getPackageName();
+ final boolean providerIsInCallerProfile =
+ mSecurityPolicy.isProviderInCallerOrInProfileAndWhitelListed(
+ providerPackageName, providerProfileId);
+ final boolean shouldFilterAppAccess = mPackageManagerInternal.filterAppAccess(
+ providerPackageName, callingUid, providerProfileId);
+ final boolean providerIsInCallerPackage =
+ mSecurityPolicy.isProviderInPackageForUid(provider, callingUid,
+ callingPackage);
+ final boolean hasBindAppWidgetPermission =
+ mSecurityPolicy.hasCallerBindPermissionOrBindWhiteListedLocked(
+ callingPackage);
+ if (providerIsInCallerProfile && !shouldFilterAppAccess
+ && (providerIsInCallerPackage || hasBindAppWidgetPermission)) {
+ return provider.getGeneratedPreviewLocked(widgetCategory);
+ }
+ }
+ }
+ throw new IllegalArgumentException(
+ providerComponent + " is not a valid AppWidget provider");
+ }
+
+ @Override
+ public void setWidgetPreview(@NonNull ComponentName providerComponent,
+ @AppWidgetProviderInfo.CategoryFlags int widgetCategories,
+ @NonNull RemoteViews preview) {
+ final int userId = UserHandle.getCallingUserId();
+ if (DEBUG) {
+ Slog.i(TAG, "setWidgetPreview() " + userId);
+ }
+
+ // Make sure callers only set previews for their own package.
+ mSecurityPolicy.enforceCallFromPackage(providerComponent.getPackageName());
+
+ ensureWidgetCategoryCombinationIsValid(widgetCategories);
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ final ProviderId providerId = new ProviderId(Binder.getCallingUid(), providerComponent);
+ final Provider provider = lookupProviderLocked(providerId);
+ if (provider == null) {
+ throw new IllegalArgumentException(
+ providerComponent + " is not a valid AppWidget provider");
+ }
+ provider.setGeneratedPreviewLocked(widgetCategories, preview);
+ scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+ }
+ }
+
+ @Override
+ public void removeWidgetPreview(@NonNull ComponentName providerComponent,
+ @AppWidgetProviderInfo.CategoryFlags int widgetCategories) {
+ final int userId = UserHandle.getCallingUserId();
+ if (DEBUG) {
+ Slog.i(TAG, "removeWidgetPreview() " + userId);
+ }
+
+ // Make sure callers only remove previews for their own package.
+ mSecurityPolicy.enforceCallFromPackage(providerComponent.getPackageName());
+
+ ensureWidgetCategoryCombinationIsValid(widgetCategories);
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ final ProviderId providerId = new ProviderId(Binder.getCallingUid(), providerComponent);
+ final Provider provider = lookupProviderLocked(providerId);
+ if (provider == null) {
+ throw new IllegalArgumentException(
+ providerComponent + " is not a valid AppWidget provider");
+ }
+ final boolean changed = provider.removeGeneratedPreviewLocked(widgetCategories);
+ if (changed) scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+ }
+ }
+
+ private static void ensureWidgetCategoryCombinationIsValid(int widgetCategories) {
+ int validCategories = AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN
+ | AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD
+ | AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX;
+ int invalid = ~validCategories;
+ if ((widgetCategories & invalid) != 0) {
+ throw new IllegalArgumentException(widgetCategories
+ + " is not a valid widget category combination");
+ }
+ }
+
private final class CallbackHandler extends Handler {
public static final int MSG_NOTIFY_UPDATE_APP_WIDGET = 1;
public static final int MSG_NOTIFY_PROVIDER_CHANGED = 2;
@@ -4201,6 +4340,12 @@
ArrayList<Widget> widgets = new ArrayList<>();
PendingIntent broadcast;
String infoTag;
+ SparseArray<RemoteViews> generatedPreviews = new SparseArray<>(3);
+ private static final int[] WIDGET_CATEGORY_FLAGS = new int[]{
+ AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN,
+ AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD,
+ AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX,
+ };
boolean zombie; // if we're in safe mode, don't prune this just because nobody references it
@@ -4234,7 +4379,7 @@
return false;
}
- @GuardedBy("AppWidgetServiceImpl.mLock")
+ @GuardedBy("this.mLock")
public AppWidgetProviderInfo getInfoLocked(Context context) {
if (!mInfoParsed) {
// parse
@@ -4250,6 +4395,7 @@
}
if (newInfo != null) {
info = newInfo;
+ updateGeneratedPreviewCategoriesLocked();
}
}
mInfoParsed = true;
@@ -4279,6 +4425,62 @@
mInfoParsed = true;
}
+ @GuardedBy("this.mLock")
+ @Nullable
+ public RemoteViews getGeneratedPreviewLocked(
+ @AppWidgetProviderInfo.CategoryFlags int widgetCategories) {
+ for (int i = 0; i < generatedPreviews.size(); i++) {
+ if ((widgetCategories & generatedPreviews.keyAt(i)) != 0) {
+ return generatedPreviews.valueAt(i);
+ }
+ }
+ return null;
+ }
+
+ @GuardedBy("this.mLock")
+ public void setGeneratedPreviewLocked(
+ @AppWidgetProviderInfo.CategoryFlags int widgetCategories,
+ @NonNull RemoteViews preview) {
+ for (int flag : WIDGET_CATEGORY_FLAGS) {
+ if ((widgetCategories & flag) != 0) {
+ generatedPreviews.put(flag, preview);
+ }
+ }
+ updateGeneratedPreviewCategoriesLocked();
+ }
+
+ @GuardedBy("this.mLock")
+ public boolean removeGeneratedPreviewLocked(int widgetCategories) {
+ boolean changed = false;
+ for (int flag : WIDGET_CATEGORY_FLAGS) {
+ if ((widgetCategories & flag) != 0) {
+ changed |= generatedPreviews.removeReturnOld(flag) != null;
+ }
+ }
+ if (changed) {
+ updateGeneratedPreviewCategoriesLocked();
+ }
+ return changed;
+ }
+
+ @GuardedBy("this.mLock")
+ public boolean clearGeneratedPreviewsLocked() {
+ if (generatedPreviews.size() > 0) {
+ generatedPreviews.clear();
+ updateGeneratedPreviewCategoriesLocked();
+ return true;
+ }
+ return false;
+ }
+
+ @GuardedBy("this.mLock")
+ private void updateGeneratedPreviewCategoriesLocked() {
+ info.generatedPreviewCategories = 0;
+ for (int i = 0; i < generatedPreviews.size(); i++) {
+ info.generatedPreviewCategories |= generatedPreviews.keyAt(i);
+ }
+ }
+
@Override
public String toString() {
return "Provider{" + id + (zombie ? " Z" : "") + '}';
diff --git a/services/autofill/features.aconfig b/services/autofill/features.aconfig
index 92e00ee..727721d 100644
--- a/services/autofill/features.aconfig
+++ b/services/autofill/features.aconfig
@@ -5,4 +5,11 @@
namespace: "autofill"
description: "Guards Autofill Framework against Autofill-Credman integration"
bug: "296907283"
+}
+
+flag {
+ name: "autofill_credman_integration_phase2"
+ namespace: "autofill"
+ description: "Guards against Autofill-Credman integration phase 2"
+ bug: "320730001"
}
\ No newline at end of file
diff --git a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
index 7fc1738..1234703 100644
--- a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
+++ b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
@@ -108,9 +108,9 @@
mLastFlag = flag;
if (mRemoteFillService != null && mRemoteFillService.isCredentialAutofillService()) {
Slog.v(TAG, "About to call CredAutofill service as secondary provider");
- addSessionIdAndRequestIdToClientState(pendingFillRequest,
+ FillRequest request = addSessionIdAndRequestIdToClientState(pendingFillRequest,
pendingInlineSuggestionsRequest, id);
- mRemoteFillService.onFillCredentialRequest(pendingFillRequest, client);
+ mRemoteFillService.onFillCredentialRequest(request, client);
} else {
mRemoteFillService.onFillRequest(pendingFillRequest);
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 6a81425..f3b74ea 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -1735,6 +1735,7 @@
processResponseLockedForPcc(response, response.getClientState(), requestFlags);
mFillResponseEventLogger.maybeSetLatencyResponseProcessingMillis();
+ mFillResponseEventLogger.logAndEndEvent();
}
@@ -1847,6 +1848,33 @@
return;
}
synchronized (mLock) {
+ // TODO(b/319913595): refactor logging for fill response for primary and secondary
+ // providers
+ // Start a new FillResponse logger for the success case.
+ mFillResponseEventLogger.startLogForNewResponse();
+ mFillResponseEventLogger.maybeSetRequestId(fillResponse.getRequestId());
+ mFillResponseEventLogger.maybeSetAppPackageUid(uid);
+ mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_SUCCESS);
+ mFillResponseEventLogger.startResponseProcessingTime();
+ // Time passed since session was created
+ final long fillRequestReceivedRelativeTimestamp =
+ SystemClock.elapsedRealtime() - mLatencyBaseTime;
+ mPresentationStatsEventLogger.maybeSetFillResponseReceivedTimestampMs(
+ (int) (fillRequestReceivedRelativeTimestamp));
+ mFillResponseEventLogger.maybeSetLatencyFillResponseReceivedMillis(
+ (int) (fillRequestReceivedRelativeTimestamp));
+ if (mDestroyed) {
+ Slog.w(TAG, "Call to Session#onSecondaryFillResponse() rejected - session: "
+ + id + " destroyed");
+ mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_SESSION_DESTROYED);
+ mFillResponseEventLogger.logAndEndEvent();
+ return;
+ }
+
+ List<Dataset> datasetList = fillResponse.getDatasets();
+ int datasetCount = (datasetList == null) ? 0 : datasetList.size();
+ mFillResponseEventLogger.maybeSetTotalDatasetsProvided(datasetCount);
+ mFillResponseEventLogger.maybeSetAvailableCount(datasetCount);
if (mSecondaryResponses == null) {
mSecondaryResponses = new SparseArray<>(2);
}
@@ -1859,6 +1887,8 @@
if (currentView != null) {
currentView.maybeCallOnFillReady(flags);
}
+ mFillResponseEventLogger.maybeSetLatencyResponseProcessingMillis();
+ mFillResponseEventLogger.logAndEndEvent();
}
}
@@ -4271,13 +4301,19 @@
if (value != null) {
viewState.setCurrentValue(value);
}
-
+ boolean isCredmanRequested = (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0;
if (shouldRequestSecondaryProvider(flags)) {
if (requestNewFillResponseOnViewEnteredIfNecessaryLocked(
id, viewState, flags)) {
Slog.v(TAG, "Started a new fill request for secondary provider.");
return;
}
+
+ FillResponse response = viewState.getSecondaryResponse();
+ if (response != null) {
+ logPresentationStatsOnViewEntered(response, isCredmanRequested);
+ }
+
// If the ViewState is ready to be displayed, onReady() will be called.
viewState.update(value, virtualBounds, flags);
@@ -4363,15 +4399,9 @@
return;
}
- if (viewState.getResponse() != null) {
- boolean isCredmanRequested = (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0;
- FillResponse response = viewState.getResponse();
- mPresentationStatsEventLogger.maybeSetRequestId(response.getRequestId());
- mPresentationStatsEventLogger.maybeSetIsCredentialRequest(isCredmanRequested);
- mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId(
- mFieldClassificationIdSnapshot);
- mPresentationStatsEventLogger.maybeSetAvailableCount(
- response.getDatasets(), mCurrentViewId);
+ FillResponse response = viewState.getResponse();
+ if (response != null) {
+ logPresentationStatsOnViewEntered(response, isCredmanRequested);
}
if (isSameViewEntered) {
@@ -4412,6 +4442,17 @@
}
@GuardedBy("mLock")
+ private void logPresentationStatsOnViewEntered(FillResponse response,
+ boolean isCredmanRequested) {
+ mPresentationStatsEventLogger.maybeSetRequestId(response.getRequestId());
+ mPresentationStatsEventLogger.maybeSetIsCredentialRequest(isCredmanRequested);
+ mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId(
+ mFieldClassificationIdSnapshot);
+ mPresentationStatsEventLogger.maybeSetAvailableCount(
+ response.getDatasets(), mCurrentViewId);
+ }
+
+ @GuardedBy("mLock")
private void hideAugmentedAutofillLocked(@NonNull ViewState viewState) {
if ((viewState.getState()
& ViewState.STATE_TRIGGERED_AUGMENTED_AUTOFILL) != 0) {
diff --git a/services/backup/flags.aconfig b/services/backup/flags.aconfig
index d695d36..549fa36 100644
--- a/services/backup/flags.aconfig
+++ b/services/backup/flags.aconfig
@@ -7,4 +7,12 @@
"restore for apps that have been launched."
bug: "308401499"
is_fixed_read_only: true
+}
+
+flag {
+ name: "enable_max_size_writes_to_pipes"
+ namespace: "onboarding"
+ description: "Enables the write buffer to pipes to be of maximum size."
+ bug: "265976737"
+ is_fixed_read_only: true
}
\ No newline at end of file
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index 6aed9aa..cca166b 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -40,8 +40,8 @@
import com.android.server.EventLogTags;
import com.android.server.backup.BackupAgentTimeoutParameters;
-import com.android.server.backup.BackupAndRestoreFeatureFlags;
import com.android.server.backup.BackupRestoreTask;
+import com.android.server.backup.Flags;
import com.android.server.backup.FullBackupJob;
import com.android.server.backup.OperationStorage;
import com.android.server.backup.OperationStorage.OpState;
@@ -390,8 +390,11 @@
// Set up to send data to the transport
final int N = mPackages.size();
- final int chunkSizeInBytes =
- BackupAndRestoreFeatureFlags.getFullBackupWriteToTransportBufferSizeBytes();
+ int chunkSizeInBytes = 8 * 1024; // 8KB
+ if (Flags.enableMaxSizeWritesToPipes()) {
+ // Linux pipe capacity (buffer size) is 16 pages where each page is 4KB
+ chunkSizeInBytes = 64 * 1024; // 64KB
+ }
final byte[] buffer = new byte[chunkSizeInBytes];
for (int i = 0; i < N; i++) {
mBackupRunner = null;
diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
index ff72476..2c9eb51 100644
--- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
+++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
@@ -29,7 +29,6 @@
import android.app.IBackupAgent;
import android.app.backup.BackupAgent;
import android.app.backup.BackupAnnotations;
-import android.app.backup.BackupManager;
import android.app.backup.FullBackup;
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.IFullBackupRestoreObserver;
@@ -51,6 +50,7 @@
import com.android.server.backup.BackupAgentTimeoutParameters;
import com.android.server.backup.BackupRestoreTask;
import com.android.server.backup.FileMetadata;
+import com.android.server.backup.Flags;
import com.android.server.backup.KeyValueAdbRestoreEngine;
import com.android.server.backup.OperationStorage;
import com.android.server.backup.OperationStorage.OpType;
@@ -157,13 +157,19 @@
mMonitor = monitor;
mOnlyPackage = onlyPackage;
mAllowApks = allowApks;
- mBuffer = new byte[32 * 1024];
mAgentTimeoutParameters = Objects.requireNonNull(
backupManagerService.getAgentTimeoutParameters(),
"Timeout parameters cannot be null");
mIsAdbRestore = isAdbRestore;
mUserId = backupManagerService.getUserId();
mBackupEligibilityRules = backupEligibilityRules;
+
+ if (Flags.enableMaxSizeWritesToPipes()) {
+ // Linux pipe capacity (buffer size) is 16 pages where each page is 4KB
+ mBuffer = new byte[64 * 1024]; // 64KB
+ } else {
+ mBuffer = new byte[32 * 1024];
+ }
}
@VisibleForTesting
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index 316a16d..0559708 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -140,8 +140,8 @@
// Widget-related data handled as part of this restore operation
private byte[] mWidgetData;
- // Number of apps restored in this pass
- private int mCount;
+ // Number of apps attempted to restore in this pass
+ private int mRestoreAttemptedAppsCount;
// When did we start?
private long mStartRealtime;
@@ -574,7 +574,8 @@
Slog.v(TAG, "No more packages; finishing restore");
}
int millis = (int) (SystemClock.elapsedRealtime() - mStartRealtime);
- EventLog.writeEvent(EventLogTags.RESTORE_SUCCESS, mCount, millis);
+ EventLog.writeEvent(
+ EventLogTags.RESTORE_SUCCESS, mRestoreAttemptedAppsCount, millis);
nextState = UnifiedRestoreState.FINAL;
return;
}
@@ -582,7 +583,8 @@
if (DEBUG) {
Slog.i(TAG, "Next restore package: " + mRestoreDescription);
}
- sendOnRestorePackage(pkgName);
+ mRestoreAttemptedAppsCount++;
+ sendOnRestorePackage(mRestoreAttemptedAppsCount, pkgName);
Metadata metaInfo = mPmAgent.getRestoredMetadata(pkgName);
if (metaInfo == null) {
@@ -810,7 +812,6 @@
// And then finally start the restore on this agent
try {
initiateOneRestore(mCurrentPackage, metaInfo.versionCode);
- ++mCount;
} catch (Exception e) {
Slog.e(TAG, "Error when attempting restore: " + e.toString());
Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null);
@@ -968,7 +969,12 @@
throws Exception {
Set<String> excludedKeysForPackage = getExcludedKeysForPackage(packageName);
- byte[] buffer = new byte[8192]; // will grow when needed
+ int bufferSize = 8192; // 8KB
+ if (Flags.enableMaxSizeWritesToPipes()) {
+ // Linux pipe capacity (buffer size) is 16 pages where each page is 4KB
+ bufferSize = 64 * 1024; // 64KB
+ }
+ byte[] buffer = new byte[bufferSize]; // will grow when needed
while (in.readNextHeader()) {
final String key = in.getKey();
final int size = in.getDataSize();
@@ -1116,7 +1122,11 @@
ParcelFileDescriptor tReadEnd = mTransportPipes[0];
ParcelFileDescriptor tWriteEnd = mTransportPipes[1];
- int bufferSize = 32 * 1024;
+ int bufferSize = 32 * 1024; // 32KB
+ if (Flags.enableMaxSizeWritesToPipes()) {
+ // Linux pipe capacity (buffer size) is 16 pages where each page is 4KB
+ bufferSize = 64 * 1024; // 64KB
+ }
byte[] buffer = new byte[bufferSize];
FileOutputStream engineOut = new FileOutputStream(eWriteEnd.getFileDescriptor());
FileInputStream transportIn = new FileInputStream(tReadEnd.getFileDescriptor());
@@ -1322,13 +1332,7 @@
}
// Tell the observer we're done
- if (mObserver != null) {
- try {
- mObserver.restoreFinished(mStatus);
- } catch (RemoteException e) {
- Slog.d(TAG, "Restore observer died at restoreFinished");
- }
- }
+ sendEndRestore();
// Clear any ongoing session timeout.
backupManagerService.getBackupHandler().removeMessages(MSG_RESTORE_SESSION_TIMEOUT);
@@ -1642,10 +1646,10 @@
}
}
- void sendOnRestorePackage(String name) {
+ void sendOnRestorePackage(int index, String name) {
if (mObserver != null) {
try {
- mObserver.onUpdate(mCount, name);
+ mObserver.onUpdate(index, name);
} catch (RemoteException e) {
Slog.d(TAG, "Restore observer died in onUpdate");
mObserver = null;
diff --git a/services/backup/java/com/android/server/backup/utils/FullBackupUtils.java b/services/backup/java/com/android/server/backup/utils/FullBackupUtils.java
index 1c0cd87..843354e 100644
--- a/services/backup/java/com/android/server/backup/utils/FullBackupUtils.java
+++ b/services/backup/java/com/android/server/backup/utils/FullBackupUtils.java
@@ -21,7 +21,7 @@
import android.os.ParcelFileDescriptor;
import android.util.Slog;
-import com.android.server.backup.BackupAndRestoreFeatureFlags;
+import com.android.server.backup.Flags;
import java.io.DataInputStream;
import java.io.EOFException;
@@ -46,8 +46,11 @@
// We do not take close() responsibility for the pipe FD
FileInputStream raw = new FileInputStream(inPipe.getFileDescriptor());
DataInputStream in = new DataInputStream(raw);
- final int chunkSizeInBytes =
- BackupAndRestoreFeatureFlags.getFullBackupUtilsRouteBufferSizeBytes();
+ int chunkSizeInBytes = 32 * 1024; // 32KB
+ if (Flags.enableMaxSizeWritesToPipes()) {
+ // Linux pipe capacity (buffer size) is 16 pages where each page is 4KB
+ chunkSizeInBytes = 64 * 1024; // 64KB
+ }
byte[] buffer = new byte[chunkSizeInBytes];
int chunkTotal;
while ((chunkTotal = in.readInt()) > 0) {
diff --git a/services/backup/java/com/android/server/backup/utils/RestoreUtils.java b/services/backup/java/com/android/server/backup/utils/RestoreUtils.java
index 0accb9f..5a8533a 100644
--- a/services/backup/java/com/android/server/backup/utils/RestoreUtils.java
+++ b/services/backup/java/com/android/server/backup/utils/RestoreUtils.java
@@ -40,6 +40,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.server.LocalServices;
import com.android.server.backup.FileMetadata;
+import com.android.server.backup.Flags;
import com.android.server.backup.restore.RestoreDeleteObserver;
import com.android.server.backup.restore.RestorePolicy;
@@ -93,7 +94,12 @@
try (Session session = installer.openSession(sessionId)) {
try (OutputStream apkStream = session.openWrite(info.packageName, 0,
info.size)) {
- byte[] buffer = new byte[32 * 1024];
+ int bufferSize = 32 * 1024; // 32KB
+ if (Flags.enableMaxSizeWritesToPipes()) {
+ // Linux pipe capacity (buffer size) is 16 pages where each page is 4KB
+ bufferSize = 64 * 1024; // 64KB
+ }
+ byte[] buffer = new byte[bufferSize];
long size = info.size;
while (size > 0) {
long toRead = (buffer.length < size) ? buffer.length : size;
diff --git a/services/backup/lint-baseline.xml b/services/backup/lint-baseline.xml
index 93c9390..46de2cdd 100644
--- a/services/backup/lint-baseline.xml
+++ b/services/backup/lint-baseline.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 7.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="7.1.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
<issue
id="NonUserGetterCalled"
@@ -36,4 +36,4 @@
line="207"/>
</issue>
-</issues>
+</issues>
\ No newline at end of file
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
index 4b3772a..d0eb59d 100644
--- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
@@ -22,6 +22,7 @@
import static android.companion.CompanionDeviceManager.REASON_INTERNAL_ERROR;
import static android.companion.CompanionDeviceManager.RESULT_INTERNAL_ERROR;
import static android.content.ComponentName.createRelative;
+import static android.content.pm.PackageManager.FEATURE_WATCH;
import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
import static com.android.server.companion.MetricUtils.logCreateAssociation;
@@ -169,16 +170,29 @@
enforcePermissionsForAssociation(mContext, request, packageUid);
enforceUsesCompanionDeviceFeature(mContext, userId, packageName);
- // 2. Check if association can be created without launching UI (i.e. CDM needs NEITHER
+ // 2a. Check if association can be created without launching UI (i.e. CDM needs NEITHER
// to perform discovery NOR to collect user consent).
if (request.isSelfManaged() && !request.isForceConfirmation()
&& !willAddRoleHolder(request, packageName, userId)) {
- // 2a. Create association right away.
+ // 2a.1. Create association right away.
createAssociationAndNotifyApplication(request, packageName, userId,
/* macAddress */ null, callback, /* resultReceiver */ null);
return;
}
+ // 2a.2. Report an error if a 3p app tries to create a non-self-managed association and
+ // launch UI on watch.
+ if (mContext.getPackageManager().hasSystemFeature(FEATURE_WATCH)) {
+ String errorMessage = "3p apps are not allowed to create associations on watch.";
+ Slog.e(TAG, errorMessage);
+ try {
+ callback.onFailure(errorMessage);
+ } catch (RemoteException e) {
+ // ignored
+ }
+ return;
+ }
+
// 2b. Build a PendingIntent for launching the confirmation UI, and send it back to the app:
// 2b.1. Populate the request with required info.
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 056ec89..50e1862 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -21,6 +21,7 @@
import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES;
import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
import static android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE;
+import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION;
import static android.Manifest.permission.USE_COMPANION_TRANSPORTS;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
@@ -82,6 +83,7 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
+import android.hardware.power.Mode;
import android.net.MacAddress;
import android.net.NetworkPolicyManager;
import android.os.Binder;
@@ -90,6 +92,7 @@
import android.os.Message;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
+import android.os.PowerManagerInternal;
import android.os.PowerWhitelistManager;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
@@ -175,6 +178,7 @@
private final PowerWhitelistManager mPowerWhitelistManager;
private final UserManager mUserManager;
final PackageManagerInternal mPackageManagerInternal;
+ private final PowerManagerInternal mPowerManagerInternal;
/**
* A structure that consists of two nested maps, and effectively maps (userId + packageName) to
@@ -235,6 +239,7 @@
mOnPackageVisibilityChangeListener =
new OnPackageVisibilityChangeListener(mActivityManager);
+ mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
}
@Override
@@ -949,6 +954,10 @@
mAssociationStore.updateAssociation(association);
mDevicePresenceMonitor.onSelfManagedDeviceConnected(associationId);
+
+ if (association.getDeviceProfile() == REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION) {
+ mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, true);
+ }
}
@Override
@@ -963,6 +972,10 @@
}
mDevicePresenceMonitor.onSelfManagedDeviceDisconnected(associationId);
+
+ if (association.getDeviceProfile() == REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION) {
+ mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, false);
+ }
}
@Override
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 53c0184c..e5a8c4f 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -101,7 +101,7 @@
String deviceProfile = getNextArg();
final MacAddress macAddress = MacAddress.fromString(address);
mService.createNewAssociation(userId, packageName, macAddress,
- null, deviceProfile, false);
+ /* displayName= */ deviceProfile, deviceProfile, false);
}
break;
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index 4e471f5..260b21f 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -21,6 +21,7 @@
import static android.app.PendingIntent.FLAG_ONE_SHOT;
import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_PERMISSION_RESTORE;
import static android.content.ComponentName.createRelative;
+import static android.content.pm.PackageManager.FEATURE_WATCH;
import static com.android.server.companion.Utils.prepareForIpc;
@@ -40,6 +41,7 @@
import android.content.Intent;
import android.content.pm.PackageManagerInternal;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -306,6 +308,13 @@
}
private void onReceivePermissionRestore(byte[] message) {
+ // TODO: Disable Permissions Sync for non-watch devices until we figure out a better UX
+ // model
+ if (!Build.isDebuggable() && !mContext.getPackageManager().hasSystemFeature(
+ FEATURE_WATCH)) {
+ Slog.e(LOG_TAG, "Permissions restore is only available on watch.");
+ return;
+ }
Slog.i(LOG_TAG, "Applying permissions.");
// Start applying permissions
UserHandle user = mContext.getUser();
diff --git a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
index 720687e..0e66fbc 100644
--- a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
+++ b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
@@ -23,12 +23,12 @@
import android.os.Build;
import android.util.Slog;
-import com.google.security.cryptauth.lib.securegcm.BadHandleException;
-import com.google.security.cryptauth.lib.securegcm.CryptoException;
-import com.google.security.cryptauth.lib.securegcm.D2DConnectionContextV1;
-import com.google.security.cryptauth.lib.securegcm.D2DHandshakeContext;
-import com.google.security.cryptauth.lib.securegcm.D2DHandshakeContext.Role;
-import com.google.security.cryptauth.lib.securegcm.HandshakeException;
+import com.google.security.cryptauth.lib.securegcm.ukey2.BadHandleException;
+import com.google.security.cryptauth.lib.securegcm.ukey2.CryptoException;
+import com.google.security.cryptauth.lib.securegcm.ukey2.D2DConnectionContextV1;
+import com.google.security.cryptauth.lib.securegcm.ukey2.D2DHandshakeContext;
+import com.google.security.cryptauth.lib.securegcm.ukey2.D2DHandshakeContext.Role;
+import com.google.security.cryptauth.lib.securegcm.ukey2.HandshakeException;
import libcore.io.IoUtils;
import libcore.io.Streams;
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 62c6703..3e45626 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -125,7 +125,7 @@
* Send a message to remote devices through the transports
*/
public void sendMessage(int message, byte[] data, int[] associationIds) {
- Slog.i(TAG, "Sending message 0x" + Integer.toHexString(message)
+ Slog.d(TAG, "Sending message 0x" + Integer.toHexString(message)
+ " data length " + data.length);
synchronized (mTransports) {
for (int i = 0; i < associationIds.length; i++) {
diff --git a/services/companion/java/com/android/server/companion/transport/SecureTransport.java b/services/companion/java/com/android/server/companion/transport/SecureTransport.java
index a0301a9..6e906eb 100644
--- a/services/companion/java/com/android/server/companion/transport/SecureTransport.java
+++ b/services/companion/java/com/android/server/companion/transport/SecureTransport.java
@@ -34,7 +34,7 @@
private volatile boolean mShouldProcessRequests = false;
- private final BlockingQueue<byte[]> mRequestQueue = new ArrayBlockingQueue<>(100);
+ private final BlockingQueue<byte[]> mRequestQueue = new ArrayBlockingQueue<>(500);
SecureTransport(int associationId, ParcelFileDescriptor fd, Context context) {
super(associationId, fd, context);
diff --git a/services/companion/java/com/android/server/companion/transport/Transport.java b/services/companion/java/com/android/server/companion/transport/Transport.java
index 22b18ac..8a5774e 100644
--- a/services/companion/java/com/android/server/companion/transport/Transport.java
+++ b/services/companion/java/com/android/server/companion/transport/Transport.java
@@ -284,7 +284,7 @@
if (mListeners.containsKey(message)) {
try {
mListeners.get(message).onMessageReceived(getAssociationId(), data);
- Slog.i(TAG, "Message 0x" + Integer.toHexString(message)
+ Slog.d(TAG, "Message 0x" + Integer.toHexString(message)
+ " is received from associationId " + mAssociationId
+ ", sending data length " + data.length + " to the listener.");
} catch (RemoteException ignored) {
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index d1274d4..3b9d92d 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -30,6 +30,8 @@
import android.hardware.input.VirtualMouseButtonEvent;
import android.hardware.input.VirtualMouseRelativeEvent;
import android.hardware.input.VirtualMouseScrollEvent;
+import android.hardware.input.VirtualStylusButtonEvent;
+import android.hardware.input.VirtualStylusMotionEvent;
import android.hardware.input.VirtualTouchEvent;
import android.os.Handler;
import android.os.IBinder;
@@ -71,12 +73,14 @@
static final String PHYS_TYPE_MOUSE = "Mouse";
static final String PHYS_TYPE_TOUCHSCREEN = "Touchscreen";
static final String PHYS_TYPE_NAVIGATION_TOUCHPAD = "NavigationTouchpad";
+ static final String PHYS_TYPE_STYLUS = "Stylus";
@StringDef(prefix = { "PHYS_TYPE_" }, value = {
PHYS_TYPE_DPAD,
PHYS_TYPE_KEYBOARD,
PHYS_TYPE_MOUSE,
PHYS_TYPE_TOUCHSCREEN,
PHYS_TYPE_NAVIGATION_TOUCHPAD,
+ PHYS_TYPE_STYLUS,
})
@Retention(RetentionPolicy.SOURCE)
@interface PhysType {
@@ -188,6 +192,16 @@
}
}
+ void createStylus(@NonNull String deviceName, int vendorId, int productId,
+ @NonNull IBinder deviceToken, int displayId, int height, int width)
+ throws DeviceCreationException {
+ final String phys = createPhys(PHYS_TYPE_STYLUS);
+ createDeviceInternal(InputDeviceDescriptor.TYPE_STYLUS, deviceName, vendorId,
+ productId, deviceToken, displayId, phys,
+ () -> mNativeWrapper.openUinputStylus(deviceName, vendorId, productId, phys,
+ height, width));
+ }
+
void unregisterInputDevice(@NonNull IBinder token) {
synchronized (mLock) {
final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.remove(
@@ -410,6 +424,32 @@
}
}
+ boolean sendStylusMotionEvent(@NonNull IBinder token, @NonNull VirtualStylusMotionEvent event) {
+ synchronized (mLock) {
+ final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(
+ token);
+ if (inputDeviceDescriptor == null) {
+ return false;
+ }
+ return mNativeWrapper.writeStylusMotionEvent(inputDeviceDescriptor.getNativePointer(),
+ event.getToolType(), event.getAction(), event.getX(), event.getY(),
+ event.getPressure(), event.getTiltX(), event.getTiltY(),
+ event.getEventTimeNanos());
+ }
+ }
+
+ boolean sendStylusButtonEvent(@NonNull IBinder token, @NonNull VirtualStylusButtonEvent event) {
+ synchronized (mLock) {
+ final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(
+ token);
+ if (inputDeviceDescriptor == null) {
+ return false;
+ }
+ return mNativeWrapper.writeStylusButtonEvent(inputDeviceDescriptor.getNativePointer(),
+ event.getButtonCode(), event.getAction(), event.getEventTimeNanos());
+ }
+ }
+
public void dump(@NonNull PrintWriter fout) {
fout.println(" InputController: ");
synchronized (mLock) {
@@ -437,7 +477,7 @@
}
}
- @VisibleForTesting
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
Map<IBinder, InputDeviceDescriptor> getInputDeviceDescriptors() {
final Map<IBinder, InputDeviceDescriptor> inputDeviceDescriptors = new ArrayMap<>();
synchronized (mLock) {
@@ -454,6 +494,8 @@
String phys);
private static native long nativeOpenUinputTouchscreen(String deviceName, int vendorId,
int productId, String phys, int height, int width);
+ private static native long nativeOpenUinputStylus(String deviceName, int vendorId,
+ int productId, String phys, int height, int width);
private static native void nativeCloseUinput(long ptr);
private static native boolean nativeWriteDpadKeyEvent(long ptr, int androidKeyCode, int action,
long eventTimeNanos);
@@ -468,6 +510,10 @@
float relativeY, long eventTimeNanos);
private static native boolean nativeWriteScrollEvent(long ptr, float xAxisMovement,
float yAxisMovement, long eventTimeNanos);
+ private static native boolean nativeWriteStylusMotionEvent(long ptr, int toolType, int action,
+ int locationX, int locationY, int pressure, int tiltX, int tiltY, long eventTimeNanos);
+ private static native boolean nativeWriteStylusButtonEvent(long ptr, int buttonCode, int action,
+ long eventTimeNanos);
/** Wrapper around the static native methods for tests. */
@VisibleForTesting
@@ -491,6 +537,11 @@
width);
}
+ public long openUinputStylus(String deviceName, int vendorId, int productId, String phys,
+ int height, int width) {
+ return nativeOpenUinputStylus(deviceName, vendorId, productId, phys, height, width);
+ }
+
public void closeUinput(long ptr) {
nativeCloseUinput(ptr);
}
@@ -527,21 +578,35 @@
long eventTimeNanos) {
return nativeWriteScrollEvent(ptr, xAxisMovement, yAxisMovement, eventTimeNanos);
}
+
+ public boolean writeStylusMotionEvent(long ptr, int toolType, int action, int locationX,
+ int locationY, int pressure, int tiltX, int tiltY, long eventTimeNanos) {
+ return nativeWriteStylusMotionEvent(ptr, toolType, action, locationX, locationY,
+ pressure, tiltX, tiltY, eventTimeNanos);
+ }
+
+ public boolean writeStylusButtonEvent(long ptr, int buttonCode, int action,
+ long eventTimeNanos) {
+ return nativeWriteStylusButtonEvent(ptr, buttonCode, action, eventTimeNanos);
+ }
}
- @VisibleForTesting static final class InputDeviceDescriptor {
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ static final class InputDeviceDescriptor {
static final int TYPE_KEYBOARD = 1;
static final int TYPE_MOUSE = 2;
static final int TYPE_TOUCHSCREEN = 3;
static final int TYPE_DPAD = 4;
static final int TYPE_NAVIGATION_TOUCHPAD = 5;
+ static final int TYPE_STYLUS = 6;
@IntDef(prefix = { "TYPE_" }, value = {
TYPE_KEYBOARD,
TYPE_MOUSE,
TYPE_TOUCHSCREEN,
TYPE_DPAD,
TYPE_NAVIGATION_TOUCHPAD,
+ TYPE_STYLUS,
})
@Retention(RetentionPolicy.SOURCE)
@interface Type {
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 44c3a8d..f13f49a 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -23,6 +23,7 @@
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
import static android.companion.virtual.VirtualDeviceParams.NAVIGATION_POLICY_DEFAULT_ALLOWED;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_ACTIVITY;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CLIPBOARD;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS;
import static android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS;
@@ -75,6 +76,9 @@
import android.hardware.input.VirtualMouseRelativeEvent;
import android.hardware.input.VirtualMouseScrollEvent;
import android.hardware.input.VirtualNavigationTouchpadConfig;
+import android.hardware.input.VirtualStylusButtonEvent;
+import android.hardware.input.VirtualStylusConfig;
+import android.hardware.input.VirtualStylusMotionEvent;
import android.hardware.input.VirtualTouchEvent;
import android.hardware.input.VirtualTouchscreenConfig;
import android.os.Binder;
@@ -258,7 +262,9 @@
runningAppsChangedCallback,
params,
DisplayManagerGlobal.getInstance(),
- Flags.virtualCamera() ? new VirtualCameraController() : null);
+ Flags.virtualCamera()
+ ? new VirtualCameraController(params.getDevicePolicy(POLICY_TYPE_CAMERA))
+ : null);
}
@VisibleForTesting
@@ -776,6 +782,26 @@
@Override // Binder call
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void createVirtualStylus(@NonNull VirtualStylusConfig config,
+ @NonNull IBinder deviceToken) {
+ super.createVirtualStylus_enforcePermission();
+ Objects.requireNonNull(config);
+ Objects.requireNonNull(deviceToken);
+ checkVirtualInputDeviceDisplayIdAssociation(config.getAssociatedDisplayId());
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mInputController.createStylus(config.getInputDeviceName(), config.getVendorId(),
+ config.getProductId(), deviceToken, config.getAssociatedDisplayId(),
+ config.getHeight(), config.getWidth());
+ } catch (InputController.DeviceCreationException e) {
+ throw new IllegalArgumentException(e);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
+ @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void unregisterInputDevice(IBinder token) {
super.unregisterInputDevice_enforcePermission();
final long ident = Binder.clearCallingIdentity();
@@ -881,6 +907,36 @@
@Override // Binder call
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public boolean sendStylusMotionEvent(@NonNull IBinder token,
+ @NonNull VirtualStylusMotionEvent event) {
+ super.sendStylusMotionEvent_enforcePermission();
+ Objects.requireNonNull(token);
+ Objects.requireNonNull(event);
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return mInputController.sendStylusMotionEvent(token, event);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
+ @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public boolean sendStylusButtonEvent(@NonNull IBinder token,
+ @NonNull VirtualStylusButtonEvent event) {
+ super.sendStylusButtonEvent_enforcePermission();
+ Objects.requireNonNull(token);
+ Objects.requireNonNull(event);
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return mInputController.sendStylusButtonEvent(token, event);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
+ @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void setShowPointerIcon(boolean showPointerIcon) {
super.setShowPointerIcon_enforcePermission();
final long ident = Binder.clearCallingIdentity();
@@ -1337,6 +1393,11 @@
}
}
+ boolean isInputDeviceOwnedByVirtualDevice(int inputDeviceId) {
+ return mInputController.getInputDeviceDescriptors().values().stream().anyMatch(
+ inputDeviceDescriptor -> inputDeviceDescriptor.getInputDeviceId() == inputDeviceId);
+ }
+
void onEnteringPipBlocked(int uid) {
// Do nothing. ActivityRecord#checkEnterPictureInPictureState logs that the display does not
// support PiP.
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 0d5cdcb..b6e1140 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -56,6 +56,7 @@
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.ExceptionUtils;
import android.util.Slog;
@@ -112,7 +113,7 @@
Context.DEVICE_ID_DEFAULT + 1);
@GuardedBy("mVirtualDeviceManagerLock")
- private List<AssociationInfo> mActiveAssociations = new ArrayList<>();
+ private ArrayMap<String, AssociationInfo> mActiveAssociations = new ArrayMap<>();
private final CompanionDeviceManager.OnAssociationsChangedListener mCdmAssociationListener =
new CompanionDeviceManager.OnAssociationsChangedListener() {
@@ -343,34 +344,29 @@
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
void onCdmAssociationsChanged(List<AssociationInfo> associations) {
- List<AssociationInfo> vdmAssociations = new ArrayList<>();
- Set<Integer> activeAssociationIds = new HashSet<>();
+ ArrayMap<String, AssociationInfo> vdmAssociations = new ArrayMap<>();
for (int i = 0; i < associations.size(); ++i) {
AssociationInfo association = associations.get(i);
- if (VIRTUAL_DEVICE_COMPANION_DEVICE_PROFILES.contains(association.getDeviceProfile())) {
- vdmAssociations.add(association);
- activeAssociationIds.add(association.getId());
+ if (VIRTUAL_DEVICE_COMPANION_DEVICE_PROFILES.contains(association.getDeviceProfile())
+ && !association.isRevoked()) {
+ String persistentId =
+ VirtualDeviceImpl.createPersistentDeviceId(association.getId());
+ vdmAssociations.put(persistentId, association);
}
}
Set<VirtualDeviceImpl> virtualDevicesToRemove = new HashSet<>();
- Set<String> removedPersistentDeviceIds = new HashSet<>();
+ Set<String> removedPersistentDeviceIds;
synchronized (mVirtualDeviceManagerLock) {
- for (int i = 0; i < mActiveAssociations.size(); ++i) {
- AssociationInfo associationInfo = mActiveAssociations.get(i);
- if (!activeAssociationIds.contains(associationInfo.getId())) {
- removedPersistentDeviceIds.add(
- VirtualDeviceImpl.createPersistentDeviceId(associationInfo.getId()));
- }
- }
+ removedPersistentDeviceIds = mActiveAssociations.keySet();
+ removedPersistentDeviceIds.removeAll(vdmAssociations.keySet());
+ mActiveAssociations = vdmAssociations;
for (int i = 0; i < mVirtualDevices.size(); i++) {
VirtualDeviceImpl virtualDevice = mVirtualDevices.valueAt(i);
- if (!activeAssociationIds.contains(virtualDevice.getAssociationId())) {
+ if (removedPersistentDeviceIds.contains(virtualDevice.getPersistentDeviceId())) {
virtualDevicesToRemove.add(virtualDevice);
}
}
-
- mActiveAssociations = vdmAssociations;
}
for (VirtualDeviceImpl virtualDevice : virtualDevicesToRemove) {
@@ -577,6 +573,16 @@
return Context.DEVICE_ID_DEFAULT;
}
+ @Override // Binder call
+ public @Nullable CharSequence getDisplayNameForPersistentDeviceId(
+ @NonNull String persistentDeviceId) {
+ final AssociationInfo associationInfo;
+ synchronized (mVirtualDeviceManagerLock) {
+ associationInfo = mActiveAssociations.get(persistentDeviceId);
+ }
+ return associationInfo == null ? null : associationInfo.getDisplayName();
+ }
+
// Binder call
@Override
public boolean isValidVirtualDeviceId(int deviceId) {
@@ -838,10 +844,11 @@
}
@Override
- public boolean isDisplayOwnedByAnyVirtualDevice(int displayId) {
+ public boolean isInputDeviceOwnedByVirtualDevice(int inputDeviceId) {
ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot();
for (int i = 0; i < virtualDevicesSnapshot.size(); i++) {
- if (virtualDevicesSnapshot.get(i).isDisplayOwnedByVirtualDevice(displayId)) {
+ if (virtualDevicesSnapshot.get(i)
+ .isInputDeviceOwnedByVirtualDevice(inputDeviceId)) {
return true;
}
}
@@ -884,15 +891,9 @@
@Override
public @NonNull Set<String> getAllPersistentDeviceIds() {
- Set<String> persistentIds = new ArraySet<>();
synchronized (mVirtualDeviceManagerLock) {
- for (int i = 0; i < mActiveAssociations.size(); ++i) {
- AssociationInfo associationInfo = mActiveAssociations.get(i);
- persistentIds.add(
- VirtualDeviceImpl.createPersistentDeviceId(associationInfo.getId()));
- }
+ return Set.copyOf(mActiveAssociations.keySet());
}
- return persistentIds;
}
@Override
diff --git a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
index 2f9b6a5..2d82b5e 100644
--- a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
+++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
@@ -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.
@@ -16,10 +16,13 @@
package com.android.server.companion.virtual.camera;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+
import static com.android.server.companion.virtual.camera.VirtualCameraConversionUtil.getServiceCameraConfiguration;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.companion.virtual.VirtualDeviceParams.DevicePolicy;
import android.companion.virtual.camera.VirtualCameraConfig;
import android.companion.virtualcamera.IVirtualCameraService;
import android.companion.virtualcamera.VirtualCameraConfiguration;
@@ -51,15 +54,21 @@
@GuardedBy("mServiceLock")
@Nullable private IVirtualCameraService mVirtualCameraService;
+ @DevicePolicy
+ private final int mCameraPolicy;
@GuardedBy("mCameras")
private final Map<IBinder, CameraDescriptor> mCameras = new ArrayMap<>();
- public VirtualCameraController() {}
+ public VirtualCameraController(@DevicePolicy int cameraPolicy) {
+ this(/* virtualCameraService= */ null, cameraPolicy);
+ }
@VisibleForTesting
- VirtualCameraController(IVirtualCameraService virtualCameraService) {
+ VirtualCameraController(IVirtualCameraService virtualCameraService,
+ @DevicePolicy int cameraPolicy) {
mVirtualCameraService = virtualCameraService;
+ mCameraPolicy = cameraPolicy;
}
/**
@@ -68,6 +77,8 @@
* @param cameraConfig The {@link VirtualCameraConfig} sent by the client.
*/
public void registerCamera(@NonNull VirtualCameraConfig cameraConfig) {
+ checkConfigByPolicy(cameraConfig);
+
connectVirtualCameraServiceIfNeeded();
try {
@@ -173,6 +184,29 @@
}
}
+ private void checkConfigByPolicy(VirtualCameraConfig config) {
+ if (mCameraPolicy == DEVICE_POLICY_DEFAULT) {
+ throw new IllegalArgumentException(
+ "Cannot create virtual camera with DEVICE_POLICY_DEFAULT for "
+ + "POLICY_TYPE_CAMERA");
+ } else if (isLensFacingAlreadyPresent(config.getLensFacing())) {
+ throw new IllegalArgumentException(
+ "Only a single virtual camera can be created with lens facing "
+ + config.getLensFacing());
+ }
+ }
+
+ private boolean isLensFacingAlreadyPresent(int lensFacing) {
+ synchronized (mCameras) {
+ for (CameraDescriptor cameraDescriptor : mCameras.values()) {
+ if (cameraDescriptor.mConfig.getLensFacing() == lensFacing) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
private void connectVirtualCameraServiceIfNeeded() {
synchronized (mServiceLock) {
// Try to connect to service if not connected already.
diff --git a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java
index f24c4cc..c4a84b0 100644
--- a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java
+++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java
@@ -25,6 +25,7 @@
import android.companion.virtualcamera.SupportedStreamConfiguration;
import android.companion.virtualcamera.VirtualCameraConfiguration;
import android.graphics.ImageFormat;
+import android.graphics.PixelFormat;
import android.os.RemoteException;
import android.view.Surface;
@@ -45,12 +46,12 @@
getServiceCameraConfiguration(@NonNull VirtualCameraConfig cameraConfig)
throws RemoteException {
VirtualCameraConfiguration serviceConfiguration = new VirtualCameraConfiguration();
-
serviceConfiguration.supportedStreamConfigs =
cameraConfig.getStreamConfigs().stream()
.map(VirtualCameraConversionUtil::convertSupportedStreamConfiguration)
.toArray(SupportedStreamConfiguration[]::new);
-
+ serviceConfiguration.sensorOrientation = cameraConfig.getSensorOrientation();
+ serviceConfiguration.lensFacing = cameraConfig.getLensFacing();
serviceConfiguration.virtualCameraCallback = convertCallback(cameraConfig.getCallback());
return serviceConfiguration;
}
@@ -60,12 +61,10 @@
@NonNull IVirtualCameraCallback camera) {
return new android.companion.virtualcamera.IVirtualCameraCallback.Stub() {
@Override
- public void onStreamConfigured(
- int streamId, Surface surface, int width, int height, int pixelFormat)
- throws RemoteException {
- VirtualCameraStreamConfig streamConfig =
- createStreamConfig(width, height, pixelFormat);
- camera.onStreamConfigured(streamId, surface, streamConfig);
+ public void onStreamConfigured(int streamId, Surface surface, int width, int height,
+ int format) throws RemoteException {
+ camera.onStreamConfigured(streamId, surface, width, height,
+ convertToJavaFormat(format));
}
@Override
@@ -81,23 +80,30 @@
}
@NonNull
- private static VirtualCameraStreamConfig createStreamConfig(
- int width, int height, int pixelFormat) {
- return new VirtualCameraStreamConfig(width, height, pixelFormat);
- }
-
- @NonNull
private static SupportedStreamConfiguration convertSupportedStreamConfiguration(
VirtualCameraStreamConfig stream) {
SupportedStreamConfiguration supportedConfig = new SupportedStreamConfiguration();
supportedConfig.height = stream.getHeight();
supportedConfig.width = stream.getWidth();
- supportedConfig.pixelFormat = convertFormat(stream.getFormat());
+ supportedConfig.pixelFormat = convertToHalFormat(stream.getFormat());
+ supportedConfig.maxFps = stream.getMaximumFramesPerSecond();
return supportedConfig;
}
- private static int convertFormat(int format) {
- return format == ImageFormat.YUV_420_888 ? Format.YUV_420_888 : Format.UNKNOWN;
+ private static int convertToHalFormat(int javaFormat) {
+ return switch (javaFormat) {
+ case ImageFormat.YUV_420_888 -> Format.YUV_420_888;
+ case PixelFormat.RGBA_8888 -> Format.RGBA_8888;
+ default -> Format.UNKNOWN;
+ };
+ }
+
+ private static int convertToJavaFormat(int halFormat) {
+ return switch (halFormat) {
+ case Format.YUV_420_888 -> ImageFormat.YUV_420_888;
+ case Format.RGBA_8888 -> PixelFormat.RGBA_8888;
+ default -> ImageFormat.UNKNOWN;
+ };
}
private VirtualCameraConversionUtil() {
diff --git a/services/companion/lint-baseline.xml b/services/companion/lint-baseline.xml
index 03eae39..020126f 100644
--- a/services/companion/lint-baseline.xml
+++ b/services/companion/lint-baseline.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 7.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="7.1.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
<issue
id="NonUserGetterCalled"
@@ -12,4 +12,4 @@
column="14"/>
</issue>
-</issues>
+</issues>
\ No newline at end of file
diff --git a/services/core/Android.bp b/services/core/Android.bp
index a3fc3bf..fdcd27d 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -30,6 +30,14 @@
],
}
+java_library_static {
+ name: "services-config-update",
+ srcs: [
+ "java/**/ConfigUpdateInstallReceiver.java",
+ "java/**/*.logtags",
+ ],
+}
+
genrule {
name: "services.core.protologsrc",
srcs: [
@@ -234,9 +242,6 @@
java_library {
name: "services.core",
static_libs: ["services.core.priorityboosted"],
- lint: {
- baseline_filename: "lint-baseline.xml",
- },
}
java_library_host {
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 5a44ac8..9d9e7c9 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -1387,6 +1387,8 @@
case BatteryManager.BATTERY_PROPERTY_MANUFACTURING_DATE:
case BatteryManager.BATTERY_PROPERTY_FIRST_USAGE_DATE:
case BatteryManager.BATTERY_PROPERTY_CHARGING_POLICY:
+ case BatteryManager.BATTERY_PROPERTY_SERIAL_NUMBER:
+ case BatteryManager.BATTERY_PROPERTY_PART_STATUS:
mContext.enforceCallingPermission(
android.Manifest.permission.BATTERY_STATS, null);
break;
diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java
index 329aac6..9f279b1 100644
--- a/services/core/java/com/android/server/BootReceiver.java
+++ b/services/core/java/com/android/server/BootReceiver.java
@@ -48,8 +48,6 @@
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.am.DropboxRateLimiter;
-import com.android.server.os.TombstoneProtos;
-import com.android.server.os.TombstoneProtos.Tombstone;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -62,14 +60,11 @@
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.attribute.PosixFilePermissions;
-import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Iterator;
-import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import java.util.stream.Collectors;
/**
* Performs a number of miscellaneous, non-system-critical actions
@@ -337,12 +332,12 @@
*
* @param ctx Context
* @param tombstone path to the tombstone
- * @param tombstoneProto the parsed proto tombstone
+ * @param proto whether the tombstone is stored as proto
* @param processName the name of the process corresponding to the tombstone
* @param tmpFileLock the lock for reading/writing tmp files
*/
public static void addTombstoneToDropBox(
- Context ctx, File tombstone, Tombstone tombstoneProto, String processName,
+ Context ctx, File tombstone, boolean proto, String processName,
ReentrantLock tmpFileLock) {
final DropBoxManager db = ctx.getSystemService(DropBoxManager.class);
if (db == null) {
@@ -352,33 +347,31 @@
// Check if we should rate limit and abort early if needed.
DropboxRateLimiter.RateLimitResult rateLimitResult =
- sDropboxRateLimiter.shouldRateLimit(TAG_TOMBSTONE_PROTO_WITH_HEADERS, processName);
+ sDropboxRateLimiter.shouldRateLimit(
+ proto ? TAG_TOMBSTONE_PROTO_WITH_HEADERS : TAG_TOMBSTONE, processName);
if (rateLimitResult.shouldRateLimit()) return;
HashMap<String, Long> timestamps = readTimestamps();
try {
- // Remove the memory data from the proto.
- Tombstone tombstoneProtoWithoutMemory = removeMemoryFromTombstone(tombstoneProto);
-
- final byte[] tombstoneBytes = tombstoneProtoWithoutMemory.toByteArray();
-
- // Use JNI to call the c++ proto to text converter and add the headers to the tombstone.
- String tombstoneWithoutMemory = new StringBuilder(getBootHeadersToLogAndUpdate())
- .append(rateLimitResult.createHeader())
- .append(getTombstoneText(tombstoneBytes))
- .toString();
-
- // Add the tombstone without memory data to dropbox.
- db.addText(TAG_TOMBSTONE, tombstoneWithoutMemory);
-
- // Add the tombstone proto to dropbox.
- if (recordFileTimestamp(tombstone, timestamps)) {
- tmpFileLock.lock();
- try {
- addAugmentedProtoToDropbox(tombstone, tombstoneBytes, db, rateLimitResult);
- } finally {
- tmpFileLock.unlock();
+ if (proto) {
+ if (recordFileTimestamp(tombstone, timestamps)) {
+ // We need to attach the count indicating the number of dropped dropbox entries
+ // due to rate limiting. Do this by enclosing the proto tombsstone in a
+ // container proto that has the dropped entry count and the proto tombstone as
+ // bytes (to avoid the complexity of reading and writing nested protos).
+ tmpFileLock.lock();
+ try {
+ addAugmentedProtoToDropbox(tombstone, db, rateLimitResult);
+ } finally {
+ tmpFileLock.unlock();
+ }
}
+ } else {
+ // Add the header indicating how many events have been dropped due to rate limiting.
+ final String headers = getBootHeadersToLogAndUpdate()
+ + rateLimitResult.createHeader();
+ addFileToDropBox(db, timestamps, headers, tombstone.getPath(), LOG_SIZE,
+ TAG_TOMBSTONE);
}
} catch (IOException e) {
Slog.e(TAG, "Can't log tombstone", e);
@@ -387,8 +380,11 @@
}
private static void addAugmentedProtoToDropbox(
- File tombstone, byte[] tombstoneBytes, DropBoxManager db,
+ File tombstone, DropBoxManager db,
DropboxRateLimiter.RateLimitResult rateLimitResult) throws IOException {
+ // Read the proto tombstone file as bytes.
+ final byte[] tombstoneBytes = Files.readAllBytes(tombstone.toPath());
+
final File tombstoneProtoWithHeaders = File.createTempFile(
tombstone.getName(), ".tmp", TOMBSTONE_TMP_DIR);
Files.setPosixFilePermissions(
@@ -421,8 +417,6 @@
}
}
- private static native String getTombstoneText(byte[] tombstoneBytes);
-
private static void addLastkToDropBox(
DropBoxManager db, HashMap<String, Long> timestamps,
String headers, String footers, String filename, int maxSize,
@@ -440,31 +434,6 @@
addFileWithFootersToDropBox(db, timestamps, headers, footers, filename, maxSize, tag);
}
- /** Removes memory information from the Tombstone proto. */
- @VisibleForTesting
- public static Tombstone removeMemoryFromTombstone(Tombstone tombstoneProto) {
- Tombstone.Builder tombstoneBuilder = tombstoneProto.toBuilder()
- .clearMemoryMappings()
- .clearThreads()
- .putAllThreads(tombstoneProto.getThreadsMap().entrySet()
- .stream()
- .map(BootReceiver::clearMemoryDump)
- .collect(Collectors.toMap(e->e.getKey(), e->e.getValue())));
-
- if (tombstoneProto.hasSignalInfo()) {
- tombstoneBuilder.setSignalInfo(
- tombstoneProto.getSignalInfo().toBuilder().clearFaultAdjacentMetadata());
- }
-
- return tombstoneBuilder.build();
- }
-
- private static AbstractMap.SimpleEntry<Integer, TombstoneProtos.Thread> clearMemoryDump(
- Map.Entry<Integer, TombstoneProtos.Thread> e) {
- return new AbstractMap.SimpleEntry<Integer, TombstoneProtos.Thread>(
- e.getKey(), e.getValue().toBuilder().clearMemoryDump().build());
- }
-
private static void addFileToDropBox(
DropBoxManager db, HashMap<String, Long> timestamps,
String headers, String filename, int maxSize, String tag) throws IOException {
diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
index 70bd4b3..c391642 100644
--- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
+++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
@@ -34,11 +34,11 @@
import android.util.ArraySet;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.wm.SensitiveContentPackages.PackageInfo;
import com.android.server.wm.WindowManagerInternal;
-import java.util.Collections;
import java.util.Set;
/**
@@ -54,6 +54,10 @@
private @Nullable MediaProjectionManager mProjectionManager;
private @Nullable WindowManagerInternal mWindowManager;
+ final Object mSensitiveContentProtectionLock = new Object();
+ @GuardedBy("mSensitiveContentProtectionLock")
+ private boolean mProjectionActive = false;
+
private final MediaProjectionManager.Callback mProjectionCallback =
new MediaProjectionManager.Callback() {
@Override
@@ -132,14 +136,23 @@
}
private void onProjectionStart() {
- StatusBarNotification[] notifications;
- try {
- notifications = mNotificationListener.getActiveNotifications();
- } catch (SecurityException e) {
- Log.e(TAG, "SensitiveContentProtectionManagerService doesn't have access.", e);
- notifications = new StatusBarNotification[0];
+ synchronized (mSensitiveContentProtectionLock) {
+ mProjectionActive = true;
+ updateAppsThatShouldBlockScreenCapture();
}
+ }
+ private void onProjectionEnd() {
+ synchronized (mSensitiveContentProtectionLock) {
+ mProjectionActive = false;
+
+ // notify windowmanager to clear any sensitive notifications observed during projection
+ // session
+ mWindowManager.clearBlockedApps();
+ }
+ }
+
+ private void updateAppsThatShouldBlockScreenCapture() {
RankingMap rankingMap;
try {
rankingMap = mNotificationListener.getCurrentRanking();
@@ -148,41 +161,98 @@
rankingMap = null;
}
- // notify windowmanager of any currently posted sensitive content notifications
- Set<PackageInfo> packageInfos = getSensitivePackagesFromNotifications(
- notifications,
- rankingMap);
-
- mWindowManager.setShouldBlockScreenCaptureForApp(packageInfos);
+ updateAppsThatShouldBlockScreenCapture(rankingMap);
}
- private void onProjectionEnd() {
- // notify windowmanager to clear any sensitive notifications observed during projection
- // session
- mWindowManager.setShouldBlockScreenCaptureForApp(Collections.emptySet());
- }
-
- private Set<PackageInfo> getSensitivePackagesFromNotifications(
- StatusBarNotification[] notifications, RankingMap rankingMap) {
- if (rankingMap == null) {
- Log.w(TAG, "Ranking map not initialized.");
- return Collections.emptySet();
+ private void updateAppsThatShouldBlockScreenCapture(RankingMap rankingMap) {
+ StatusBarNotification[] notifications;
+ try {
+ notifications = mNotificationListener.getActiveNotifications();
+ } catch (SecurityException e) {
+ Log.e(TAG, "SensitiveContentProtectionManagerService doesn't have access.", e);
+ notifications = new StatusBarNotification[0];
}
- Set<PackageInfo> sensitivePackages = new ArraySet<>();
+ // notify windowmanager of any currently posted sensitive content notifications
+ ArraySet<PackageInfo> packageInfos = getSensitivePackagesFromNotifications(
+ notifications, rankingMap);
+
+ mWindowManager.addBlockScreenCaptureForApps(packageInfos);
+ }
+
+ private ArraySet<PackageInfo> getSensitivePackagesFromNotifications(
+ @NonNull StatusBarNotification[] notifications, RankingMap rankingMap) {
+ ArraySet<PackageInfo> sensitivePackages = new ArraySet<>();
+ if (rankingMap == null) {
+ Log.w(TAG, "Ranking map not initialized.");
+ return sensitivePackages;
+ }
+
for (StatusBarNotification sbn : notifications) {
- NotificationListenerService.Ranking ranking =
- rankingMap.getRawRankingObject(sbn.getKey());
- if (ranking != null && ranking.hasSensitiveContent()) {
- PackageInfo info = new PackageInfo(sbn.getPackageName(), sbn.getUid());
+ PackageInfo info = getSensitivePackageFromNotification(sbn, rankingMap);
+ if (info != null) {
sensitivePackages.add(info);
}
}
return sensitivePackages;
}
- // TODO(b/317251408): add trigger that updates on onNotificationPosted,
- // onNotificationRankingUpdate and onListenerConnected
+ private PackageInfo getSensitivePackageFromNotification(StatusBarNotification sbn,
+ RankingMap rankingMap) {
+ if (sbn == null) {
+ Log.w(TAG, "Unable to protect null notification");
+ return null;
+ }
+ if (rankingMap == null) {
+ Log.w(TAG, "Ranking map not initialized.");
+ return null;
+ }
+
+ NotificationListenerService.Ranking ranking = rankingMap.getRawRankingObject(sbn.getKey());
+ if (ranking != null && ranking.hasSensitiveContent()) {
+ return new PackageInfo(sbn.getPackageName(), sbn.getUid());
+ }
+ return null;
+ }
+
@VisibleForTesting
- static class NotificationListener extends NotificationListenerService {}
+ class NotificationListener extends NotificationListenerService {
+ @Override
+ public void onListenerConnected() {
+ super.onListenerConnected();
+ // Projection started before notification listener was connected
+ synchronized (mSensitiveContentProtectionLock) {
+ if (mProjectionActive) {
+ updateAppsThatShouldBlockScreenCapture();
+ }
+ }
+ }
+
+ @Override
+ public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
+ super.onNotificationPosted(sbn, rankingMap);
+ synchronized (mSensitiveContentProtectionLock) {
+ if (!mProjectionActive) {
+ return;
+ }
+
+ // notify windowmanager of any currently posted sensitive content notifications
+ PackageInfo packageInfo = getSensitivePackageFromNotification(sbn, rankingMap);
+
+ if (packageInfo != null) {
+ mWindowManager.addBlockScreenCaptureForApps(new ArraySet(Set.of(packageInfo)));
+ }
+ }
+ }
+
+ @Override
+ public void onNotificationRankingUpdate(RankingMap rankingMap) {
+ super.onNotificationRankingUpdate(rankingMap);
+ synchronized (mSensitiveContentProtectionLock) {
+ if (mProjectionActive) {
+ updateAppsThatShouldBlockScreenCapture(rankingMap);
+ }
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 7a4ac6a..ea1b0f5 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1076,6 +1076,7 @@
final UserManager userManager = mContext.getSystemService(UserManager.class);
final List<UserInfo> users = userManager.getUsers();
+ extendWatchdogTimeout("#onReset might be slow");
mStorageSessionController.onReset(mVold, () -> {
mHandler.removeCallbacksAndMessages(null);
});
@@ -5040,9 +5041,9 @@
@Override
public IFsveritySetupAuthToken createFsveritySetupAuthToken(ParcelFileDescriptor authFd,
- int appUid, @UserIdInt int userId) throws IOException {
+ int uid) throws IOException {
try {
- return mInstaller.createFsveritySetupAuthToken(authFd, appUid, userId);
+ return mInstaller.createFsveritySetupAuthToken(authFd, uid);
} catch (Installer.InstallerException e) {
throw new IOException(e);
}
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 9eb35fd..bd67cf420 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -101,6 +101,7 @@
import com.android.internal.telephony.IPhoneStateListener;
import com.android.internal.telephony.ITelephonyRegistry;
import com.android.internal.telephony.TelephonyPermissions;
+import com.android.internal.telephony.flags.Flags;
import com.android.internal.telephony.util.TelephonyUtils;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
@@ -417,6 +418,8 @@
LinkCapacityEstimate.INVALID, LinkCapacityEstimate.INVALID)));
private List<List<LinkCapacityEstimate>> mLinkCapacityEstimateLists;
+ private int[] mSimultaneousCellularCallingSubIds = {};
+
private int[] mECBMReason;
private boolean[] mECBMStarted;
private int[] mSCBMReason;
@@ -563,7 +566,9 @@
|| events.contains(TelephonyCallback.EVENT_VOICE_ACTIVATION_STATE_CHANGED)
|| events.contains(TelephonyCallback.EVENT_RADIO_POWER_STATE_CHANGED)
|| events.contains(TelephonyCallback.EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED)
- || events.contains(TelephonyCallback.EVENT_EMERGENCY_CALLBACK_MODE_CHANGED);
+ || events.contains(TelephonyCallback.EVENT_EMERGENCY_CALLBACK_MODE_CHANGED)
+ || events.contains(TelephonyCallback
+ .EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED);
}
private static final int MSG_USER_SWITCHED = 1;
@@ -1121,6 +1126,21 @@
return;
}
+ int phoneId = -1;
+ int subscriptionId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+ if(Flags.preventSystemServerAndPhoneDeadlock()) {
+ // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID,
+ // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ if (DBG) {
+ log("invalid subscription id, use default id");
+ }
+ } else { //APP specify subID
+ subscriptionId = subId;
+ }
+ phoneId = getPhoneIdFromSubId(subscriptionId);
+ }
+
synchronized (mRecords) {
// register
IBinder b = callback.asBinder();
@@ -1140,17 +1160,23 @@
r.renounceFineLocationAccess = renounceFineLocationAccess;
r.callerUid = Binder.getCallingUid();
r.callerPid = Binder.getCallingPid();
- // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID,
- // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID
- if (!SubscriptionManager.isValidSubscriptionId(subId)) {
- if (DBG) {
- log("invalid subscription id, use default id");
+
+ if(!Flags.preventSystemServerAndPhoneDeadlock()) {
+ // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID,
+ // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ if (DBG) {
+ log("invalid subscription id, use default id");
+ }
+ r.subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+ } else {//APP specify subID
+ r.subId = subId;
}
- r.subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
- } else {//APP specify subID
- r.subId = subId;
+ r.phoneId = getPhoneIdFromSubId(r.subId);
+ } else {
+ r.subId = subscriptionId;
+ r.phoneId = phoneId;
}
- r.phoneId = getPhoneIdFromSubId(r.subId);
r.eventList = events;
if (DBG) {
@@ -1426,6 +1452,15 @@
remove(r.binder);
}
}
+ if (events.contains(TelephonyCallback
+ .EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED)) {
+ try {
+ r.callback.onSimultaneousCallingStateChanged(
+ mSimultaneousCellularCallingSubIds);
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
+ }
if (events.contains(
TelephonyCallback.EVENT_LINK_CAPACITY_ESTIMATE_CHANGED)) {
try {
@@ -1879,8 +1914,14 @@
}
private void notifyCarrierNetworkChangeWithPermission(int subId, boolean active) {
+ int phoneId = -1;
+ if(Flags.preventSystemServerAndPhoneDeadlock()) {
+ phoneId = getPhoneIdFromSubId(subId);
+ }
synchronized (mRecords) {
- int phoneId = getPhoneIdFromSubId(subId);
+ if(!Flags.preventSystemServerAndPhoneDeadlock()) {
+ phoneId = getPhoneIdFromSubId(subId);
+ }
mCarrierNetworkChangeState[phoneId] = active;
if (VDBG) {
@@ -2679,6 +2720,14 @@
if (!checkNotifyPermission("notifyEmergencyNumberList()")) {
return;
}
+ if (Flags.enforceTelephonyFeatureMappingForPublicApis()) {
+ if (!mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_CALLING)) {
+ // TelephonyManager.getEmergencyNumberList() throws an exception if
+ // FEATURE_TELEPHONY_CALLING is not defined.
+ return;
+ }
+ }
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
@@ -3083,6 +3132,43 @@
}
}
+ /**
+ * Notify the listeners that simultaneous cellular calling subscriptions have changed
+ * @param subIds The set of subIds that support simultaneous cellular calling
+ */
+ public void notifySimultaneousCellularCallingSubscriptionsChanged(int[] subIds) {
+ if (!checkNotifyPermission("notifySimultaneousCellularCallingSubscriptionsChanged()")) {
+ return;
+ }
+
+ if (VDBG) {
+ StringBuilder b = new StringBuilder();
+ b.append("notifySimultaneousCellularCallingSubscriptionsChanged: ");
+ b.append("subIds = {");
+ for (int i : subIds) {
+ b.append(" ");
+ b.append(i);
+ }
+ b.append("}");
+ log(b.toString());
+ }
+
+ synchronized (mRecords) {
+ mSimultaneousCellularCallingSubIds = subIds;
+ for (Record r : mRecords) {
+ if (r.matchTelephonyCallbackEvent(TelephonyCallback
+ .EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED)) {
+ try {
+ r.callback.onSimultaneousCallingStateChanged(subIds);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
+ }
+ }
+ handleRemoveListLocked();
+ }
+ }
+
@Override
public void addCarrierPrivilegesCallback(
int phoneId,
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 66a10e4a..cd8be33 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -16,8 +16,10 @@
package com.android.server;
+import static android.app.Flags.modesApi;
import static android.app.UiModeManager.ContrastUtils.CONTRAST_DEFAULT_VALUE;
import static android.app.UiModeManager.DEFAULT_PRIORITY;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
import static android.app.UiModeManager.MODE_NIGHT_AUTO;
import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME;
@@ -50,6 +52,7 @@
import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.app.UiModeManager;
+import android.app.UiModeManager.AttentionModeThemeOverlayType;
import android.app.UiModeManager.NightModeCustomReturnType;
import android.app.UiModeManager.NightModeCustomType;
import android.content.BroadcastReceiver;
@@ -134,6 +137,7 @@
private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
private int mNightMode = UiModeManager.MODE_NIGHT_NO;
private int mNightModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
+ private int mAttentionModeThemeOverlay = UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
private final LocalTime DEFAULT_CUSTOM_NIGHT_START_TIME = LocalTime.of(22, 0);
private final LocalTime DEFAULT_CUSTOM_NIGHT_END_TIME = LocalTime.of(6, 0);
private LocalTime mCustomAutoNightModeStartMilliseconds = DEFAULT_CUSTOM_NIGHT_START_TIME;
@@ -839,6 +843,8 @@
? customModeType
: MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
mNightMode = mode;
+ //deactivates AttentionMode if user toggles DarkTheme
+ mAttentionModeThemeOverlay = MODE_ATTENTION_THEME_OVERLAY_OFF;
resetNightModeOverrideLocked();
persistNightMode(user);
// on screen off will update configuration instead
@@ -879,6 +885,29 @@
}
}
+ @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
+ @Override
+ public void setAttentionModeThemeOverlay(
+ @AttentionModeThemeOverlayType int attentionModeThemeOverlayType) {
+ setAttentionModeThemeOverlay_enforcePermission();
+
+ synchronized (mLock) {
+ if (mAttentionModeThemeOverlay != attentionModeThemeOverlayType) {
+ mAttentionModeThemeOverlay = attentionModeThemeOverlayType;
+ Binder.withCleanCallingIdentity(()-> updateLocked(0, 0));
+ }
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
+ @Override
+ public @AttentionModeThemeOverlayType int getAttentionModeThemeOverlay() {
+ getAttentionModeThemeOverlay_enforcePermission();
+ synchronized (mLock) {
+ return mAttentionModeThemeOverlay;
+ }
+ }
+
@Override
public void setApplicationNightMode(@UiModeManager.NightMode int mode) {
switch (mode) {
@@ -1406,7 +1435,7 @@
pw.print(Shell.nightModeToStr(mNightMode, mNightModeCustomType)); pw.print(") ");
pw.print(" mOverrideOn/Off="); pw.print(mOverrideNightModeOn);
pw.print("/"); pw.print(mOverrideNightModeOff);
-
+ pw.print(" mAttentionModeThemeOverlay="); pw.print(mAttentionModeThemeOverlay);
pw.print(" mNightModeLocked="); pw.println(mNightModeLocked);
pw.print(" mCarModeEnabled="); pw.print(mCarModeEnabled);
@@ -1685,7 +1714,7 @@
}
@UiModeManager.NightMode
- private int getComputedUiModeConfiguration(@UiModeManager.NightMode int uiMode) {
+ private int getComputedUiModeConfiguration(int uiMode) {
uiMode |= mComputedNightMode ? Configuration.UI_MODE_NIGHT_YES
: Configuration.UI_MODE_NIGHT_NO;
uiMode &= mComputedNightMode ? ~Configuration.UI_MODE_NIGHT_NO
@@ -1980,18 +2009,26 @@
}
private void updateComputedNightModeLocked(boolean activate) {
- mComputedNightMode = activate;
- if (mNightMode == MODE_NIGHT_YES || mNightMode == UiModeManager.MODE_NIGHT_NO) {
- return;
+ boolean newComputedValue = activate;
+ if (mNightMode != MODE_NIGHT_YES && mNightMode != UiModeManager.MODE_NIGHT_NO) {
+ if (mOverrideNightModeOn && !newComputedValue) {
+ newComputedValue = true;
+ } else if (mOverrideNightModeOff && newComputedValue) {
+ newComputedValue = false;
+ }
}
- if (mOverrideNightModeOn && !mComputedNightMode) {
- mComputedNightMode = true;
- return;
+
+ if (modesApi()) {
+ // Computes final night mode values based on Attention Mode.
+ mComputedNightMode = switch (mAttentionModeThemeOverlay) {
+ case (UiModeManager.MODE_ATTENTION_THEME_OVERLAY_NIGHT) -> true;
+ case (UiModeManager.MODE_ATTENTION_THEME_OVERLAY_DAY) -> false;
+ default -> newComputedValue; // case OFF
+ };
+ } else {
+ mComputedNightMode = newComputedValue;
}
- if (mOverrideNightModeOff && mComputedNightMode) {
- mComputedNightMode = false;
- return;
- }
+
if (mNightMode != MODE_NIGHT_AUTO || (mTwilightManager != null
&& mTwilightManager.getLastTwilightState() != null)) {
resetNightModeOverrideLocked();
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index fd17261..c18bacb 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -172,6 +172,7 @@
public static final String[] AIDL_INTERFACE_PREFIXES_OF_INTEREST = new String[] {
"android.hardware.audio.core.IModule/",
"android.hardware.audio.core.IConfig/",
+ "android.hardware.audio.effect.IFactory/",
"android.hardware.biometrics.face.IFace/",
"android.hardware.biometrics.fingerprint.IFingerprint/",
"android.hardware.bluetooth.IBluetoothHci/",
diff --git a/services/core/java/com/android/server/ZramWriteback.java b/services/core/java/com/android/server/ZramWriteback.java
index 5d97def..39ca29e 100644
--- a/services/core/java/com/android/server/ZramWriteback.java
+++ b/services/core/java/com/android/server/ZramWriteback.java
@@ -177,7 +177,6 @@
// back at later point if they remain untouched.
js.schedule(new JobInfo.Builder(MARK_IDLE_JOB_ID, sZramWriteback)
.setMinimumLatency(TimeUnit.MINUTES.toMillis(markIdleDelay))
- .setOverrideDeadline(TimeUnit.MINUTES.toMillis(markIdleDelay))
.build());
// Schedule a one time job to flush idle pages to disk.
diff --git a/services/core/java/com/android/server/adaptiveauth/OWNERS b/services/core/java/com/android/server/adaptiveauth/OWNERS
new file mode 100644
index 0000000..b188105
--- /dev/null
+++ b/services/core/java/com/android/server/adaptiveauth/OWNERS
@@ -0,0 +1 @@
[email protected]
\ No newline at end of file
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 7b14a02..9b1fade 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -94,6 +94,8 @@
import static android.os.Process.SHELL_UID;
import static android.os.Process.SYSTEM_UID;
import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
+import static android.content.flags.Flags.enableBindPackageIsolatedProcess;
+
import static com.android.internal.messages.nano.SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICE_BG_LAUNCH;
import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_DELEGATE;
@@ -791,13 +793,15 @@
static String getProcessNameForService(ServiceInfo sInfo, ComponentName name,
String callingPackage, String instanceName, boolean isSdkSandbox,
- boolean inSharedIsolatedProcess) {
+ boolean inSharedIsolatedProcess, boolean inPrivateSharedIsolatedProcess) {
if (isSdkSandbox) {
// For SDK sandbox, the process name is passed in as the instanceName
return instanceName;
}
- if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) == 0) {
- // For regular processes, just the name in sInfo
+ if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) == 0
+ || (inPrivateSharedIsolatedProcess && !isDefaultProcessService(sInfo))) {
+ // For regular processes, or private package-shared isolated processes, just the name
+ // in sInfo
return sInfo.processName;
}
// Isolated processes remain.
@@ -809,6 +813,10 @@
}
}
+ private static boolean isDefaultProcessService(ServiceInfo serviceInfo) {
+ return serviceInfo.applicationInfo.processName.equals(serviceInfo.processName);
+ }
+
private static void traceInstant(@NonNull String message, @NonNull ServiceRecord service) {
if (!Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
return;
@@ -864,7 +872,7 @@
ServiceLookupResult res = retrieveServiceLocked(service, instanceName, isSdkSandboxService,
sdkSandboxClientAppUid, sdkSandboxClientAppPackage, resolvedType, callingPackage,
- callingPid, callingUid, userId, true, callerFg, false, false, null, false);
+ callingPid, callingUid, userId, true, callerFg, false, false, null, false, false);
if (res == null) {
return null;
}
@@ -1550,7 +1558,7 @@
ServiceLookupResult r = retrieveServiceLocked(service, instanceName, isSdkSandboxService,
sdkSandboxClientAppUid, sdkSandboxClientAppPackage, resolvedType, null,
Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false, false, false,
- null, false);
+ null, false, false);
if (r != null) {
if (r.record != null) {
final long origId = Binder.clearCallingIdentity();
@@ -1642,7 +1650,7 @@
IBinder peekServiceLocked(Intent service, String resolvedType, String callingPackage) {
ServiceLookupResult r = retrieveServiceLocked(service, null, resolvedType, callingPackage,
Binder.getCallingPid(), Binder.getCallingUid(),
- UserHandle.getCallingUserId(), false, false, false, false, false);
+ UserHandle.getCallingUserId(), false, false, false, false, false, false);
IBinder ret = null;
if (r != null) {
@@ -3714,6 +3722,9 @@
|| (flags & Context.BIND_EXTERNAL_SERVICE_LONG) != 0;
final boolean allowInstant = (flags & Context.BIND_ALLOW_INSTANT) != 0;
final boolean inSharedIsolatedProcess = (flags & Context.BIND_SHARED_ISOLATED_PROCESS) != 0;
+ final boolean inPrivateSharedIsolatedProcess =
+ ((flags & Context.BIND_PACKAGE_ISOLATED_PROCESS) != 0)
+ && enableBindPackageIsolatedProcess();
final boolean matchQuarantined =
(flags & Context.BIND_MATCH_QUARANTINED_COMPONENTS) != 0;
@@ -3725,7 +3736,7 @@
isSdkSandboxService, sdkSandboxClientAppUid, sdkSandboxClientAppPackage,
resolvedType, callingPackage, callingPid, callingUid, userId, true, callerFg,
isBindExternal, allowInstant, null /* fgsDelegateOptions */,
- inSharedIsolatedProcess, matchQuarantined);
+ inSharedIsolatedProcess, inPrivateSharedIsolatedProcess, matchQuarantined);
if (res == null) {
return 0;
}
@@ -4204,14 +4215,14 @@
}
private ServiceLookupResult retrieveServiceLocked(Intent service,
- String instanceName, String resolvedType, String callingPackage,
- int callingPid, int callingUid, int userId,
- boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
- boolean allowInstant, boolean inSharedIsolatedProcess) {
+ String instanceName, String resolvedType, String callingPackage, int callingPid,
+ int callingUid, int userId, boolean createIfNeeded, boolean callingFromFg,
+ boolean isBindExternal, boolean allowInstant, boolean inSharedIsolatedProcess,
+ boolean inPrivateSharedIsolatedProcess) {
return retrieveServiceLocked(service, instanceName, false, INVALID_UID, null, resolvedType,
callingPackage, callingPid, callingUid, userId, createIfNeeded, callingFromFg,
isBindExternal, allowInstant, null /* fgsDelegateOptions */,
- inSharedIsolatedProcess);
+ inSharedIsolatedProcess, inPrivateSharedIsolatedProcess);
}
// TODO(b/265746493): Special case for HotwordDetectionService,
@@ -4233,21 +4244,22 @@
String callingPackage, int callingPid, int callingUid, int userId,
boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
boolean allowInstant, ForegroundServiceDelegationOptions fgsDelegateOptions,
- boolean inSharedIsolatedProcess) {
+ boolean inSharedIsolatedProcess, boolean inPrivateSharedIsolatedProcess) {
return retrieveServiceLocked(service, instanceName, isSdkSandboxService,
sdkSandboxClientAppUid, sdkSandboxClientAppPackage, resolvedType, callingPackage,
callingPid, callingUid, userId, createIfNeeded, callingFromFg, isBindExternal,
allowInstant, fgsDelegateOptions, inSharedIsolatedProcess,
- false /* matchQuarantined */);
+ inPrivateSharedIsolatedProcess, false /* matchQuarantined */);
}
- private ServiceLookupResult retrieveServiceLocked(Intent service,
- String instanceName, boolean isSdkSandboxService, int sdkSandboxClientAppUid,
- String sdkSandboxClientAppPackage, String resolvedType,
+ private ServiceLookupResult retrieveServiceLocked(
+ Intent service, String instanceName, boolean isSdkSandboxService,
+ int sdkSandboxClientAppUid, String sdkSandboxClientAppPackage, String resolvedType,
String callingPackage, int callingPid, int callingUid, int userId,
boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
boolean allowInstant, ForegroundServiceDelegationOptions fgsDelegateOptions,
- boolean inSharedIsolatedProcess, boolean matchQuarantined) {
+ boolean inSharedIsolatedProcess, boolean inPrivateSharedIsolatedProcess,
+ boolean matchQuarantined) {
if (isSdkSandboxService && instanceName == null) {
throw new IllegalArgumentException("No instanceName provided for sdk sandbox process");
}
@@ -4344,7 +4356,8 @@
final ServiceRestarter res = new ServiceRestarter();
final String processName = getProcessNameForService(sInfo, cn, callingPackage,
null /* instanceName */, false /* isSdkSandbox */,
- false /* inSharedIsolatedProcess */);
+ false /* inSharedIsolatedProcess */,
+ false /*inPrivateSharedIsolatedProcess*/);
r = new ServiceRecord(mAm, cn /* name */, cn /* instanceName */,
sInfo.applicationInfo.packageName, sInfo.applicationInfo.uid, filter, sInfo,
callingFromFg, res, processName,
@@ -4415,6 +4428,10 @@
throw new SecurityException("BIND_EXTERNAL_SERVICE failed, "
+ className + " is not exported");
}
+ if (inPrivateSharedIsolatedProcess) {
+ throw new SecurityException("BIND_PACKAGE_ISOLATED_PROCESS cannot be "
+ + "applied to an external service.");
+ }
if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) == 0) {
throw new SecurityException("BIND_EXTERNAL_SERVICE failed, "
+ className + " is not an isolatedProcess");
@@ -4448,28 +4465,32 @@
throw new SecurityException("BIND_EXTERNAL_SERVICE failed, " + name +
" is not an externalService");
}
- if (inSharedIsolatedProcess) {
+ if (inSharedIsolatedProcess && inPrivateSharedIsolatedProcess) {
+ throw new SecurityException("Either BIND_SHARED_ISOLATED_PROCESS or "
+ + "BIND_PACKAGE_ISOLATED_PROCESS should be set. Not both.");
+ }
+ if (inSharedIsolatedProcess || inPrivateSharedIsolatedProcess) {
if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) == 0) {
throw new SecurityException("BIND_SHARED_ISOLATED_PROCESS failed, "
+ className + " is not an isolatedProcess");
}
+ }
+ if (inPrivateSharedIsolatedProcess && isDefaultProcessService(sInfo)) {
+ throw new SecurityException("BIND_PACKAGE_ISOLATED_PROCESS cannot be used for "
+ + "services running in the main app process.");
+ }
+ if (inSharedIsolatedProcess) {
+ if (instanceName == null) {
+ throw new IllegalArgumentException("instanceName must be provided for "
+ + "binding a service into a shared isolated process.");
+ }
if ((sInfo.flags & ServiceInfo.FLAG_ALLOW_SHARED_ISOLATED_PROCESS) == 0) {
throw new SecurityException("BIND_SHARED_ISOLATED_PROCESS failed, "
+ className + " has not set the allowSharedIsolatedProcess "
+ " attribute.");
}
- if (instanceName == null) {
- throw new IllegalArgumentException("instanceName must be provided for "
- + "binding a service into a shared isolated process.");
- }
}
if (userId > 0) {
- if (mAm.isSystemUserOnly(sInfo.flags)) {
- Slog.w(TAG_SERVICE, service + " is only available for the SYSTEM user,"
- + " calling userId is: " + userId);
- return null;
- }
-
if (mAm.isSingleton(sInfo.processName, sInfo.applicationInfo,
sInfo.name, sInfo.flags)
&& mAm.isValidSingletonCall(callingUid, sInfo.applicationInfo.uid)) {
@@ -4503,11 +4524,13 @@
= new Intent.FilterComparison(service.cloneFilter());
final ServiceRestarter res = new ServiceRestarter();
String processName = getProcessNameForService(sInfo, name, callingPackage,
- instanceName, isSdkSandboxService, inSharedIsolatedProcess);
+ instanceName, isSdkSandboxService, inSharedIsolatedProcess,
+ inPrivateSharedIsolatedProcess);
r = new ServiceRecord(mAm, className, name, definingPackageName,
definingUid, filter, sInfo, callingFromFg, res,
processName, sdkSandboxClientAppUid,
- sdkSandboxClientAppPackage, inSharedIsolatedProcess);
+ sdkSandboxClientAppPackage,
+ (inSharedIsolatedProcess || inPrivateSharedIsolatedProcess));
res.setService(r);
smap.mServicesByInstanceName.put(name, r);
smap.mServicesByIntent.put(filter, r);
@@ -5377,13 +5400,13 @@
return msg;
}
mAm.mProcessList.getAppStartInfoTracker().handleProcessServiceStart(startTimeNs, app, r,
- hostingRecord, true);
+ true);
if (isolated) {
r.isolationHostProc = app;
}
} else {
mAm.mProcessList.getAppStartInfoTracker().handleProcessServiceStart(startTimeNs, app, r,
- hostingRecord, false);
+ false);
}
if (r.fgRequired) {
@@ -5459,7 +5482,7 @@
// Force an immediate oomAdjUpdate, so the client app could be in the correct process state
// before doing any service related transactions
mAm.enqueueOomAdjTargetLocked(app);
- mAm.updateOomAdjLocked(app, OOM_ADJ_REASON_START_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
boolean created = false;
try {
@@ -8504,7 +8527,8 @@
null /* sdkSandboxClientAppPackage */, null /* resolvedType */, callingPackage,
callingPid, callingUid, userId, true /* createIfNeeded */,
false /* callingFromFg */, false /* isBindExternal */, false /* allowInstant */ ,
- options, false /* inSharedIsolatedProcess */);
+ options, false /* inSharedIsolatedProcess */,
+ false /*inPrivateSharedIsolatedProcess*/);
if (res == null || res.record == null) {
Slog.d(TAG,
"startForegroundServiceDelegateLocked retrieveServiceLocked returns null");
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 8ad60e6..72e62c3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -243,7 +243,7 @@
/**
* The default value to {@link #KEY_ENABLE_NEW_OOMADJ}.
*/
- private static final boolean DEFAULT_ENABLE_NEW_OOM_ADJ = Flags.oomadjusterCorrectnessRewrite();
+ private static final boolean DEFAULT_ENABLE_NEW_OOM_ADJ = false;
/**
* Same as {@link TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 0c70bfe..c9bd0b4 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -4830,7 +4830,11 @@
if (!mConstants.mEnableWaitForFinishAttachApplication) {
finishAttachApplicationInner(startSeq, callingUid, pid);
}
- maybeSendBootCompletedLocked(app);
+
+ // Temporarily disable sending BOOT_COMPLETED to see if this was impacting perf tests
+ if (false) {
+ maybeSendBootCompletedLocked(app);
+ }
} catch (Exception e) {
// We need kill the process group here. (b/148588589)
Slog.wtf(TAG, "Exception thrown during bind of " + app, e);
@@ -6525,7 +6529,24 @@
@Override
public int checkUriPermission(Uri uri, int pid, int uid,
final int modeFlags, int userId, IBinder callerToken) {
- enforceNotIsolatedCaller("checkUriPermission");
+ return checkUriPermission(uri, pid, uid, modeFlags, userId,
+ /* isFullAccessForContentUri */ false, "checkUriPermission");
+ }
+
+ /**
+ * @param uri This uri must NOT contain an embedded userId.
+ * @param userId The userId in which the uri is to be resolved.
+ */
+ @Override
+ public int checkContentUriPermissionFull(Uri uri, int pid, int uid,
+ final int modeFlags, int userId) {
+ return checkUriPermission(uri, pid, uid, modeFlags, userId,
+ /* isFullAccessForContentUri */ true, "checkContentUriPermissionFull");
+ }
+
+ private int checkUriPermission(Uri uri, int pid, int uid,
+ final int modeFlags, int userId, boolean isFullAccessForContentUri, String methodName) {
+ enforceNotIsolatedCaller(methodName);
// Our own process gets to do everything.
if (pid == MY_PID) {
@@ -6536,8 +6557,10 @@
return PackageManager.PERMISSION_DENIED;
}
}
- return mUgmInternal.checkUriPermission(new GrantUri(userId, uri, modeFlags), uid, modeFlags)
- ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED;
+ boolean granted = mUgmInternal.checkUriPermission(new GrantUri(userId, uri, modeFlags), uid,
+ modeFlags, isFullAccessForContentUri);
+
+ return granted ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED;
}
@Override
@@ -7800,12 +7823,7 @@
@Override
public void registerUidObserver(IUidObserver observer, int which, int cutpoint,
String callingPackage) {
- if (!hasUsageStatsPermission(callingPackage)) {
- enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
- "registerUidObserver");
- }
- mUidObserverController.register(observer, which, cutpoint, callingPackage,
- Binder.getCallingUid(), /*uids*/null);
+ registerUidObserverForUids(observer, which, cutpoint, callingPackage, null /* uids */);
}
/**
@@ -9858,7 +9876,7 @@
@Override
- public void setApplicationStartInfoCompleteListener(
+ public void addApplicationStartInfoCompleteListener(
IApplicationStartInfoCompleteListener listener, int userId) {
enforceNotIsolatedCaller("setApplicationStartInfoCompleteListener");
@@ -9873,7 +9891,8 @@
@Override
- public void clearApplicationStartInfoCompleteListener(int userId) {
+ public void removeApplicationStartInfoCompleteListener(
+ IApplicationStartInfoCompleteListener listener, int userId) {
enforceNotIsolatedCaller("clearApplicationStartInfoCompleteListener");
// For the simplification, we don't support USER_ALL nor USER_CURRENT here.
@@ -9882,7 +9901,8 @@
}
final int callingUid = Binder.getCallingUid();
- mProcessList.getAppStartInfoTracker().clearStartInfoCompleteListener(callingUid, true);
+ mProcessList.getAppStartInfoTracker().removeStartInfoCompleteListener(listener, callingUid,
+ true);
}
@Override
@@ -13747,11 +13767,6 @@
return result;
}
- boolean isSystemUserOnly(int flags) {
- return android.multiuser.Flags.enableSystemUserOnlyForServicesAndProviders()
- && (flags & ServiceInfo.FLAG_SYSTEM_USER_ONLY) != 0;
- }
-
/**
* Checks to see if the caller is in the same app as the singleton
* component, or the component is in a special app. It allows special apps
@@ -20149,8 +20164,7 @@
* Returns the {@link BatteryStatsService} instance
*/
public BatteryStatsService getBatteryStatsService() {
- return new BatteryStatsService(mContext, SystemServiceManager.ensureSystemDir(),
- BackgroundThread.get().getHandler());
+ return new BatteryStatsService(mContext, SystemServiceManager.ensureSystemDir());
}
/**
diff --git a/services/core/java/com/android/server/am/AnrHelper.java b/services/core/java/com/android/server/am/AnrHelper.java
index e0a2246..9fc0bf9 100644
--- a/services/core/java/com/android/server/am/AnrHelper.java
+++ b/services/core/java/com/android/server/am/AnrHelper.java
@@ -63,6 +63,11 @@
private static final long CONSECUTIVE_ANR_TIME_MS = TimeUnit.MINUTES.toMillis(2);
/**
+ * Time to wait before taking dumps for other processes to reduce load at boot time.
+ */
+ private static final long SELF_ONLY_AFTER_BOOT_MS = TimeUnit.MINUTES.toMillis(10);
+
+ /**
* The keep alive time for the threads in the helper threadpool executor
*/
private static final int DEFAULT_THREAD_KEEP_ALIVE_SECOND = 10;
@@ -231,7 +236,8 @@
// If there are many ANR at the same time, the latency may be larger.
// If the latency is too large, the stack trace might not be meaningful.
final long reportLatency = startTime - r.mTimestamp;
- final boolean onlyDumpSelf = reportLatency > EXPIRED_REPORT_TIME_MS;
+ final boolean onlyDumpSelf = reportLatency > EXPIRED_REPORT_TIME_MS
+ || startTime < SELF_ONLY_AFTER_BOOT_MS;
r.appNotResponding(onlyDumpSelf);
final long endTime = SystemClock.uptimeMillis();
Slog.d(TAG, "Completed ANR of " + r.mApp.processName + " in "
diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index 82e554e..c857235 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -81,18 +81,18 @@
private static final int FOREACH_ACTION_REMOVE_ITEM = 1;
private static final int FOREACH_ACTION_STOP_ITERATION = 2;
- private static final int APP_START_INFO_HISTORY_LIST_SIZE = 16;
+ @VisibleForTesting static final int APP_START_INFO_HISTORY_LIST_SIZE = 16;
@VisibleForTesting static final String APP_START_STORE_DIR = "procstartstore";
@VisibleForTesting static final String APP_START_INFO_FILE = "procstartinfo";
- private final Object mLock = new Object();
+ @VisibleForTesting final Object mLock = new Object();
- private boolean mEnabled = false;
+ @VisibleForTesting boolean mEnabled = false;
/** Initialized in {@link #init} and read-only after that. */
- private ActivityManagerService mService;
+ @VisibleForTesting ActivityManagerService mService;
/** Initialized in {@link #init} and read-only after that. */
private Handler mHandler;
@@ -112,14 +112,14 @@
*
* <p>Initialized in {@link #init} and read-only after that. No lock is needed.
*/
- private int mAppStartInfoHistoryListSize;
+ @VisibleForTesting int mAppStartInfoHistoryListSize;
@GuardedBy("mLock")
private final ProcessMap<AppStartInfoContainer> mData;
/** UID as key. */
@GuardedBy("mLock")
- private final SparseArray<ApplicationStartInfoCompleteCallback> mCallbacks;
+ private final SparseArray<ArrayList<ApplicationStartInfoCompleteCallback>> mCallbacks;
/**
* Whether or not we've loaded the historical app process start info from persistent storage.
@@ -146,7 +146,8 @@
* Key is timestamp of launch from {@link #ActivityMetricsLaunchObserver}.
*/
@GuardedBy("mLock")
- private ArrayMap<Long, ApplicationStartInfo> mInProgRecords = new ArrayMap<>();
+ @VisibleForTesting
+ final ArrayMap<Long, ApplicationStartInfo> mInProgRecords = new ArrayMap<>();
AppStartInfoTracker() {
mCallbacks = new SparseArray<>();
@@ -229,7 +230,7 @@
ApplicationStartInfo info = mInProgRecords.get(id);
info.setStartType((int) temperature);
addBaseFieldsFromProcessRecord(info, app);
- addStartInfoLocked(info);
+ mInProgRecords.put(id, addStartInfoLocked(info));
} else {
mInProgRecords.remove(id);
}
@@ -262,6 +263,7 @@
ApplicationStartInfo info = mInProgRecords.get(id);
info.setStartupState(ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN);
info.setLaunchMode(launchMode);
+ checkCompletenessAndCallback(info);
}
}
@@ -281,7 +283,7 @@
}
public void handleProcessServiceStart(long startTimeNs, ProcessRecord app,
- ServiceRecord serviceRecord, HostingRecord hostingRecord, boolean cold) {
+ ServiceRecord serviceRecord, boolean cold) {
synchronized (mLock) {
if (!mEnabled) {
return;
@@ -297,7 +299,9 @@
&& serviceRecord.permission.contains("android.permission.BIND_JOB_SERVICE")
? ApplicationStartInfo.START_REASON_JOB
: ApplicationStartInfo.START_REASON_SERVICE);
- start.setIntent(serviceRecord.intent.getIntent());
+ if (serviceRecord.intent != null) {
+ start.setIntent(serviceRecord.intent.getIntent());
+ }
addStartInfoLocked(start);
}
}
@@ -378,6 +382,7 @@
start.setPackageUid(app.info.uid);
start.setDefiningUid(definingUid > 0 ? definingUid : app.info.uid);
start.setProcessName(app.processName);
+ start.setPackageName(app.info.packageName);
}
void reportApplicationOnCreateTimeNanos(ProcessRecord app, long timeNs) {
@@ -419,12 +424,12 @@
}
private void addTimestampToStart(ProcessRecord app, long timeNs, int key) {
- addTimestampToStart(app.processName, app.uid, timeNs, key);
+ addTimestampToStart(app.info.packageName, app.uid, timeNs, key);
}
- private void addTimestampToStart(String processName, int uid, long timeNs, int key) {
+ private void addTimestampToStart(String packageName, int uid, long timeNs, int key) {
synchronized (mLock) {
- AppStartInfoContainer container = mData.get(processName, uid);
+ AppStartInfoContainer container = mData.get(packageName, uid);
if (container == null) {
// Record was not created, discard new data.
return;
@@ -443,11 +448,11 @@
final ApplicationStartInfo info = new ApplicationStartInfo(raw);
- AppStartInfoContainer container = mData.get(raw.getProcessName(), raw.getRealUid());
+ AppStartInfoContainer container = mData.get(raw.getPackageName(), raw.getRealUid());
if (container == null) {
container = new AppStartInfoContainer(mAppStartInfoHistoryListSize);
container.mUid = info.getRealUid();
- mData.put(raw.getProcessName(), raw.getRealUid(), container);
+ mData.put(raw.getPackageName(), raw.getRealUid(), container);
}
container.addStartInfoLocked(info);
@@ -465,11 +470,18 @@
synchronized (mLock) {
if (startInfo.getStartupState()
== ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN) {
- ApplicationStartInfoCompleteCallback callback =
+ final List<ApplicationStartInfoCompleteCallback> callbacks =
mCallbacks.get(startInfo.getRealUid());
- if (callback != null) {
- callback.onApplicationStartInfoComplete(startInfo);
+ if (callbacks == null) {
+ return;
}
+ final int size = callbacks.size();
+ for (int i = 0; i < size; i++) {
+ if (callbacks.get(i) != null) {
+ callbacks.get(i).onApplicationStartInfoComplete(startInfo);
+ }
+ }
+ mCallbacks.remove(startInfo.getRealUid());
}
}
}
@@ -479,6 +491,9 @@
if (!mEnabled) {
return;
}
+ if (maxNum == 0) {
+ maxNum = APP_START_INFO_HISTORY_LIST_SIZE;
+ }
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -542,7 +557,6 @@
} catch (RemoteException e) {
/*ignored*/
}
- clearStartInfoCompleteListener(mUid, true);
}
void unlinkToDeath() {
@@ -551,7 +565,7 @@
@Override
public void binderDied() {
- clearStartInfoCompleteListener(mUid, false);
+ removeStartInfoCompleteListener(mCallback, mUid, false);
}
}
@@ -561,22 +575,43 @@
if (!mEnabled) {
return;
}
- mCallbacks.put(uid, new ApplicationStartInfoCompleteCallback(listener, uid));
+ ArrayList<ApplicationStartInfoCompleteCallback> callbacks = mCallbacks.get(uid);
+ if (callbacks == null) {
+ mCallbacks.set(uid,
+ callbacks = new ArrayList<ApplicationStartInfoCompleteCallback>());
+ }
+ callbacks.add(new ApplicationStartInfoCompleteCallback(listener, uid));
}
}
- void clearStartInfoCompleteListener(final int uid, boolean unlinkDeathRecipient) {
+ void removeStartInfoCompleteListener(
+ final IApplicationStartInfoCompleteListener listener, final int uid,
+ boolean unlinkDeathRecipient) {
synchronized (mLock) {
if (!mEnabled) {
return;
}
- if (unlinkDeathRecipient) {
- ApplicationStartInfoCompleteCallback callback = mCallbacks.get(uid);
- if (callback != null) {
- callback.unlinkToDeath();
+ final ArrayList<ApplicationStartInfoCompleteCallback> callbacks = mCallbacks.get(uid);
+ if (callbacks == null) {
+ return;
+ }
+ final int size = callbacks.size();
+ int index;
+ for (index = 0; index < size; index++) {
+ final ApplicationStartInfoCompleteCallback callback = callbacks.get(index);
+ if (callback.mCallback == listener) {
+ if (unlinkDeathRecipient) {
+ callback.unlinkToDeath();
+ }
+ break;
}
}
- mCallbacks.remove(uid);
+ if (index < size) {
+ callbacks.remove(index);
+ }
+ if (callbacks.isEmpty()) {
+ mCallbacks.remove(uid);
+ }
}
}
@@ -864,6 +899,7 @@
mProcStartInfoFile.delete();
}
mData.getMap().clear();
+ mInProgRecords.clear();
}
}
@@ -933,6 +969,10 @@
/** Convenience method to obtain timestamp of beginning of start.*/
private static long getStartTimestamp(ApplicationStartInfo startInfo) {
+ if (startInfo.getStartupTimestamps() == null
+ || !startInfo.getStartupTimestamps().containsKey(START_TIMESTAMP_LAUNCH)) {
+ return -1;
+ }
return startInfo.getStartupTimestamps().get(START_TIMESTAMP_LAUNCH);
}
@@ -970,7 +1010,6 @@
if (oldestIndex >= 0) {
mInfos.remove(oldestIndex);
}
- mInfos.remove(0);
}
mInfos.add(info);
Collections.sort(mInfos, (a, b) ->
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index eea9337..c96c2ff 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -381,8 +381,7 @@
}
};
- BatteryStatsService(Context context, File systemDir, Handler handler) {
- // BatteryStatsImpl expects the ActivityManagerService handler, so pass that one through.
+ BatteryStatsService(Context context, File systemDir) {
mContext = context;
mUserManagerUserInfoProvider = new BatteryStatsImpl.UserInfoProvider() {
private UserManagerInternal umi;
@@ -416,7 +415,7 @@
.build();
mPowerStatsUidResolver = new PowerStatsUidResolver();
mStats = new BatteryStatsImpl(mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock,
- systemDir, handler, this, this, mUserManagerUserInfoProvider, mPowerProfile,
+ systemDir, mHandler, this, this, mUserManagerUserInfoProvider, mPowerProfile,
mCpuScalingPolicies, mPowerStatsUidResolver);
mWorker = new BatteryExternalStatsWorker(context, mStats);
mStats.setExternalStatsSyncLocked(mWorker);
@@ -477,7 +476,7 @@
*/
public static BatteryStatsService create(Context context, File systemDir, Handler handler,
BatteryStatsImpl.BatteryCallback callback) {
- BatteryStatsService service = new BatteryStatsService(context, systemDir, handler);
+ BatteryStatsService service = new BatteryStatsService(context, systemDir);
service.mStats.setCallback(callback);
synchronized (service.mStats) {
service.mStats.readLocked();
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 30f21a6..095d907 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -1249,9 +1249,9 @@
ProviderInfo cpi = providers.get(i);
boolean singleton = mService.isSingleton(cpi.processName, cpi.applicationInfo,
cpi.name, cpi.flags);
- if (isSingletonOrSystemUserOnly(cpi) && app.userId != UserHandle.USER_SYSTEM) {
- // This is a singleton or a SYSTEM user only provider, but a user besides the
- // SYSTEM user is asking to initialize a process it runs
+ if (singleton && app.userId != UserHandle.USER_SYSTEM) {
+ // This is a singleton provider, but a user besides the
+ // default user is asking to initialize a process it runs
// in... well, no, it doesn't actually run in this process,
// it runs in the process of the default user. Get rid of it.
providers.remove(i);
@@ -1398,7 +1398,8 @@
final boolean processMatch =
Objects.equals(pi.processName, app.processName)
|| pi.multiprocess;
- final boolean userMatch = !isSingletonOrSystemUserOnly(pi)
+ final boolean userMatch = !mService.isSingleton(
+ pi.processName, pi.applicationInfo, pi.name, pi.flags)
|| app.userId == UserHandle.USER_SYSTEM;
final boolean isInstantApp = pi.applicationInfo.isInstantApp();
final boolean splitInstalled = pi.splitName == null
@@ -1984,13 +1985,4 @@
return isAuthRedirected;
}
}
-
- /**
- * Returns true if Provider is either singleUser or systemUserOnly provider.
- */
- private boolean isSingletonOrSystemUserOnly(ProviderInfo pi) {
- return (android.multiuser.Flags.enableSystemUserOnlyForServicesAndProviders()
- && mService.isSystemUserOnly(pi.flags))
- || mService.isSingleton(pi.processName, pi.applicationInfo, pi.name, pi.flags);
- }
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index b03183c..fa5dbd2 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2935,7 +2935,11 @@
return true;
}
- private static void freezeBinderAndPackageCgroup(ArrayList<Pair<ProcessRecord, Boolean>> procs,
+ private static boolean unfreezePackageCgroup(int packageUID) {
+ return freezePackageCgroup(packageUID, false);
+ }
+
+ private static void freezeBinderAndPackageCgroup(List<Pair<ProcessRecord, Boolean>> procs,
int packageUID) {
// Freeze all binder processes under the target UID (whose cgroup is about to be frozen).
// Since we're going to kill these, we don't need to unfreze them later.
@@ -2943,12 +2947,9 @@
// processes (forks) should not be Binder users.
int N = procs.size();
for (int i = 0; i < N; i++) {
- final int uid = procs.get(i).first.uid;
final int pid = procs.get(i).first.getPid();
int nRetries = 0;
- // We only freeze the cgroup of the target package, so we do not need to freeze the
- // Binder interfaces of dependant processes in other UIDs.
- if (pid > 0 && uid == packageUID) {
+ if (pid > 0) {
try {
int rc;
do {
@@ -2962,12 +2963,19 @@
}
// We freeze the entire UID (parent) cgroup so that newly-specialized processes also freeze
- // despite being added to a new child cgroup. The cgroups of package dependant processes are
- // not frozen, since it's possible this would freeze processes with no dependency on the
- // package being killed here.
+ // despite being added to a child cgroup created after this call that would otherwise be
+ // unfrozen.
freezePackageCgroup(packageUID, true);
}
+ private static List<Pair<ProcessRecord, Boolean>> getUIDSublist(
+ List<Pair<ProcessRecord, Boolean>> procs, int startIdx) {
+ final int uid = procs.get(startIdx).first.uid;
+ int endIdx = startIdx + 1;
+ while (endIdx < procs.size() && procs.get(endIdx).first.uid == uid) ++endIdx;
+ return procs.subList(startIdx, endIdx);
+ }
+
@GuardedBy({"mService", "mProcLock"})
boolean killPackageProcessesLSP(String packageName, int appId,
int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart,
@@ -3063,25 +3071,36 @@
}
}
- final int packageUID = UserHandle.getUid(userId, appId);
- final boolean doFreeze = appId >= Process.FIRST_APPLICATION_UID
- && appId <= Process.LAST_APPLICATION_UID;
- if (doFreeze) {
- freezeBinderAndPackageCgroup(procs, packageUID);
+ final boolean killingUserApp = appId >= Process.FIRST_APPLICATION_UID
+ && appId <= Process.LAST_APPLICATION_UID;
+
+ if (killingUserApp) {
+ procs.sort((o1, o2) -> Integer.compare(o1.first.uid, o2.first.uid));
}
- int N = procs.size();
- for (int i=0; i<N; i++) {
- final Pair<ProcessRecord, Boolean> proc = procs.get(i);
- removeProcessLocked(proc.first, callerWillRestart, allowRestart || proc.second,
- reasonCode, subReason, reason, !doFreeze /* async */);
+ int idx = 0;
+ while (idx < procs.size()) {
+ final List<Pair<ProcessRecord, Boolean>> uidProcs = getUIDSublist(procs, idx);
+ final int packageUID = uidProcs.get(0).first.uid;
+
+ // Do not freeze for system apps or for dependencies of the targeted package, but
+ // make sure to freeze the targeted package for all users if called with USER_ALL.
+ final boolean doFreeze = killingUserApp && UserHandle.getAppId(packageUID) == appId;
+
+ if (doFreeze) freezeBinderAndPackageCgroup(uidProcs, packageUID);
+
+ for (Pair<ProcessRecord, Boolean> proc : uidProcs) {
+ removeProcessLocked(proc.first, callerWillRestart, allowRestart || proc.second,
+ reasonCode, subReason, reason, !doFreeze /* async */);
+ }
+ killAppZygotesLocked(packageName, appId, userId, false /* force */);
+
+ if (doFreeze) unfreezePackageCgroup(packageUID);
+
+ idx += uidProcs.size();
}
- killAppZygotesLocked(packageName, appId, userId, false /* force */);
mService.updateOomAdjLocked(OOM_ADJ_REASON_PROCESS_END);
- if (doFreeze) {
- freezePackageCgroup(packageUID, false);
- }
- return N > 0;
+ return procs.size() > 0;
}
@GuardedBy("mService")
diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java
index 9f31f37..5f12ce1 100644
--- a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java
+++ b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java
@@ -73,7 +73,8 @@
private static final Set<Integer> DEFAULT_EVENT_SET = Sets.newHashSet(
AmbientContextEvent.EVENT_COUGH,
AmbientContextEvent.EVENT_SNORE,
- AmbientContextEvent.EVENT_BACK_DOUBLE_TAP);
+ AmbientContextEvent.EVENT_BACK_DOUBLE_TAP,
+ AmbientContextEvent.EVENT_HEART_RATE);
/** Default value in absence of {@link DeviceConfig} override. */
private static final boolean DEFAULT_SERVICE_ENABLED = true;
diff --git a/services/core/java/com/android/server/app/GameManagerSettings.java b/services/core/java/com/android/server/app/GameManagerSettings.java
index 5189017..b084cf3 100644
--- a/services/core/java/com/android/server/app/GameManagerSettings.java
+++ b/services/core/java/com/android/server/app/GameManagerSettings.java
@@ -251,6 +251,7 @@
+ type);
}
}
+ str.close();
} catch (XmlPullParserException | java.io.IOException e) {
Slog.wtf(TAG, "Error reading game manager settings", e);
return false;
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index df8d9e1..2ed217a 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -65,6 +65,9 @@
import static android.app.AppOpsManager.opToName;
import static android.app.AppOpsManager.opToPublicName;
import static android.companion.virtual.VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT;
+import static android.content.Intent.ACTION_PACKAGE_ADDED;
+import static android.content.Intent.ACTION_PACKAGE_REMOVED;
+import static android.content.Intent.EXTRA_REPLACING;
import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP;
@@ -973,7 +976,29 @@
String pkgName = intent.getData().getEncodedSchemeSpecificPart();
int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID);
- if (action.equals(Intent.ACTION_PACKAGE_REPLACED)) {
+ if (action.equals(ACTION_PACKAGE_ADDED)
+ && !intent.getBooleanExtra(EXTRA_REPLACING, false)) {
+ PackageInfo pi = getPackageManagerInternal().getPackageInfo(pkgName,
+ PackageManager.GET_PERMISSIONS, Process.myUid(),
+ UserHandle.getUserId(uid));
+ boolean isSamplingTarget = isSamplingTarget(pi);
+ synchronized (AppOpsService.this) {
+ if (isSamplingTarget) {
+ mRarelyUsedPackages.add(pkgName);
+ }
+ UidState uidState = getUidStateLocked(uid, true);
+ if (!uidState.pkgOps.containsKey(pkgName)) {
+ uidState.pkgOps.put(pkgName,
+ new Ops(pkgName, uidState));
+ }
+
+ createSandboxUidStateIfNotExistsForAppLocked(uid);
+ }
+ } else if (action.equals(ACTION_PACKAGE_REMOVED) && !intent.hasExtra(EXTRA_REPLACING)) {
+ synchronized (AppOpsService.this) {
+ packageRemovedLocked(uid, pkgName);
+ }
+ } else if (action.equals(Intent.ACTION_PACKAGE_REPLACED)) {
AndroidPackage pkg = getPackageManagerInternal().getPackage(pkgName);
if (pkg == null) {
return;
@@ -1052,7 +1077,9 @@
mHistoricalRegistry.systemReady(mContext.getContentResolver());
IntentFilter packageUpdateFilter = new IntentFilter();
+ packageUpdateFilter.addAction(ACTION_PACKAGE_ADDED);
packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+ packageUpdateFilter.addAction(ACTION_PACKAGE_REMOVED);
packageUpdateFilter.addDataScheme("package");
mContext.registerReceiverAsUser(mOnPackageUpdatedReceiver, UserHandle.ALL,
@@ -1079,7 +1106,7 @@
String action;
if (!ArrayUtils.contains(pkgsInUid, pkg)) {
- action = Intent.ACTION_PACKAGE_REMOVED;
+ action = ACTION_PACKAGE_REMOVED;
} else {
action = Intent.ACTION_PACKAGE_REPLACED;
}
@@ -1160,44 +1187,6 @@
// onUserRemoved handled by #removeUser
});
-
- getPackageManagerInternal().getPackageList(
- new PackageManagerInternal.PackageListObserver() {
- @Override
- public void onPackageAdded(String packageName, int appId) {
- PackageInfo pi = getPackageManagerInternal().getPackageInfo(packageName,
- PackageManager.GET_PERMISSIONS, Process.myUid(),
- mContext.getUserId());
- boolean isSamplingTarget = isSamplingTarget(pi);
- int[] userIds = getUserManagerInternal().getUserIds();
- synchronized (AppOpsService.this) {
- if (isSamplingTarget) {
- mRarelyUsedPackages.add(packageName);
- }
- for (int i = 0; i < userIds.length; i++) {
- int uid = UserHandle.getUid(userIds[i], appId);
- UidState uidState = getUidStateLocked(uid, true);
- if (!uidState.pkgOps.containsKey(packageName)) {
- uidState.pkgOps.put(packageName,
- new Ops(packageName, uidState));
- }
-
- createSandboxUidStateIfNotExistsForAppLocked(uid);
- }
- }
- }
-
- @Override
- public void onPackageRemoved(String packageName, int appId) {
- int[] userIds = getUserManagerInternal().getUserIds();
- synchronized (AppOpsService.this) {
- for (int i = 0; i < userIds.length; i++) {
- int uid = UserHandle.getUid(userIds[i], appId);
- packageRemovedLocked(uid, packageName);
- }
- }
- }
- });
}
/**
@@ -2893,6 +2882,10 @@
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
}
+ if (proxyAttributionTag != null
+ && !isAttributionTagDefined(packageName, proxyPackageName, proxyAttributionTag)) {
+ proxyAttributionTag = null;
+ }
synchronized (this) {
final Ops ops = getOpsLocked(uid, packageName, attributionTag,
@@ -3487,6 +3480,10 @@
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
}
+ if (proxyAttributionTag != null
+ && !isAttributionTagDefined(packageName, proxyPackageName, proxyAttributionTag)) {
+ proxyAttributionTag = null;
+ }
boolean isRestricted = false;
int startType = START_TYPE_FAILED;
@@ -4340,6 +4337,36 @@
return false;
}
+ /**
+ * Checks to see if the attribution tag is defined in either package or proxyPackage.
+ * This method is intended for ProxyAttributionTag validation and returns false
+ * if it does not exist in either one of them.
+ *
+ * @param packageName Name of the package
+ * @param proxyPackageName Name of the proxy package
+ * @param attributionTag attribution tag to be checked
+ *
+ * @return boolean specifying if attribution tag is valid or not
+ */
+ private boolean isAttributionTagDefined(@Nullable String packageName,
+ @Nullable String proxyPackageName,
+ @Nullable String attributionTag) {
+ if (packageName == null) {
+ return false;
+ } else if (attributionTag == null) {
+ return true;
+ }
+ PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
+ if (proxyPackageName != null) {
+ AndroidPackage proxyPkg = pmInt.getPackage(proxyPackageName);
+ if (proxyPkg != null && isAttributionInPackage(proxyPkg, attributionTag)) {
+ return true;
+ }
+ }
+ AndroidPackage pkg = pmInt.getPackage(packageName);
+ return isAttributionInPackage(pkg, attributionTag);
+ }
+
private void logVerifyAndGetBypassFailure(int uid, @NonNull SecurityException e,
@NonNull String methodName) {
if (Process.isIsolated(uid)) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index f80228a..99b45ec 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1812,22 +1812,21 @@
"msg: MSG_L_SET_BT_ACTIVE_DEVICE "
+ "received with null profile proxy: "
+ btInfo)).printLog(TAG));
- sendMsg(MSG_CHECK_MUTE_MUSIC, SENDMSG_REPLACE, 0 /*delay*/);
- return;
- }
- @AudioSystem.AudioFormatNativeEnumForBtCodec final int codec =
- mBtHelper.getCodecWithFallback(btInfo.mDevice,
- btInfo.mProfile, btInfo.mIsLeOutput,
- "MSG_L_SET_BT_ACTIVE_DEVICE");
- mDeviceInventory.onSetBtActiveDevice(btInfo, codec,
- (btInfo.mProfile
- != BluetoothProfile.LE_AUDIO || btInfo.mIsLeOutput)
- ? mAudioService.getBluetoothContextualVolumeStream()
- : AudioSystem.STREAM_DEFAULT);
- if (btInfo.mProfile == BluetoothProfile.LE_AUDIO
- || btInfo.mProfile == BluetoothProfile.HEARING_AID) {
- onUpdateCommunicationRouteClient(isBluetoothScoRequested(),
- "setBluetoothActiveDevice");
+ } else {
+ @AudioSystem.AudioFormatNativeEnumForBtCodec final int codec =
+ mBtHelper.getCodecWithFallback(btInfo.mDevice,
+ btInfo.mProfile, btInfo.mIsLeOutput,
+ "MSG_L_SET_BT_ACTIVE_DEVICE");
+ mDeviceInventory.onSetBtActiveDevice(btInfo, codec,
+ (btInfo.mProfile
+ != BluetoothProfile.LE_AUDIO || btInfo.mIsLeOutput)
+ ? mAudioService.getBluetoothContextualVolumeStream()
+ : AudioSystem.STREAM_DEFAULT);
+ if (btInfo.mProfile == BluetoothProfile.LE_AUDIO
+ || btInfo.mProfile == BluetoothProfile.HEARING_AID) {
+ onUpdateCommunicationRouteClient(isBluetoothScoRequested(),
+ "setBluetoothActiveDevice");
+ }
}
}
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index e05824a..57b19cd 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -764,7 +764,7 @@
/** only public for mocking/spying, do not call outside of AudioService */
// @GuardedBy("mDeviceBroker.mSetModeLock")
@VisibleForTesting
- @GuardedBy("mDeviceBroker.mDeviceStateLock")
+ //@GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
public void onSetBtActiveDevice(@NonNull AudioDeviceBroker.BtDeviceInfo btInfo,
@AudioSystem.AudioFormatNativeEnumForBtCodec int codec,
int streamType) {
@@ -1992,7 +1992,7 @@
// TODO: return;
} else {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
- "A2DP source device addr=" + Utils.anonymizeBluetoothAddress(address)
+ "A2DP sink device addr=" + Utils.anonymizeBluetoothAddress(address)
+ " now available").printLog(TAG));
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 91d533c..4cbee2b 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -8835,6 +8835,8 @@
synchronized (VolumeStreamState.class) {
oldIndex = getIndex(device);
index = getValidIndex(index, hasModifyAudioSettings);
+ // for STREAM_SYSTEM_ENFORCED, do not sync aliased streams on the enforced index
+ int aliasIndex = index;
if ((mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED) && mCameraSoundForced) {
index = mIndexMax;
}
@@ -8853,7 +8855,8 @@
if (streamType != mStreamType &&
mStreamVolumeAlias[streamType] == mStreamType &&
(changed || !aliasStreamState.hasIndexForDevice(device))) {
- final int scaledIndex = rescaleIndex(index, mStreamType, streamType);
+ final int scaledIndex =
+ rescaleIndex(aliasIndex, mStreamType, streamType);
aliasStreamState.setIndex(scaledIndex, device, caller,
hasModifyAudioSettings);
if (isCurrentDevice) {
@@ -9375,6 +9378,14 @@
if (mIsSingleVolume && (streamState.mStreamType != AudioSystem.STREAM_MUSIC)) {
return;
}
+
+ // Persisting STREAM_SYSTEM_ENFORCED index is not needed as its alias (STREAM_RING)
+ // is persisted. This can also be problematic when the enforcement is active as it will
+ // override current SYSTEM_RING persisted value given they share the same settings name
+ // (due to aliasing).
+ if (streamState.mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED) {
+ return;
+ }
if (streamState.hasValidSettingsName()) {
mSettings.putSystemIntForUser(mContentResolver,
streamState.getSettingNameForDevice(device),
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index a818c30..f51043d 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -634,16 +634,17 @@
return;
}
List<BluetoothDevice> activeDevices = adapter.getActiveDevices(profile);
- if (activeDevices.isEmpty() || activeDevices.get(0) == null) {
- return;
+ BluetoothProfileConnectionInfo bpci = new BluetoothProfileConnectionInfo(profile);
+ for (BluetoothDevice device : activeDevices) {
+ if (device == null) {
+ continue;
+ }
+ AudioDeviceBroker.BtDeviceChangedData data = new AudioDeviceBroker.BtDeviceChangedData(
+ device, null, bpci, "mBluetoothProfileServiceListener");
+ AudioDeviceBroker.BtDeviceInfo info = mDeviceBroker.createBtDeviceInfo(
+ data, device, BluetoothProfile.STATE_CONNECTED);
+ mDeviceBroker.postBluetoothActiveDevice(info, 0 /* delay */);
}
- AudioDeviceBroker.BtDeviceChangedData data = new AudioDeviceBroker.BtDeviceChangedData(
- activeDevices.get(0), null, new BluetoothProfileConnectionInfo(profile),
- "mBluetoothProfileServiceListener");
- AudioDeviceBroker.BtDeviceInfo info =
- mDeviceBroker.createBtDeviceInfo(data, activeDevices.get(0),
- BluetoothProfile.STATE_CONNECTED);
- mDeviceBroker.postBluetoothActiveDevice(info, 0 /* delay */);
}
// @GuardedBy("mDeviceBroker.mSetModeLock")
@@ -678,8 +679,11 @@
if (adapter != null) {
List<BluetoothDevice> activeDevices =
adapter.getActiveDevices(BluetoothProfile.HEADSET);
- if (activeDevices.size() > 0 && activeDevices.get(0) != null) {
- onSetBtScoActiveDevice(activeDevices.get(0));
+ for (BluetoothDevice device : activeDevices) {
+ if (device == null) {
+ continue;
+ }
+ onSetBtScoActiveDevice(device);
}
} else {
Log.e(TAG, "onHeadsetProfileConnected: Null BluetoothAdapter");
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index c2bc1e4..a30cdc4 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -62,6 +62,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -147,6 +148,15 @@
private static final int SAFE_MEDIA_VOLUME_UNINITIALIZED = -1;
+ // see {@link #recordToPersistedString(SoundDoseRecord)}
+ // this is computed conservatively to accommodate the legacy persisting of SoundDoseRecords in
+ // which we materialized more decimal values.
+ // TODO: adjust value after soaking in
+ private static final int MAX_RECORDS_STRING_LENGTH = 50;
+ private static final int MAX_SETTINGS_LENGTH = 32768;
+ private static final int MAX_NUMBER_OF_CACHED_RECORDS =
+ MAX_SETTINGS_LENGTH / MAX_RECORDS_STRING_LENGTH;
+
private final EventLogger mLogger = new EventLogger(AudioService.LOG_NB_EVENTS_SOUND_DOSE,
"CSD updates");
@@ -923,7 +933,7 @@
Log.v(TAG, "Initializing sound dose");
try {
- if (mCachedAudioDeviceCategories.size() > 0) {
+ if (!mCachedAudioDeviceCategories.isEmpty()) {
soundDose.initCachedAudioDeviceCategories(mCachedAudioDeviceCategories.toArray(
new ISoundDose.AudioDeviceCategory[0]));
mCachedAudioDeviceCategories.clear();
@@ -957,6 +967,7 @@
mGlobalTimeOffsetInSecs);
if (records != null) {
mDoseRecords.addAll(records);
+ sanitizeDoseRecords_l();
}
}
}
@@ -1176,17 +1187,35 @@
&& r.duration == record.duration)) {
Log.w(TAG, "Could not find cached record to remove: " + record);
}
- } else {
+ } else if (record.value > 0) {
mDoseRecords.add(record);
}
}
+ sanitizeDoseRecords_l();
+
mAudioHandler.sendMessageAtTime(mAudioHandler.obtainMessage(MSG_PERSIST_CSD_VALUES,
/* arg1= */0, /* arg2= */0, /* obj= */null), /* delay= */0);
mLogger.enqueue(SoundDoseEvent.getDoseUpdateEvent(currentCsd, totalDuration));
}
+ @GuardedBy("mCsdStateLock")
+ private void sanitizeDoseRecords_l() {
+ if (mDoseRecords.size() > MAX_NUMBER_OF_CACHED_RECORDS) {
+ int nrToRemove = MAX_NUMBER_OF_CACHED_RECORDS - mDoseRecords.size();
+ Log.w(TAG,
+ "Removing " + nrToRemove + " records from the total of " + mDoseRecords.size());
+ // Remove older elements to fit into persisted settings max length
+ Iterator<SoundDoseRecord> recordIterator = mDoseRecords.iterator();
+ while (recordIterator.hasNext() && nrToRemove > 0) {
+ recordIterator.next();
+ recordIterator.remove();
+ --nrToRemove;
+ }
+ }
+ }
+
@SuppressWarnings("GuardedBy") // avoid limitation with intra-procedural analysis of lambdas
private void onPersistSoundDoseRecords() {
synchronized (mCsdStateLock) {
@@ -1213,8 +1242,8 @@
long globalTimeOffsetInSecs) {
return convertToGlobalTime(record.timestamp, globalTimeOffsetInSecs)
+ PERSIST_CSD_RECORD_FIELD_SEPARATOR + record.duration
- + PERSIST_CSD_RECORD_FIELD_SEPARATOR + record.value
- + PERSIST_CSD_RECORD_FIELD_SEPARATOR + record.averageMel;
+ + PERSIST_CSD_RECORD_FIELD_SEPARATOR + String.format("%.3f", record.value)
+ + PERSIST_CSD_RECORD_FIELD_SEPARATOR + String.format("%.3f", record.averageMel);
}
private static long convertToGlobalTime(long bootTimeInSecs, long globalTimeOffsetInSecs) {
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index d5d8fd2..8fd2ee2 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -20,6 +20,7 @@
// TODO(b/141025588): Create separate internal and external permissions for AuthService.
// TODO(b/141025588): Get rid of the USE_FINGERPRINT permission.
+import static android.Manifest.permission.MANAGE_BIOMETRIC_DIALOG;
import static android.Manifest.permission.TEST_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
@@ -304,6 +305,9 @@
if (promptInfo.containsPrivateApiConfigurations()) {
checkInternalPermission();
}
+ if (promptInfo.containsManageBioApiConfigurations()) {
+ checkManageBiometricPermission();
+ }
final long identity = Binder.clearCallingIdentity();
try {
@@ -984,6 +988,11 @@
"Must have USE_BIOMETRIC_INTERNAL permission");
}
+ private void checkManageBiometricPermission() {
+ getContext().enforceCallingOrSelfPermission(MANAGE_BIOMETRIC_DIALOG,
+ "Must have MANAGE_BIOMETRIC_DIALOG permission");
+ }
+
private void checkPermission() {
if (getContext().checkCallingOrSelfPermission(USE_FINGERPRINT)
!= PackageManager.PERMISSION_GRANTED) {
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContext.java b/services/core/java/com/android/server/biometrics/log/BiometricContext.java
index 3dcea19..f8a9867 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricContext.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricContext.java
@@ -77,6 +77,9 @@
@AuthenticateOptions.DisplayState
int getDisplayState();
+ /** Gets whether touches on sensor are ignored by HAL */
+ boolean isHardwareIgnoringTouches();
+
/**
* Subscribe to context changes.
*
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
index 95a047f..535b7b7 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
@@ -86,8 +86,8 @@
@Nullable private final Handler mHandler;
private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
private int mFoldState = IBiometricContextListener.FoldState.UNKNOWN;
-
private int mDisplayState = AuthenticateOptions.DISPLAY_STATE_UNKNOWN;
+ private boolean mIsHardwareIgnoringTouches = false;
@VisibleForTesting
final BroadcastReceiver mDockStateReceiver = new BroadcastReceiver() {
@Override
@@ -129,6 +129,14 @@
notifyChanged();
}
}
+
+ @Override
+ public void onHardwareIgnoreTouchesChanged(boolean shouldIgnore) {
+ if (mIsHardwareIgnoringTouches != shouldIgnore) {
+ mIsHardwareIgnoringTouches = shouldIgnore;
+ notifyChanged();
+ }
+ }
});
service.registerSessionListener(SESSION_TYPES, new ISessionListener.Stub() {
@Override
@@ -215,6 +223,11 @@
}
@Override
+ public boolean isHardwareIgnoringTouches() {
+ return mIsHardwareIgnoringTouches;
+ }
+
+ @Override
public void subscribe(@NonNull OperationContextExt context,
@NonNull Consumer<OperationContext> consumer) {
mSubscribers.put(context, consumer);
@@ -254,6 +267,7 @@
+ "bp session: " + getBiometricPromptSessionInfo() + ", "
+ "displayState: " + getDisplayState() + ", "
+ "isAwake: " + isAwake() + ", "
+ + "isHardwareIgnoring: " + isHardwareIgnoringTouches() + ", "
+ "isDisplayOn: " + isDisplayOn() + ", "
+ "dock: " + getDockedState() + ", "
+ "rotation: " + getCurrentRotation() + ", "
diff --git a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
index b4e0dff..0045d44 100644
--- a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
+++ b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
@@ -20,12 +20,14 @@
import android.annotation.Nullable;
import android.content.Intent;
import android.hardware.biometrics.AuthenticateOptions;
+import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.IBiometricContextListener;
import android.hardware.biometrics.common.AuthenticateReason;
import android.hardware.biometrics.common.DisplayState;
import android.hardware.biometrics.common.FoldState;
import android.hardware.biometrics.common.OperationContext;
import android.hardware.biometrics.common.OperationReason;
+import android.hardware.biometrics.common.OperationState;
import android.hardware.biometrics.common.WakeReason;
import android.hardware.face.FaceAuthenticateOptions;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
@@ -51,13 +53,26 @@
/** Create a context. */
public OperationContextExt(boolean isBP) {
- this(new OperationContext(), isBP);
+ this(new OperationContext(), isBP, BiometricAuthenticator.TYPE_NONE);
+ }
+
+ public OperationContextExt(boolean isBP, @BiometricAuthenticator.Modality int modality) {
+ this(new OperationContext(), isBP, modality);
}
/** Create a wrapped context. */
- public OperationContextExt(@NonNull OperationContext context, boolean isBP) {
+ public OperationContextExt(@NonNull OperationContext context, boolean isBP,
+ @BiometricAuthenticator.Modality int modality) {
mAidlContext = context;
mIsBP = isBP;
+
+ if (modality == BiometricAuthenticator.TYPE_FINGERPRINT) {
+ mAidlContext.operationState = OperationState.fingerprintOperationState(
+ new OperationState.FingerprintOperationState());
+ } else if (modality == BiometricAuthenticator.TYPE_FACE) {
+ mAidlContext.operationState = OperationState.faceOperationState(
+ new OperationState.FaceOperationState());
+ }
}
/**
@@ -247,12 +262,23 @@
return mOrientation;
}
+ /** The current operation state */
+ public OperationState getOperationState() {
+ return mAidlContext.operationState;
+ }
+
/** Update this object with the latest values from the given context. */
OperationContextExt update(@NonNull BiometricContext biometricContext, boolean isCrypto) {
mAidlContext.isAod = biometricContext.isAod();
mAidlContext.displayState = toAidlDisplayState(biometricContext.getDisplayState());
mAidlContext.foldState = toAidlFoldState(biometricContext.getFoldState());
mAidlContext.isCrypto = isCrypto;
+
+ if (mAidlContext.operationState != null && mAidlContext.operationState.getTag()
+ == OperationState.fingerprintOperationState) {
+ mAidlContext.operationState.getFingerprintOperationState().isHardwareIgnoringTouches =
+ biometricContext.isHardwareIgnoringTouches();
+ }
setFirstSessionId(biometricContext);
mIsDisplayOn = biometricContext.isDisplayOn();
diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
index 1a682a9..1fc7c70 100644
--- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
@@ -41,22 +41,42 @@
* a common interface.
*/
public class ClientMonitorCallbackConverter {
- private IBiometricSensorReceiver mSensorReceiver; // BiometricService
- private IFaceServiceReceiver mFaceServiceReceiver; // FaceManager
- private IFingerprintServiceReceiver mFingerprintServiceReceiver; // FingerprintManager
+ private final IBiometricSensorReceiver mSensorReceiver; // BiometricService
+ private final IFaceServiceReceiver mFaceServiceReceiver; // FaceManager
+ private final IFingerprintServiceReceiver mFingerprintServiceReceiver; // FingerprintManager
public ClientMonitorCallbackConverter(IBiometricSensorReceiver sensorReceiver) {
mSensorReceiver = sensorReceiver;
+ mFaceServiceReceiver = null;
+ mFingerprintServiceReceiver = null;
}
public ClientMonitorCallbackConverter(IFaceServiceReceiver faceServiceReceiver) {
+ mSensorReceiver = null;
mFaceServiceReceiver = faceServiceReceiver;
+ mFingerprintServiceReceiver = null;
}
public ClientMonitorCallbackConverter(IFingerprintServiceReceiver fingerprintServiceReceiver) {
+ mSensorReceiver = null;
+ mFaceServiceReceiver = null;
mFingerprintServiceReceiver = fingerprintServiceReceiver;
}
+ /**
+ * Returns an int representing the {@link BiometricAuthenticator.Modality} of the active
+ * ServiceReceiver
+ */
+ @BiometricAuthenticator.Modality
+ public int getModality() {
+ if (mFaceServiceReceiver != null) {
+ return BiometricAuthenticator.TYPE_FACE;
+ } else if (mFingerprintServiceReceiver != null) {
+ return BiometricAuthenticator.TYPE_FINGERPRINT;
+ }
+ return BiometricAuthenticator.TYPE_NONE;
+ }
+
// The following apply to all clients
public void onAcquired(int sensorId, int acquiredInfo, int vendorCode) throws RemoteException {
diff --git a/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
index 03658ce..0f01510 100644
--- a/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.hardware.biometrics.BiometricAuthenticator;
import android.os.IBinder;
import com.android.server.biometrics.log.BiometricContext;
@@ -58,7 +59,8 @@
super(context, token, listener, userId, owner, cookie, sensorId,
biometricLogger, biometricContext);
mLazyDaemon = lazyDaemon;
- mOperationContext = new OperationContextExt(isBiometricPrompt());
+ int modality = listener != null ? listener.getModality() : BiometricAuthenticator.TYPE_NONE;
+ mOperationContext = new OperationContextExt(isBiometricPrompt(), modality);
}
@Nullable
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 29c5a3d..145885d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -28,6 +28,7 @@
import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
import android.hardware.biometrics.BiometricManager.Authenticators;
import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.common.OperationState;
import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
import android.hardware.fingerprint.FingerprintManager;
@@ -326,6 +327,12 @@
if (session.hasContextMethods()) {
try {
session.getSession().onContextChanged(ctx);
+ // TODO(b/317414324): Deprecate setIgnoreDisplayTouches
+ if (ctx.operationState != null && ctx.operationState.getTag()
+ == OperationState.fingerprintOperationState) {
+ session.getSession().setIgnoreDisplayTouches(
+ ctx.operationState.getFingerprintOperationState().isHardwareIgnoringTouches);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Unable to notify context changed", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index e58e5ae..3aab7b3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.hardware.biometrics.BiometricRequestConstants;
import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.common.OperationState;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
@@ -122,6 +123,12 @@
getBiometricContext().subscribe(opContext, ctx -> {
try {
session.getSession().onContextChanged(ctx);
+ // TODO(b/317414324): Deprecate setIgnoreDisplayTouches
+ if (ctx.operationState != null && ctx.operationState.getTag()
+ == OperationState.fingerprintOperationState) {
+ session.getSession().setIgnoreDisplayTouches(
+ ctx.operationState.getFingerprintOperationState().isHardwareIgnoringTouches);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Unable to notify context changed", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index c0761ed..bf5011d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -26,6 +26,7 @@
import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
import android.hardware.biometrics.BiometricStateListener;
import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.common.OperationState;
import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
@@ -220,6 +221,12 @@
getBiometricContext().subscribe(opContext, ctx -> {
try {
session.getSession().onContextChanged(ctx);
+ // TODO(b/317414324): Deprecate setIgnoreDisplayTouches
+ if (ctx.operationState != null && ctx.operationState.getTag()
+ == OperationState.fingerprintOperationState) {
+ session.getSession().setIgnoreDisplayTouches(
+ ctx.operationState.getFingerprintOperationState().isHardwareIgnoringTouches);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Unable to notify context changed", e);
}
diff --git a/services/core/java/com/android/server/broadcastradio/TEST_MAPPING b/services/core/java/com/android/server/broadcastradio/TEST_MAPPING
new file mode 100644
index 0000000..ee4eeb6
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "imports": [
+ {
+ "path": "frameworks/base/core/tests/BroadcastRadioTests"
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
index 823788f..b179783 100644
--- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -137,9 +137,9 @@
public abstract boolean isAppRunningOnAnyVirtualDevice(int uid);
/**
- * Returns true if the {@code displayId} is owned by any virtual device
+ * @return whether the input device with the given id was created by a virtual device.
*/
- public abstract boolean isDisplayOwnedByAnyVirtualDevice(int displayId);
+ public abstract boolean isInputDeviceOwnedByVirtualDevice(int inputDeviceId);
/**
* Gets the ids of VirtualDisplays owned by a VirtualDevice.
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 8910b6e..082776a 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -624,10 +624,10 @@
pw.println(" Current mode="
+ autoBrightnessModeToString(mCurrentBrightnessMapper.getMode()));
- pw.println();
for (int i = 0; i < mBrightnessMappingStrategyMap.size(); i++) {
+ pw.println();
pw.println(" Mapper for mode "
- + autoBrightnessModeToString(mBrightnessMappingStrategyMap.keyAt(i)) + "=");
+ + autoBrightnessModeToString(mBrightnessMappingStrategyMap.keyAt(i)) + ":");
mBrightnessMappingStrategyMap.valueAt(i).dump(pw,
mBrightnessRangeController.getNormalBrightnessMax());
}
@@ -1159,7 +1159,7 @@
if (mCurrentBrightnessMapper.getMode() == mode) {
return;
}
- Slog.i(TAG, "Switching to mode " + mode);
+ Slog.i(TAG, "Switching to mode " + autoBrightnessModeToString(mode));
if (mode == AUTO_BRIGHTNESS_MODE_IDLE
|| mCurrentBrightnessMapper.getMode() == AUTO_BRIGHTNESS_MODE_IDLE) {
switchModeAndShortTermModels(mode);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index fbac924..9cf9119 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -3390,17 +3390,10 @@
// with the corresponding displaydevice.
HighBrightnessModeMetadata hbmMetadata =
mHighBrightnessModeMetadataMapper.getHighBrightnessModeMetadataLocked(display);
- if (mConfigParameterProvider.isNewPowerControllerFeatureEnabled()) {
- displayPowerController = new DisplayPowerController2(
- mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler,
- mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting,
- () -> handleBrightnessChange(display), hbmMetadata, mBootCompleted, mFlags);
- } else {
- displayPowerController = new DisplayPowerController(
- mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler,
- mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting,
- () -> handleBrightnessChange(display), hbmMetadata, mBootCompleted, mFlags);
- }
+ displayPowerController = new DisplayPowerController(
+ mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler,
+ mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting,
+ () -> handleBrightnessChange(display), hbmMetadata, mBootCompleted, mFlags);
mDisplayPowerControllers.append(display.getDisplayIdLocked(), displayPowerController);
return displayPowerController;
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 734381b..087cacf 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 The Android Open Source Project
+ * 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.
@@ -17,11 +17,12 @@
package com.android.server.display;
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 com.android.server.display.config.DisplayBrightnessMappingConfig.autoBrightnessPresetToString;
import android.animation.Animator;
import android.animation.ObjectAnimator;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
@@ -31,8 +32,6 @@
import android.content.res.Resources;
import android.database.ContentObserver;
import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.display.AmbientBrightnessDayStats;
import android.hardware.display.BrightnessChangeEvent;
@@ -45,6 +44,7 @@
import android.metrics.LogMaker;
import android.net.Uri;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
@@ -56,12 +56,12 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.util.FloatProperty;
+import android.util.IndentingPrintWriter;
import android.util.MathUtils;
import android.util.MutableFloat;
import android.util.MutableInt;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.TimeUtils;
import android.view.Display;
import com.android.internal.R;
@@ -78,10 +78,15 @@
import com.android.server.display.RampAnimator.DualRampAnimator;
import com.android.server.display.brightness.BrightnessEvent;
import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.brightness.DisplayBrightnessController;
+import com.android.server.display.brightness.clamper.BrightnessClamperController;
+import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.layout.Layout;
+import com.android.server.display.state.DisplayStateController;
import com.android.server.display.utils.DebugUtils;
import com.android.server.display.utils.SensorUtils;
import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
@@ -119,12 +124,12 @@
private static final String SCREEN_ON_BLOCKED_TRACE_NAME = "Screen on blocked";
private static final String SCREEN_OFF_BLOCKED_TRACE_NAME = "Screen off blocked";
- private static final String TAG = "DisplayPowerController";
+ private static final String TAG = "DisplayPowerController2";
// To enable these logs, run:
- // 'adb shell setprop persist.log.tag.DisplayPowerController DEBUG && adb reboot'
+ // 'adb shell setprop persist.log.tag.DisplayPowerController2 DEBUG && adb reboot'
private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
-
- private static final boolean DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT = false;
+ private static final String SCREEN_ON_BLOCKED_BY_DISPLAYOFFLOAD_TRACE_NAME =
+ "Screen on blocked by displayoffload";
// If true, uses the color fade on animation.
// We might want to turn this off if we cannot get a guarantee that the screen
@@ -138,36 +143,28 @@
private static final int COLOR_FADE_OFF_ANIMATION_DURATION_MILLIS = 400;
private static final int MSG_UPDATE_POWER_STATE = 1;
- private static final int MSG_PROXIMITY_SENSOR_DEBOUNCED = 2;
- private static final int MSG_SCREEN_ON_UNBLOCKED = 3;
- private static final int MSG_SCREEN_OFF_UNBLOCKED = 4;
- private static final int MSG_CONFIGURE_BRIGHTNESS = 5;
- private static final int MSG_SET_TEMPORARY_BRIGHTNESS = 6;
- private static final int MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT = 7;
- private static final int MSG_IGNORE_PROXIMITY = 8;
- private static final int MSG_STOP = 9;
- private static final int MSG_UPDATE_BRIGHTNESS = 10;
- private static final int MSG_UPDATE_RBC = 11;
- private static final int MSG_BRIGHTNESS_RAMP_DONE = 12;
- private static final int MSG_STATSD_HBM_BRIGHTNESS = 13;
- private static final int MSG_SWITCH_USER = 14;
- private static final int MSG_BOOT_COMPLETED = 15;
- private static final int MSG_SET_DWBC_STRONG_MODE = 16;
- private static final int MSG_SET_DWBC_COLOR_OVERRIDE = 17;
- private static final int MSG_SET_DWBC_LOGGING_ENABLED = 18;
+ private static final int MSG_SCREEN_ON_UNBLOCKED = 2;
+ private static final int MSG_SCREEN_OFF_UNBLOCKED = 3;
+ private static final int MSG_CONFIGURE_BRIGHTNESS = 4;
+ private static final int MSG_SET_TEMPORARY_BRIGHTNESS = 5;
+ private static final int MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT = 6;
+ private static final int MSG_STOP = 7;
+ private static final int MSG_UPDATE_BRIGHTNESS = 8;
+ private static final int MSG_UPDATE_RBC = 9;
+ private static final int MSG_BRIGHTNESS_RAMP_DONE = 10;
+ private static final int MSG_STATSD_HBM_BRIGHTNESS = 11;
+ private static final int MSG_SWITCH_USER = 12;
+ private static final int MSG_BOOT_COMPLETED = 13;
+ private static final int MSG_SET_DWBC_STRONG_MODE = 14;
+ private static final int MSG_SET_DWBC_COLOR_OVERRIDE = 15;
+ private static final int MSG_SET_DWBC_LOGGING_ENABLED = 16;
+ private static final int MSG_SET_BRIGHTNESS_FROM_OFFLOAD = 17;
+ private static final int MSG_OFFLOADING_SCREEN_ON_UNBLOCKED = 18;
- private static final int PROXIMITY_UNKNOWN = -1;
- private static final int PROXIMITY_NEGATIVE = 0;
- private static final int PROXIMITY_POSITIVE = 1;
- // Proximity sensor debounce delay in milliseconds for positive or negative transitions.
- private static final int PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY = 0;
- private static final int PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY = 250;
private static final int BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS = 500;
- // Trigger proximity if distance is less than 5 cm.
- private static final float TYPICAL_PROXIMITY_THRESHOLD = 5.0f;
// State machine constants for tracking initial brightness ramp skipping when enabled.
private static final int RAMP_STATE_SKIP_NONE = 0;
@@ -181,6 +178,7 @@
private static final int REPORTED_TO_POLICY_SCREEN_TURNING_OFF = 3;
private static final int RINGBUFFER_MAX = 100;
+ private static final int RINGBUFFER_RBC_MAX = 20;
private static final float[] BRIGHTNESS_RANGE_BOUNDARIES = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80,
@@ -236,10 +234,6 @@
// Our handler.
private final DisplayControllerHandler mHandler;
- // Asynchronous callbacks into the power manager service.
- // Only invoked from the handler thread while no locks are held.
- private final DisplayPowerCallbacks mCallbacks;
-
// Battery stats.
@Nullable
private final IBatteryStats mBatteryStats;
@@ -253,10 +247,10 @@
// The display blanker.
private final DisplayBlanker mBlanker;
- // The LogicalDisplay tied to this DisplayPowerController.
+ // The LogicalDisplay tied to this DisplayPowerController2.
private final LogicalDisplay mLogicalDisplay;
- // The ID of the LogicalDisplay tied to this DisplayPowerController.
+ // The ID of the LogicalDisplay tied to this DisplayPowerController2.
private final int mDisplayId;
// The ID of the display which this display follows for brightness purposes.
@@ -272,34 +266,12 @@
// Tracker for brightness settings changes.
private final SettingsObserver mSettingsObserver;
- // The proximity sensor, or null if not available or needed.
- private Sensor mProximitySensor;
-
// The doze screen brightness.
private final float mScreenBrightnessDozeConfig;
- // The dim screen brightness.
- private final float mScreenBrightnessDimConfig;
-
- // The minimum dim amount to use if the screen brightness is already below
- // mScreenBrightnessDimConfig.
- private final float mScreenBrightnessMinimumDimAmount;
-
- private final float mScreenBrightnessDefault;
-
// True if auto-brightness should be used.
private boolean mUseSoftwareAutoBrightnessConfig;
- // True if should use light sensor to automatically determine doze screen brightness.
- private final boolean mAllowAutoBrightnessWhileDozingConfig;
-
- // True if we want to persist the brightness value in nits even if the underlying display
- // device changes.
- private final boolean mPersistBrightnessNitsForDefaultDisplay;
-
- // True if the brightness config has changed and the short-term model needs to be reset
- private boolean mShouldResetShortTermModel;
-
// Whether or not the color fade on screen on / off is enabled.
private final boolean mColorFadeEnabled;
@@ -340,10 +312,6 @@
@GuardedBy("mLock")
private DisplayPowerRequest mPendingRequestLocked;
- // True if a request has been made to wait for the proximity sensor to go negative.
- @GuardedBy("mLock")
- private boolean mPendingWaitForNegativeProximityLocked;
-
// True if the pending power request or wait for negative proximity flag
// has been changed since the last update occurred.
@GuardedBy("mLock")
@@ -370,67 +338,36 @@
// Must only be accessed on the handler thread.
private DisplayPowerState mPowerState;
- // True if the device should wait for negative proximity sensor before
- // waking up the screen. This is set to false as soon as a negative
- // proximity sensor measurement is observed or when the device is forced to
- // go to sleep by the user. While true, the screen remains off.
- private boolean mWaitingForNegativeProximity;
- // True if the device should not take into account the proximity sensor
- // until either the proximity sensor state changes, or there is no longer a
- // request to listen to proximity sensor.
- private boolean mIgnoreProximityUntilChanged;
-
- // The actual proximity sensor threshold value.
- private float mProximityThreshold;
-
- // Set to true if the proximity sensor listener has been registered
- // with the sensor manager.
- private boolean mProximitySensorEnabled;
-
- // The debounced proximity sensor state.
- private int mProximity = PROXIMITY_UNKNOWN;
-
- // The raw non-debounced proximity sensor state.
- private int mPendingProximity = PROXIMITY_UNKNOWN;
- private long mPendingProximityDebounceTime = -1; // -1 if fully debounced
-
- // True if the screen was turned off because of the proximity sensor.
- // When the screen turns on again, we report user activity to the power manager.
- private boolean mScreenOffBecauseOfProximity;
// The currently active screen on unblocker. This field is non-null whenever
// we are waiting for a callback to release it and unblock the screen.
private ScreenOnUnblocker mPendingScreenOnUnblocker;
private ScreenOffUnblocker mPendingScreenOffUnblocker;
+ private Runnable mPendingScreenOnUnblockerByDisplayOffload;
// True if we were in the process of turning off the screen.
// This allows us to recover more gracefully from situations where we abort
// turning off the screen.
private boolean mPendingScreenOff;
- // True if we have unfinished business and are holding a suspend blocker.
- private boolean mUnfinishedBusiness;
-
// The elapsed real time when the screen on was blocked.
private long mScreenOnBlockStartRealTime;
private long mScreenOffBlockStartRealTime;
+ private long mScreenOnBlockByDisplayOffloadStartRealTime;
// Screen state we reported to policy. Must be one of REPORTED_TO_POLICY_* fields.
private int mReportedScreenStateToPolicy = REPORTED_TO_POLICY_UNREPORTED;
+ // Used to deduplicate the displayoffload blocking screen on logic. One block per turning on.
+ // This value is reset when screen on is reported or the blocking is cancelled.
+ private boolean mScreenTurningOnWasBlockedByDisplayOffload;
+
// If the last recorded screen state was dozing or not.
private boolean mDozing;
- // Remembers whether certain kinds of brightness adjustments
- // were recently applied so that we can decide how to transition.
- private boolean mAppliedAutoBrightness;
private boolean mAppliedDimming;
- private boolean mAppliedLowPower;
- private boolean mAppliedScreenBrightnessOverride;
- private boolean mAppliedTemporaryBrightness;
- private boolean mAppliedTemporaryAutoBrightnessAdjustment;
- private boolean mAppliedBrightnessBoost;
+
private boolean mAppliedThrottling;
// Reason for which the brightness was last changed. See {@link BrightnessReason} for more
@@ -456,7 +393,7 @@
private final boolean mSkipScreenOnBrightnessRamp;
// Display white balance components.
- // Critical methods must be called on DPC handler thread.
+ // Critical methods must be called on DPC2 handler thread.
@Nullable
private final DisplayWhiteBalanceSettings mDisplayWhiteBalanceSettings;
@Nullable
@@ -468,21 +405,39 @@
private final BrightnessRangeController mBrightnessRangeController;
- @Nullable
- private final HighBrightnessModeMetadata mHighBrightnessModeMetadata;
-
private final BrightnessThrottler mBrightnessThrottler;
- private final BrightnessSetting mBrightnessSetting;
+ private final BrightnessClamperController mBrightnessClamperController;
private final Runnable mOnBrightnessChangeRunnable;
private final BrightnessEvent mLastBrightnessEvent;
private final BrightnessEvent mTempBrightnessEvent;
+ private final DisplayBrightnessController mDisplayBrightnessController;
+
// Keeps a record of brightness changes for dumpsys.
private RingBuffer<BrightnessEvent> mBrightnessEventRingBuffer;
+ // Keeps a record of rbc changes for dumpsys.
+ private final RingBuffer<BrightnessEvent> mRbcEventRingBuffer =
+ new RingBuffer<>(BrightnessEvent.class, RINGBUFFER_RBC_MAX);
+
+ // Controls and tracks all the wakelocks that are acquired/released by the system. Also acts as
+ // a medium of communication between this class and the PowerManagerService.
+ private final WakelockController mWakelockController;
+
+ // Tracks and manages the proximity state of the associated display.
+ private final DisplayPowerProximityStateController mDisplayPowerProximityStateController;
+
+ // Tracks and manages the display state of the associated display.
+ private final DisplayStateController mDisplayStateController;
+
+
+ // Responsible for evaluating and tracking the automatic brightness relevant states.
+ // Todo: This is a temporary workaround. Ideally DPC2 should never talk to the strategies
+ private final AutomaticBrightnessStrategy mAutomaticBrightnessStrategy;
+
// A record of state for skipping brightness ramps.
private int mSkipRampState = RAMP_STATE_SKIP_NONE;
@@ -500,81 +455,18 @@
private Sensor mLightSensor;
private Sensor mScreenOffBrightnessSensor;
- // The current brightness configuration.
- @Nullable
- private BrightnessConfiguration mBrightnessConfiguration;
-
- // The last brightness that was set by the user and not temporary. Set to
- // PowerManager.BRIGHTNESS_INVALID_FLOAT when a brightness has yet to be recorded.
- private float mLastUserSetScreenBrightness = Float.NaN;
-
- // The screen brightness setting has changed but not taken effect yet. If this is different
- // from the current screen brightness setting then this is coming from something other than us
- // and should be considered a user interaction.
- private float mPendingScreenBrightnessSetting;
-
- // The last observed screen brightness setting, either set by us or by the settings app on
- // behalf of the user.
- private float mCurrentScreenBrightnessSetting;
-
- // The temporary screen brightness. Typically set when a user is interacting with the
- // brightness slider but hasn't settled on a choice yet. Set to
- // PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary brightness set.
- private float mTemporaryScreenBrightness;
-
- // This brightness value is set in concurrent displays mode. It is the brightness value
- // of the lead display that this DPC should follow.
- private float mBrightnessToFollow;
-
- // Indicates whether we should ramp slowly to the brightness value to follow.
- private boolean mBrightnessToFollowSlowChange;
-
- // The last auto brightness adjustment that was set by the user and not temporary. Set to
- // Float.NaN when an auto-brightness adjustment hasn't been recorded yet.
- private float mAutoBrightnessAdjustment;
-
- // The pending auto brightness adjustment that will take effect on the next power state update.
- private float mPendingAutoBrightnessAdjustment;
-
- // The temporary auto brightness adjustment. Typically set when a user is interacting with the
- // adjustment slider but hasn't settled on a choice yet. Set to
- // PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary adjustment set.
- private float mTemporaryAutoBrightnessAdjustment;
-
- private boolean mUseAutoBrightness;
-
private boolean mIsRbcActive;
- // Whether there's a callback to tell listeners the display has changed scheduled to run. When
- // true it implies a wakelock is being held to guarantee the update happens before we collapse
- // into suspend and so needs to be cleaned up if the thread is exiting.
- // Should only be accessed on the Handler thread.
- private boolean mOnStateChangedPending;
-
- // Count of proximity messages currently on this DPC's Handler. Used to keep track of how many
- // suspend blocker acquisitions are pending when shutting down this DPC.
- // Should only be accessed on the Handler thread.
- private int mOnProximityPositiveMessages;
- private int mOnProximityNegativeMessages;
-
// Animators.
private ObjectAnimator mColorFadeOnAnimator;
private ObjectAnimator mColorFadeOffAnimator;
private DualRampAnimator<DisplayPowerState> mScreenBrightnessRampAnimator;
- private BrightnessSetting.BrightnessSettingListener mBrightnessSettingListener;
- // True if this DisplayPowerController has been stopped and should no longer be running.
+ // True if this DisplayPowerController2 has been stopped and should no longer be running.
private boolean mStopped;
private DisplayDeviceConfig mDisplayDeviceConfig;
- // Identifiers for suspend blocker acquisition requests
- private final String mSuspendBlockerIdUnfinishedBusiness;
- private final String mSuspendBlockerIdOnStateChanged;
- private final String mSuspendBlockerIdProxPositive;
- private final String mSuspendBlockerIdProxNegative;
- private final String mSuspendBlockerIdProxDebounce;
-
private boolean mIsEnabled;
private boolean mIsInTransition;
private boolean mIsDisplayInternal;
@@ -585,13 +477,13 @@
// DPCs following the brightness of this DPC. This is used in concurrent displays mode - there
// is one lead display, the additional displays follow the brightness value of the lead display.
@GuardedBy("mLock")
- private final SparseArray<DisplayPowerControllerInterface> mDisplayBrightnessFollowers =
- new SparseArray<>();
+ private SparseArray<DisplayPowerControllerInterface> mDisplayBrightnessFollowers =
+ new SparseArray();
private boolean mBootCompleted;
private final DisplayManagerFlags mFlags;
- private int mDozeStateOverride = Display.STATE_UNKNOWN;
- private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession;
+
+ private DisplayOffloadSession mDisplayOffloadSession;
/**
* Creates the display power controller.
@@ -607,26 +499,28 @@
mClock = mInjector.getClock();
mLogicalDisplay = logicalDisplay;
mDisplayId = mLogicalDisplay.getDisplayIdLocked();
- mTag = TAG + "[" + mDisplayId + "]";
- mHighBrightnessModeMetadata = hbmMetadata;
- mSuspendBlockerIdUnfinishedBusiness = getSuspendBlockerUnfinishedBusinessId(mDisplayId);
- mSuspendBlockerIdOnStateChanged = getSuspendBlockerOnStateChangedId(mDisplayId);
- mSuspendBlockerIdProxPositive = getSuspendBlockerProxPositiveId(mDisplayId);
- mSuspendBlockerIdProxNegative = getSuspendBlockerProxNegativeId(mDisplayId);
- mSuspendBlockerIdProxDebounce = getSuspendBlockerProxDebounceId(mDisplayId);
-
- mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
- mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
- mDisplayStatsId = mUniqueDisplayId.hashCode();
+ mSensorManager = sensorManager;
+ mHandler = new DisplayControllerHandler(handler.getLooper());
+ mDisplayDeviceConfig = logicalDisplay.getPrimaryDisplayDeviceLocked()
+ .getDisplayDeviceConfig();
mIsEnabled = logicalDisplay.isEnabledLocked();
mIsInTransition = logicalDisplay.isInTransitionLocked();
mIsDisplayInternal = logicalDisplay.getPrimaryDisplayDeviceLocked()
.getDisplayDeviceInfoLocked().type == Display.TYPE_INTERNAL;
- mHandler = new DisplayControllerHandler(handler.getLooper());
- mLastBrightnessEvent = new BrightnessEvent(mDisplayId);
- mTempBrightnessEvent = new BrightnessEvent(mDisplayId);
+ mWakelockController = mInjector.getWakelockController(mDisplayId, callbacks);
+ mDisplayPowerProximityStateController = mInjector.getDisplayPowerProximityStateController(
+ mWakelockController, mDisplayDeviceConfig, mHandler.getLooper(),
+ () -> updatePowerState(), mDisplayId, mSensorManager);
+ mDisplayStateController = new DisplayStateController(mDisplayPowerProximityStateController);
+ mTag = TAG + "[" + mDisplayId + "]";
mThermalBrightnessThrottlingDataId =
logicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId;
+ mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
+ mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
+ mDisplayStatsId = mUniqueDisplayId.hashCode();
+
+ mLastBrightnessEvent = new BrightnessEvent(mDisplayId);
+ mTempBrightnessEvent = new BrightnessEvent(mDisplayId);
if (mDisplayId == Display.DEFAULT_DISPLAY) {
mBatteryStats = BatteryStatsService.getService();
@@ -635,14 +529,10 @@
}
mSettingsObserver = new SettingsObserver(mHandler);
- mCallbacks = callbacks;
- mSensorManager = sensorManager;
mWindowManagerPolicy = LocalServices.getService(WindowManagerPolicy.class);
mBlanker = blanker;
mContext = context;
mBrightnessTracker = brightnessTracker;
- // TODO: b/186428377 update brightness setting when display changes
- mBrightnessSetting = brightnessSetting;
mOnBrightnessChangeRunnable = onBrightnessChangeRunnable;
PowerManager pm = context.getSystemService(PowerManager.class);
@@ -650,30 +540,12 @@
final Resources resources = context.getResources();
// DOZE AND DIM SETTINGS
- mScreenBrightnessDozeConfig = clampAbsoluteBrightness(
+ mScreenBrightnessDozeConfig = BrightnessUtils.clampAbsoluteBrightness(
pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE));
- mScreenBrightnessDimConfig = clampAbsoluteBrightness(
- pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM));
- mScreenBrightnessMinimumDimAmount = resources.getFloat(
- com.android.internal.R.dimen.config_screenBrightnessMinimumDimAmountFloat);
-
-
- // NORMAL SCREEN SETTINGS
- mScreenBrightnessDefault = clampAbsoluteBrightness(
- mLogicalDisplay.getDisplayInfoLocked().brightnessDefault);
-
- mAllowAutoBrightnessWhileDozingConfig = resources.getBoolean(
- com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing);
-
- mPersistBrightnessNitsForDefaultDisplay = resources.getBoolean(
- com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay);
-
- mDisplayDeviceConfig = logicalDisplay.getPrimaryDisplayDeviceLocked()
- .getDisplayDeviceConfig();
-
loadBrightnessRampRates();
mSkipScreenOnBrightnessRamp = resources.getBoolean(
- com.android.internal.R.bool.config_skipScreenOnBrightnessRamp);
+ R.bool.config_skipScreenOnBrightnessRamp);
+
Runnable modeChangeCallback = () -> {
sendUpdatePowerState();
postBrightnessChangeRunnable();
@@ -683,23 +555,38 @@
}
};
- HighBrightnessModeController hbmController = createHbmControllerLocked(modeChangeCallback);
+ HighBrightnessModeController hbmController = createHbmControllerLocked(hbmMetadata,
+ modeChangeCallback);
+ mBrightnessThrottler = createBrightnessThrottlerLocked();
- mBrightnessRangeController = new BrightnessRangeController(hbmController,
+ mBrightnessRangeController = mInjector.getBrightnessRangeController(hbmController,
modeChangeCallback, mDisplayDeviceConfig, mHandler, flags,
mDisplayDevice.getDisplayTokenLocked(),
mDisplayDevice.getDisplayDeviceInfoLocked());
- mBrightnessThrottler = createBrightnessThrottlerLocked();
+ mDisplayBrightnessController =
+ new DisplayBrightnessController(context, null,
+ mDisplayId, mLogicalDisplay.getDisplayInfoLocked().brightnessDefault,
+ brightnessSetting, () -> postBrightnessChangeRunnable(),
+ new HandlerExecutor(mHandler), flags);
+ mBrightnessClamperController = mInjector.getBrightnessClamperController(
+ mHandler, modeChangeCallback::run,
+ new BrightnessClamperController.DisplayDeviceData(
+ mUniqueDisplayId,
+ mThermalBrightnessThrottlingDataId,
+ logicalDisplay.getPowerThrottlingDataIdLocked(),
+ mDisplayDeviceConfig), mContext, flags);
// Seed the cached brightness
saveBrightnessInfo(getScreenBrightnessSetting());
+ mAutomaticBrightnessStrategy =
+ mDisplayBrightnessController.getAutomaticBrightnessStrategy();
DisplayWhiteBalanceSettings displayWhiteBalanceSettings = null;
DisplayWhiteBalanceController displayWhiteBalanceController = null;
if (mDisplayId == Display.DEFAULT_DISPLAY) {
try {
- displayWhiteBalanceController = injector.getDisplayWhiteBalanceController(
+ displayWhiteBalanceController = mInjector.getDisplayWhiteBalanceController(
mHandler, mSensorManager, resources);
displayWhiteBalanceSettings = new DisplayWhiteBalanceSettings(mContext, mHandler);
displayWhiteBalanceSettings.setCallbacks(this);
@@ -715,21 +602,24 @@
if (mDisplayId == Display.DEFAULT_DISPLAY) {
mCdsi = LocalServices.getService(ColorDisplayServiceInternal.class);
- boolean active = mCdsi.setReduceBrightColorsListener(new ReduceBrightColorsListener() {
- @Override
- public void onReduceBrightColorsActivationChanged(boolean activated,
- boolean userInitiated) {
- applyReduceBrightColorsSplineAdjustment();
+ if (mCdsi != null) {
+ boolean active = mCdsi.setReduceBrightColorsListener(
+ new ReduceBrightColorsListener() {
+ @Override
+ public void onReduceBrightColorsActivationChanged(boolean activated,
+ boolean userInitiated) {
+ applyReduceBrightColorsSplineAdjustment();
- }
+ }
- @Override
- public void onReduceBrightColorsStrengthChanged(int strength) {
+ @Override
+ public void onReduceBrightColorsStrengthChanged(int strength) {
+ applyReduceBrightColorsSplineAdjustment();
+ }
+ });
+ if (active) {
applyReduceBrightColorsSplineAdjustment();
}
- });
- if (active) {
- applyReduceBrightColorsSplineAdjustment();
}
} else {
mCdsi = null;
@@ -737,27 +627,17 @@
setUpAutoBrightness(context, handler);
- mColorFadeEnabled = !ActivityManager.isLowRamDeviceStatic()
+ mColorFadeEnabled = mInjector.isColorFadeEnabled()
&& !resources.getBoolean(
com.android.internal.R.bool.config_displayColorFadeDisabled);
mColorFadeFadesConfig = resources.getBoolean(
- com.android.internal.R.bool.config_animateScreenLights);
+ R.bool.config_animateScreenLights);
mDisplayBlanksAfterDozeConfig = resources.getBoolean(
- com.android.internal.R.bool.config_displayBlanksAfterDoze);
+ R.bool.config_displayBlanksAfterDoze);
mBrightnessBucketsInDozeConfig = resources.getBoolean(
- com.android.internal.R.bool.config_displayBrightnessBucketsInDoze);
-
- loadProximitySensor();
-
- loadNitBasedBrightnessSetting();
- mBrightnessToFollow = PowerManager.BRIGHTNESS_INVALID_FLOAT;
- mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
- mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
- mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
- mTemporaryAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
- mPendingAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ R.bool.config_displayBrightnessBucketsInDoze);
mBootCompleted = bootCompleted;
}
@@ -785,7 +665,7 @@
*/
@Override
public boolean isProximitySensorAvailable() {
- return mProximitySensor != null;
+ return mDisplayPowerProximityStateController.isProximitySensorAvailable();
}
/**
@@ -818,64 +698,6 @@
}
}
- @Override
- public int getDisplayId() {
- return mDisplayId;
- }
-
- @Override
- public int getLeadDisplayId() {
- return mLeadDisplayId;
- }
-
- @Override
- public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux,
- boolean slowChange) {
- mBrightnessRangeController.onAmbientLuxChange(ambientLux);
- if (nits == BrightnessMappingStrategy.INVALID_NITS) {
- mBrightnessToFollow = leadDisplayBrightness;
- } else {
- float brightness = getBrightnessFromNits(nits);
- if (isValidBrightnessValue(brightness)) {
- mBrightnessToFollow = brightness;
- } else {
- // The device does not support nits
- mBrightnessToFollow = leadDisplayBrightness;
- }
- }
- mBrightnessToFollowSlowChange = slowChange;
- sendUpdatePowerState();
- }
-
- @Override
- public void addDisplayBrightnessFollower(@NonNull DisplayPowerControllerInterface follower) {
- synchronized (mLock) {
- mDisplayBrightnessFollowers.append(follower.getDisplayId(), follower);
- sendUpdatePowerStateLocked();
- }
- }
-
- @Override
- public void removeDisplayBrightnessFollower(@NonNull DisplayPowerControllerInterface follower) {
- synchronized (mLock) {
- mDisplayBrightnessFollowers.remove(follower.getDisplayId());
- mHandler.postAtTime(() -> follower.setBrightnessToFollow(
- PowerManager.BRIGHTNESS_INVALID_FLOAT, BrightnessMappingStrategy.INVALID_NITS,
- /* ambientLux= */ 0, /* slowChange= */ false), mClock.uptimeMillis());
- }
- }
-
- @GuardedBy("mLock")
- private void clearDisplayBrightnessFollowersLocked() {
- for (int i = 0; i < mDisplayBrightnessFollowers.size(); i++) {
- DisplayPowerControllerInterface follower = mDisplayBrightnessFollowers.valueAt(i);
- mHandler.postAtTime(() -> follower.setBrightnessToFollow(
- PowerManager.BRIGHTNESS_INVALID_FLOAT, BrightnessMappingStrategy.INVALID_NITS,
- /* ambientLux= */ 0, /* slowChange= */ false), mClock.uptimeMillis());
- }
- mDisplayBrightnessFollowers.clear();
- }
-
@Nullable
@Override
public ParceledListSlice<AmbientBrightnessDayStats> getAmbientBrightnessStats(
@@ -923,13 +745,8 @@
return true;
}
- boolean changed = false;
-
- if (waitForNegativeProximity
- && !mPendingWaitForNegativeProximityLocked) {
- mPendingWaitForNegativeProximityLocked = true;
- changed = true;
- }
+ boolean changed = mDisplayPowerProximityStateController
+ .setPendingWaitForNegativeProximityLocked(waitForNegativeProximity);
if (mPendingRequestLocked == null) {
mPendingRequestLocked = new DisplayPowerRequest(request);
@@ -953,18 +770,23 @@
@Override
public void overrideDozeScreenState(int displayState) {
- synchronized (mLock) {
- if (mDisplayOffloadSession == null ||
- !DisplayOffloadSession.isSupportedOffloadState(displayState)) {
+ mHandler.postAtTime(() -> {
+ if (mDisplayOffloadSession == null
+ || !(DisplayOffloadSession.isSupportedOffloadState(displayState)
+ || displayState == Display.STATE_UNKNOWN)) {
return;
}
- mDozeStateOverride = displayState;
+ mDisplayStateController.overrideDozeScreenState(displayState);
sendUpdatePowerState();
- }
+ }, mClock.uptimeMillis());
}
@Override
public void setDisplayOffloadSession(DisplayOffloadSession session) {
+ if (session == mDisplayOffloadSession) {
+ return;
+ }
+ unblockScreenOnByDisplayOffload();
mDisplayOffloadSession = session;
}
@@ -981,14 +803,14 @@
* when displays get swapped on foldable devices. For example, different brightness properties
* of each display need to be properly reflected in AutomaticBrightnessController.
*
- * Make sure DisplayManagerService.mSyncRoot is held when this is called
+ * Make sure DisplayManagerService.mSyncRoot lock is held when this is called
*/
@Override
public void onDisplayChanged(HighBrightnessModeMetadata hbmMetadata, int leadDisplayId) {
mLeadDisplayId = leadDisplayId;
final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
if (device == null) {
- Slog.wtf(mTag, "Display Device is null in DisplayPowerController for display: "
+ Slog.wtf(mTag, "Display Device is null in DisplayPowerController2 for display: "
+ mLogicalDisplay.getDisplayIdLocked());
return;
}
@@ -1004,6 +826,9 @@
.getDisplayDeviceInfoLocked().type == Display.TYPE_INTERNAL;
final String thermalBrightnessThrottlingDataId =
mLogicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId;
+ final String powerThrottlingDataId =
+ mLogicalDisplay.getPowerThrottlingDataIdLocked();
+
mHandler.postAtTime(() -> {
boolean changed = false;
if (mDisplayDevice != device) {
@@ -1014,9 +839,9 @@
mDisplayDeviceConfig = config;
mThermalBrightnessThrottlingDataId = thermalBrightnessThrottlingDataId;
loadFromDisplayDeviceConfig(token, info, hbmMetadata);
- loadNitBasedBrightnessSetting();
+ mDisplayPowerProximityStateController.notifyDisplayDeviceChanged(config);
- /// Since the underlying display-device changed, we really don't know the
+ // Since the underlying display-device changed, we really don't know the
// last command that was sent to change it's state. Let's assume it is unknown so
// that we trigger a change immediately.
mPowerState.resetScreenState();
@@ -1034,7 +859,16 @@
mIsEnabled = isEnabled;
mIsInTransition = isInTransition;
}
+
mIsDisplayInternal = isDisplayInternal;
+ // using local variables here, when mBrightnessThrottler is removed,
+ // mThermalBrightnessThrottlingDataId could be removed as well
+ // changed = true will be not needed - clampers are maintaining their state and
+ // will call updatePowerState if needed.
+ mBrightnessClamperController.onDisplayChanged(
+ new BrightnessClamperController.DisplayDeviceData(uniqueId,
+ thermalBrightnessThrottlingDataId, powerThrottlingDataId, config));
+
if (changed) {
updatePowerState();
}
@@ -1044,7 +878,7 @@
/**
* Unregisters all listeners and interrupts all running threads; halting future work.
*
- * This method should be called when the DisplayPowerController is no longer in use; i.e. when
+ * This method should be called when the DisplayPowerController2 is no longer in use; i.e. when
* the {@link #mDisplayId display} has been removed.
*/
@Override
@@ -1060,9 +894,7 @@
mAutomaticBrightnessController.stop();
}
- if (mBrightnessSetting != null) {
- mBrightnessSetting.unregisterListener(mBrightnessSettingListener);
- }
+ mDisplayBrightnessController.stop();
mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
}
@@ -1073,11 +905,11 @@
// All properties that depend on the associated DisplayDevice and the DDC must be
// updated here.
loadBrightnessRampRates();
- loadProximitySensor();
loadNitsRange(mContext.getResources());
setUpAutoBrightness(mContext, mHandler);
reloadReduceBrightColours();
setAnimatorRampSpeeds(/* isIdleMode= */ false);
+
mBrightnessRangeController.loadFromConfig(hbmMetadata, token, info, mDisplayDeviceConfig);
mBrightnessThrottler.loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig(
mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId(),
@@ -1126,22 +958,30 @@
noteScreenBrightness(mPowerState.getScreenBrightness());
// Initialize all of the brightness tracking state
- final float brightness = convertToAdjustedNits(mPowerState.getScreenBrightness());
+ final float brightness = mDisplayBrightnessController.convertToAdjustedNits(
+ mPowerState.getScreenBrightness());
if (mBrightnessTracker != null && brightness >= PowerManager.BRIGHTNESS_MIN) {
mBrightnessTracker.start(brightness);
}
- mBrightnessSettingListener = brightnessValue -> {
+
+ BrightnessSetting.BrightnessSettingListener brightnessSettingListener = brightnessValue -> {
Message msg = mHandler.obtainMessage(MSG_UPDATE_BRIGHTNESS, brightnessValue);
mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
};
+ mDisplayBrightnessController
+ .registerBrightnessSettingChangeListener(brightnessSettingListener);
- mBrightnessSetting.registerListener(mBrightnessSettingListener);
mContext.getContentResolver().registerContentObserver(
Settings.System.getUriFor(Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ),
false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
mContext.getContentResolver().registerContentObserver(
Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE),
false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
+ if (mFlags.areAutoBrightnessModesEnabled()) {
+ mContext.getContentResolver().registerContentObserver(
+ Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FOR_ALS),
+ /* notifyForDescendants= */ false, mSettingsObserver, UserHandle.USER_CURRENT);
+ }
handleBrightnessModeChange();
}
@@ -1165,12 +1005,21 @@
if (isIdleScreenBrightnessEnabled) {
BrightnessMappingStrategy idleModeBrightnessMapper =
BrightnessMappingStrategy.create(context, mDisplayDeviceConfig,
- AUTO_BRIGHTNESS_MODE_IDLE, mDisplayWhiteBalanceController);
+ AUTO_BRIGHTNESS_MODE_IDLE,
+ mDisplayWhiteBalanceController);
if (idleModeBrightnessMapper != null) {
- brightnessMappers.append(AUTO_BRIGHTNESS_MODE_IDLE, idleModeBrightnessMapper);
+ brightnessMappers.append(AUTO_BRIGHTNESS_MODE_IDLE,
+ idleModeBrightnessMapper);
}
}
+ BrightnessMappingStrategy dozeModeBrightnessMapper =
+ BrightnessMappingStrategy.create(context, mDisplayDeviceConfig,
+ AUTO_BRIGHTNESS_MODE_DOZE, mDisplayWhiteBalanceController);
+ if (mFlags.areAutoBrightnessModesEnabled() && dozeModeBrightnessMapper != null) {
+ brightnessMappers.put(AUTO_BRIGHTNESS_MODE_DOZE, dozeModeBrightnessMapper);
+ }
+
float userLux = BrightnessMappingStrategy.INVALID_LUX;
float userNits = BrightnessMappingStrategy.INVALID_NITS;
if (mAutomaticBrightnessController != null) {
@@ -1180,7 +1029,7 @@
if (defaultModeBrightnessMapper != null) {
final float dozeScaleFactor = context.getResources().getFraction(
- com.android.internal.R.fraction.config_screenAutoBrightnessDozeScaleFactor,
+ R.fraction.config_screenAutoBrightnessDozeScaleFactor,
1, 1);
// Ambient Lux - Active Mode Brightness Thresholds
@@ -1264,14 +1113,14 @@
long darkeningLightDebounceIdle = mDisplayDeviceConfig
.getAutoBrightnessDarkeningLightDebounceIdle();
boolean autoBrightnessResetAmbientLuxAfterWarmUp = context.getResources().getBoolean(
- com.android.internal.R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp);
+ R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp);
int lightSensorWarmUpTimeConfig = context.getResources().getInteger(
- com.android.internal.R.integer.config_lightSensorWarmupTime);
+ R.integer.config_lightSensorWarmupTime);
int lightSensorRate = context.getResources().getInteger(
- com.android.internal.R.integer.config_autoBrightnessLightSensorRate);
+ R.integer.config_autoBrightnessLightSensorRate);
int initialLightSensorRate = context.getResources().getInteger(
- com.android.internal.R.integer.config_autoBrightnessInitialLightSensorRate);
+ R.integer.config_autoBrightnessInitialLightSensorRate);
if (initialLightSensorRate == -1) {
initialLightSensorRate = lightSensorRate;
} else if (initialLightSensorRate > lightSensorRate) {
@@ -1301,7 +1150,11 @@
screenBrightnessThresholdsIdle, mContext, mBrightnessRangeController,
mBrightnessThrottler, mDisplayDeviceConfig.getAmbientHorizonShort(),
mDisplayDeviceConfig.getAmbientHorizonLong(), userLux, userNits);
+ mDisplayBrightnessController.setAutomaticBrightnessController(
+ mAutomaticBrightnessController);
+ mAutomaticBrightnessStrategy
+ .setAutomaticBrightnessController(mAutomaticBrightnessController);
mBrightnessEventRingBuffer =
new RingBuffer<>(BrightnessEvent.class, RINGBUFFER_MAX);
@@ -1351,7 +1204,7 @@
} else {
Slog.w(mTag, "Screen brightness nits configuration is unavailable; falling back");
mNitsRange = BrightnessMappingStrategy.getFloatArray(resources
- .obtainTypedArray(com.android.internal.R.array.config_screenBrightnessNits));
+ .obtainTypedArray(R.array.config_screenBrightnessNits));
}
}
@@ -1369,7 +1222,6 @@
mAutomaticBrightnessController.switchMode(mode);
setAnimatorRampSpeeds(isIdle);
}
-
Message msg = mHandler.obtainMessage();
msg.what = MSG_SET_DWBC_STRONG_MODE;
msg.arg1 = isIdle ? 1 : 0;
@@ -1421,28 +1273,14 @@
/** Clean up all resources that are accessed via the {@link #mHandler} thread. */
private void cleanupHandlerThreadAfterStop() {
- setProximitySensorEnabled(false);
+ mDisplayPowerProximityStateController.cleanup();
mBrightnessRangeController.stop();
mBrightnessThrottler.stop();
+ mBrightnessClamperController.stop();
mHandler.removeCallbacksAndMessages(null);
// Release any outstanding wakelocks we're still holding because of pending messages.
- if (mUnfinishedBusiness) {
- mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdUnfinishedBusiness);
- mUnfinishedBusiness = false;
- }
- if (mOnStateChangedPending) {
- mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdOnStateChanged);
- mOnStateChangedPending = false;
- }
- for (int i = 0; i < mOnProximityPositiveMessages; i++) {
- mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxPositive);
- }
- mOnProximityPositiveMessages = 0;
- for (int i = 0; i < mOnProximityNegativeMessages; i++) {
- mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxNegative);
- }
- mOnProximityNegativeMessages = 0;
+ mWakelockController.releaseAll();
final float brightness = mPowerState != null
? mPowerState.getScreenBrightness()
@@ -1457,10 +1295,10 @@
if (mScreenOffBrightnessSensorController != null) {
mScreenOffBrightnessSensorController.stop();
}
+
if (mDisplayWhiteBalanceController != null) {
mDisplayWhiteBalanceController.setEnabled(false);
}
-
}
// Call from handler thread
@@ -1476,7 +1314,6 @@
final boolean mustNotify;
final int previousPolicy;
boolean mustInitialize = false;
- int brightnessAdjustmentFlags = 0;
mBrightnessReasonTemp.set(null);
mTempBrightnessEvent.reset();
SparseArray<DisplayPowerControllerInterface> displayBrightnessFollowers;
@@ -1491,7 +1328,7 @@
if (mPowerRequest == null) {
mPowerRequest = new DisplayPowerRequest(mPendingRequestLocked);
- updatePendingProximityRequestsLocked();
+ mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
mPendingRequestChangedLocked = false;
mustInitialize = true;
// Assume we're on and bright until told otherwise, since that's the state we turn
@@ -1500,7 +1337,7 @@
} else if (mPendingRequestChangedLocked) {
previousPolicy = mPowerRequest.policy;
mPowerRequest.copyFrom(mPendingRequestLocked);
- updatePendingProximityRequestsLocked();
+ mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
mPendingRequestChangedLocked = false;
mDisplayReadyLocked = false;
} else {
@@ -1512,105 +1349,8 @@
displayBrightnessFollowers = mDisplayBrightnessFollowers.clone();
}
- // Compute the basic display state using the policy.
- // We might override this below based on other factors.
- // Initialise brightness as invalid.
- int state;
- float brightnessState = PowerManager.BRIGHTNESS_INVALID_FLOAT;
- boolean performScreenOffTransition = false;
- switch (mPowerRequest.policy) {
- case DisplayPowerRequest.POLICY_OFF:
- state = Display.STATE_OFF;
- performScreenOffTransition = true;
- break;
- case DisplayPowerRequest.POLICY_DOZE:
- if (mDozeStateOverride != Display.STATE_UNKNOWN) {
- state = mDozeStateOverride;
- } else if (mPowerRequest.dozeScreenState != Display.STATE_UNKNOWN) {
- state = mPowerRequest.dozeScreenState;
- } else {
- state = Display.STATE_DOZE;
- }
- if (!mAllowAutoBrightnessWhileDozingConfig) {
- brightnessState = mPowerRequest.dozeScreenBrightness;
- mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE);
- }
- break;
- case DisplayPowerRequest.POLICY_DIM:
- case DisplayPowerRequest.POLICY_BRIGHT:
- default:
- state = Display.STATE_ON;
- break;
- }
- assert (state != Display.STATE_UNKNOWN);
-
- if (mScreenOffBrightnessSensorController != null) {
- mScreenOffBrightnessSensorController.setLightSensorEnabled(mUseAutoBrightness
- && mIsEnabled && (state == Display.STATE_OFF || (state == Display.STATE_DOZE
- && !mAllowAutoBrightnessWhileDozingConfig))
- && mLeadDisplayId == Layout.NO_LEAD_DISPLAY);
- }
-
- boolean skipRampBecauseOfProximityChangeToNegative = false;
- // Apply the proximity sensor.
- if (mProximitySensor != null) {
- if (mPowerRequest.useProximitySensor && state != Display.STATE_OFF) {
- // At this point the policy says that the screen should be on, but we've been
- // asked to listen to the prox sensor to adjust the display state, so lets make
- // sure the sensor is on.
- setProximitySensorEnabled(true);
- if (!mScreenOffBecauseOfProximity
- && mProximity == PROXIMITY_POSITIVE
- && !mIgnoreProximityUntilChanged) {
- // Prox sensor already reporting "near" so we should turn off the screen.
- // Also checked that we aren't currently set to ignore the proximity sensor
- // temporarily.
- mScreenOffBecauseOfProximity = true;
- sendOnProximityPositiveWithWakelock();
- }
- } else if (mWaitingForNegativeProximity
- && mScreenOffBecauseOfProximity
- && mProximity == PROXIMITY_POSITIVE
- && state != Display.STATE_OFF) {
- // The policy says that we should have the screen on, but it's off due to the prox
- // and we've been asked to wait until the screen is far from the user to turn it
- // back on. Let keep the prox sensor on so we can tell when it's far again.
- setProximitySensorEnabled(true);
- } else {
- // We haven't been asked to use the prox sensor and we're not waiting on the screen
- // to turn back on...so lets shut down the prox sensor.
- setProximitySensorEnabled(false);
- mWaitingForNegativeProximity = false;
- }
-
- if (mScreenOffBecauseOfProximity
- && (mProximity != PROXIMITY_POSITIVE || mIgnoreProximityUntilChanged)) {
- // The screen *was* off due to prox being near, but now it's "far" so lets turn
- // the screen back on. Also turn it back on if we've been asked to ignore the
- // prox sensor temporarily.
- mScreenOffBecauseOfProximity = false;
- skipRampBecauseOfProximityChangeToNegative = true;
- sendOnProximityNegativeWithWakelock();
- }
- } else {
- setProximitySensorEnabled(false);
- mWaitingForNegativeProximity = false;
- mIgnoreProximityUntilChanged = false;
-
- if (mScreenOffBecauseOfProximity) {
- // The screen *was* off due to prox being near, but now there's no prox sensor, so
- // let's turn the screen back on.
- mScreenOffBecauseOfProximity = false;
- skipRampBecauseOfProximityChangeToNegative = true;
- sendOnProximityNegativeWithWakelock();
- }
- }
-
- if (!mIsEnabled
- || mIsInTransition
- || mScreenOffBecauseOfProximity) {
- state = Display.STATE_OFF;
- }
+ int state = mDisplayStateController
+ .updateDisplayState(mPowerRequest, mIsEnabled, mIsInTransition);
// Initialize things the first time the power state is changed.
if (mustInitialize) {
@@ -1620,157 +1360,100 @@
// Animate the screen state change unless already animating.
// The transition may be deferred, so after this point we will use the
// actual state instead of the desired one.
- final int oldState = mPowerState.getScreenState();
- animateScreenStateChange(state, performScreenOffTransition);
+ animateScreenStateChange(state, mDisplayStateController.shouldPerformScreenOffTransition());
state = mPowerState.getScreenState();
- boolean slowChange = false;
- if (state == Display.STATE_OFF) {
- brightnessState = PowerManager.BRIGHTNESS_OFF_FLOAT;
- mBrightnessReasonTemp.setReason(BrightnessReason.REASON_SCREEN_OFF);
+ // Switch to doze auto-brightness mode if needed
+ if (mFlags.areAutoBrightnessModesEnabled() && mAutomaticBrightnessController != null
+ && !mAutomaticBrightnessController.isInIdleMode()) {
+ setAutomaticScreenBrightnessMode(Display.isDozeState(state)
+ ? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT);
}
- if (Float.isNaN(brightnessState) && isValidBrightnessValue(mBrightnessToFollow)) {
- brightnessState = mBrightnessToFollow;
- slowChange = mBrightnessToFollowSlowChange;
- mBrightnessReasonTemp.setReason(BrightnessReason.REASON_FOLLOWER);
+ final boolean userSetBrightnessChanged = mDisplayBrightnessController
+ .updateUserSetScreenBrightness();
+
+ DisplayBrightnessState displayBrightnessState = mDisplayBrightnessController
+ .updateBrightness(mPowerRequest, state);
+ float brightnessState = displayBrightnessState.getBrightness();
+ float rawBrightnessState = displayBrightnessState.getBrightness();
+ mBrightnessReasonTemp.set(displayBrightnessState.getBrightnessReason());
+ boolean slowChange = displayBrightnessState.isSlowChange();
+ // custom transition duration
+ float customAnimationRate = displayBrightnessState.getCustomAnimationRate();
+
+ // Set up the ScreenOff controller used when coming out of SCREEN_OFF and the ALS sensor
+ // doesn't yet have a valid lux value to use with auto-brightness.
+ if (mScreenOffBrightnessSensorController != null) {
+ mScreenOffBrightnessSensorController
+ .setLightSensorEnabled(displayBrightnessState.getShouldUseAutoBrightness()
+ && mIsEnabled && (state == Display.STATE_OFF
+ || (state == Display.STATE_DOZE
+ && !mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()))
+ && mLeadDisplayId == Layout.NO_LEAD_DISPLAY);
}
- if ((Float.isNaN(brightnessState))
- && isValidBrightnessValue(mPowerRequest.screenBrightnessOverride)) {
- brightnessState = mPowerRequest.screenBrightnessOverride;
- mBrightnessReasonTemp.setReason(BrightnessReason.REASON_OVERRIDE);
- mAppliedScreenBrightnessOverride = true;
- } else {
- mAppliedScreenBrightnessOverride = false;
- }
-
- final boolean autoBrightnessEnabledInDoze =
- mAllowAutoBrightnessWhileDozingConfig && Display.isDozeState(state);
- final boolean autoBrightnessEnabled = mUseAutoBrightness
- && (state == Display.STATE_ON || autoBrightnessEnabledInDoze)
- && mBrightnessReasonTemp.getReason() != BrightnessReason.REASON_OVERRIDE
- && mAutomaticBrightnessController != null;
- final boolean autoBrightnessDisabledDueToDisplayOff = mUseAutoBrightness
- && !(state == Display.STATE_ON || autoBrightnessEnabledInDoze);
- final int autoBrightnessState = autoBrightnessEnabled
- && mBrightnessReasonTemp.getReason() != BrightnessReason.REASON_FOLLOWER
- ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
- : autoBrightnessDisabledDueToDisplayOff
- ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
- : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
-
- final boolean userSetBrightnessChanged = updateUserSetScreenBrightness();
-
- // Use the temporary screen brightness if there isn't an override, either from
- // WindowManager or based on the display state.
- if (isValidBrightnessValue(mTemporaryScreenBrightness)) {
- brightnessState = mTemporaryScreenBrightness;
- mAppliedTemporaryBrightness = true;
- mBrightnessReasonTemp.setReason(BrightnessReason.REASON_TEMPORARY);
- } else {
- mAppliedTemporaryBrightness = false;
- }
-
- final boolean autoBrightnessAdjustmentChanged = updateAutoBrightnessAdjustment();
-
- // Use the autobrightness adjustment override if set.
- final float autoBrightnessAdjustment;
- if (!Float.isNaN(mTemporaryAutoBrightnessAdjustment)) {
- autoBrightnessAdjustment = mTemporaryAutoBrightnessAdjustment;
- brightnessAdjustmentFlags = BrightnessReason.ADJUSTMENT_AUTO_TEMP;
- mAppliedTemporaryAutoBrightnessAdjustment = true;
- } else {
- autoBrightnessAdjustment = mAutoBrightnessAdjustment;
- brightnessAdjustmentFlags = BrightnessReason.ADJUSTMENT_AUTO;
- mAppliedTemporaryAutoBrightnessAdjustment = false;
- }
- // Apply brightness boost.
- // We do this here after deciding whether auto-brightness is enabled so that we don't
- // disable the light sensor during this temporary state. That way when boost ends we will
- // be able to resume normal auto-brightness behavior without any delay.
- if (mPowerRequest.boostScreenBrightness
- && brightnessState != PowerManager.BRIGHTNESS_OFF_FLOAT) {
- brightnessState = PowerManager.BRIGHTNESS_MAX;
- mBrightnessReasonTemp.setReason(BrightnessReason.REASON_BOOST);
- mAppliedBrightnessBoost = true;
- } else {
- mAppliedBrightnessBoost = false;
- }
+ // Take note if the short term model was already active before applying the current
+ // request changes.
+ final boolean wasShortTermModelActive =
+ mAutomaticBrightnessStrategy.isShortTermModelActive();
+ mAutomaticBrightnessStrategy.setAutoBrightnessState(state,
+ mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig(),
+ mBrightnessReasonTemp.getReason(), mPowerRequest.policy,
+ mDisplayBrightnessController.getLastUserSetScreenBrightness(),
+ userSetBrightnessChanged);
// If the brightness is already set then it's been overridden by something other than the
// user, or is a temporary adjustment.
boolean userInitiatedChange = (Float.isNaN(brightnessState))
- && (autoBrightnessAdjustmentChanged || userSetBrightnessChanged);
- boolean wasShortTermModelActive = false;
- // Configure auto-brightness.
- if (mAutomaticBrightnessController != null) {
- wasShortTermModelActive = mAutomaticBrightnessController.hasUserDataPoints();
- mAutomaticBrightnessController.configure(autoBrightnessState,
- mBrightnessConfiguration,
- mLastUserSetScreenBrightness,
- userSetBrightnessChanged, autoBrightnessAdjustment,
- autoBrightnessAdjustmentChanged, mPowerRequest.policy,
- mShouldResetShortTermModel);
- mShouldResetShortTermModel = false;
- }
- mBrightnessRangeController.setAutoBrightnessEnabled(autoBrightnessEnabled
+ && (mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged()
+ || userSetBrightnessChanged);
+
+ mBrightnessRangeController.setAutoBrightnessEnabled(
+ mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()
? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
- : autoBrightnessDisabledDueToDisplayOff
+ : mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff()
? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
: AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
- if (mBrightnessTracker != null) {
- mBrightnessTracker.setShouldCollectColorSample(mBrightnessConfiguration != null
- && mBrightnessConfiguration.shouldCollectColorSamples());
- }
-
- boolean updateScreenBrightnessSetting = false;
- float rawBrightnessState = brightnessState;
-
+ boolean updateScreenBrightnessSetting =
+ displayBrightnessState.shouldUpdateScreenBrightnessSetting();
+ float currentBrightnessSetting = mDisplayBrightnessController.getCurrentBrightness();
// Apply auto-brightness.
+ int brightnessAdjustmentFlags = 0;
if (Float.isNaN(brightnessState)) {
- float newAutoBrightnessAdjustment = autoBrightnessAdjustment;
- if (autoBrightnessEnabled) {
- rawBrightnessState = mAutomaticBrightnessController
- .getRawAutomaticScreenBrightness();
- brightnessState = mAutomaticBrightnessController.getAutomaticScreenBrightness(
+ if (mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()) {
+ brightnessState = mAutomaticBrightnessStrategy.getAutomaticScreenBrightness(
mTempBrightnessEvent);
- newAutoBrightnessAdjustment =
- mAutomaticBrightnessController.getAutomaticScreenBrightnessAdjustment();
- }
- if (isValidBrightnessValue(brightnessState)
- || brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT) {
- // Use current auto-brightness value and slowly adjust to changes.
- brightnessState = clampScreenBrightness(brightnessState);
- if (mAppliedAutoBrightness && !autoBrightnessAdjustmentChanged) {
- slowChange = true; // slowly adapt to auto-brightness
+ if (BrightnessUtils.isValidBrightnessValue(brightnessState)
+ || brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT) {
+ rawBrightnessState = mAutomaticBrightnessController
+ .getRawAutomaticScreenBrightness();
+ brightnessState = clampScreenBrightness(brightnessState);
+ // slowly adapt to auto-brightness
+ // TODO(b/253226419): slowChange should be decided by strategy.updateBrightness
+ slowChange = mAutomaticBrightnessStrategy.hasAppliedAutoBrightness()
+ && !mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged();
+ brightnessAdjustmentFlags =
+ mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentReasonsFlags();
+ updateScreenBrightnessSetting = currentBrightnessSetting != brightnessState;
+ mAutomaticBrightnessStrategy.setAutoBrightnessApplied(true);
+ mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC);
+ if (mScreenOffBrightnessSensorController != null) {
+ mScreenOffBrightnessSensorController.setLightSensorEnabled(false);
+ }
+ } else {
+ mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
}
- updateScreenBrightnessSetting = mCurrentScreenBrightnessSetting != brightnessState;
- mAppliedAutoBrightness = true;
- mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC);
- if (mScreenOffBrightnessSensorController != null) {
- mScreenOffBrightnessSensorController.setLightSensorEnabled(false);
- }
- } else {
- mAppliedAutoBrightness = false;
- }
- if (autoBrightnessAdjustment != newAutoBrightnessAdjustment) {
- // If the autobrightness controller has decided to change the adjustment value
- // used, make sure that's reflected in settings.
- putAutoBrightnessAdjustmentSetting(newAutoBrightnessAdjustment);
- } else {
- // Adjustment values resulted in no change
- brightnessAdjustmentFlags = 0;
}
} else {
// Any non-auto-brightness values such as override or temporary should still be subject
// to clamping so that they don't go beyond the current max as specified by HBM
// Controller.
brightnessState = clampScreenBrightness(brightnessState);
- mAppliedAutoBrightness = false;
- brightnessAdjustmentFlags = 0;
+ mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
}
+
// Use default brightness when dozing unless overridden.
if ((Float.isNaN(brightnessState))
&& Display.isDozeState(state)) {
@@ -1781,14 +1464,15 @@
// The ALS is not available yet - use the screen off sensor to determine the initial
// brightness
- if (Float.isNaN(brightnessState) && autoBrightnessEnabled
+ if (Float.isNaN(brightnessState) && mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()
&& mScreenOffBrightnessSensorController != null) {
rawBrightnessState =
mScreenOffBrightnessSensorController.getAutomaticScreenBrightness();
brightnessState = rawBrightnessState;
- if (isValidBrightnessValue(brightnessState)) {
+ if (BrightnessUtils.isValidBrightnessValue(brightnessState)) {
brightnessState = clampScreenBrightness(brightnessState);
- updateScreenBrightnessSetting = mCurrentScreenBrightnessSetting != brightnessState;
+ updateScreenBrightnessSetting = mDisplayBrightnessController.getCurrentBrightness()
+ != brightnessState;
mBrightnessReasonTemp.setReason(
BrightnessReason.REASON_SCREEN_OFF_BRIGHTNESS_SENSOR);
}
@@ -1796,9 +1480,9 @@
// Apply manual brightness.
if (Float.isNaN(brightnessState)) {
- rawBrightnessState = mCurrentScreenBrightnessSetting;
+ rawBrightnessState = currentBrightnessSetting;
brightnessState = clampScreenBrightness(rawBrightnessState);
- if (brightnessState != mCurrentScreenBrightnessSetting) {
+ if (brightnessState != currentBrightnessSetting) {
// The manually chosen screen brightness is outside of the currently allowed
// range (i.e., high-brightness-mode), make sure we tell the rest of the system
// by updating the setting.
@@ -1811,7 +1495,8 @@
: mAutomaticBrightnessController.getAmbientLux();
for (int i = 0; i < displayBrightnessFollowers.size(); i++) {
DisplayPowerControllerInterface follower = displayBrightnessFollowers.valueAt(i);
- follower.setBrightnessToFollow(rawBrightnessState, convertToNits(rawBrightnessState),
+ follower.setBrightnessToFollow(rawBrightnessState,
+ mDisplayBrightnessController.convertToNits(rawBrightnessState),
ambientLux, slowChange);
}
@@ -1823,64 +1508,25 @@
// Note throttling effectively changes the allowed brightness range, so, similarly to HBM,
// we broadcast this change through setting.
final float unthrottledBrightnessState = brightnessState;
- if (mBrightnessThrottler.isThrottled()) {
- mTempBrightnessEvent.setThermalMax(mBrightnessThrottler.getBrightnessCap());
- brightnessState = Math.min(brightnessState, mBrightnessThrottler.getBrightnessCap());
- mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_THROTTLED);
- if (!mAppliedThrottling) {
- // Brightness throttling is needed, so do so quickly.
- // Later, when throttling is removed, we let other mechanisms decide on speed.
- slowChange = false;
- }
- mAppliedThrottling = true;
- } else if (mAppliedThrottling) {
- mAppliedThrottling = false;
- }
+ DisplayBrightnessState clampedState = mBrightnessClamperController.clamp(mPowerRequest,
+ brightnessState, slowChange);
+
+ brightnessState = clampedState.getBrightness();
+ slowChange = clampedState.isSlowChange();
+ // faster rate wins, at this point customAnimationRate == -1, strategy does not control
+ // customAnimationRate. Should be revisited if strategy start setting this value
+ customAnimationRate = Math.max(customAnimationRate, clampedState.getCustomAnimationRate());
+ mBrightnessReasonTemp.addModifier(clampedState.getBrightnessReason().getModifier());
if (updateScreenBrightnessSetting) {
// Tell the rest of the system about the new brightness in case we had to change it
// for things like auto-brightness or high-brightness-mode. Note that we do this
- // before applying the low power or dim transformations so that the slider
- // accurately represents the full possible range, even if they range changes what
- // it means in absolute terms.
- updateScreenBrightnessSetting(brightnessState);
- }
-
- // Apply dimming by at least some minimum amount when user activity
- // timeout is about to expire.
- if (mPowerRequest.policy == DisplayPowerRequest.POLICY_DIM) {
- if (brightnessState > PowerManager.BRIGHTNESS_MIN) {
- brightnessState = Math.max(
- Math.min(brightnessState - mScreenBrightnessMinimumDimAmount,
- mScreenBrightnessDimConfig),
- PowerManager.BRIGHTNESS_MIN);
- mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_DIMMED);
- }
- if (!mAppliedDimming) {
- slowChange = false;
- }
- mAppliedDimming = true;
- } else if (mAppliedDimming) {
- slowChange = false;
- mAppliedDimming = false;
- }
- // If low power mode is enabled, scale brightness by screenLowPowerBrightnessFactor
- // as long as it is above the minimum threshold.
- if (mPowerRequest.lowPowerMode) {
- if (brightnessState > PowerManager.BRIGHTNESS_MIN) {
- final float brightnessFactor =
- Math.min(mPowerRequest.screenLowPowerBrightnessFactor, 1);
- final float lowPowerBrightnessFloat = (brightnessState * brightnessFactor);
- brightnessState = Math.max(lowPowerBrightnessFloat, PowerManager.BRIGHTNESS_MIN);
- mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_LOW_POWER);
- }
- if (!mAppliedLowPower) {
- slowChange = false;
- }
- mAppliedLowPower = true;
- } else if (mAppliedLowPower) {
- slowChange = false;
- mAppliedLowPower = false;
+ // only considering maxBrightness (ignoring brightness modifiers like low power or dim)
+ // so that the slider accurately represents the full possible range,
+ // even if they range changes what it means in absolute terms.
+ mDisplayBrightnessController.updateScreenBrightnessSetting(
+ MathUtils.constrain(unthrottledBrightnessState,
+ clampedState.getMinBrightness(), clampedState.getMaxBrightness()));
}
// The current brightness to use has been calculated at this point, and HbmController should
@@ -1889,13 +1535,15 @@
// brightness sources (such as an app override) are not saved to the setting, but should be
// reflected in HBM calculations.
mBrightnessRangeController.onBrightnessChanged(brightnessState, unthrottledBrightnessState,
- mBrightnessThrottler.getBrightnessMaxReason());
+ mBrightnessClamperController.getBrightnessMaxReason());
// Animate the screen brightness when the screen is on or dozing.
- // Skip the animation when the screen is off.
+ // Skip the animation when the screen is off or suspended.
boolean brightnessAdjusted = false;
final boolean brightnessIsTemporary =
- mAppliedTemporaryBrightness || mAppliedTemporaryAutoBrightnessAdjustment;
+ (mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_TEMPORARY)
+ || mAutomaticBrightnessStrategy
+ .isTemporaryAutoBrightnessAdjustmentApplied();
if (!mPendingScreenOff) {
if (mSkipScreenOnBrightnessRamp) {
if (state == Display.STATE_ON) {
@@ -1916,7 +1564,8 @@
}
final boolean initialRampSkip = (state == Display.STATE_ON && mSkipRampState
- != RAMP_STATE_SKIP_NONE) || skipRampBecauseOfProximityChangeToNegative;
+ != RAMP_STATE_SKIP_NONE) || mDisplayPowerProximityStateController
+ .shouldSkipRampBecauseOfProximityChangeToNegative();
// While dozing, sometimes the brightness is split into buckets. Rather than animating
// through the buckets, which is unlikely to be smooth in the first place, just jump
// right to the suggested brightness.
@@ -1950,13 +1599,25 @@
// We want to scale HDR brightness level with the SDR level, we also need to restore
// SDR brightness immediately when entering dim or low power mode.
animateValue = mBrightnessRangeController.getHdrBrightnessValue();
+ customAnimationRate = Math.max(customAnimationRate,
+ mBrightnessRangeController.getHdrTransitionRate());
mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_HDR);
}
+ // if doze or suspend state is requested, we want to finish brightnes animation fast
+ // to allow state animation to start
+ if (mPowerRequest.policy == DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE
+ && (mPowerRequest.dozeScreenState == Display.STATE_UNKNOWN // dozing
+ || mPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND
+ || mPowerRequest.dozeScreenState == Display.STATE_ON_SUSPEND)) {
+ customAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
+ slowChange = false;
+ }
+
final float currentBrightness = mPowerState.getScreenBrightness();
final float currentSdrBrightness = mPowerState.getSdrScreenBrightness();
- if (isValidBrightnessValue(animateValue)
+ if (BrightnessUtils.isValidBrightnessValue(animateValue)
&& (animateValue != currentBrightness
|| sdrAnimateValue != currentSdrBrightness)) {
boolean skipAnimation = initialRampSkip || hasBrightnessBuckets
@@ -1986,6 +1647,9 @@
if (skipAnimation) {
animateScreenBrightness(animateValue, sdrAnimateValue,
SCREEN_ANIMATION_RATE_MINIMUM);
+ } else if (customAnimationRate > 0) {
+ animateScreenBrightness(animateValue, sdrAnimateValue,
+ customAnimationRate, /* ignoreAnimationLimits = */true);
} else {
boolean isIncreasing = animateValue > currentBrightness;
final float rampSpeed;
@@ -2007,13 +1671,15 @@
}
notifyBrightnessTrackerChanged(brightnessState, userInitiatedChange,
- wasShortTermModelActive, autoBrightnessEnabled, brightnessIsTemporary);
+ wasShortTermModelActive, mAutomaticBrightnessStrategy.isAutoBrightnessEnabled(),
+ brightnessIsTemporary, displayBrightnessState.getShouldUseAutoBrightness());
// We save the brightness info *after* the brightness setting has been changed and
// adjustments made so that the brightness info reflects the latest value.
- brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting(), animateValue);
+ brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting(),
+ animateValue, clampedState);
} else {
- brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting());
+ brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting(), clampedState);
}
// Only notify if the brightness adjustment is not temporary (i.e. slider has been released)
@@ -2049,13 +1715,20 @@
? mCdsi.getReduceBrightColorsStrength() : -1);
mTempBrightnessEvent.setPowerFactor(mPowerRequest.screenLowPowerBrightnessFactor);
mTempBrightnessEvent.setWasShortTermModelActive(wasShortTermModelActive);
- mTempBrightnessEvent.setAutomaticBrightnessEnabled(mUseAutoBrightness);
+ mTempBrightnessEvent.setDisplayBrightnessStrategyName(displayBrightnessState
+ .getDisplayBrightnessStrategyName());
+ mTempBrightnessEvent.setAutomaticBrightnessEnabled(
+ displayBrightnessState.getShouldUseAutoBrightness());
// Temporary is what we use during slider interactions. We avoid logging those so that
// we don't spam logcat when the slider is being used.
boolean tempToTempTransition =
mTempBrightnessEvent.getReason().getReason() == BrightnessReason.REASON_TEMPORARY
&& mLastBrightnessEvent.getReason().getReason()
== BrightnessReason.REASON_TEMPORARY;
+ // Purely for dumpsys;
+ final boolean isRbcEvent =
+ mLastBrightnessEvent.isRbcEnabled() != mTempBrightnessEvent.isRbcEnabled();
+
if ((!mTempBrightnessEvent.equalsMainData(mLastBrightnessEvent) && !tempToTempTransition)
|| brightnessAdjustmentFlags != 0) {
mTempBrightnessEvent.setInitialBrightness(mLastBrightnessEvent.getBrightness());
@@ -2075,6 +1748,10 @@
if (mBrightnessEventRingBuffer != null) {
mBrightnessEventRingBuffer.append(newEvent);
}
+ if (isRbcEvent) {
+ mRbcEventRingBuffer.append(newEvent);
+ }
+
}
// Update display white-balance.
@@ -2092,6 +1769,7 @@
// reporting the display is ready because we only need to ensure the screen is in the
// right power state even as it continues to converge on the desired brightness.
final boolean ready = mPendingScreenOnUnblocker == null
+ && mPendingScreenOnUnblockerByDisplayOffload == null
&& (!mColorFadeEnabled || (!mColorFadeOnAnimator.isStarted()
&& !mColorFadeOffAnimator.isStarted()))
&& mPowerState.waitUntilClean(mCleanListener);
@@ -2106,12 +1784,8 @@
}
// Grab a wake lock if we have unfinished business.
- if (!finished && !mUnfinishedBusiness) {
- if (DEBUG) {
- Slog.d(mTag, "Unfinished business...");
- }
- mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdUnfinishedBusiness);
- mUnfinishedBusiness = true;
+ if (!finished) {
+ mWakelockController.acquireWakelock(WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
}
// Notify the power manager when ready.
@@ -2130,12 +1804,8 @@
}
// Release the wake lock when we have no unfinished business.
- if (finished && mUnfinishedBusiness) {
- if (DEBUG) {
- Slog.d(mTag, "Finished business...");
- }
- mUnfinishedBusiness = false;
- mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdUnfinishedBusiness);
+ if (finished) {
+ mWakelockController.releaseWakelock(WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
}
// Record if dozing for future comparison.
@@ -2166,9 +1836,9 @@
private void setDwbcLoggingEnabled(int arg) {
if (mDisplayWhiteBalanceController != null) {
- final boolean shouldEnable = (arg == 1);
- mDisplayWhiteBalanceController.setLoggingEnabled(shouldEnable);
- mDisplayWhiteBalanceSettings.setLoggingEnabled(shouldEnable);
+ final boolean enabled = (arg == 1);
+ mDisplayWhiteBalanceController.setLoggingEnabled(enabled);
+ mDisplayWhiteBalanceSettings.setLoggingEnabled(enabled);
}
}
@@ -2183,7 +1853,7 @@
*/
@Override
public void ignoreProximitySensorUntilChanged() {
- mHandler.sendEmptyMessage(MSG_IGNORE_PROXIMITY);
+ mDisplayPowerProximityStateController.ignoreProximitySensorUntilChanged();
}
@Override
@@ -2210,21 +1880,27 @@
@Override
public void setBrightnessFromOffload(float brightness) {
- // The old DPC is no longer supported
+ Message msg = mHandler.obtainMessage(MSG_SET_BRIGHTNESS_FROM_OFFLOAD,
+ Float.floatToIntBits(brightness), 0 /*unused*/);
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
}
@Override
public float[] getAutoBrightnessLevels(
@AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
- // The old DPC is no longer supported
- return null;
+ int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL, UserHandle.USER_CURRENT);
+ return mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(mode, preset);
}
@Override
public float[] getAutoBrightnessLuxLevels(
@AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
- // The old DPC is no longer supported
- return null;
+ int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL, UserHandle.USER_CURRENT);
+ return mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(mode, preset);
}
@Override
@@ -2241,18 +1917,29 @@
}
}
- private boolean saveBrightnessInfo(float brightness) {
- return saveBrightnessInfo(brightness, brightness);
+ @Override
+ public void onBootCompleted() {
+ Message msg = mHandler.obtainMessage(MSG_BOOT_COMPLETED);
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
}
- private boolean saveBrightnessInfo(float brightness, float adjustedBrightness) {
+ private boolean saveBrightnessInfo(float brightness) {
+ return saveBrightnessInfo(brightness, /* state= */ null);
+ }
+
+ private boolean saveBrightnessInfo(float brightness, @Nullable DisplayBrightnessState state) {
+ return saveBrightnessInfo(brightness, brightness, state);
+ }
+
+ private boolean saveBrightnessInfo(float brightness, float adjustedBrightness,
+ @Nullable DisplayBrightnessState state) {
synchronized (mCachedBrightnessInfo) {
- final float minBrightness = Math.min(
- mBrightnessRangeController.getCurrentBrightnessMin(),
- mBrightnessThrottler.getBrightnessCap());
+ float stateMax = state != null ? state.getMaxBrightness() : PowerManager.BRIGHTNESS_MAX;
+ float stateMin = state != null ? state.getMinBrightness() : PowerManager.BRIGHTNESS_MAX;
+ final float minBrightness = Math.max(stateMin, Math.min(
+ mBrightnessRangeController.getCurrentBrightnessMin(), stateMax));
final float maxBrightness = Math.min(
- mBrightnessRangeController.getCurrentBrightnessMax(),
- mBrightnessThrottler.getBrightnessCap());
+ mBrightnessRangeController.getCurrentBrightnessMax(), stateMax);
boolean changed = false;
changed |=
@@ -2275,8 +1962,7 @@
mBrightnessRangeController.getTransitionPoint());
changed |=
mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessMaxReason,
- mBrightnessThrottler.getBrightnessMaxReason());
-
+ mBrightnessClamperController.getBrightnessMaxReason());
return changed;
}
}
@@ -2288,27 +1974,18 @@
}
private HighBrightnessModeController createHbmControllerLocked(
- Runnable modeChangeCallback) {
- final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
- final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig();
- final IBinder displayToken =
- mLogicalDisplay.getPrimaryDisplayDeviceLocked().getDisplayTokenLocked();
- final String displayUniqueId =
- mLogicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
+ HighBrightnessModeMetadata hbmMetadata, Runnable modeChangeCallback) {
+ final DisplayDeviceConfig ddConfig = mDisplayDevice.getDisplayDeviceConfig();
+ final IBinder displayToken = mDisplayDevice.getDisplayTokenLocked();
+ final String displayUniqueId = mDisplayDevice.getUniqueId();
final DisplayDeviceConfig.HighBrightnessModeData hbmData =
ddConfig != null ? ddConfig.getHighBrightnessModeData() : null;
- final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+ final DisplayDeviceInfo info = mDisplayDevice.getDisplayDeviceInfoLocked();
return mInjector.getHighBrightnessModeController(mHandler, info.width, info.height,
displayToken, displayUniqueId, PowerManager.BRIGHTNESS_MIN,
- PowerManager.BRIGHTNESS_MAX, hbmData,
- new HighBrightnessModeController.HdrBrightnessDeviceConfig() {
- @Override
- public float getHdrBrightnessFromSdr(
- float sdrBrightness, float maxDesiredHdrSdrRatio) {
- return mDisplayDeviceConfig.getHdrBrightnessFromSdr(
- sdrBrightness, maxDesiredHdrSdrRatio);
- }
- }, modeChangeCallback, mHighBrightnessModeMetadata, mContext);
+ PowerManager.BRIGHTNESS_MAX, hbmData, (sdrBrightness, maxDesiredHdrSdrRatio) ->
+ mDisplayDeviceConfig.getHdrBrightnessFromSdr(sdrBrightness,
+ maxDesiredHdrSdrRatio), modeChangeCallback, hbmMetadata, mContext);
}
private BrightnessThrottler createBrightnessThrottlerLocked() {
@@ -2359,18 +2036,72 @@
}
}
+ private void blockScreenOnByDisplayOffload(DisplayOffloadSession displayOffloadSession) {
+ if (mPendingScreenOnUnblockerByDisplayOffload != null || displayOffloadSession == null) {
+ return;
+ }
+ mScreenTurningOnWasBlockedByDisplayOffload = true;
+
+ Trace.asyncTraceBegin(
+ Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_BY_DISPLAYOFFLOAD_TRACE_NAME, 0);
+ mScreenOnBlockByDisplayOffloadStartRealTime = SystemClock.elapsedRealtime();
+
+ mPendingScreenOnUnblockerByDisplayOffload =
+ () -> onDisplayOffloadUnblockScreenOn(displayOffloadSession);
+ if (!displayOffloadSession.blockScreenOn(mPendingScreenOnUnblockerByDisplayOffload)) {
+ mPendingScreenOnUnblockerByDisplayOffload = null;
+ long delay =
+ SystemClock.elapsedRealtime() - mScreenOnBlockByDisplayOffloadStartRealTime;
+ Slog.w(mTag, "Tried blocking screen on for offloading but failed. So, end trace after "
+ + delay + " ms.");
+ Trace.asyncTraceEnd(
+ Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_BY_DISPLAYOFFLOAD_TRACE_NAME, 0);
+ return;
+ }
+ Slog.i(mTag, "Blocking screen on for offloading.");
+ }
+
+ private void onDisplayOffloadUnblockScreenOn(DisplayOffloadSession displayOffloadSession) {
+ Message msg = mHandler.obtainMessage(MSG_OFFLOADING_SCREEN_ON_UNBLOCKED,
+ displayOffloadSession);
+ mHandler.sendMessage(msg);
+ }
+
+ private void unblockScreenOnByDisplayOffload() {
+ if (mPendingScreenOnUnblockerByDisplayOffload == null) {
+ return;
+ }
+ mPendingScreenOnUnblockerByDisplayOffload = null;
+ long delay = SystemClock.elapsedRealtime() - mScreenOnBlockByDisplayOffloadStartRealTime;
+ Slog.i(mTag, "Unblocked screen on for offloading after " + delay + " ms");
+ Trace.asyncTraceEnd(
+ Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_BY_DISPLAYOFFLOAD_TRACE_NAME, 0);
+ }
+
private boolean setScreenState(int state) {
return setScreenState(state, false /*reportOnly*/);
}
private boolean setScreenState(int state, boolean reportOnly) {
final boolean isOff = (state == Display.STATE_OFF);
+ final boolean isOn = (state == Display.STATE_ON);
+ final boolean changed = mPowerState.getScreenState() != state;
- if (mPowerState.getScreenState() != state
- || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED) {
+ // If the screen is turning on, give displayoffload a chance to do something before the
+ // screen actually turns on.
+ // TODO(b/316941732): add tests for this displayoffload screen-on blocker.
+ if (isOn && changed && !mScreenTurningOnWasBlockedByDisplayOffload) {
+ blockScreenOnByDisplayOffload(mDisplayOffloadSession);
+ } else if (!isOn && mScreenTurningOnWasBlockedByDisplayOffload) {
+ // No longer turning screen on, so unblock previous screen on blocking immediately.
+ unblockScreenOnByDisplayOffload();
+ mScreenTurningOnWasBlockedByDisplayOffload = false;
+ }
+
+ if (changed || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED) {
// If we are trying to turn screen off, give policy a chance to do something before we
// actually turn the screen off.
- if (isOff && !mScreenOffBecauseOfProximity) {
+ if (isOff && !mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()) {
if (mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_ON
|| mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED) {
setReportedScreenState(REPORTED_TO_POLICY_SCREEN_TURNING_OFF);
@@ -2383,8 +2114,9 @@
}
}
- if (!reportOnly && mPowerState.getScreenState() != state
- && readyToUpdateDisplayState()) {
+ if (!reportOnly && changed && readyToUpdateDisplayState()
+ && mPendingScreenOffUnblocker == null
+ && mPendingScreenOnUnblockerByDisplayOffload == null) {
Trace.traceCounter(Trace.TRACE_TAG_POWER, "ScreenState", state);
String propertyKey = "debug.tracing.screen_state";
@@ -2410,7 +2142,7 @@
// it is only removed once the window manager tells us that the activity has
// finished drawing underneath.
if (isOff && mReportedScreenStateToPolicy != REPORTED_TO_POLICY_SCREEN_OFF
- && !mScreenOffBecauseOfProximity) {
+ && !mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()) {
setReportedScreenState(REPORTED_TO_POLICY_SCREEN_OFF);
unblockScreenOn();
mWindowManagerPolicy.screenTurnedOff(mDisplayId, mIsInTransition);
@@ -2436,12 +2168,16 @@
}
// Return true if the screen isn't blocked.
- return mPendingScreenOnUnblocker == null;
+ return mPendingScreenOnUnblocker == null
+ && mPendingScreenOnUnblockerByDisplayOffload == null;
}
private void setReportedScreenState(int state) {
Trace.traceCounter(Trace.TRACE_TAG_POWER, "ReportedScreenStateToPolicy", state);
mReportedScreenStateToPolicy = state;
+ if (state == REPORTED_TO_POLICY_SCREEN_ON) {
+ mScreenTurningOnWasBlockedByDisplayOffload = false;
+ }
}
private void loadAmbientLightSensor() {
@@ -2456,18 +2192,6 @@
mDisplayDeviceConfig.getScreenOffBrightnessSensor(), SensorUtils.NO_FALLBACK);
}
- private void loadProximitySensor() {
- if (DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT || mDisplayId != Display.DEFAULT_DISPLAY) {
- return;
- }
- mProximitySensor = SensorUtils.findSensor(mSensorManager,
- mDisplayDeviceConfig.getProximitySensor(), Sensor.TYPE_PROXIMITY);
- if (mProximitySensor != null) {
- mProximityThreshold = Math.min(mProximitySensor.getMaximumRange(),
- TYPICAL_PROXIMITY_THRESHOLD);
- }
- }
-
private float clampScreenBrightness(float value) {
if (Float.isNaN(value)) {
value = PowerManager.BRIGHTNESS_MIN;
@@ -2476,18 +2200,18 @@
mBrightnessRangeController.getCurrentBrightnessMax());
}
- // Checks whether the brightness is within the valid brightness range, not including off.
- private boolean isValidBrightnessValue(float brightness) {
- return brightness >= PowerManager.BRIGHTNESS_MIN
- && brightness <= PowerManager.BRIGHTNESS_MAX;
+ private void animateScreenBrightness(float target, float sdrTarget, float rate) {
+ animateScreenBrightness(target, sdrTarget, rate, /* ignoreAnimationLimits = */false);
}
- private void animateScreenBrightness(float target, float sdrTarget, float rate) {
+ private void animateScreenBrightness(float target, float sdrTarget, float rate,
+ boolean ignoreAnimationLimits) {
if (DEBUG) {
Slog.d(mTag, "Animating brightness: target=" + target + ", sdrTarget=" + sdrTarget
+ ", rate=" + rate);
}
- if (mScreenBrightnessRampAnimator.animateTo(target, sdrTarget, rate, false)) {
+ if (mScreenBrightnessRampAnimator.animateTo(target, sdrTarget, rate,
+ ignoreAnimationLimits)) {
Trace.traceCounter(Trace.TRACE_TAG_POWER, "TargetScreenBrightness", (int) target);
String propertyKey = "debug.tracing.screen_brightness";
@@ -2655,102 +2379,11 @@
private final Runnable mCleanListener = this::sendUpdatePowerState;
- private void setProximitySensorEnabled(boolean enable) {
- if (enable) {
- if (!mProximitySensorEnabled) {
- // Register the listener.
- // Proximity sensor state already cleared initially.
- mProximitySensorEnabled = true;
- mIgnoreProximityUntilChanged = false;
- mSensorManager.registerListener(mProximitySensorListener, mProximitySensor,
- SensorManager.SENSOR_DELAY_NORMAL, mHandler);
- }
- } else {
- if (mProximitySensorEnabled) {
- // Unregister the listener.
- // Clear the proximity sensor state for next time.
- mProximitySensorEnabled = false;
- mProximity = PROXIMITY_UNKNOWN;
- mIgnoreProximityUntilChanged = false;
- mPendingProximity = PROXIMITY_UNKNOWN;
- mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED);
- mSensorManager.unregisterListener(mProximitySensorListener);
- clearPendingProximityDebounceTime(); // release wake lock (must be last)
- }
- }
- }
-
- private void handleProximitySensorEvent(long time, boolean positive) {
- if (mProximitySensorEnabled) {
- if (mPendingProximity == PROXIMITY_NEGATIVE && !positive) {
- return; // no change
- }
- if (mPendingProximity == PROXIMITY_POSITIVE && positive) {
- return; // no change
- }
-
- // Only accept a proximity sensor reading if it remains
- // stable for the entire debounce delay. We hold a wake lock while
- // debouncing the sensor.
- mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED);
- if (positive) {
- mPendingProximity = PROXIMITY_POSITIVE;
- setPendingProximityDebounceTime(
- time + PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY); // acquire wake lock
- } else {
- mPendingProximity = PROXIMITY_NEGATIVE;
- setPendingProximityDebounceTime(
- time + PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY); // acquire wake lock
- }
-
- // Debounce the new sensor reading.
- debounceProximitySensor();
- }
- }
-
- private void debounceProximitySensor() {
- if (mProximitySensorEnabled
- && mPendingProximity != PROXIMITY_UNKNOWN
- && mPendingProximityDebounceTime >= 0) {
- final long now = mClock.uptimeMillis();
- if (mPendingProximityDebounceTime <= now) {
- if (mProximity != mPendingProximity) {
- // if the status of the sensor changed, stop ignoring.
- mIgnoreProximityUntilChanged = false;
- Slog.i(mTag, "No longer ignoring proximity [" + mPendingProximity + "]");
- }
- // Sensor reading accepted. Apply the change then release the wake lock.
- mProximity = mPendingProximity;
- updatePowerState();
- clearPendingProximityDebounceTime(); // release wake lock (must be last)
- } else {
- // Need to wait a little longer.
- // Debounce again later. We continue holding a wake lock while waiting.
- Message msg = mHandler.obtainMessage(MSG_PROXIMITY_SENSOR_DEBOUNCED);
- mHandler.sendMessageAtTime(msg, mPendingProximityDebounceTime);
- }
- }
- }
-
- private void clearPendingProximityDebounceTime() {
- if (mPendingProximityDebounceTime >= 0) {
- mPendingProximityDebounceTime = -1;
- mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxDebounce);
- }
- }
-
- private void setPendingProximityDebounceTime(long debounceTime) {
- if (mPendingProximityDebounceTime < 0) {
- mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxDebounce);
- }
- mPendingProximityDebounceTime = debounceTime;
- }
-
private void sendOnStateChangedWithWakelock() {
- if (!mOnStateChangedPending) {
- mOnStateChangedPending = true;
- mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdOnStateChanged);
- mHandler.post(mOnStateChangedRunnable);
+ boolean wakeLockAcquired = mWakelockController.acquireWakelock(
+ WakelockController.WAKE_LOCK_STATE_CHANGED);
+ if (wakeLockAcquired) {
+ mHandler.post(mWakelockController.getOnStateChangedRunnable());
}
}
@@ -2762,12 +2395,15 @@
}
private void handleSettingsChange(boolean userSwitch) {
- mPendingScreenBrightnessSetting = getScreenBrightnessSetting();
- mPendingAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
+ mDisplayBrightnessController
+ .setPendingScreenBrightness(mDisplayBrightnessController
+ .getScreenBrightnessSetting());
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(userSwitch);
if (userSwitch) {
// Don't treat user switches as user initiated change.
- setCurrentScreenBrightness(mPendingScreenBrightnessSetting);
- updateAutoBrightnessAdjustment();
+ mDisplayBrightnessController
+ .setAndNotifyCurrentScreenBrightness(mDisplayBrightnessController
+ .getPendingScreenBrightness());
if (mAutomaticBrightnessController != null) {
mAutomaticBrightnessController.resetShortTermModel();
}
@@ -2781,129 +2417,59 @@
Settings.System.SCREEN_BRIGHTNESS_MODE,
Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
mHandler.postAtTime(() -> {
- mUseAutoBrightness = screenBrightnessModeSetting
- == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
+ mAutomaticBrightnessStrategy.setUseAutoBrightness(screenBrightnessModeSetting
+ == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
updatePowerState();
}, mClock.uptimeMillis());
}
- private float getAutoBrightnessAdjustmentSetting() {
- final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT);
- return Float.isNaN(adj) ? 0.0f : clampAutoBrightnessAdjustment(adj);
- }
@Override
public float getScreenBrightnessSetting() {
- float brightness = mBrightnessSetting.getBrightness();
- if (Float.isNaN(brightness)) {
- brightness = mScreenBrightnessDefault;
- }
- return clampAbsoluteBrightness(brightness);
- }
-
- private void loadNitBasedBrightnessSetting() {
- if (mDisplayId == Display.DEFAULT_DISPLAY && mPersistBrightnessNitsForDefaultDisplay) {
- float brightnessNitsForDefaultDisplay =
- mBrightnessSetting.getBrightnessNitsForDefaultDisplay();
- if (brightnessNitsForDefaultDisplay >= 0) {
- float brightnessForDefaultDisplay = getBrightnessFromNits(
- brightnessNitsForDefaultDisplay);
- if (isValidBrightnessValue(brightnessForDefaultDisplay)) {
- mBrightnessSetting.setBrightness(brightnessForDefaultDisplay);
- mCurrentScreenBrightnessSetting = brightnessForDefaultDisplay;
- return;
- }
- }
- }
- mCurrentScreenBrightnessSetting = getScreenBrightnessSetting();
+ return mDisplayBrightnessController.getScreenBrightnessSetting();
}
@Override
public void setBrightness(float brightnessValue, int userSerial) {
- // Update the setting, which will eventually call back into DPC to have us actually update
- // the display with the new value.
- float clampedBrightnessValue = clampScreenBrightness(brightnessValue);
- mBrightnessSetting.setUserSerial(userSerial);
- mBrightnessSetting.setBrightness(clampedBrightnessValue);
- if (mDisplayId == Display.DEFAULT_DISPLAY && mPersistBrightnessNitsForDefaultDisplay) {
- float nits = convertToNits(clampedBrightnessValue);
- if (nits >= 0) {
- mBrightnessSetting.setBrightnessNitsForDefaultDisplay(nits);
- }
- }
+ mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightnessValue),
+ userSerial);
}
@Override
- public void onBootCompleted() {
- Message msg = mHandler.obtainMessage(MSG_BOOT_COMPLETED);
- mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
+ public int getDisplayId() {
+ return mDisplayId;
}
- private void updateScreenBrightnessSetting(float brightnessValue) {
- if (!isValidBrightnessValue(brightnessValue)
- || brightnessValue == mCurrentScreenBrightnessSetting) {
- return;
- }
- setCurrentScreenBrightness(brightnessValue);
- setBrightness(brightnessValue);
+ @Override
+ public int getLeadDisplayId() {
+ return mLeadDisplayId;
}
- private void setCurrentScreenBrightness(float brightnessValue) {
- if (brightnessValue != mCurrentScreenBrightnessSetting) {
- mCurrentScreenBrightnessSetting = brightnessValue;
- postBrightnessChangeRunnable();
+ @Override
+ public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux,
+ boolean slowChange) {
+ mBrightnessRangeController.onAmbientLuxChange(ambientLux);
+ if (nits == BrightnessMappingStrategy.INVALID_NITS) {
+ mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness, slowChange);
+ } else {
+ float brightness = mDisplayBrightnessController.getBrightnessFromNits(nits);
+ if (BrightnessUtils.isValidBrightnessValue(brightness)) {
+ mDisplayBrightnessController.setBrightnessToFollow(brightness, slowChange);
+ } else {
+ // The device does not support nits
+ mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness,
+ slowChange);
+ }
}
- }
-
- private void putAutoBrightnessAdjustmentSetting(float adjustment) {
- if (mDisplayId == Display.DEFAULT_DISPLAY) {
- mAutoBrightnessAdjustment = adjustment;
- Settings.System.putFloatForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, adjustment,
- UserHandle.USER_CURRENT);
- }
- }
-
- private boolean updateAutoBrightnessAdjustment() {
- if (Float.isNaN(mPendingAutoBrightnessAdjustment)) {
- return false;
- }
- if (mAutoBrightnessAdjustment == mPendingAutoBrightnessAdjustment) {
- mPendingAutoBrightnessAdjustment = Float.NaN;
- return false;
- }
- mAutoBrightnessAdjustment = mPendingAutoBrightnessAdjustment;
- mPendingAutoBrightnessAdjustment = Float.NaN;
- mTemporaryAutoBrightnessAdjustment = Float.NaN;
- return true;
- }
-
- // We want to return true if the user has set the screen brightness.
- // RBC on, off, and intensity changes will return false.
- // Slider interactions whilst in RBC will return true, just as when in non-rbc.
- private boolean updateUserSetScreenBrightness() {
- if ((Float.isNaN(mPendingScreenBrightnessSetting)
- || mPendingScreenBrightnessSetting < 0.0f)) {
- return false;
- }
- if (mCurrentScreenBrightnessSetting == mPendingScreenBrightnessSetting) {
- mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
- mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
- return false;
- }
- setCurrentScreenBrightness(mPendingScreenBrightnessSetting);
- mLastUserSetScreenBrightness = mPendingScreenBrightnessSetting;
- mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
- mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
- return true;
+ sendUpdatePowerState();
}
private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
boolean wasShortTermModelActive, boolean autobrightnessEnabled,
- boolean brightnessIsTemporary) {
- final float brightnessInNits = convertToAdjustedNits(brightness);
+ boolean brightnessIsTemporary, boolean shouldUseAutoBrightness) {
+ final float brightnessInNits =
+ mDisplayBrightnessController.convertToAdjustedNits(brightness);
// Don't report brightness to brightnessTracker:
// If brightness is temporary (ie the slider has not been released)
// or if we are in idle screen brightness mode.
@@ -2915,12 +2481,13 @@
|| mAutomaticBrightnessController.isInIdleMode()
|| !autobrightnessEnabled
|| mBrightnessTracker == null
- || !mUseAutoBrightness
+ || !shouldUseAutoBrightness
|| brightnessInNits < 0.0f) {
return;
}
- if (userInitiated && !mAutomaticBrightnessController.hasValidAmbientLux()) {
+ if (userInitiated && (mAutomaticBrightnessController == null
+ || !mAutomaticBrightnessController.hasValidAmbientLux())) {
// If we don't have a valid lux reading we can't report a valid
// slider event so notify as if the system changed the brightness.
userInitiated = false;
@@ -2939,96 +2506,33 @@
mAutomaticBrightnessController.getLastSensorTimestamps());
}
- private float convertToNits(float brightness) {
- if (mAutomaticBrightnessController == null) {
- return BrightnessMappingStrategy.INVALID_NITS;
+ @Override
+ public void addDisplayBrightnessFollower(DisplayPowerControllerInterface follower) {
+ synchronized (mLock) {
+ mDisplayBrightnessFollowers.append(follower.getDisplayId(), follower);
+ sendUpdatePowerStateLocked();
}
- return mAutomaticBrightnessController.convertToNits(brightness);
}
- private float convertToAdjustedNits(float brightness) {
- if (mAutomaticBrightnessController == null) {
- return BrightnessMappingStrategy.INVALID_NITS;
+ @Override
+ public void removeDisplayBrightnessFollower(DisplayPowerControllerInterface follower) {
+ synchronized (mLock) {
+ mDisplayBrightnessFollowers.remove(follower.getDisplayId());
+ mHandler.postAtTime(() -> follower.setBrightnessToFollow(
+ PowerManager.BRIGHTNESS_INVALID_FLOAT, BrightnessMappingStrategy.INVALID_NITS,
+ /* ambientLux= */ 0, /* slowChange= */ false), mClock.uptimeMillis());
}
- return mAutomaticBrightnessController.convertToAdjustedNits(brightness);
- }
-
- private float getBrightnessFromNits(float nits) {
- if (mAutomaticBrightnessController == null) {
- return PowerManager.BRIGHTNESS_INVALID_FLOAT;
- }
- return mAutomaticBrightnessController.getBrightnessFromNits(nits);
}
@GuardedBy("mLock")
- private void updatePendingProximityRequestsLocked() {
- mWaitingForNegativeProximity |= mPendingWaitForNegativeProximityLocked;
- mPendingWaitForNegativeProximityLocked = false;
-
- if (mIgnoreProximityUntilChanged) {
- // Also, lets stop waiting for negative proximity if we're ignoring it.
- mWaitingForNegativeProximity = false;
+ private void clearDisplayBrightnessFollowersLocked() {
+ for (int i = 0; i < mDisplayBrightnessFollowers.size(); i++) {
+ DisplayPowerControllerInterface follower = mDisplayBrightnessFollowers.valueAt(i);
+ mHandler.postAtTime(() -> follower.setBrightnessToFollow(
+ PowerManager.BRIGHTNESS_INVALID_FLOAT, BrightnessMappingStrategy.INVALID_NITS,
+ /* ambientLux= */ 0, /* slowChange= */ false), mClock.uptimeMillis());
}
- }
-
- private void ignoreProximitySensorUntilChangedInternal() {
- if (!mIgnoreProximityUntilChanged
- && mProximity == PROXIMITY_POSITIVE) {
- // Only ignore if it is still reporting positive (near)
- mIgnoreProximityUntilChanged = true;
- Slog.i(mTag, "Ignoring proximity");
- updatePowerState();
- }
- }
-
- private final Runnable mOnStateChangedRunnable = new Runnable() {
- @Override
- public void run() {
- mOnStateChangedPending = false;
- mCallbacks.onStateChanged();
- mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdOnStateChanged);
- }
- };
-
- private void sendOnProximityPositiveWithWakelock() {
- mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxPositive);
- mHandler.post(mOnProximityPositiveRunnable);
- mOnProximityPositiveMessages++;
- }
-
- private final Runnable mOnProximityPositiveRunnable = new Runnable() {
- @Override
- public void run() {
- mOnProximityPositiveMessages--;
- mCallbacks.onProximityPositive();
- mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxPositive);
- }
- };
-
- private void sendOnProximityNegativeWithWakelock() {
- mOnProximityNegativeMessages++;
- mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxNegative);
- mHandler.post(mOnProximityNegativeRunnable);
- }
-
- private final Runnable mOnProximityNegativeRunnable = new Runnable() {
- @Override
- public void run() {
- mOnProximityNegativeMessages--;
- mCallbacks.onProximityNegative();
- mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxNegative);
- }
- };
-
- /**
- * Indicates whether the display state is ready to update. If this is the default display, we
- * want to update it right away so that we can draw the boot animation on it. If it is not
- * the default display, drawing the boot animation on it would look incorrect, so we need
- * to wait until boot is completed.
- * @return True if the display state is ready to update
- */
- private boolean readyToUpdateDisplayState() {
- return mDisplayId == Display.DEFAULT_DISPLAY || mBootCompleted;
+ mDisplayBrightnessFollowers.clear();
}
@Override
@@ -3040,31 +2544,23 @@
pw.println(" mLeadDisplayId=" + mLeadDisplayId);
pw.println(" mLightSensor=" + mLightSensor);
pw.println(" mDisplayBrightnessFollowers=" + mDisplayBrightnessFollowers);
- pw.println(" mDozeStateOverride=" + mDozeStateOverride);
pw.println();
pw.println("Display Power Controller Locked State:");
pw.println(" mDisplayReadyLocked=" + mDisplayReadyLocked);
pw.println(" mPendingRequestLocked=" + mPendingRequestLocked);
pw.println(" mPendingRequestChangedLocked=" + mPendingRequestChangedLocked);
- pw.println(" mPendingWaitForNegativeProximityLocked="
- + mPendingWaitForNegativeProximityLocked);
pw.println(" mPendingUpdatePowerStateLocked=" + mPendingUpdatePowerStateLocked);
}
pw.println();
pw.println("Display Power Controller Configuration:");
- pw.println(" mScreenBrightnessRangeDefault=" + mScreenBrightnessDefault);
pw.println(" mScreenBrightnessDozeConfig=" + mScreenBrightnessDozeConfig);
- pw.println(" mScreenBrightnessDimConfig=" + mScreenBrightnessDimConfig);
pw.println(" mUseSoftwareAutoBrightnessConfig=" + mUseSoftwareAutoBrightnessConfig);
- pw.println(" mAllowAutoBrightnessWhileDozingConfig="
- + mAllowAutoBrightnessWhileDozingConfig);
- pw.println(" mPersistBrightnessNitsForDefaultDisplay="
- + mPersistBrightnessNitsForDefaultDisplay);
pw.println(" mSkipScreenOnBrightnessRamp=" + mSkipScreenOnBrightnessRamp);
pw.println(" mColorFadeFadesConfig=" + mColorFadeFadesConfig);
pw.println(" mColorFadeEnabled=" + mColorFadeEnabled);
+ pw.println(" mIsDisplayInternal=" + mIsDisplayInternal);
synchronized (mCachedBrightnessInfo) {
pw.println(" mCachedBrightnessInfo.brightness="
+ mCachedBrightnessInfo.brightness.value);
@@ -3082,7 +2578,6 @@
}
pw.println(" mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig);
pw.println(" mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig);
-
mHandler.runWithScissors(() -> dumpLocal(pw), 1000);
}
@@ -3090,35 +2585,9 @@
pw.println();
pw.println("Display Power Controller Thread State:");
pw.println(" mPowerRequest=" + mPowerRequest);
- pw.println(" mUnfinishedBusiness=" + mUnfinishedBusiness);
- pw.println(" mWaitingForNegativeProximity=" + mWaitingForNegativeProximity);
- pw.println(" mProximitySensor=" + mProximitySensor);
- pw.println(" mProximitySensorEnabled=" + mProximitySensorEnabled);
- pw.println(" mProximityThreshold=" + mProximityThreshold);
- pw.println(" mProximity=" + proximityToString(mProximity));
- pw.println(" mPendingProximity=" + proximityToString(mPendingProximity));
- pw.println(" mPendingProximityDebounceTime="
- + TimeUtils.formatUptime(mPendingProximityDebounceTime));
- pw.println(" mScreenOffBecauseOfProximity=" + mScreenOffBecauseOfProximity);
- pw.println(" mLastUserSetScreenBrightness=" + mLastUserSetScreenBrightness);
- pw.println(" mPendingScreenBrightnessSetting="
- + mPendingScreenBrightnessSetting);
- pw.println(" mTemporaryScreenBrightness=" + mTemporaryScreenBrightness);
- pw.println(" mBrightnessToFollow=" + mBrightnessToFollow);
- pw.println(" mBrightnessToFollowSlowChange=" + mBrightnessToFollowSlowChange);
- pw.println(" mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment);
pw.println(" mBrightnessReason=" + mBrightnessReason);
- pw.println(" mTemporaryAutoBrightnessAdjustment=" + mTemporaryAutoBrightnessAdjustment);
- pw.println(" mPendingAutoBrightnessAdjustment=" + mPendingAutoBrightnessAdjustment);
- pw.println(" mAppliedAutoBrightness=" + mAppliedAutoBrightness);
pw.println(" mAppliedDimming=" + mAppliedDimming);
- pw.println(" mAppliedLowPower=" + mAppliedLowPower);
pw.println(" mAppliedThrottling=" + mAppliedThrottling);
- pw.println(" mAppliedScreenBrightnessOverride=" + mAppliedScreenBrightnessOverride);
- pw.println(" mAppliedTemporaryBrightness=" + mAppliedTemporaryBrightness);
- pw.println(" mAppliedTemporaryAutoBrightnessAdjustment="
- + mAppliedTemporaryAutoBrightnessAdjustment);
- pw.println(" mAppliedBrightnessBoost=" + mAppliedBrightnessBoost);
pw.println(" mDozing=" + mDozing);
pw.println(" mSkipRampState=" + skipRampStateToString(mSkipRampState));
pw.println(" mScreenOnBlockStartRealTime=" + mScreenOnBlockStartRealTime);
@@ -3129,9 +2598,8 @@
pw.println(" mReportedToPolicy="
+ reportedToPolicyToString(mReportedScreenStateToPolicy));
pw.println(" mIsRbcActive=" + mIsRbcActive);
- pw.println(" mOnStateChangePending=" + mOnStateChangedPending);
- pw.println(" mOnProximityPositiveMessages=" + mOnProximityPositiveMessages);
- pw.println(" mOnProximityNegativeMessages=" + mOnProximityNegativeMessages);
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ mAutomaticBrightnessStrategy.dump(ipw);
if (mScreenBrightnessRampAnimator != null) {
pw.println(" mScreenBrightnessRampAnimator.isAnimating()="
@@ -3156,6 +2624,8 @@
dumpBrightnessEvents(pw);
}
+ dumpRbcEvents(pw);
+
if (mScreenOffBrightnessSensorController != null) {
mScreenOffBrightnessSensorController.dump(pw);
}
@@ -3173,21 +2643,30 @@
mDisplayWhiteBalanceController.dump(pw);
mDisplayWhiteBalanceSettings.dump(pw);
}
- }
- private static String proximityToString(int state) {
- switch (state) {
- case PROXIMITY_UNKNOWN:
- return "Unknown";
- case PROXIMITY_NEGATIVE:
- return "Negative";
- case PROXIMITY_POSITIVE:
- return "Positive";
- default:
- return Integer.toString(state);
+ pw.println();
+
+ if (mWakelockController != null) {
+ mWakelockController.dumpLocal(pw);
+ }
+
+ pw.println();
+ if (mDisplayBrightnessController != null) {
+ mDisplayBrightnessController.dump(pw);
+ }
+
+ pw.println();
+ if (mDisplayStateController != null) {
+ mDisplayStateController.dumpsys(pw);
+ }
+
+ pw.println();
+ if (mBrightnessClamperController != null) {
+ mBrightnessClamperController.dump(ipw);
}
}
+
private static String reportedToPolicyToString(int state) {
switch (state) {
case REPORTED_TO_POLICY_SCREEN_OFF:
@@ -3228,14 +2707,20 @@
}
}
- private static float clampAbsoluteBrightness(float value) {
- return MathUtils.constrain(value, PowerManager.BRIGHTNESS_MIN,
- PowerManager.BRIGHTNESS_MAX);
+ private void dumpRbcEvents(PrintWriter pw) {
+ int size = mRbcEventRingBuffer.size();
+ if (size < 1) {
+ pw.println("No Reduce Bright Colors Adjustments");
+ return;
+ }
+
+ pw.println("Reduce Bright Colors Adjustments Last " + size + " Events: ");
+ BrightnessEvent[] eventArray = mRbcEventRingBuffer.toArray();
+ for (int i = 0; i < mRbcEventRingBuffer.size(); i++) {
+ pw.println(" " + eventArray[i]);
+ }
}
- private static float clampAutoBrightnessAdjustment(float value) {
- return MathUtils.constrain(value, -1.0f, 1.0f);
- }
private void noteScreenState(int screenState) {
// Log screen state change with display id
@@ -3363,20 +2848,21 @@
// It's easier to check if the brightness is at maximum level using the brightness
// value untouched by any modifiers
boolean brightnessIsMax = unmodifiedBrightness == event.getHbmMax();
- float brightnessInNits = convertToAdjustedNits(event.getBrightness());
+ float brightnessInNits =
+ mDisplayBrightnessController.convertToAdjustedNits(event.getBrightness());
float appliedLowPowerMode = event.isLowPowerModeSet() ? event.getPowerFactor() : -1f;
int appliedRbcStrength = event.isRbcEnabled() ? event.getRbcStrength() : -1;
float appliedHbmMaxNits =
event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF
- ? -1f : convertToAdjustedNits(event.getHbmMax());
+ ? -1f : mDisplayBrightnessController.convertToAdjustedNits(event.getHbmMax());
// thermalCapNits set to -1 if not currently capping max brightness
float appliedThermalCapNits =
event.getThermalMax() == PowerManager.BRIGHTNESS_MAX
- ? -1f : convertToAdjustedNits(event.getThermalMax());
-
+ ? -1f : mDisplayBrightnessController.convertToAdjustedNits(event.getThermalMax());
if (mIsDisplayInternal) {
FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
- convertToAdjustedNits(event.getInitialBrightness()),
+ mDisplayBrightnessController
+ .convertToAdjustedNits(event.getInitialBrightness()),
brightnessInNits,
event.getLux(),
event.getPhysicalDisplayId(),
@@ -3393,7 +2879,8 @@
event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR,
(modifier & BrightnessReason.MODIFIER_LOW_POWER) > 0,
- mBrightnessThrottler.getBrightnessMaxReason(),
+ mBrightnessClamperController.getBrightnessMaxReason(),
+ // TODO: (flc) add brightnessMinReason here too.
(modifier & BrightnessReason.MODIFIER_DIMMED) > 0,
event.isRbcEnabled(),
(flags & BrightnessEvent.FLAG_INVALID_LUX) > 0,
@@ -3404,6 +2891,17 @@
}
}
+ /**
+ * Indicates whether the display state is ready to update. If this is the default display, we
+ * want to update it right away so that we can draw the boot animation on it. If it is not
+ * the default display, drawing the boot animation on it would look incorrect, so we need
+ * to wait until boot is completed.
+ * @return True if the display state is ready to update
+ */
+ private boolean readyToUpdateDisplayState() {
+ return mDisplayId == Display.DEFAULT_DISPLAY || mBootCompleted;
+ }
+
private final class DisplayControllerHandler extends Handler {
DisplayControllerHandler(Looper looper) {
super(looper, null, true /*async*/);
@@ -3416,10 +2914,6 @@
updatePowerState();
break;
- case MSG_PROXIMITY_SENSOR_DEBOUNCED:
- debounceProximitySensor();
- break;
-
case MSG_SCREEN_ON_UNBLOCKED:
if (mPendingScreenOnUnblocker == msg.obj) {
unblockScreenOn();
@@ -3432,27 +2926,38 @@
updatePowerState();
}
break;
+ case MSG_OFFLOADING_SCREEN_ON_UNBLOCKED:
+ if (mDisplayOffloadSession == msg.obj) {
+ unblockScreenOnByDisplayOffload();
+ updatePowerState();
+ }
+ break;
case MSG_CONFIGURE_BRIGHTNESS:
- mBrightnessConfiguration = (BrightnessConfiguration) msg.obj;
- mShouldResetShortTermModel = msg.arg1 == 1;
+ BrightnessConfiguration brightnessConfiguration =
+ (BrightnessConfiguration) msg.obj;
+ mAutomaticBrightnessStrategy.setBrightnessConfiguration(brightnessConfiguration,
+ msg.arg1 == 1);
+ if (mBrightnessTracker != null) {
+ mBrightnessTracker
+ .setShouldCollectColorSample(brightnessConfiguration != null
+ && brightnessConfiguration.shouldCollectColorSamples());
+ }
updatePowerState();
break;
case MSG_SET_TEMPORARY_BRIGHTNESS:
// TODO: Should we have a a timeout for the temporary brightness?
- mTemporaryScreenBrightness = Float.intBitsToFloat(msg.arg1);
+ mDisplayBrightnessController
+ .setTemporaryBrightness(Float.intBitsToFloat(msg.arg1));
updatePowerState();
break;
case MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT:
- mTemporaryAutoBrightnessAdjustment = Float.intBitsToFloat(msg.arg1);
+ mAutomaticBrightnessStrategy
+ .setTemporaryAutoBrightnessAdjustment(Float.intBitsToFloat(msg.arg1));
updatePowerState();
break;
- case MSG_IGNORE_PROXIMITY:
- ignoreProximitySensorUntilChangedInternal();
- break;
-
case MSG_STOP:
cleanupHandlerThreadAfterStop();
break;
@@ -3500,27 +3005,15 @@
case MSG_SET_DWBC_LOGGING_ENABLED:
setDwbcLoggingEnabled(msg.arg1);
break;
+ case MSG_SET_BRIGHTNESS_FROM_OFFLOAD:
+ mDisplayBrightnessController.setBrightnessFromOffload(
+ Float.intBitsToFloat(msg.arg1));
+ updatePowerState();
+ break;
}
}
}
- private final SensorEventListener mProximitySensorListener = new SensorEventListener() {
- @Override
- public void onSensorChanged(SensorEvent event) {
- if (mProximitySensorEnabled) {
- final long time = mClock.uptimeMillis();
- final float distance = event.values[0];
- boolean positive = distance >= 0.0f && distance < mProximityThreshold;
- handleProximitySensorEvent(time, positive);
- }
- }
-
- @Override
- public void onAccuracyChanged(Sensor sensor, int accuracy) {
- // Not used.
- }
- };
-
private final class SettingsObserver extends ContentObserver {
SettingsObserver(Handler handler) {
@@ -3531,6 +3024,16 @@
public void onChange(boolean selfChange, Uri uri) {
if (uri.equals(Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE))) {
handleBrightnessModeChange();
+ } else if (uri.equals(Settings.System.getUriFor(
+ Settings.System.SCREEN_BRIGHTNESS_FOR_ALS))) {
+ int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL,
+ UserHandle.USER_CURRENT);
+ Slog.i(mTag, "Setting up auto-brightness for preset "
+ + autoBrightnessPresetToString(preset));
+ setUpAutoBrightness(mContext, mHandler);
+ sendUpdatePowerState();
} else {
handleSettingsChange(false /* userSwitch */);
}
@@ -3581,28 +3084,6 @@
msg.sendToTarget();
}
- @VisibleForTesting
- String getSuspendBlockerUnfinishedBusinessId(int displayId) {
- return "[" + displayId + "]unfinished business";
- }
-
- String getSuspendBlockerOnStateChangedId(int displayId) {
- return "[" + displayId + "]on state changed";
- }
-
- String getSuspendBlockerProxPositiveId(int displayId) {
- return "[" + displayId + "]prox positive";
- }
-
- String getSuspendBlockerProxNegativeId(int displayId) {
- return "[" + displayId + "]prox negative";
- }
-
- @VisibleForTesting
- String getSuspendBlockerProxDebounceId(int displayId) {
- return "[" + displayId + "]prox debounce";
- }
-
/** Functional interface for providing time. */
@VisibleForTesting
interface Clock {
@@ -3629,6 +3110,20 @@
return new DualRampAnimator(dps, firstProperty, secondProperty);
}
+ WakelockController getWakelockController(int displayId,
+ DisplayPowerCallbacks displayPowerCallbacks) {
+ return new WakelockController(displayId, displayPowerCallbacks);
+ }
+
+ DisplayPowerProximityStateController getDisplayPowerProximityStateController(
+ WakelockController wakelockController, DisplayDeviceConfig displayDeviceConfig,
+ Looper looper, Runnable nudgeUpdatePowerState,
+ int displayId, SensorManager sensorManager) {
+ return new DisplayPowerProximityStateController(wakelockController, displayDeviceConfig,
+ looper, nudgeUpdatePowerState,
+ displayId, sensorManager, /* injector= */ null);
+ }
+
AutomaticBrightnessController getAutomaticBrightnessController(
AutomaticBrightnessController.Callbacks callbacks, Looper looper,
SensorManager sensorManager, Sensor lightSensor,
@@ -3711,11 +3206,32 @@
hbmChangeCallback, hbmMetadata, context);
}
+ BrightnessRangeController getBrightnessRangeController(
+ HighBrightnessModeController hbmController, Runnable modeChangeCallback,
+ DisplayDeviceConfig displayDeviceConfig, Handler handler,
+ DisplayManagerFlags flags, IBinder displayToken, DisplayDeviceInfo info) {
+ return new BrightnessRangeController(hbmController,
+ modeChangeCallback, displayDeviceConfig, handler, flags, displayToken, info);
+ }
+
+ BrightnessClamperController getBrightnessClamperController(Handler handler,
+ BrightnessClamperController.ClamperChangeListener clamperChangeListener,
+ BrightnessClamperController.DisplayDeviceData data, Context context,
+ DisplayManagerFlags flags) {
+
+ return new BrightnessClamperController(handler, clamperChangeListener, data, context,
+ flags);
+ }
+
DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
SensorManager sensorManager, Resources resources) {
return DisplayWhiteBalanceFactory.create(handler,
sensorManager, resources);
}
+
+ boolean isColorFadeEnabled() {
+ return !ActivityManager.isLowRamDeviceStatic();
+ }
}
static class CachedBrightnessInfo {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
deleted file mode 100644
index 2d860c0..0000000
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ /dev/null
@@ -1,3267 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.display;
-
-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 com.android.server.display.config.DisplayBrightnessMappingConfig.autoBrightnessPresetToString;
-
-import android.animation.Animator;
-import android.animation.ObjectAnimator;
-import android.annotation.Nullable;
-import android.annotation.SuppressLint;
-import android.annotation.UserIdInt;
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.pm.ParceledListSlice;
-import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.hardware.Sensor;
-import android.hardware.SensorManager;
-import android.hardware.display.AmbientBrightnessDayStats;
-import android.hardware.display.BrightnessChangeEvent;
-import android.hardware.display.BrightnessConfiguration;
-import android.hardware.display.BrightnessInfo;
-import android.hardware.display.DisplayManagerInternal;
-import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession;
-import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
-import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
-import android.metrics.LogMaker;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.HandlerExecutor;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.util.FloatProperty;
-import android.util.IndentingPrintWriter;
-import android.util.MathUtils;
-import android.util.MutableFloat;
-import android.util.MutableInt;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.view.Display;
-
-import com.android.internal.R;
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.IBatteryStats;
-import com.android.internal.display.BrightnessSynchronizer;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.internal.util.RingBuffer;
-import com.android.server.LocalServices;
-import com.android.server.am.BatteryStatsService;
-import com.android.server.display.RampAnimator.DualRampAnimator;
-import com.android.server.display.brightness.BrightnessEvent;
-import com.android.server.display.brightness.BrightnessReason;
-import com.android.server.display.brightness.BrightnessUtils;
-import com.android.server.display.brightness.DisplayBrightnessController;
-import com.android.server.display.brightness.clamper.BrightnessClamperController;
-import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
-import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
-import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
-import com.android.server.display.feature.DisplayManagerFlags;
-import com.android.server.display.layout.Layout;
-import com.android.server.display.state.DisplayStateController;
-import com.android.server.display.utils.DebugUtils;
-import com.android.server.display.utils.SensorUtils;
-import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
-import com.android.server.display.whitebalance.DisplayWhiteBalanceFactory;
-import com.android.server.display.whitebalance.DisplayWhiteBalanceSettings;
-import com.android.server.policy.WindowManagerPolicy;
-
-import java.io.PrintWriter;
-import java.util.Objects;
-
-/**
- * Controls the power state of the display.
- *
- * Handles the proximity sensor, light sensor, and animations between states
- * including the screen off animation.
- *
- * This component acts independently of the rest of the power manager service.
- * In particular, it does not share any state and it only communicates
- * via asynchronous callbacks to inform the power manager that something has
- * changed.
- *
- * Everything this class does internally is serialized on its handler although
- * it may be accessed by other threads from the outside.
- *
- * Note that the power manager service guarantees that it will hold a suspend
- * blocker as long as the display is not ready. So most of the work done here
- * does not need to worry about holding a suspend blocker unless it happens
- * independently of the display ready signal.
- *
- * For debugging, you can make the color fade and brightness animations run
- * slower by changing the "animator duration scale" option in Development Settings.
- */
-final class DisplayPowerController2 implements AutomaticBrightnessController.Callbacks,
- DisplayWhiteBalanceController.Callbacks, DisplayPowerControllerInterface {
- private static final String SCREEN_ON_BLOCKED_TRACE_NAME = "Screen on blocked";
- private static final String SCREEN_OFF_BLOCKED_TRACE_NAME = "Screen off blocked";
-
- private static final String TAG = "DisplayPowerController2";
- // To enable these logs, run:
- // 'adb shell setprop persist.log.tag.DisplayPowerController2 DEBUG && adb reboot'
- private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
- private static final String SCREEN_ON_BLOCKED_BY_DISPLAYOFFLOAD_TRACE_NAME =
- "Screen on blocked by displayoffload";
-
- // If true, uses the color fade on animation.
- // We might want to turn this off if we cannot get a guarantee that the screen
- // actually turns on and starts showing new content after the call to set the
- // screen state returns. Playing the animation can also be somewhat slow.
- private static final boolean USE_COLOR_FADE_ON_ANIMATION = false;
-
- private static final float SCREEN_ANIMATION_RATE_MINIMUM = 0.0f;
-
- private static final int COLOR_FADE_ON_ANIMATION_DURATION_MILLIS = 250;
- private static final int COLOR_FADE_OFF_ANIMATION_DURATION_MILLIS = 400;
-
- private static final int MSG_UPDATE_POWER_STATE = 1;
- private static final int MSG_SCREEN_ON_UNBLOCKED = 2;
- private static final int MSG_SCREEN_OFF_UNBLOCKED = 3;
- private static final int MSG_CONFIGURE_BRIGHTNESS = 4;
- private static final int MSG_SET_TEMPORARY_BRIGHTNESS = 5;
- private static final int MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT = 6;
- private static final int MSG_STOP = 7;
- private static final int MSG_UPDATE_BRIGHTNESS = 8;
- private static final int MSG_UPDATE_RBC = 9;
- private static final int MSG_BRIGHTNESS_RAMP_DONE = 10;
- private static final int MSG_STATSD_HBM_BRIGHTNESS = 11;
- private static final int MSG_SWITCH_USER = 12;
- private static final int MSG_BOOT_COMPLETED = 13;
- private static final int MSG_SET_DWBC_STRONG_MODE = 14;
- private static final int MSG_SET_DWBC_COLOR_OVERRIDE = 15;
- private static final int MSG_SET_DWBC_LOGGING_ENABLED = 16;
- private static final int MSG_SET_BRIGHTNESS_FROM_OFFLOAD = 17;
- private static final int MSG_OFFLOADING_SCREEN_ON_UNBLOCKED = 18;
-
-
-
- private static final int BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS = 500;
-
-
- // State machine constants for tracking initial brightness ramp skipping when enabled.
- private static final int RAMP_STATE_SKIP_NONE = 0;
- private static final int RAMP_STATE_SKIP_INITIAL = 1;
- private static final int RAMP_STATE_SKIP_AUTOBRIGHT = 2;
-
- private static final int REPORTED_TO_POLICY_UNREPORTED = -1;
- private static final int REPORTED_TO_POLICY_SCREEN_OFF = 0;
- private static final int REPORTED_TO_POLICY_SCREEN_TURNING_ON = 1;
- private static final int REPORTED_TO_POLICY_SCREEN_ON = 2;
- private static final int REPORTED_TO_POLICY_SCREEN_TURNING_OFF = 3;
-
- private static final int RINGBUFFER_MAX = 100;
- private static final int RINGBUFFER_RBC_MAX = 20;
-
- private static final float[] BRIGHTNESS_RANGE_BOUNDARIES = {
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80,
- 90, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1200,
- 1400, 1600, 1800, 2000, 2250, 2500, 2750, 3000};
- private static final int[] BRIGHTNESS_RANGE_INDEX = {
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_UNKNOWN,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_0_1,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_1_2,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_2_3,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_3_4,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_4_5,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_5_6,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_6_7,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_7_8,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_8_9,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_9_10,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_10_20,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_20_30,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_30_40,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_40_50,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_50_60,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_60_70,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_70_80,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_80_90,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_90_100,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_100_200,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_200_300,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_300_400,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_400_500,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_500_600,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_600_700,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_700_800,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_800_900,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_900_1000,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_1000_1200,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_1200_1400,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_1400_1600,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_1600_1800,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_1800_2000,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_2000_2250,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_2250_2500,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_2500_2750,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_2750_3000,
- };
-
- private final String mTag;
-
- private final Object mLock = new Object();
-
- private final Context mContext;
-
- // Our handler.
- private final DisplayControllerHandler mHandler;
-
- // Battery stats.
- @Nullable
- private final IBatteryStats mBatteryStats;
-
- // The sensor manager.
- private final SensorManager mSensorManager;
-
- // The window manager policy.
- private final WindowManagerPolicy mWindowManagerPolicy;
-
- // The display blanker.
- private final DisplayBlanker mBlanker;
-
- // The LogicalDisplay tied to this DisplayPowerController2.
- private final LogicalDisplay mLogicalDisplay;
-
- // The ID of the LogicalDisplay tied to this DisplayPowerController2.
- private final int mDisplayId;
-
- // The ID of the display which this display follows for brightness purposes.
- private int mLeadDisplayId = Layout.NO_LEAD_DISPLAY;
-
- // The unique ID of the primary display device currently tied to this logical display
- private String mUniqueDisplayId;
-
- // Tracker for brightness changes.
- @Nullable
- private final BrightnessTracker mBrightnessTracker;
-
- // Tracker for brightness settings changes.
- private final SettingsObserver mSettingsObserver;
-
- // The doze screen brightness.
- private final float mScreenBrightnessDozeConfig;
-
- // True if auto-brightness should be used.
- private boolean mUseSoftwareAutoBrightnessConfig;
-
- // Whether or not the color fade on screen on / off is enabled.
- private final boolean mColorFadeEnabled;
-
- @GuardedBy("mCachedBrightnessInfo")
- private final CachedBrightnessInfo mCachedBrightnessInfo = new CachedBrightnessInfo();
-
- private DisplayDevice mDisplayDevice;
-
- // True if we should fade the screen while turning it off, false if we should play
- // a stylish color fade animation instead.
- private final boolean mColorFadeFadesConfig;
-
- // True if we need to fake a transition to off when coming out of a doze state.
- // Some display hardware will blank itself when coming out of doze in order to hide
- // artifacts. For these displays we fake a transition into OFF so that policy can appropriately
- // blank itself and begin an appropriate power on animation.
- private final boolean mDisplayBlanksAfterDozeConfig;
-
- // True if there are only buckets of brightness values when the display is in the doze state,
- // rather than a full range of values. If this is true, then we'll avoid animating the screen
- // brightness since it'd likely be multiple jarring brightness transitions instead of just one
- // to reach the final state.
- private final boolean mBrightnessBucketsInDozeConfig;
-
- private final Clock mClock;
- private final Injector mInjector;
-
- // Maximum time a ramp animation can take.
- private long mBrightnessRampIncreaseMaxTimeMillis;
- private long mBrightnessRampDecreaseMaxTimeMillis;
-
- // Maximum time a ramp animation can take in idle mode.
- private long mBrightnessRampIncreaseMaxTimeIdleMillis;
- private long mBrightnessRampDecreaseMaxTimeIdleMillis;
-
- // The pending power request.
- // Initially null until the first call to requestPowerState.
- @GuardedBy("mLock")
- private DisplayPowerRequest mPendingRequestLocked;
-
- // True if the pending power request or wait for negative proximity flag
- // has been changed since the last update occurred.
- @GuardedBy("mLock")
- private boolean mPendingRequestChangedLocked;
-
- // Set to true when the important parts of the pending power request have been applied.
- // The important parts are mainly the screen state. Brightness changes may occur
- // concurrently.
- @GuardedBy("mLock")
- private boolean mDisplayReadyLocked;
-
- // Set to true if a power state update is required.
- @GuardedBy("mLock")
- private boolean mPendingUpdatePowerStateLocked;
-
- /* The following state must only be accessed by the handler thread. */
-
- // The currently requested power state.
- // The power controller will progressively update its internal state to match
- // the requested power state. Initially null until the first update.
- private DisplayPowerRequest mPowerRequest;
-
- // The current power state.
- // Must only be accessed on the handler thread.
- private DisplayPowerState mPowerState;
-
-
-
- // The currently active screen on unblocker. This field is non-null whenever
- // we are waiting for a callback to release it and unblock the screen.
- private ScreenOnUnblocker mPendingScreenOnUnblocker;
- private ScreenOffUnblocker mPendingScreenOffUnblocker;
- private Runnable mPendingScreenOnUnblockerByDisplayOffload;
-
- // True if we were in the process of turning off the screen.
- // This allows us to recover more gracefully from situations where we abort
- // turning off the screen.
- private boolean mPendingScreenOff;
-
- // The elapsed real time when the screen on was blocked.
- private long mScreenOnBlockStartRealTime;
- private long mScreenOffBlockStartRealTime;
- private long mScreenOnBlockByDisplayOffloadStartRealTime;
-
- // Screen state we reported to policy. Must be one of REPORTED_TO_POLICY_* fields.
- private int mReportedScreenStateToPolicy = REPORTED_TO_POLICY_UNREPORTED;
-
- // Used to deduplicate the displayoffload blocking screen on logic. One block per turning on.
- // This value is reset when screen on is reported or the blocking is cancelled.
- private boolean mScreenTurningOnWasBlockedByDisplayOffload;
-
- // If the last recorded screen state was dozing or not.
- private boolean mDozing;
-
- private boolean mAppliedDimming;
-
- private boolean mAppliedThrottling;
-
- // Reason for which the brightness was last changed. See {@link BrightnessReason} for more
- // information.
- // At the time of this writing, this value is changed within updatePowerState() only, which is
- // limited to the thread used by DisplayControllerHandler.
- private final BrightnessReason mBrightnessReason = new BrightnessReason();
- private final BrightnessReason mBrightnessReasonTemp = new BrightnessReason();
-
- // Brightness animation ramp rates in brightness units per second
- private float mBrightnessRampRateFastDecrease;
- private float mBrightnessRampRateFastIncrease;
- private float mBrightnessRampRateSlowDecrease;
- private float mBrightnessRampRateSlowIncrease;
- private float mBrightnessRampRateSlowDecreaseIdle;
- private float mBrightnessRampRateSlowIncreaseIdle;
-
- // Report HBM brightness change to StatsD
- private int mDisplayStatsId;
- private float mLastStatsBrightness = PowerManager.BRIGHTNESS_MIN;
-
- // Whether or not to skip the initial brightness ramps into STATE_ON.
- private final boolean mSkipScreenOnBrightnessRamp;
-
- // Display white balance components.
- // Critical methods must be called on DPC2 handler thread.
- @Nullable
- private final DisplayWhiteBalanceSettings mDisplayWhiteBalanceSettings;
- @Nullable
- private final DisplayWhiteBalanceController mDisplayWhiteBalanceController;
-
- @Nullable
- private final ColorDisplayServiceInternal mCdsi;
- private float[] mNitsRange;
-
- private final BrightnessRangeController mBrightnessRangeController;
-
- private final BrightnessThrottler mBrightnessThrottler;
-
- private final BrightnessClamperController mBrightnessClamperController;
-
- private final Runnable mOnBrightnessChangeRunnable;
-
- private final BrightnessEvent mLastBrightnessEvent;
- private final BrightnessEvent mTempBrightnessEvent;
-
- private final DisplayBrightnessController mDisplayBrightnessController;
-
- // Keeps a record of brightness changes for dumpsys.
- private RingBuffer<BrightnessEvent> mBrightnessEventRingBuffer;
-
- // Keeps a record of rbc changes for dumpsys.
- private final RingBuffer<BrightnessEvent> mRbcEventRingBuffer =
- new RingBuffer<>(BrightnessEvent.class, RINGBUFFER_RBC_MAX);
-
- // Controls and tracks all the wakelocks that are acquired/released by the system. Also acts as
- // a medium of communication between this class and the PowerManagerService.
- private final WakelockController mWakelockController;
-
- // Tracks and manages the proximity state of the associated display.
- private final DisplayPowerProximityStateController mDisplayPowerProximityStateController;
-
- // Tracks and manages the display state of the associated display.
- private final DisplayStateController mDisplayStateController;
-
-
- // Responsible for evaluating and tracking the automatic brightness relevant states.
- // Todo: This is a temporary workaround. Ideally DPC2 should never talk to the strategies
- private final AutomaticBrightnessStrategy mAutomaticBrightnessStrategy;
-
- // A record of state for skipping brightness ramps.
- private int mSkipRampState = RAMP_STATE_SKIP_NONE;
-
- // The first autobrightness value set when entering RAMP_STATE_SKIP_INITIAL.
- private float mInitialAutoBrightness;
-
- // The controller for the automatic brightness level.
- @Nullable
- private AutomaticBrightnessController mAutomaticBrightnessController;
-
- // The controller for the sensor used to estimate ambient lux while the display is off.
- @Nullable
- private ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController;
-
- private Sensor mLightSensor;
- private Sensor mScreenOffBrightnessSensor;
-
- private boolean mIsRbcActive;
-
- // Animators.
- private ObjectAnimator mColorFadeOnAnimator;
- private ObjectAnimator mColorFadeOffAnimator;
- private DualRampAnimator<DisplayPowerState> mScreenBrightnessRampAnimator;
-
- // True if this DisplayPowerController2 has been stopped and should no longer be running.
- private boolean mStopped;
-
- private DisplayDeviceConfig mDisplayDeviceConfig;
-
- private boolean mIsEnabled;
- private boolean mIsInTransition;
- private boolean mIsDisplayInternal;
-
- // The id of the thermal brightness throttling policy that should be used.
- private String mThermalBrightnessThrottlingDataId;
-
- // DPCs following the brightness of this DPC. This is used in concurrent displays mode - there
- // is one lead display, the additional displays follow the brightness value of the lead display.
- @GuardedBy("mLock")
- private SparseArray<DisplayPowerControllerInterface> mDisplayBrightnessFollowers =
- new SparseArray();
-
- private boolean mBootCompleted;
- private final DisplayManagerFlags mFlags;
-
- private DisplayOffloadSession mDisplayOffloadSession;
-
- /**
- * Creates the display power controller.
- */
- DisplayPowerController2(Context context, Injector injector,
- DisplayPowerCallbacks callbacks, Handler handler,
- SensorManager sensorManager, DisplayBlanker blanker, LogicalDisplay logicalDisplay,
- BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting,
- Runnable onBrightnessChangeRunnable, HighBrightnessModeMetadata hbmMetadata,
- boolean bootCompleted, DisplayManagerFlags flags) {
- mFlags = flags;
- mInjector = injector != null ? injector : new Injector();
- mClock = mInjector.getClock();
- mLogicalDisplay = logicalDisplay;
- mDisplayId = mLogicalDisplay.getDisplayIdLocked();
- mSensorManager = sensorManager;
- mHandler = new DisplayControllerHandler(handler.getLooper());
- mDisplayDeviceConfig = logicalDisplay.getPrimaryDisplayDeviceLocked()
- .getDisplayDeviceConfig();
- mIsEnabled = logicalDisplay.isEnabledLocked();
- mIsInTransition = logicalDisplay.isInTransitionLocked();
- mIsDisplayInternal = logicalDisplay.getPrimaryDisplayDeviceLocked()
- .getDisplayDeviceInfoLocked().type == Display.TYPE_INTERNAL;
- mWakelockController = mInjector.getWakelockController(mDisplayId, callbacks);
- mDisplayPowerProximityStateController = mInjector.getDisplayPowerProximityStateController(
- mWakelockController, mDisplayDeviceConfig, mHandler.getLooper(),
- () -> updatePowerState(), mDisplayId, mSensorManager);
- mDisplayStateController = new DisplayStateController(mDisplayPowerProximityStateController);
- mTag = TAG + "[" + mDisplayId + "]";
- mThermalBrightnessThrottlingDataId =
- logicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId;
- mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
- mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
- mDisplayStatsId = mUniqueDisplayId.hashCode();
-
- mLastBrightnessEvent = new BrightnessEvent(mDisplayId);
- mTempBrightnessEvent = new BrightnessEvent(mDisplayId);
-
- if (mDisplayId == Display.DEFAULT_DISPLAY) {
- mBatteryStats = BatteryStatsService.getService();
- } else {
- mBatteryStats = null;
- }
-
- mSettingsObserver = new SettingsObserver(mHandler);
- mWindowManagerPolicy = LocalServices.getService(WindowManagerPolicy.class);
- mBlanker = blanker;
- mContext = context;
- mBrightnessTracker = brightnessTracker;
- mOnBrightnessChangeRunnable = onBrightnessChangeRunnable;
-
- PowerManager pm = context.getSystemService(PowerManager.class);
-
- final Resources resources = context.getResources();
-
- // DOZE AND DIM SETTINGS
- mScreenBrightnessDozeConfig = BrightnessUtils.clampAbsoluteBrightness(
- pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE));
- loadBrightnessRampRates();
- mSkipScreenOnBrightnessRamp = resources.getBoolean(
- R.bool.config_skipScreenOnBrightnessRamp);
-
- Runnable modeChangeCallback = () -> {
- sendUpdatePowerState();
- postBrightnessChangeRunnable();
- // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern.
- if (mAutomaticBrightnessController != null) {
- mAutomaticBrightnessController.update();
- }
- };
-
- HighBrightnessModeController hbmController = createHbmControllerLocked(hbmMetadata,
- modeChangeCallback);
- mBrightnessThrottler = createBrightnessThrottlerLocked();
-
- mBrightnessRangeController = mInjector.getBrightnessRangeController(hbmController,
- modeChangeCallback, mDisplayDeviceConfig, mHandler, flags,
- mDisplayDevice.getDisplayTokenLocked(),
- mDisplayDevice.getDisplayDeviceInfoLocked());
-
- mDisplayBrightnessController =
- new DisplayBrightnessController(context, null,
- mDisplayId, mLogicalDisplay.getDisplayInfoLocked().brightnessDefault,
- brightnessSetting, () -> postBrightnessChangeRunnable(),
- new HandlerExecutor(mHandler), flags);
-
- mBrightnessClamperController = mInjector.getBrightnessClamperController(
- mHandler, modeChangeCallback::run,
- new BrightnessClamperController.DisplayDeviceData(
- mUniqueDisplayId,
- mThermalBrightnessThrottlingDataId,
- logicalDisplay.getPowerThrottlingDataIdLocked(),
- mDisplayDeviceConfig), mContext, flags);
- // Seed the cached brightness
- saveBrightnessInfo(getScreenBrightnessSetting());
- mAutomaticBrightnessStrategy =
- mDisplayBrightnessController.getAutomaticBrightnessStrategy();
-
- DisplayWhiteBalanceSettings displayWhiteBalanceSettings = null;
- DisplayWhiteBalanceController displayWhiteBalanceController = null;
- if (mDisplayId == Display.DEFAULT_DISPLAY) {
- try {
- displayWhiteBalanceController = mInjector.getDisplayWhiteBalanceController(
- mHandler, mSensorManager, resources);
- displayWhiteBalanceSettings = new DisplayWhiteBalanceSettings(mContext, mHandler);
- displayWhiteBalanceSettings.setCallbacks(this);
- displayWhiteBalanceController.setCallbacks(this);
- } catch (Exception e) {
- Slog.e(mTag, "failed to set up display white-balance: " + e);
- }
- }
- mDisplayWhiteBalanceSettings = displayWhiteBalanceSettings;
- mDisplayWhiteBalanceController = displayWhiteBalanceController;
-
- loadNitsRange(resources);
-
- if (mDisplayId == Display.DEFAULT_DISPLAY) {
- mCdsi = LocalServices.getService(ColorDisplayServiceInternal.class);
- if (mCdsi != null) {
- boolean active = mCdsi.setReduceBrightColorsListener(
- new ReduceBrightColorsListener() {
- @Override
- public void onReduceBrightColorsActivationChanged(boolean activated,
- boolean userInitiated) {
- applyReduceBrightColorsSplineAdjustment();
-
- }
-
- @Override
- public void onReduceBrightColorsStrengthChanged(int strength) {
- applyReduceBrightColorsSplineAdjustment();
- }
- });
- if (active) {
- applyReduceBrightColorsSplineAdjustment();
- }
- }
- } else {
- mCdsi = null;
- }
-
- setUpAutoBrightness(context, handler);
-
- mColorFadeEnabled = mInjector.isColorFadeEnabled()
- && !resources.getBoolean(
- com.android.internal.R.bool.config_displayColorFadeDisabled);
- mColorFadeFadesConfig = resources.getBoolean(
- R.bool.config_animateScreenLights);
-
- mDisplayBlanksAfterDozeConfig = resources.getBoolean(
- R.bool.config_displayBlanksAfterDoze);
-
- mBrightnessBucketsInDozeConfig = resources.getBoolean(
- R.bool.config_displayBrightnessBucketsInDoze);
-
- mBootCompleted = bootCompleted;
- }
-
- private void applyReduceBrightColorsSplineAdjustment() {
- mHandler.obtainMessage(MSG_UPDATE_RBC).sendToTarget();
- sendUpdatePowerState();
- }
-
- private void handleRbcChanged() {
- if (mAutomaticBrightnessController == null) {
- return;
- }
-
- float[] adjustedNits = new float[mNitsRange.length];
- for (int i = 0; i < mNitsRange.length; i++) {
- adjustedNits[i] = mCdsi.getReduceBrightColorsAdjustedBrightnessNits(mNitsRange[i]);
- }
- mIsRbcActive = mCdsi.isReduceBrightColorsActivated();
- mAutomaticBrightnessController.recalculateSplines(mIsRbcActive, adjustedNits);
- }
-
- /**
- * Returns true if the proximity sensor screen-off function is available.
- */
- @Override
- public boolean isProximitySensorAvailable() {
- return mDisplayPowerProximityStateController.isProximitySensorAvailable();
- }
-
- /**
- * Get the {@link BrightnessChangeEvent}s for the specified user.
- *
- * @param userId userId to fetch data for
- * @param includePackage if false will null out the package name in events
- */
- @Nullable
- @Override
- public ParceledListSlice<BrightnessChangeEvent> getBrightnessEvents(
- @UserIdInt int userId, boolean includePackage) {
- if (mBrightnessTracker == null) {
- return null;
- }
- return mBrightnessTracker.getEvents(userId, includePackage);
- }
-
- @Override
- public void onSwitchUser(@UserIdInt int newUserId) {
- Message msg = mHandler.obtainMessage(MSG_SWITCH_USER, newUserId);
- mHandler.sendMessage(msg);
- }
-
- private void handleOnSwitchUser(@UserIdInt int newUserId) {
- handleSettingsChange(true /* userSwitch */);
- handleBrightnessModeChange();
- if (mBrightnessTracker != null) {
- mBrightnessTracker.onSwitchUser(newUserId);
- }
- }
-
- @Nullable
- @Override
- public ParceledListSlice<AmbientBrightnessDayStats> getAmbientBrightnessStats(
- @UserIdInt int userId) {
- if (mBrightnessTracker == null) {
- return null;
- }
- return mBrightnessTracker.getAmbientBrightnessStats(userId);
- }
-
- /**
- * Persist the brightness slider events and ambient brightness stats to disk.
- */
- @Override
- public void persistBrightnessTrackerState() {
- if (mBrightnessTracker != null) {
- mBrightnessTracker.persistBrightnessTrackerState();
- }
- }
-
- /**
- * Requests a new power state.
- * The controller makes a copy of the provided object and then
- * begins adjusting the power state to match what was requested.
- *
- * @param request The requested power state.
- * @param waitForNegativeProximity If true, issues a request to wait for
- * negative proximity before turning the screen back on,
- * assuming the screen
- * was turned off by the proximity sensor.
- * @return True if display is ready, false if there are important changes that must
- * be made asynchronously (such as turning the screen on), in which case the caller
- * should grab a wake lock, watch for {@link DisplayPowerCallbacks#onStateChanged()}
- * then try the request again later until the state converges.
- */
- public boolean requestPowerState(DisplayPowerRequest request,
- boolean waitForNegativeProximity) {
- if (DEBUG) {
- Slog.d(mTag, "requestPowerState: "
- + request + ", waitForNegativeProximity=" + waitForNegativeProximity);
- }
-
- synchronized (mLock) {
- if (mStopped) {
- return true;
- }
-
- boolean changed = mDisplayPowerProximityStateController
- .setPendingWaitForNegativeProximityLocked(waitForNegativeProximity);
-
- if (mPendingRequestLocked == null) {
- mPendingRequestLocked = new DisplayPowerRequest(request);
- changed = true;
- } else if (!mPendingRequestLocked.equals(request)) {
- mPendingRequestLocked.copyFrom(request);
- changed = true;
- }
-
- if (changed) {
- mDisplayReadyLocked = false;
- if (!mPendingRequestChangedLocked) {
- mPendingRequestChangedLocked = true;
- sendUpdatePowerStateLocked();
- }
- }
-
- return mDisplayReadyLocked;
- }
- }
-
- @Override
- public void overrideDozeScreenState(int displayState) {
- mHandler.postAtTime(() -> {
- if (mDisplayOffloadSession == null
- || !(DisplayOffloadSession.isSupportedOffloadState(displayState)
- || displayState == Display.STATE_UNKNOWN)) {
- return;
- }
- mDisplayStateController.overrideDozeScreenState(displayState);
- sendUpdatePowerState();
- }, mClock.uptimeMillis());
- }
-
- @Override
- public void setDisplayOffloadSession(DisplayOffloadSession session) {
- if (session == mDisplayOffloadSession) {
- return;
- }
- unblockScreenOnByDisplayOffload();
- mDisplayOffloadSession = session;
- }
-
- @Override
- public BrightnessConfiguration getDefaultBrightnessConfiguration() {
- if (mAutomaticBrightnessController == null) {
- return null;
- }
- return mAutomaticBrightnessController.getDefaultConfig();
- }
-
- /**
- * Notified when the display is changed. We use this to apply any changes that might be needed
- * when displays get swapped on foldable devices. For example, different brightness properties
- * of each display need to be properly reflected in AutomaticBrightnessController.
- *
- * Make sure DisplayManagerService.mSyncRoot lock is held when this is called
- */
- @Override
- public void onDisplayChanged(HighBrightnessModeMetadata hbmMetadata, int leadDisplayId) {
- mLeadDisplayId = leadDisplayId;
- final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
- if (device == null) {
- Slog.wtf(mTag, "Display Device is null in DisplayPowerController2 for display: "
- + mLogicalDisplay.getDisplayIdLocked());
- return;
- }
-
- final String uniqueId = device.getUniqueId();
- final DisplayDeviceConfig config = device.getDisplayDeviceConfig();
- final IBinder token = device.getDisplayTokenLocked();
- final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
- final boolean isEnabled = mLogicalDisplay.isEnabledLocked();
- final boolean isInTransition = mLogicalDisplay.isInTransitionLocked();
- final boolean isDisplayInternal = mLogicalDisplay.getPrimaryDisplayDeviceLocked() != null
- && mLogicalDisplay.getPrimaryDisplayDeviceLocked()
- .getDisplayDeviceInfoLocked().type == Display.TYPE_INTERNAL;
- final String thermalBrightnessThrottlingDataId =
- mLogicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId;
- final String powerThrottlingDataId =
- mLogicalDisplay.getPowerThrottlingDataIdLocked();
-
- mHandler.postAtTime(() -> {
- boolean changed = false;
- if (mDisplayDevice != device) {
- changed = true;
- mDisplayDevice = device;
- mUniqueDisplayId = uniqueId;
- mDisplayStatsId = mUniqueDisplayId.hashCode();
- mDisplayDeviceConfig = config;
- mThermalBrightnessThrottlingDataId = thermalBrightnessThrottlingDataId;
- loadFromDisplayDeviceConfig(token, info, hbmMetadata);
- mDisplayPowerProximityStateController.notifyDisplayDeviceChanged(config);
-
- // Since the underlying display-device changed, we really don't know the
- // last command that was sent to change it's state. Let's assume it is unknown so
- // that we trigger a change immediately.
- mPowerState.resetScreenState();
- } else if (!Objects.equals(mThermalBrightnessThrottlingDataId,
- thermalBrightnessThrottlingDataId)) {
- changed = true;
- mThermalBrightnessThrottlingDataId = thermalBrightnessThrottlingDataId;
- mBrightnessThrottler.loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig(
- config.getThermalBrightnessThrottlingDataMapByThrottlingId(),
- mThermalBrightnessThrottlingDataId,
- mUniqueDisplayId);
- }
- if (mIsEnabled != isEnabled || mIsInTransition != isInTransition) {
- changed = true;
- mIsEnabled = isEnabled;
- mIsInTransition = isInTransition;
- }
-
- mIsDisplayInternal = isDisplayInternal;
- // using local variables here, when mBrightnessThrottler is removed,
- // mThermalBrightnessThrottlingDataId could be removed as well
- // changed = true will be not needed - clampers are maintaining their state and
- // will call updatePowerState if needed.
- mBrightnessClamperController.onDisplayChanged(
- new BrightnessClamperController.DisplayDeviceData(uniqueId,
- thermalBrightnessThrottlingDataId, powerThrottlingDataId, config));
-
- if (changed) {
- updatePowerState();
- }
- }, mClock.uptimeMillis());
- }
-
- /**
- * Unregisters all listeners and interrupts all running threads; halting future work.
- *
- * This method should be called when the DisplayPowerController2 is no longer in use; i.e. when
- * the {@link #mDisplayId display} has been removed.
- */
- @Override
- public void stop() {
- synchronized (mLock) {
- clearDisplayBrightnessFollowersLocked();
-
- mStopped = true;
- Message msg = mHandler.obtainMessage(MSG_STOP);
- mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
-
- if (mAutomaticBrightnessController != null) {
- mAutomaticBrightnessController.stop();
- }
-
- mDisplayBrightnessController.stop();
-
- mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
- }
- }
-
- private void loadFromDisplayDeviceConfig(IBinder token, DisplayDeviceInfo info,
- HighBrightnessModeMetadata hbmMetadata) {
- // All properties that depend on the associated DisplayDevice and the DDC must be
- // updated here.
- loadBrightnessRampRates();
- loadNitsRange(mContext.getResources());
- setUpAutoBrightness(mContext, mHandler);
- reloadReduceBrightColours();
- setAnimatorRampSpeeds(/* isIdleMode= */ false);
-
- mBrightnessRangeController.loadFromConfig(hbmMetadata, token, info, mDisplayDeviceConfig);
- mBrightnessThrottler.loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig(
- mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId(),
- mThermalBrightnessThrottlingDataId, mUniqueDisplayId);
- }
-
- private void sendUpdatePowerState() {
- synchronized (mLock) {
- sendUpdatePowerStateLocked();
- }
- }
-
- @GuardedBy("mLock")
- private void sendUpdatePowerStateLocked() {
- if (!mStopped && !mPendingUpdatePowerStateLocked) {
- mPendingUpdatePowerStateLocked = true;
- Message msg = mHandler.obtainMessage(MSG_UPDATE_POWER_STATE);
- mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
- }
- }
-
- private void initialize(int displayState) {
- mPowerState = mInjector.getDisplayPowerState(mBlanker,
- mColorFadeEnabled ? new ColorFade(mDisplayId) : null, mDisplayId, displayState);
-
- if (mColorFadeEnabled) {
- mColorFadeOnAnimator = ObjectAnimator.ofFloat(
- mPowerState, DisplayPowerState.COLOR_FADE_LEVEL, 0.0f, 1.0f);
- mColorFadeOnAnimator.setDuration(COLOR_FADE_ON_ANIMATION_DURATION_MILLIS);
- mColorFadeOnAnimator.addListener(mAnimatorListener);
-
- mColorFadeOffAnimator = ObjectAnimator.ofFloat(
- mPowerState, DisplayPowerState.COLOR_FADE_LEVEL, 1.0f, 0.0f);
- mColorFadeOffAnimator.setDuration(COLOR_FADE_OFF_ANIMATION_DURATION_MILLIS);
- mColorFadeOffAnimator.addListener(mAnimatorListener);
- }
-
- mScreenBrightnessRampAnimator = mInjector.getDualRampAnimator(mPowerState,
- DisplayPowerState.SCREEN_BRIGHTNESS_FLOAT,
- DisplayPowerState.SCREEN_SDR_BRIGHTNESS_FLOAT);
- setAnimatorRampSpeeds(mAutomaticBrightnessController != null
- && mAutomaticBrightnessController.isInIdleMode());
- mScreenBrightnessRampAnimator.setListener(mRampAnimatorListener);
-
- noteScreenState(mPowerState.getScreenState());
- noteScreenBrightness(mPowerState.getScreenBrightness());
-
- // Initialize all of the brightness tracking state
- final float brightness = mDisplayBrightnessController.convertToAdjustedNits(
- mPowerState.getScreenBrightness());
- if (mBrightnessTracker != null && brightness >= PowerManager.BRIGHTNESS_MIN) {
- mBrightnessTracker.start(brightness);
- }
-
- BrightnessSetting.BrightnessSettingListener brightnessSettingListener = brightnessValue -> {
- Message msg = mHandler.obtainMessage(MSG_UPDATE_BRIGHTNESS, brightnessValue);
- mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
- };
- mDisplayBrightnessController
- .registerBrightnessSettingChangeListener(brightnessSettingListener);
-
- mContext.getContentResolver().registerContentObserver(
- Settings.System.getUriFor(Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ),
- false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
- mContext.getContentResolver().registerContentObserver(
- Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE),
- false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
- if (mFlags.areAutoBrightnessModesEnabled()) {
- mContext.getContentResolver().registerContentObserver(
- Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FOR_ALS),
- /* notifyForDescendants= */ false, mSettingsObserver, UserHandle.USER_CURRENT);
- }
- handleBrightnessModeChange();
- }
-
- private void setUpAutoBrightness(Context context, Handler handler) {
- mUseSoftwareAutoBrightnessConfig = mDisplayDeviceConfig.isAutoBrightnessAvailable();
-
- if (!mUseSoftwareAutoBrightnessConfig) {
- return;
- }
-
- SparseArray<BrightnessMappingStrategy> brightnessMappers = new SparseArray<>();
-
- BrightnessMappingStrategy defaultModeBrightnessMapper =
- mInjector.getDefaultModeBrightnessMapper(context, mDisplayDeviceConfig,
- mDisplayWhiteBalanceController);
- brightnessMappers.append(AUTO_BRIGHTNESS_MODE_DEFAULT,
- defaultModeBrightnessMapper);
-
- final boolean isIdleScreenBrightnessEnabled = context.getResources().getBoolean(
- R.bool.config_enableIdleScreenBrightnessMode);
- if (isIdleScreenBrightnessEnabled) {
- BrightnessMappingStrategy idleModeBrightnessMapper =
- BrightnessMappingStrategy.create(context, mDisplayDeviceConfig,
- AUTO_BRIGHTNESS_MODE_IDLE,
- mDisplayWhiteBalanceController);
- if (idleModeBrightnessMapper != null) {
- brightnessMappers.append(AUTO_BRIGHTNESS_MODE_IDLE,
- idleModeBrightnessMapper);
- }
- }
-
- BrightnessMappingStrategy dozeModeBrightnessMapper =
- BrightnessMappingStrategy.create(context, mDisplayDeviceConfig,
- AUTO_BRIGHTNESS_MODE_DOZE, mDisplayWhiteBalanceController);
- if (mFlags.areAutoBrightnessModesEnabled() && dozeModeBrightnessMapper != null) {
- brightnessMappers.put(AUTO_BRIGHTNESS_MODE_DOZE, dozeModeBrightnessMapper);
- }
-
- float userLux = BrightnessMappingStrategy.INVALID_LUX;
- float userNits = BrightnessMappingStrategy.INVALID_NITS;
- if (mAutomaticBrightnessController != null) {
- userLux = mAutomaticBrightnessController.getUserLux();
- userNits = mAutomaticBrightnessController.getUserNits();
- }
-
- 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();
- float[] ambientDarkeningThresholds =
- mDisplayDeviceConfig.getAmbientDarkeningPercentages();
- float[] ambientBrighteningLevels =
- mDisplayDeviceConfig.getAmbientBrighteningLevels();
- float[] ambientDarkeningLevels =
- mDisplayDeviceConfig.getAmbientDarkeningLevels();
- float ambientDarkeningMinThreshold =
- mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold();
- float ambientBrighteningMinThreshold =
- mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold();
- HysteresisLevels ambientBrightnessThresholds = mInjector.getHysteresisLevels(
- ambientBrighteningThresholds, ambientDarkeningThresholds,
- ambientBrighteningLevels, ambientDarkeningLevels, ambientDarkeningMinThreshold,
- ambientBrighteningMinThreshold);
-
- // Display - Active Mode Brightness Thresholds
- float[] screenBrighteningThresholds =
- mDisplayDeviceConfig.getScreenBrighteningPercentages();
- float[] screenDarkeningThresholds =
- mDisplayDeviceConfig.getScreenDarkeningPercentages();
- float[] screenBrighteningLevels =
- mDisplayDeviceConfig.getScreenBrighteningLevels();
- float[] screenDarkeningLevels =
- mDisplayDeviceConfig.getScreenDarkeningLevels();
- float screenDarkeningMinThreshold =
- mDisplayDeviceConfig.getScreenDarkeningMinThreshold();
- float screenBrighteningMinThreshold =
- mDisplayDeviceConfig.getScreenBrighteningMinThreshold();
- HysteresisLevels screenBrightnessThresholds = mInjector.getHysteresisLevels(
- screenBrighteningThresholds, screenDarkeningThresholds,
- screenBrighteningLevels, screenDarkeningLevels, screenDarkeningMinThreshold,
- screenBrighteningMinThreshold, true);
-
- // Ambient Lux - Idle Screen Brightness Thresholds
- float ambientDarkeningMinThresholdIdle =
- mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle();
- float ambientBrighteningMinThresholdIdle =
- mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle();
- float[] ambientBrighteningThresholdsIdle =
- mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle();
- float[] ambientDarkeningThresholdsIdle =
- mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle();
- float[] ambientBrighteningLevelsIdle =
- mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle();
- float[] ambientDarkeningLevelsIdle =
- mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle();
- HysteresisLevels ambientBrightnessThresholdsIdle = mInjector.getHysteresisLevels(
- ambientBrighteningThresholdsIdle, ambientDarkeningThresholdsIdle,
- ambientBrighteningLevelsIdle, ambientDarkeningLevelsIdle,
- ambientDarkeningMinThresholdIdle, ambientBrighteningMinThresholdIdle);
-
- // Display - Idle Screen Brightness Thresholds
- float screenDarkeningMinThresholdIdle =
- mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle();
- float screenBrighteningMinThresholdIdle =
- mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle();
- float[] screenBrighteningThresholdsIdle =
- mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle();
- float[] screenDarkeningThresholdsIdle =
- mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle();
- float[] screenBrighteningLevelsIdle =
- mDisplayDeviceConfig.getScreenBrighteningLevelsIdle();
- float[] screenDarkeningLevelsIdle =
- mDisplayDeviceConfig.getScreenDarkeningLevelsIdle();
- HysteresisLevels screenBrightnessThresholdsIdle = mInjector.getHysteresisLevels(
- screenBrighteningThresholdsIdle, screenDarkeningThresholdsIdle,
- screenBrighteningLevelsIdle, screenDarkeningLevelsIdle,
- screenDarkeningMinThresholdIdle, screenBrighteningMinThresholdIdle);
-
- long brighteningLightDebounce = mDisplayDeviceConfig
- .getAutoBrightnessBrighteningLightDebounce();
- long darkeningLightDebounce = mDisplayDeviceConfig
- .getAutoBrightnessDarkeningLightDebounce();
- long brighteningLightDebounceIdle = mDisplayDeviceConfig
- .getAutoBrightnessBrighteningLightDebounceIdle();
- long darkeningLightDebounceIdle = mDisplayDeviceConfig
- .getAutoBrightnessDarkeningLightDebounceIdle();
- boolean autoBrightnessResetAmbientLuxAfterWarmUp = context.getResources().getBoolean(
- R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp);
-
- int lightSensorWarmUpTimeConfig = context.getResources().getInteger(
- R.integer.config_lightSensorWarmupTime);
- int lightSensorRate = context.getResources().getInteger(
- R.integer.config_autoBrightnessLightSensorRate);
- int initialLightSensorRate = context.getResources().getInteger(
- R.integer.config_autoBrightnessInitialLightSensorRate);
- if (initialLightSensorRate == -1) {
- initialLightSensorRate = lightSensorRate;
- } else if (initialLightSensorRate > lightSensorRate) {
- Slog.w(mTag, "Expected config_autoBrightnessInitialLightSensorRate ("
- + initialLightSensorRate + ") to be less than or equal to "
- + "config_autoBrightnessLightSensorRate (" + lightSensorRate + ").");
- }
-
- loadAmbientLightSensor();
- // BrightnessTracker should only use one light sensor, we want to use the light sensor
- // from the default display and not e.g. temporary displays when switching layouts.
- if (mBrightnessTracker != null && mDisplayId == Display.DEFAULT_DISPLAY) {
- mBrightnessTracker.setLightSensor(mLightSensor);
- }
-
- if (mAutomaticBrightnessController != null) {
- mAutomaticBrightnessController.stop();
- }
- mAutomaticBrightnessController = mInjector.getAutomaticBrightnessController(
- this, handler.getLooper(), mSensorManager, mLightSensor,
- brightnessMappers, lightSensorWarmUpTimeConfig, PowerManager.BRIGHTNESS_MIN,
- PowerManager.BRIGHTNESS_MAX, dozeScaleFactor, lightSensorRate,
- initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce,
- brighteningLightDebounceIdle, darkeningLightDebounceIdle,
- autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds,
- screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
- screenBrightnessThresholdsIdle, mContext, mBrightnessRangeController,
- mBrightnessThrottler, mDisplayDeviceConfig.getAmbientHorizonShort(),
- mDisplayDeviceConfig.getAmbientHorizonLong(), userLux, userNits);
- mDisplayBrightnessController.setAutomaticBrightnessController(
- mAutomaticBrightnessController);
-
- mAutomaticBrightnessStrategy
- .setAutomaticBrightnessController(mAutomaticBrightnessController);
- mBrightnessEventRingBuffer =
- new RingBuffer<>(BrightnessEvent.class, RINGBUFFER_MAX);
-
- if (mScreenOffBrightnessSensorController != null) {
- mScreenOffBrightnessSensorController.stop();
- mScreenOffBrightnessSensorController = null;
- }
- loadScreenOffBrightnessSensor();
- int[] sensorValueToLux = mDisplayDeviceConfig.getScreenOffBrightnessSensorValueToLux();
- if (mScreenOffBrightnessSensor != null && sensorValueToLux != null) {
- mScreenOffBrightnessSensorController =
- mInjector.getScreenOffBrightnessSensorController(
- mSensorManager,
- mScreenOffBrightnessSensor,
- mHandler,
- SystemClock::uptimeMillis,
- sensorValueToLux,
- defaultModeBrightnessMapper);
- }
- } else {
- mUseSoftwareAutoBrightnessConfig = false;
- }
- }
-
- private void loadBrightnessRampRates() {
- mBrightnessRampRateFastDecrease = mDisplayDeviceConfig.getBrightnessRampFastDecrease();
- mBrightnessRampRateFastIncrease = mDisplayDeviceConfig.getBrightnessRampFastIncrease();
- mBrightnessRampRateSlowDecrease = mDisplayDeviceConfig.getBrightnessRampSlowDecrease();
- mBrightnessRampRateSlowIncrease = mDisplayDeviceConfig.getBrightnessRampSlowIncrease();
- mBrightnessRampRateSlowDecreaseIdle =
- mDisplayDeviceConfig.getBrightnessRampSlowDecreaseIdle();
- mBrightnessRampRateSlowIncreaseIdle =
- mDisplayDeviceConfig.getBrightnessRampSlowIncreaseIdle();
- mBrightnessRampDecreaseMaxTimeMillis =
- mDisplayDeviceConfig.getBrightnessRampDecreaseMaxMillis();
- mBrightnessRampIncreaseMaxTimeMillis =
- mDisplayDeviceConfig.getBrightnessRampIncreaseMaxMillis();
- mBrightnessRampDecreaseMaxTimeIdleMillis =
- mDisplayDeviceConfig.getBrightnessRampDecreaseMaxIdleMillis();
- mBrightnessRampIncreaseMaxTimeIdleMillis =
- mDisplayDeviceConfig.getBrightnessRampIncreaseMaxIdleMillis();
- }
-
- private void loadNitsRange(Resources resources) {
- if (mDisplayDeviceConfig != null && mDisplayDeviceConfig.getNits() != null) {
- mNitsRange = mDisplayDeviceConfig.getNits();
- } else {
- Slog.w(mTag, "Screen brightness nits configuration is unavailable; falling back");
- mNitsRange = BrightnessMappingStrategy.getFloatArray(resources
- .obtainTypedArray(R.array.config_screenBrightnessNits));
- }
- }
-
- private void reloadReduceBrightColours() {
- if (mCdsi != null && mCdsi.isReduceBrightColorsActivated()) {
- applyReduceBrightColorsSplineAdjustment();
- }
- }
-
- @Override
- public void setAutomaticScreenBrightnessMode(
- @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
- boolean isIdle = mode == AUTO_BRIGHTNESS_MODE_IDLE;
- if (mAutomaticBrightnessController != null) {
- mAutomaticBrightnessController.switchMode(mode);
- setAnimatorRampSpeeds(isIdle);
- }
- Message msg = mHandler.obtainMessage();
- msg.what = MSG_SET_DWBC_STRONG_MODE;
- msg.arg1 = isIdle ? 1 : 0;
- mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
- }
-
- private void setAnimatorRampSpeeds(boolean isIdle) {
- if (mScreenBrightnessRampAnimator == null) {
- return;
- }
- if (mFlags.isAdaptiveTone1Enabled() && isIdle) {
- mScreenBrightnessRampAnimator.setAnimationTimeLimits(
- mBrightnessRampIncreaseMaxTimeIdleMillis,
- mBrightnessRampDecreaseMaxTimeIdleMillis);
- } else {
- mScreenBrightnessRampAnimator.setAnimationTimeLimits(
- mBrightnessRampIncreaseMaxTimeMillis,
- mBrightnessRampDecreaseMaxTimeMillis);
- }
- }
-
- private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) {
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- sendUpdatePowerState();
- }
-
- @Override
- public void onAnimationRepeat(Animator animation) {
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- }
- };
-
- private final RampAnimator.Listener mRampAnimatorListener = new RampAnimator.Listener() {
- @Override
- public void onAnimationEnd() {
- sendUpdatePowerState();
- Message msg = mHandler.obtainMessage(MSG_BRIGHTNESS_RAMP_DONE);
- mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
- }
- };
-
- /** Clean up all resources that are accessed via the {@link #mHandler} thread. */
- private void cleanupHandlerThreadAfterStop() {
- mDisplayPowerProximityStateController.cleanup();
- mBrightnessRangeController.stop();
- mBrightnessThrottler.stop();
- mBrightnessClamperController.stop();
- mHandler.removeCallbacksAndMessages(null);
-
- // Release any outstanding wakelocks we're still holding because of pending messages.
- mWakelockController.releaseAll();
-
- final float brightness = mPowerState != null
- ? mPowerState.getScreenBrightness()
- : PowerManager.BRIGHTNESS_MIN;
- reportStats(brightness);
-
- if (mPowerState != null) {
- mPowerState.stop();
- mPowerState = null;
- }
-
- if (mScreenOffBrightnessSensorController != null) {
- mScreenOffBrightnessSensorController.stop();
- }
-
- if (mDisplayWhiteBalanceController != null) {
- mDisplayWhiteBalanceController.setEnabled(false);
- }
- }
-
- // Call from handler thread
- private void updatePowerState() {
- Trace.traceBegin(Trace.TRACE_TAG_POWER,
- "DisplayPowerController#updatePowerState");
- updatePowerStateInternal();
- Trace.traceEnd(Trace.TRACE_TAG_POWER);
- }
-
- private void updatePowerStateInternal() {
- // Update the power state request.
- final boolean mustNotify;
- final int previousPolicy;
- boolean mustInitialize = false;
- mBrightnessReasonTemp.set(null);
- mTempBrightnessEvent.reset();
- SparseArray<DisplayPowerControllerInterface> displayBrightnessFollowers;
- synchronized (mLock) {
- if (mStopped) {
- return;
- }
- mPendingUpdatePowerStateLocked = false;
- if (mPendingRequestLocked == null) {
- return; // wait until first actual power request
- }
-
- if (mPowerRequest == null) {
- mPowerRequest = new DisplayPowerRequest(mPendingRequestLocked);
- mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
- mPendingRequestChangedLocked = false;
- mustInitialize = true;
- // Assume we're on and bright until told otherwise, since that's the state we turn
- // on in.
- previousPolicy = DisplayPowerRequest.POLICY_BRIGHT;
- } else if (mPendingRequestChangedLocked) {
- previousPolicy = mPowerRequest.policy;
- mPowerRequest.copyFrom(mPendingRequestLocked);
- mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
- mPendingRequestChangedLocked = false;
- mDisplayReadyLocked = false;
- } else {
- previousPolicy = mPowerRequest.policy;
- }
-
- mustNotify = !mDisplayReadyLocked;
-
- displayBrightnessFollowers = mDisplayBrightnessFollowers.clone();
- }
-
- int state = mDisplayStateController
- .updateDisplayState(mPowerRequest, mIsEnabled, mIsInTransition);
-
- // Initialize things the first time the power state is changed.
- if (mustInitialize) {
- initialize(readyToUpdateDisplayState() ? state : Display.STATE_UNKNOWN);
- }
-
- // Animate the screen state change unless already animating.
- // The transition may be deferred, so after this point we will use the
- // actual state instead of the desired one.
- animateScreenStateChange(state, mDisplayStateController.shouldPerformScreenOffTransition());
- state = mPowerState.getScreenState();
-
- // Switch to doze auto-brightness mode if needed
- if (mFlags.areAutoBrightnessModesEnabled() && mAutomaticBrightnessController != null
- && !mAutomaticBrightnessController.isInIdleMode()) {
- setAutomaticScreenBrightnessMode(Display.isDozeState(state)
- ? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT);
- }
-
- final boolean userSetBrightnessChanged = mDisplayBrightnessController
- .updateUserSetScreenBrightness();
-
- DisplayBrightnessState displayBrightnessState = mDisplayBrightnessController
- .updateBrightness(mPowerRequest, state);
- float brightnessState = displayBrightnessState.getBrightness();
- float rawBrightnessState = displayBrightnessState.getBrightness();
- mBrightnessReasonTemp.set(displayBrightnessState.getBrightnessReason());
- boolean slowChange = displayBrightnessState.isSlowChange();
- // custom transition duration
- float customAnimationRate = displayBrightnessState.getCustomAnimationRate();
-
- // Set up the ScreenOff controller used when coming out of SCREEN_OFF and the ALS sensor
- // doesn't yet have a valid lux value to use with auto-brightness.
- if (mScreenOffBrightnessSensorController != null) {
- mScreenOffBrightnessSensorController
- .setLightSensorEnabled(displayBrightnessState.getShouldUseAutoBrightness()
- && mIsEnabled && (state == Display.STATE_OFF
- || (state == Display.STATE_DOZE
- && !mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()))
- && mLeadDisplayId == Layout.NO_LEAD_DISPLAY);
- }
-
- // Take note if the short term model was already active before applying the current
- // request changes.
- final boolean wasShortTermModelActive =
- mAutomaticBrightnessStrategy.isShortTermModelActive();
- mAutomaticBrightnessStrategy.setAutoBrightnessState(state,
- mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig(),
- mBrightnessReasonTemp.getReason(), mPowerRequest.policy,
- mDisplayBrightnessController.getLastUserSetScreenBrightness(),
- userSetBrightnessChanged);
-
- // If the brightness is already set then it's been overridden by something other than the
- // user, or is a temporary adjustment.
- boolean userInitiatedChange = (Float.isNaN(brightnessState))
- && (mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged()
- || userSetBrightnessChanged);
-
- mBrightnessRangeController.setAutoBrightnessEnabled(
- mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()
- ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
- : mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff()
- ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
- : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
-
- boolean updateScreenBrightnessSetting =
- displayBrightnessState.shouldUpdateScreenBrightnessSetting();
- float currentBrightnessSetting = mDisplayBrightnessController.getCurrentBrightness();
- // Apply auto-brightness.
- int brightnessAdjustmentFlags = 0;
- if (Float.isNaN(brightnessState)) {
- if (mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()) {
- brightnessState = mAutomaticBrightnessStrategy.getAutomaticScreenBrightness(
- mTempBrightnessEvent);
- if (BrightnessUtils.isValidBrightnessValue(brightnessState)
- || brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT) {
- rawBrightnessState = mAutomaticBrightnessController
- .getRawAutomaticScreenBrightness();
- brightnessState = clampScreenBrightness(brightnessState);
- // slowly adapt to auto-brightness
- // TODO(b/253226419): slowChange should be decided by strategy.updateBrightness
- slowChange = mAutomaticBrightnessStrategy.hasAppliedAutoBrightness()
- && !mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged();
- brightnessAdjustmentFlags =
- mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentReasonsFlags();
- updateScreenBrightnessSetting = currentBrightnessSetting != brightnessState;
- mAutomaticBrightnessStrategy.setAutoBrightnessApplied(true);
- mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC);
- if (mScreenOffBrightnessSensorController != null) {
- mScreenOffBrightnessSensorController.setLightSensorEnabled(false);
- }
- } else {
- mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
- }
- }
- } else {
- // Any non-auto-brightness values such as override or temporary should still be subject
- // to clamping so that they don't go beyond the current max as specified by HBM
- // Controller.
- brightnessState = clampScreenBrightness(brightnessState);
- mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
- }
-
- // Use default brightness when dozing unless overridden.
- if ((Float.isNaN(brightnessState))
- && Display.isDozeState(state)) {
- rawBrightnessState = mScreenBrightnessDozeConfig;
- brightnessState = clampScreenBrightness(rawBrightnessState);
- mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_DEFAULT);
- }
-
- // The ALS is not available yet - use the screen off sensor to determine the initial
- // brightness
- if (Float.isNaN(brightnessState) && mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()
- && mScreenOffBrightnessSensorController != null) {
- rawBrightnessState =
- mScreenOffBrightnessSensorController.getAutomaticScreenBrightness();
- brightnessState = rawBrightnessState;
- if (BrightnessUtils.isValidBrightnessValue(brightnessState)) {
- brightnessState = clampScreenBrightness(brightnessState);
- updateScreenBrightnessSetting = mDisplayBrightnessController.getCurrentBrightness()
- != brightnessState;
- mBrightnessReasonTemp.setReason(
- BrightnessReason.REASON_SCREEN_OFF_BRIGHTNESS_SENSOR);
- }
- }
-
- // Apply manual brightness.
- if (Float.isNaN(brightnessState)) {
- rawBrightnessState = currentBrightnessSetting;
- brightnessState = clampScreenBrightness(rawBrightnessState);
- if (brightnessState != currentBrightnessSetting) {
- // The manually chosen screen brightness is outside of the currently allowed
- // range (i.e., high-brightness-mode), make sure we tell the rest of the system
- // by updating the setting.
- updateScreenBrightnessSetting = true;
- }
- mBrightnessReasonTemp.setReason(BrightnessReason.REASON_MANUAL);
- }
-
- float ambientLux = mAutomaticBrightnessController == null ? 0
- : mAutomaticBrightnessController.getAmbientLux();
- for (int i = 0; i < displayBrightnessFollowers.size(); i++) {
- DisplayPowerControllerInterface follower = displayBrightnessFollowers.valueAt(i);
- follower.setBrightnessToFollow(rawBrightnessState,
- mDisplayBrightnessController.convertToNits(rawBrightnessState),
- ambientLux, slowChange);
- }
-
- // Now that a desired brightness has been calculated, apply brightness throttling. The
- // dimming and low power transformations that follow can only dim brightness further.
- //
- // We didn't do this earlier through brightness clamping because we need to know both
- // unthrottled (unclamped/ideal) and throttled brightness levels for subsequent operations.
- // Note throttling effectively changes the allowed brightness range, so, similarly to HBM,
- // we broadcast this change through setting.
- final float unthrottledBrightnessState = brightnessState;
- DisplayBrightnessState clampedState = mBrightnessClamperController.clamp(mPowerRequest,
- brightnessState, slowChange);
-
- brightnessState = clampedState.getBrightness();
- slowChange = clampedState.isSlowChange();
- // faster rate wins, at this point customAnimationRate == -1, strategy does not control
- // customAnimationRate. Should be revisited if strategy start setting this value
- customAnimationRate = Math.max(customAnimationRate, clampedState.getCustomAnimationRate());
- mBrightnessReasonTemp.addModifier(clampedState.getBrightnessReason().getModifier());
-
- if (updateScreenBrightnessSetting) {
- // Tell the rest of the system about the new brightness in case we had to change it
- // for things like auto-brightness or high-brightness-mode. Note that we do this
- // only considering maxBrightness (ignoring brightness modifiers like low power or dim)
- // so that the slider accurately represents the full possible range,
- // even if they range changes what it means in absolute terms.
- mDisplayBrightnessController.updateScreenBrightnessSetting(
- MathUtils.constrain(unthrottledBrightnessState,
- clampedState.getMinBrightness(), clampedState.getMaxBrightness()));
- }
-
- // The current brightness to use has been calculated at this point, and HbmController should
- // be notified so that it can accurately calculate HDR or HBM levels. We specifically do it
- // here instead of having HbmController listen to the brightness setting because certain
- // brightness sources (such as an app override) are not saved to the setting, but should be
- // reflected in HBM calculations.
- mBrightnessRangeController.onBrightnessChanged(brightnessState, unthrottledBrightnessState,
- mBrightnessClamperController.getBrightnessMaxReason());
-
- // Animate the screen brightness when the screen is on or dozing.
- // Skip the animation when the screen is off or suspended.
- boolean brightnessAdjusted = false;
- final boolean brightnessIsTemporary =
- (mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_TEMPORARY)
- || mAutomaticBrightnessStrategy
- .isTemporaryAutoBrightnessAdjustmentApplied();
- if (!mPendingScreenOff) {
- if (mSkipScreenOnBrightnessRamp) {
- if (state == Display.STATE_ON) {
- if (mSkipRampState == RAMP_STATE_SKIP_NONE && mDozing) {
- mInitialAutoBrightness = brightnessState;
- mSkipRampState = RAMP_STATE_SKIP_INITIAL;
- } else if (mSkipRampState == RAMP_STATE_SKIP_INITIAL
- && mUseSoftwareAutoBrightnessConfig
- && !BrightnessSynchronizer.floatEquals(brightnessState,
- mInitialAutoBrightness)) {
- mSkipRampState = RAMP_STATE_SKIP_AUTOBRIGHT;
- } else if (mSkipRampState == RAMP_STATE_SKIP_AUTOBRIGHT) {
- mSkipRampState = RAMP_STATE_SKIP_NONE;
- }
- } else {
- mSkipRampState = RAMP_STATE_SKIP_NONE;
- }
- }
-
- final boolean initialRampSkip = (state == Display.STATE_ON && mSkipRampState
- != RAMP_STATE_SKIP_NONE) || mDisplayPowerProximityStateController
- .shouldSkipRampBecauseOfProximityChangeToNegative();
- // While dozing, sometimes the brightness is split into buckets. Rather than animating
- // through the buckets, which is unlikely to be smooth in the first place, just jump
- // right to the suggested brightness.
- final boolean hasBrightnessBuckets =
- Display.isDozeState(state) && mBrightnessBucketsInDozeConfig;
- // If the color fade is totally covering the screen then we can change the backlight
- // level without it being a noticeable jump since any actual content isn't yet visible.
- final boolean isDisplayContentVisible =
- mColorFadeEnabled && mPowerState.getColorFadeLevel() == 1.0f;
- // We only want to animate the brightness if it is between 0.0f and 1.0f.
- // brightnessState can contain the values -1.0f and NaN, which we do not want to
- // animate to. To avoid this, we check the value first.
- // If the brightnessState is off (-1.0f) we still want to animate to the minimum
- // brightness (0.0f) to accommodate for LED displays, which can appear bright to the
- // user even when the display is all black. We also clamp here in case some
- // transformations to the brightness have pushed it outside of the currently
- // allowed range.
- float animateValue = clampScreenBrightness(brightnessState);
-
- // If there are any HDR layers on the screen, we have a special brightness value that we
- // use instead. We still preserve the calculated brightness for Standard Dynamic Range
- // (SDR) layers, but the main brightness value will be the one for HDR.
- float sdrAnimateValue = animateValue;
- // TODO(b/216365040): The decision to prevent HBM for HDR in low power mode should be
- // done in HighBrightnessModeController.
- if (mBrightnessRangeController.getHighBrightnessMode()
- == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
- && (mBrightnessReasonTemp.getModifier() & BrightnessReason.MODIFIER_DIMMED) == 0
- && (mBrightnessReasonTemp.getModifier() & BrightnessReason.MODIFIER_LOW_POWER)
- == 0) {
- // We want to scale HDR brightness level with the SDR level, we also need to restore
- // SDR brightness immediately when entering dim or low power mode.
- animateValue = mBrightnessRangeController.getHdrBrightnessValue();
- customAnimationRate = Math.max(customAnimationRate,
- mBrightnessRangeController.getHdrTransitionRate());
- mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_HDR);
- }
-
- // if doze or suspend state is requested, we want to finish brightnes animation fast
- // to allow state animation to start
- if (mPowerRequest.policy == DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE
- && (mPowerRequest.dozeScreenState == Display.STATE_UNKNOWN // dozing
- || mPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND
- || mPowerRequest.dozeScreenState == Display.STATE_ON_SUSPEND)) {
- customAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
- slowChange = false;
- }
-
- final float currentBrightness = mPowerState.getScreenBrightness();
- final float currentSdrBrightness = mPowerState.getSdrScreenBrightness();
-
- if (BrightnessUtils.isValidBrightnessValue(animateValue)
- && (animateValue != currentBrightness
- || sdrAnimateValue != currentSdrBrightness)) {
- boolean skipAnimation = initialRampSkip || hasBrightnessBuckets
- || !isDisplayContentVisible || brightnessIsTemporary;
- final boolean isHdrOnlyChange = BrightnessSynchronizer.floatEquals(
- sdrAnimateValue, currentSdrBrightness);
- if (mFlags.isFastHdrTransitionsEnabled() && !skipAnimation && isHdrOnlyChange) {
- // SDR brightness is unchanged, so animate quickly as this is only impacting
- // a likely minority amount of display content
- // ie, the highlights of an HDR video or UltraHDR image
- slowChange = false;
-
- // Going from HDR to no HDR; visually this should be a "no-op" anyway
- // as the remaining SDR content's brightness should be holding steady
- // due to the sdr brightness not shifting
- if (BrightnessSynchronizer.floatEquals(sdrAnimateValue, animateValue)) {
- skipAnimation = true;
- }
-
- // Going from no HDR to HDR; visually this is a significant scene change
- // and the animation just prevents advanced clients from doing their own
- // handling of enter/exit animations if they would like to do such a thing
- if (BrightnessSynchronizer.floatEquals(sdrAnimateValue, currentBrightness)) {
- skipAnimation = true;
- }
- }
- if (skipAnimation) {
- animateScreenBrightness(animateValue, sdrAnimateValue,
- SCREEN_ANIMATION_RATE_MINIMUM);
- } else if (customAnimationRate > 0) {
- animateScreenBrightness(animateValue, sdrAnimateValue,
- customAnimationRate, /* ignoreAnimationLimits = */true);
- } else {
- boolean isIncreasing = animateValue > currentBrightness;
- final float rampSpeed;
- final boolean idle = mAutomaticBrightnessController != null
- && mAutomaticBrightnessController.isInIdleMode();
- if (isIncreasing && slowChange) {
- rampSpeed = idle ? mBrightnessRampRateSlowIncreaseIdle
- : mBrightnessRampRateSlowIncrease;
- } else if (isIncreasing && !slowChange) {
- rampSpeed = mBrightnessRampRateFastIncrease;
- } else if (!isIncreasing && slowChange) {
- rampSpeed = idle ? mBrightnessRampRateSlowDecreaseIdle
- : mBrightnessRampRateSlowDecrease;
- } else {
- rampSpeed = mBrightnessRampRateFastDecrease;
- }
- animateScreenBrightness(animateValue, sdrAnimateValue, rampSpeed);
- }
- }
-
- notifyBrightnessTrackerChanged(brightnessState, userInitiatedChange,
- wasShortTermModelActive, mAutomaticBrightnessStrategy.isAutoBrightnessEnabled(),
- brightnessIsTemporary, displayBrightnessState.getShouldUseAutoBrightness());
-
- // We save the brightness info *after* the brightness setting has been changed and
- // adjustments made so that the brightness info reflects the latest value.
- brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting(),
- animateValue, clampedState);
- } else {
- brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting(), clampedState);
- }
-
- // Only notify if the brightness adjustment is not temporary (i.e. slider has been released)
- if (brightnessAdjusted && !brightnessIsTemporary) {
- postBrightnessChangeRunnable();
- }
-
- // Log any changes to what is currently driving the brightness setting.
- if (!mBrightnessReasonTemp.equals(mBrightnessReason) || brightnessAdjustmentFlags != 0) {
- Slog.v(mTag, "Brightness [" + brightnessState + "] reason changing to: '"
- + mBrightnessReasonTemp.toString(brightnessAdjustmentFlags)
- + "', previous reason: '" + mBrightnessReason + "'.");
- mBrightnessReason.set(mBrightnessReasonTemp);
- } else if (mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_MANUAL
- && userSetBrightnessChanged) {
- Slog.v(mTag, "Brightness [" + brightnessState + "] manual adjustment.");
- }
-
-
- // Log brightness events when a detail of significance has changed. Generally this is the
- // brightness itself changing, but also includes data like HBM cap, thermal throttling
- // brightness cap, RBC state, etc.
- mTempBrightnessEvent.setTime(System.currentTimeMillis());
- mTempBrightnessEvent.setBrightness(brightnessState);
- mTempBrightnessEvent.setPhysicalDisplayId(mUniqueDisplayId);
- mTempBrightnessEvent.setReason(mBrightnessReason);
- mTempBrightnessEvent.setHbmMax(mBrightnessRangeController.getCurrentBrightnessMax());
- mTempBrightnessEvent.setHbmMode(mBrightnessRangeController.getHighBrightnessMode());
- mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags()
- | (mIsRbcActive ? BrightnessEvent.FLAG_RBC : 0)
- | (mPowerRequest.lowPowerMode ? BrightnessEvent.FLAG_LOW_POWER_MODE : 0));
- mTempBrightnessEvent.setRbcStrength(mCdsi != null
- ? mCdsi.getReduceBrightColorsStrength() : -1);
- mTempBrightnessEvent.setPowerFactor(mPowerRequest.screenLowPowerBrightnessFactor);
- mTempBrightnessEvent.setWasShortTermModelActive(wasShortTermModelActive);
- mTempBrightnessEvent.setDisplayBrightnessStrategyName(displayBrightnessState
- .getDisplayBrightnessStrategyName());
- mTempBrightnessEvent.setAutomaticBrightnessEnabled(
- displayBrightnessState.getShouldUseAutoBrightness());
- // Temporary is what we use during slider interactions. We avoid logging those so that
- // we don't spam logcat when the slider is being used.
- boolean tempToTempTransition =
- mTempBrightnessEvent.getReason().getReason() == BrightnessReason.REASON_TEMPORARY
- && mLastBrightnessEvent.getReason().getReason()
- == BrightnessReason.REASON_TEMPORARY;
- // Purely for dumpsys;
- final boolean isRbcEvent =
- mLastBrightnessEvent.isRbcEnabled() != mTempBrightnessEvent.isRbcEnabled();
-
- if ((!mTempBrightnessEvent.equalsMainData(mLastBrightnessEvent) && !tempToTempTransition)
- || brightnessAdjustmentFlags != 0) {
- mTempBrightnessEvent.setInitialBrightness(mLastBrightnessEvent.getBrightness());
- mLastBrightnessEvent.copyFrom(mTempBrightnessEvent);
- BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent);
- // Adjustment flags (and user-set flag) only get added after the equality checks since
- // they are transient.
- newEvent.setAdjustmentFlags(brightnessAdjustmentFlags);
- newEvent.setFlags(newEvent.getFlags() | (userSetBrightnessChanged
- ? BrightnessEvent.FLAG_USER_SET : 0));
- Slog.i(mTag, newEvent.toString(/* includeTime= */ false));
-
- if (userSetBrightnessChanged
- || newEvent.getReason().getReason() != BrightnessReason.REASON_TEMPORARY) {
- logBrightnessEvent(newEvent, unthrottledBrightnessState);
- }
- if (mBrightnessEventRingBuffer != null) {
- mBrightnessEventRingBuffer.append(newEvent);
- }
- if (isRbcEvent) {
- mRbcEventRingBuffer.append(newEvent);
- }
-
- }
-
- // Update display white-balance.
- if (mDisplayWhiteBalanceController != null) {
- if (state == Display.STATE_ON && mDisplayWhiteBalanceSettings.isEnabled()) {
- mDisplayWhiteBalanceController.setEnabled(true);
- mDisplayWhiteBalanceController.updateDisplayColorTemperature();
- } else {
- mDisplayWhiteBalanceController.setEnabled(false);
- }
- }
-
- // Determine whether the display is ready for use in the newly requested state.
- // Note that we do not wait for the brightness ramp animation to complete before
- // reporting the display is ready because we only need to ensure the screen is in the
- // right power state even as it continues to converge on the desired brightness.
- final boolean ready = mPendingScreenOnUnblocker == null
- && mPendingScreenOnUnblockerByDisplayOffload == null
- && (!mColorFadeEnabled || (!mColorFadeOnAnimator.isStarted()
- && !mColorFadeOffAnimator.isStarted()))
- && mPowerState.waitUntilClean(mCleanListener);
- final boolean finished = ready
- && !mScreenBrightnessRampAnimator.isAnimating();
-
- // Notify policy about screen turned on.
- if (ready && state != Display.STATE_OFF
- && mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_TURNING_ON) {
- setReportedScreenState(REPORTED_TO_POLICY_SCREEN_ON);
- mWindowManagerPolicy.screenTurnedOn(mDisplayId);
- }
-
- // Grab a wake lock if we have unfinished business.
- if (!finished) {
- mWakelockController.acquireWakelock(WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
- }
-
- // Notify the power manager when ready.
- if (ready && mustNotify) {
- // Send state change.
- synchronized (mLock) {
- if (!mPendingRequestChangedLocked) {
- mDisplayReadyLocked = true;
-
- if (DEBUG) {
- Slog.d(mTag, "Display ready!");
- }
- }
- }
- sendOnStateChangedWithWakelock();
- }
-
- // Release the wake lock when we have no unfinished business.
- if (finished) {
- mWakelockController.releaseWakelock(WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
- }
-
- // Record if dozing for future comparison.
- mDozing = state != Display.STATE_ON;
-
- if (previousPolicy != mPowerRequest.policy) {
- logDisplayPolicyChanged(mPowerRequest.policy);
- }
- }
-
- private void setDwbcOverride(float cct) {
- if (mDisplayWhiteBalanceController != null) {
- mDisplayWhiteBalanceController.setAmbientColorTemperatureOverride(cct);
- // The ambient color temperature override is only applied when the ambient color
- // temperature changes or is updated, so it doesn't necessarily change the screen color
- // temperature immediately. So, let's make it!
- // We can call this directly, since we're already on the handler thread.
- updatePowerState();
- }
- }
-
- private void setDwbcStrongMode(int arg) {
- if (mDisplayWhiteBalanceController != null) {
- final boolean isIdle = (arg == 1);
- mDisplayWhiteBalanceController.setStrongModeEnabled(isIdle);
- }
- }
-
- private void setDwbcLoggingEnabled(int arg) {
- if (mDisplayWhiteBalanceController != null) {
- final boolean enabled = (arg == 1);
- mDisplayWhiteBalanceController.setLoggingEnabled(enabled);
- mDisplayWhiteBalanceSettings.setLoggingEnabled(enabled);
- }
- }
-
- @Override
- public void updateBrightness() {
- sendUpdatePowerState();
- }
-
- /**
- * Ignores the proximity sensor until the sensor state changes, but only if the sensor is
- * currently enabled and forcing the screen to be dark.
- */
- @Override
- public void ignoreProximitySensorUntilChanged() {
- mDisplayPowerProximityStateController.ignoreProximitySensorUntilChanged();
- }
-
- @Override
- public void setBrightnessConfiguration(BrightnessConfiguration c,
- boolean shouldResetShortTermModel) {
- Message msg = mHandler.obtainMessage(MSG_CONFIGURE_BRIGHTNESS,
- shouldResetShortTermModel ? 1 : 0, /* unused */ 0, c);
- msg.sendToTarget();
- }
-
- @Override
- public void setTemporaryBrightness(float brightness) {
- Message msg = mHandler.obtainMessage(MSG_SET_TEMPORARY_BRIGHTNESS,
- Float.floatToIntBits(brightness), 0 /*unused*/);
- msg.sendToTarget();
- }
-
- @Override
- public void setTemporaryAutoBrightnessAdjustment(float adjustment) {
- Message msg = mHandler.obtainMessage(MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT,
- Float.floatToIntBits(adjustment), 0 /*unused*/);
- msg.sendToTarget();
- }
-
- @Override
- public void setBrightnessFromOffload(float brightness) {
- Message msg = mHandler.obtainMessage(MSG_SET_BRIGHTNESS_FROM_OFFLOAD,
- Float.floatToIntBits(brightness), 0 /*unused*/);
- mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
- }
-
- @Override
- public float[] getAutoBrightnessLevels(
- @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
- int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
- Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL, UserHandle.USER_CURRENT);
- return mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(mode, preset);
- }
-
- @Override
- public float[] getAutoBrightnessLuxLevels(
- @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
- int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
- Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL, UserHandle.USER_CURRENT);
- return mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(mode, preset);
- }
-
- @Override
- public BrightnessInfo getBrightnessInfo() {
- synchronized (mCachedBrightnessInfo) {
- return new BrightnessInfo(
- mCachedBrightnessInfo.brightness.value,
- mCachedBrightnessInfo.adjustedBrightness.value,
- mCachedBrightnessInfo.brightnessMin.value,
- mCachedBrightnessInfo.brightnessMax.value,
- mCachedBrightnessInfo.hbmMode.value,
- mCachedBrightnessInfo.hbmTransitionPoint.value,
- mCachedBrightnessInfo.brightnessMaxReason.value);
- }
- }
-
- @Override
- public void onBootCompleted() {
- Message msg = mHandler.obtainMessage(MSG_BOOT_COMPLETED);
- mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
- }
-
- private boolean saveBrightnessInfo(float brightness) {
- return saveBrightnessInfo(brightness, /* state= */ null);
- }
-
- private boolean saveBrightnessInfo(float brightness, @Nullable DisplayBrightnessState state) {
- return saveBrightnessInfo(brightness, brightness, state);
- }
-
- private boolean saveBrightnessInfo(float brightness, float adjustedBrightness,
- @Nullable DisplayBrightnessState state) {
- synchronized (mCachedBrightnessInfo) {
- float stateMax = state != null ? state.getMaxBrightness() : PowerManager.BRIGHTNESS_MAX;
- float stateMin = state != null ? state.getMinBrightness() : PowerManager.BRIGHTNESS_MAX;
- final float minBrightness = Math.max(stateMin, Math.min(
- mBrightnessRangeController.getCurrentBrightnessMin(), stateMax));
- final float maxBrightness = Math.min(
- mBrightnessRangeController.getCurrentBrightnessMax(), stateMax);
- boolean changed = false;
-
- changed |=
- mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightness,
- brightness);
- changed |=
- mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.adjustedBrightness,
- adjustedBrightness);
- changed |=
- mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightnessMin,
- minBrightness);
- changed |=
- mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightnessMax,
- maxBrightness);
- changed |=
- mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.hbmMode,
- mBrightnessRangeController.getHighBrightnessMode());
- changed |=
- mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.hbmTransitionPoint,
- mBrightnessRangeController.getTransitionPoint());
- changed |=
- mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessMaxReason,
- mBrightnessClamperController.getBrightnessMaxReason());
- return changed;
- }
- }
-
- void postBrightnessChangeRunnable() {
- if (!mHandler.hasCallbacks(mOnBrightnessChangeRunnable)) {
- mHandler.post(mOnBrightnessChangeRunnable);
- }
- }
-
- private HighBrightnessModeController createHbmControllerLocked(
- HighBrightnessModeMetadata hbmMetadata, Runnable modeChangeCallback) {
- final DisplayDeviceConfig ddConfig = mDisplayDevice.getDisplayDeviceConfig();
- final IBinder displayToken = mDisplayDevice.getDisplayTokenLocked();
- final String displayUniqueId = mDisplayDevice.getUniqueId();
- final DisplayDeviceConfig.HighBrightnessModeData hbmData =
- ddConfig != null ? ddConfig.getHighBrightnessModeData() : null;
- final DisplayDeviceInfo info = mDisplayDevice.getDisplayDeviceInfoLocked();
- return mInjector.getHighBrightnessModeController(mHandler, info.width, info.height,
- displayToken, displayUniqueId, PowerManager.BRIGHTNESS_MIN,
- PowerManager.BRIGHTNESS_MAX, hbmData, (sdrBrightness, maxDesiredHdrSdrRatio) ->
- mDisplayDeviceConfig.getHdrBrightnessFromSdr(sdrBrightness,
- maxDesiredHdrSdrRatio), modeChangeCallback, hbmMetadata, mContext);
- }
-
- private BrightnessThrottler createBrightnessThrottlerLocked() {
- final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
- final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig();
- return new BrightnessThrottler(mHandler,
- () -> {
- sendUpdatePowerState();
- postBrightnessChangeRunnable();
- }, mUniqueDisplayId,
- mLogicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId,
- ddConfig.getThermalBrightnessThrottlingDataMapByThrottlingId());
- }
-
- private void blockScreenOn() {
- if (mPendingScreenOnUnblocker == null) {
- Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_TRACE_NAME, 0);
- mPendingScreenOnUnblocker = new ScreenOnUnblocker();
- mScreenOnBlockStartRealTime = SystemClock.elapsedRealtime();
- Slog.i(mTag, "Blocking screen on until initial contents have been drawn.");
- }
- }
-
- private void unblockScreenOn() {
- if (mPendingScreenOnUnblocker != null) {
- mPendingScreenOnUnblocker = null;
- long delay = SystemClock.elapsedRealtime() - mScreenOnBlockStartRealTime;
- Slog.i(mTag, "Unblocked screen on after " + delay + " ms");
- Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_TRACE_NAME, 0);
- }
- }
-
- private void blockScreenOff() {
- if (mPendingScreenOffUnblocker == null) {
- Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, SCREEN_OFF_BLOCKED_TRACE_NAME, 0);
- mPendingScreenOffUnblocker = new ScreenOffUnblocker();
- mScreenOffBlockStartRealTime = SystemClock.elapsedRealtime();
- Slog.i(mTag, "Blocking screen off");
- }
- }
-
- private void unblockScreenOff() {
- if (mPendingScreenOffUnblocker != null) {
- mPendingScreenOffUnblocker = null;
- long delay = SystemClock.elapsedRealtime() - mScreenOffBlockStartRealTime;
- Slog.i(mTag, "Unblocked screen off after " + delay + " ms");
- Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, SCREEN_OFF_BLOCKED_TRACE_NAME, 0);
- }
- }
-
- private void blockScreenOnByDisplayOffload(DisplayOffloadSession displayOffloadSession) {
- if (mPendingScreenOnUnblockerByDisplayOffload != null || displayOffloadSession == null) {
- return;
- }
- mScreenTurningOnWasBlockedByDisplayOffload = true;
-
- Trace.asyncTraceBegin(
- Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_BY_DISPLAYOFFLOAD_TRACE_NAME, 0);
- mScreenOnBlockByDisplayOffloadStartRealTime = SystemClock.elapsedRealtime();
-
- mPendingScreenOnUnblockerByDisplayOffload =
- () -> onDisplayOffloadUnblockScreenOn(displayOffloadSession);
- if (!displayOffloadSession.blockScreenOn(mPendingScreenOnUnblockerByDisplayOffload)) {
- mPendingScreenOnUnblockerByDisplayOffload = null;
- long delay =
- SystemClock.elapsedRealtime() - mScreenOnBlockByDisplayOffloadStartRealTime;
- Slog.w(mTag, "Tried blocking screen on for offloading but failed. So, end trace after "
- + delay + " ms.");
- Trace.asyncTraceEnd(
- Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_BY_DISPLAYOFFLOAD_TRACE_NAME, 0);
- return;
- }
- Slog.i(mTag, "Blocking screen on for offloading.");
- }
-
- private void onDisplayOffloadUnblockScreenOn(DisplayOffloadSession displayOffloadSession) {
- Message msg = mHandler.obtainMessage(MSG_OFFLOADING_SCREEN_ON_UNBLOCKED,
- displayOffloadSession);
- mHandler.sendMessage(msg);
- }
-
- private void unblockScreenOnByDisplayOffload() {
- if (mPendingScreenOnUnblockerByDisplayOffload == null) {
- return;
- }
- mPendingScreenOnUnblockerByDisplayOffload = null;
- long delay = SystemClock.elapsedRealtime() - mScreenOnBlockByDisplayOffloadStartRealTime;
- Slog.i(mTag, "Unblocked screen on for offloading after " + delay + " ms");
- Trace.asyncTraceEnd(
- Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_BY_DISPLAYOFFLOAD_TRACE_NAME, 0);
- }
-
- private boolean setScreenState(int state) {
- return setScreenState(state, false /*reportOnly*/);
- }
-
- private boolean setScreenState(int state, boolean reportOnly) {
- final boolean isOff = (state == Display.STATE_OFF);
- final boolean isOn = (state == Display.STATE_ON);
- final boolean changed = mPowerState.getScreenState() != state;
-
- // If the screen is turning on, give displayoffload a chance to do something before the
- // screen actually turns on.
- // TODO(b/316941732): add tests for this displayoffload screen-on blocker.
- if (isOn && changed && !mScreenTurningOnWasBlockedByDisplayOffload) {
- blockScreenOnByDisplayOffload(mDisplayOffloadSession);
- } else if (!isOn && mScreenTurningOnWasBlockedByDisplayOffload) {
- // No longer turning screen on, so unblock previous screen on blocking immediately.
- unblockScreenOnByDisplayOffload();
- mScreenTurningOnWasBlockedByDisplayOffload = false;
- }
-
- if (changed || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED) {
- // If we are trying to turn screen off, give policy a chance to do something before we
- // actually turn the screen off.
- if (isOff && !mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()) {
- if (mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_ON
- || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED) {
- setReportedScreenState(REPORTED_TO_POLICY_SCREEN_TURNING_OFF);
- blockScreenOff();
- mWindowManagerPolicy.screenTurningOff(mDisplayId, mPendingScreenOffUnblocker);
- unblockScreenOff();
- } else if (mPendingScreenOffUnblocker != null) {
- // Abort doing the state change until screen off is unblocked.
- return false;
- }
- }
-
- if (!reportOnly && changed && readyToUpdateDisplayState()
- && mPendingScreenOffUnblocker == null
- && mPendingScreenOnUnblockerByDisplayOffload == null) {
- Trace.traceCounter(Trace.TRACE_TAG_POWER, "ScreenState", state);
-
- String propertyKey = "debug.tracing.screen_state";
- String propertyValue = String.valueOf(state);
- try {
- // TODO(b/153319140) remove when we can get this from the above trace invocation
- SystemProperties.set(propertyKey, propertyValue);
- } catch (RuntimeException e) {
- Slog.e(mTag, "Failed to set a system property: key=" + propertyKey
- + " value=" + propertyValue + " " + e.getMessage());
- }
-
- mPowerState.setScreenState(state);
- // Tell battery stats about the transition.
- noteScreenState(state);
- }
- }
-
- // Tell the window manager policy when the screen is turned off or on unless it's due
- // to the proximity sensor. We temporarily block turning the screen on until the
- // window manager is ready by leaving a black surface covering the screen.
- // This surface is essentially the final state of the color fade animation and
- // it is only removed once the window manager tells us that the activity has
- // finished drawing underneath.
- if (isOff && mReportedScreenStateToPolicy != REPORTED_TO_POLICY_SCREEN_OFF
- && !mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()) {
- setReportedScreenState(REPORTED_TO_POLICY_SCREEN_OFF);
- unblockScreenOn();
- mWindowManagerPolicy.screenTurnedOff(mDisplayId, mIsInTransition);
- } else if (!isOff
- && mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_TURNING_OFF) {
-
- // We told policy already that screen was turning off, but now we changed our minds.
- // Complete the full state transition on -> turningOff -> off.
- unblockScreenOff();
- mWindowManagerPolicy.screenTurnedOff(mDisplayId, mIsInTransition);
- setReportedScreenState(REPORTED_TO_POLICY_SCREEN_OFF);
- }
- if (!isOff
- && (mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_OFF
- || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED)) {
- setReportedScreenState(REPORTED_TO_POLICY_SCREEN_TURNING_ON);
- if (mPowerState.getColorFadeLevel() == 0.0f) {
- blockScreenOn();
- } else {
- unblockScreenOn();
- }
- mWindowManagerPolicy.screenTurningOn(mDisplayId, mPendingScreenOnUnblocker);
- }
-
- // Return true if the screen isn't blocked.
- return mPendingScreenOnUnblocker == null
- && mPendingScreenOnUnblockerByDisplayOffload == null;
- }
-
- private void setReportedScreenState(int state) {
- Trace.traceCounter(Trace.TRACE_TAG_POWER, "ReportedScreenStateToPolicy", state);
- mReportedScreenStateToPolicy = state;
- if (state == REPORTED_TO_POLICY_SCREEN_ON) {
- mScreenTurningOnWasBlockedByDisplayOffload = false;
- }
- }
-
- private void loadAmbientLightSensor() {
- final int fallbackType = mDisplayId == Display.DEFAULT_DISPLAY
- ? Sensor.TYPE_LIGHT : SensorUtils.NO_FALLBACK;
- mLightSensor = SensorUtils.findSensor(mSensorManager,
- mDisplayDeviceConfig.getAmbientLightSensor(), fallbackType);
- }
-
- private void loadScreenOffBrightnessSensor() {
- mScreenOffBrightnessSensor = SensorUtils.findSensor(mSensorManager,
- mDisplayDeviceConfig.getScreenOffBrightnessSensor(), SensorUtils.NO_FALLBACK);
- }
-
- private float clampScreenBrightness(float value) {
- if (Float.isNaN(value)) {
- value = PowerManager.BRIGHTNESS_MIN;
- }
- return MathUtils.constrain(value, mBrightnessRangeController.getCurrentBrightnessMin(),
- mBrightnessRangeController.getCurrentBrightnessMax());
- }
-
- private void animateScreenBrightness(float target, float sdrTarget, float rate) {
- animateScreenBrightness(target, sdrTarget, rate, /* ignoreAnimationLimits = */false);
- }
-
- private void animateScreenBrightness(float target, float sdrTarget, float rate,
- boolean ignoreAnimationLimits) {
- if (DEBUG) {
- Slog.d(mTag, "Animating brightness: target=" + target + ", sdrTarget=" + sdrTarget
- + ", rate=" + rate);
- }
- if (mScreenBrightnessRampAnimator.animateTo(target, sdrTarget, rate,
- ignoreAnimationLimits)) {
- Trace.traceCounter(Trace.TRACE_TAG_POWER, "TargetScreenBrightness", (int) target);
-
- String propertyKey = "debug.tracing.screen_brightness";
- String propertyValue = String.valueOf(target);
- try {
- // TODO(b/153319140) remove when we can get this from the above trace invocation
- SystemProperties.set(propertyKey, propertyValue);
- } catch (RuntimeException e) {
- Slog.e(mTag, "Failed to set a system property: key=" + propertyKey
- + " value=" + propertyValue + " " + e.getMessage());
- }
-
- noteScreenBrightness(target);
- }
- }
-
- private void animateScreenStateChange(int target, boolean performScreenOffTransition) {
- // If there is already an animation in progress, don't interfere with it.
- if (mColorFadeEnabled
- && (mColorFadeOnAnimator.isStarted() || mColorFadeOffAnimator.isStarted())) {
- if (target != Display.STATE_ON) {
- return;
- }
- // If display state changed to on, proceed and stop the color fade and turn screen on.
- mPendingScreenOff = false;
- }
-
- if (mDisplayBlanksAfterDozeConfig
- && Display.isDozeState(mPowerState.getScreenState())
- && !Display.isDozeState(target)) {
- // Skip the screen off animation and add a black surface to hide the
- // contents of the screen.
- mPowerState.prepareColorFade(mContext,
- mColorFadeFadesConfig ? ColorFade.MODE_FADE : ColorFade.MODE_WARM_UP);
- if (mColorFadeOffAnimator != null) {
- mColorFadeOffAnimator.end();
- }
- // Some display hardware will blank itself on the transition between doze and non-doze
- // but still on display states. In this case we want to report to policy that the
- // display has turned off so it can prepare the appropriate power on animation, but we
- // don't want to actually transition to the fully off state since that takes
- // significantly longer to transition from.
- setScreenState(Display.STATE_OFF, target != Display.STATE_OFF /*reportOnly*/);
- }
-
- // If we were in the process of turning off the screen but didn't quite
- // finish. Then finish up now to prevent a jarring transition back
- // to screen on if we skipped blocking screen on as usual.
- if (mPendingScreenOff && target != Display.STATE_OFF) {
- setScreenState(Display.STATE_OFF);
- mPendingScreenOff = false;
- mPowerState.dismissColorFadeResources();
- }
-
- if (target == Display.STATE_ON) {
- // Want screen on. The contents of the screen may not yet
- // be visible if the color fade has not been dismissed because
- // its last frame of animation is solid black.
- if (!setScreenState(Display.STATE_ON)) {
- return; // screen on blocked
- }
- if (USE_COLOR_FADE_ON_ANIMATION && mColorFadeEnabled && mPowerRequest.isBrightOrDim()) {
- // Perform screen on animation.
- if (mPowerState.getColorFadeLevel() == 1.0f) {
- mPowerState.dismissColorFade();
- } else if (mPowerState.prepareColorFade(mContext,
- mColorFadeFadesConfig
- ? ColorFade.MODE_FADE : ColorFade.MODE_WARM_UP)) {
- mColorFadeOnAnimator.start();
- } else {
- mColorFadeOnAnimator.end();
- }
- } else {
- // Skip screen on animation.
- mPowerState.setColorFadeLevel(1.0f);
- mPowerState.dismissColorFade();
- }
- } else if (target == Display.STATE_DOZE) {
- // Want screen dozing.
- // Wait for brightness animation to complete beforehand when entering doze
- // from screen on to prevent a perceptible jump because brightness may operate
- // differently when the display is configured for dozing.
- if (mScreenBrightnessRampAnimator.isAnimating()
- && mPowerState.getScreenState() == Display.STATE_ON) {
- return;
- }
-
- // Set screen state.
- if (!setScreenState(Display.STATE_DOZE)) {
- return; // screen on blocked
- }
-
- // Dismiss the black surface without fanfare.
- mPowerState.setColorFadeLevel(1.0f);
- mPowerState.dismissColorFade();
- } else if (target == Display.STATE_DOZE_SUSPEND) {
- // Want screen dozing and suspended.
- // Wait for brightness animation to complete beforehand unless already
- // suspended because we may not be able to change it after suspension.
- if (mScreenBrightnessRampAnimator.isAnimating()
- && mPowerState.getScreenState() != Display.STATE_DOZE_SUSPEND) {
- return;
- }
-
- // If not already suspending, temporarily set the state to doze until the
- // screen on is unblocked, then suspend.
- if (mPowerState.getScreenState() != Display.STATE_DOZE_SUSPEND) {
- if (!setScreenState(Display.STATE_DOZE)) {
- return; // screen on blocked
- }
- setScreenState(Display.STATE_DOZE_SUSPEND); // already on so can't block
- }
-
- // Dismiss the black surface without fanfare.
- mPowerState.setColorFadeLevel(1.0f);
- mPowerState.dismissColorFade();
- } else if (target == Display.STATE_ON_SUSPEND) {
- // Want screen full-power and suspended.
- // Wait for brightness animation to complete beforehand unless already
- // suspended because we may not be able to change it after suspension.
- if (mScreenBrightnessRampAnimator.isAnimating()
- && mPowerState.getScreenState() != Display.STATE_ON_SUSPEND) {
- return;
- }
-
- // If not already suspending, temporarily set the state to on until the
- // screen on is unblocked, then suspend.
- if (mPowerState.getScreenState() != Display.STATE_ON_SUSPEND) {
- if (!setScreenState(Display.STATE_ON)) {
- return;
- }
- setScreenState(Display.STATE_ON_SUSPEND);
- }
-
- // Dismiss the black surface without fanfare.
- mPowerState.setColorFadeLevel(1.0f);
- mPowerState.dismissColorFade();
- } else {
- // Want screen off.
- mPendingScreenOff = true;
- if (!mColorFadeEnabled) {
- mPowerState.setColorFadeLevel(0.0f);
- }
-
- if (mPowerState.getColorFadeLevel() == 0.0f) {
- // Turn the screen off.
- // A black surface is already hiding the contents of the screen.
- setScreenState(Display.STATE_OFF);
- mPendingScreenOff = false;
- mPowerState.dismissColorFadeResources();
- } else if (performScreenOffTransition
- && mPowerState.prepareColorFade(mContext,
- mColorFadeFadesConfig
- ? ColorFade.MODE_FADE : ColorFade.MODE_COOL_DOWN)
- && mPowerState.getScreenState() != Display.STATE_OFF) {
- // Perform the screen off animation.
- mColorFadeOffAnimator.start();
- } else {
- // Skip the screen off animation and add a black surface to hide the
- // contents of the screen.
- mColorFadeOffAnimator.end();
- }
- }
- }
-
- private final Runnable mCleanListener = this::sendUpdatePowerState;
-
- private void sendOnStateChangedWithWakelock() {
- boolean wakeLockAcquired = mWakelockController.acquireWakelock(
- WakelockController.WAKE_LOCK_STATE_CHANGED);
- if (wakeLockAcquired) {
- mHandler.post(mWakelockController.getOnStateChangedRunnable());
- }
- }
-
- private void logDisplayPolicyChanged(int newPolicy) {
- LogMaker log = new LogMaker(MetricsEvent.DISPLAY_POLICY);
- log.setType(MetricsEvent.TYPE_UPDATE);
- log.setSubtype(newPolicy);
- MetricsLogger.action(log);
- }
-
- private void handleSettingsChange(boolean userSwitch) {
- mDisplayBrightnessController
- .setPendingScreenBrightness(mDisplayBrightnessController
- .getScreenBrightnessSetting());
- mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(userSwitch);
- if (userSwitch) {
- // Don't treat user switches as user initiated change.
- mDisplayBrightnessController
- .setAndNotifyCurrentScreenBrightness(mDisplayBrightnessController
- .getPendingScreenBrightness());
- if (mAutomaticBrightnessController != null) {
- mAutomaticBrightnessController.resetShortTermModel();
- }
- }
- sendUpdatePowerState();
- }
-
- private void handleBrightnessModeChange() {
- final int screenBrightnessModeSetting = Settings.System.getIntForUser(
- mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_MODE,
- Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
- mHandler.postAtTime(() -> {
- mAutomaticBrightnessStrategy.setUseAutoBrightness(screenBrightnessModeSetting
- == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
- updatePowerState();
- }, mClock.uptimeMillis());
- }
-
-
- @Override
- public float getScreenBrightnessSetting() {
- return mDisplayBrightnessController.getScreenBrightnessSetting();
- }
-
- @Override
- public void setBrightness(float brightnessValue, int userSerial) {
- mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightnessValue),
- userSerial);
- }
-
- @Override
- public int getDisplayId() {
- return mDisplayId;
- }
-
- @Override
- public int getLeadDisplayId() {
- return mLeadDisplayId;
- }
-
- @Override
- public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux,
- boolean slowChange) {
- mBrightnessRangeController.onAmbientLuxChange(ambientLux);
- if (nits == BrightnessMappingStrategy.INVALID_NITS) {
- mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness, slowChange);
- } else {
- float brightness = mDisplayBrightnessController.getBrightnessFromNits(nits);
- if (BrightnessUtils.isValidBrightnessValue(brightness)) {
- mDisplayBrightnessController.setBrightnessToFollow(brightness, slowChange);
- } else {
- // The device does not support nits
- mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness,
- slowChange);
- }
- }
- sendUpdatePowerState();
- }
-
- private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
- boolean wasShortTermModelActive, boolean autobrightnessEnabled,
- boolean brightnessIsTemporary, boolean shouldUseAutoBrightness) {
-
- final float brightnessInNits =
- mDisplayBrightnessController.convertToAdjustedNits(brightness);
- // Don't report brightness to brightnessTracker:
- // If brightness is temporary (ie the slider has not been released)
- // or if we are in idle screen brightness mode.
- // or display is not on
- // or we shouldn't be using autobrightness
- // or the nits is invalid.
- if (brightnessIsTemporary
- || mAutomaticBrightnessController == null
- || mAutomaticBrightnessController.isInIdleMode()
- || !autobrightnessEnabled
- || mBrightnessTracker == null
- || !shouldUseAutoBrightness
- || brightnessInNits < 0.0f) {
- return;
- }
-
- if (userInitiated && (mAutomaticBrightnessController == null
- || !mAutomaticBrightnessController.hasValidAmbientLux())) {
- // If we don't have a valid lux reading we can't report a valid
- // slider event so notify as if the system changed the brightness.
- userInitiated = false;
- }
-
- // We only want to track changes on devices that can actually map the display backlight
- // values into a physical brightness unit since the value provided by the API is in
- // nits and not using the arbitrary backlight units.
- final float powerFactor = mPowerRequest.lowPowerMode
- ? mPowerRequest.screenLowPowerBrightnessFactor
- : 1.0f;
- mBrightnessTracker.notifyBrightnessChanged(brightnessInNits, userInitiated,
- powerFactor, wasShortTermModelActive,
- mAutomaticBrightnessController.isDefaultConfig(), mUniqueDisplayId,
- mAutomaticBrightnessController.getLastSensorValues(),
- mAutomaticBrightnessController.getLastSensorTimestamps());
- }
-
- @Override
- public void addDisplayBrightnessFollower(DisplayPowerControllerInterface follower) {
- synchronized (mLock) {
- mDisplayBrightnessFollowers.append(follower.getDisplayId(), follower);
- sendUpdatePowerStateLocked();
- }
- }
-
- @Override
- public void removeDisplayBrightnessFollower(DisplayPowerControllerInterface follower) {
- synchronized (mLock) {
- mDisplayBrightnessFollowers.remove(follower.getDisplayId());
- mHandler.postAtTime(() -> follower.setBrightnessToFollow(
- PowerManager.BRIGHTNESS_INVALID_FLOAT, BrightnessMappingStrategy.INVALID_NITS,
- /* ambientLux= */ 0, /* slowChange= */ false), mClock.uptimeMillis());
- }
- }
-
- @GuardedBy("mLock")
- private void clearDisplayBrightnessFollowersLocked() {
- for (int i = 0; i < mDisplayBrightnessFollowers.size(); i++) {
- DisplayPowerControllerInterface follower = mDisplayBrightnessFollowers.valueAt(i);
- mHandler.postAtTime(() -> follower.setBrightnessToFollow(
- PowerManager.BRIGHTNESS_INVALID_FLOAT, BrightnessMappingStrategy.INVALID_NITS,
- /* ambientLux= */ 0, /* slowChange= */ false), mClock.uptimeMillis());
- }
- mDisplayBrightnessFollowers.clear();
- }
-
- @Override
- public void dump(final PrintWriter pw) {
- synchronized (mLock) {
- pw.println();
- pw.println("Display Power Controller:");
- pw.println(" mDisplayId=" + mDisplayId);
- pw.println(" mLeadDisplayId=" + mLeadDisplayId);
- pw.println(" mLightSensor=" + mLightSensor);
- pw.println(" mDisplayBrightnessFollowers=" + mDisplayBrightnessFollowers);
-
- pw.println();
- pw.println("Display Power Controller Locked State:");
- pw.println(" mDisplayReadyLocked=" + mDisplayReadyLocked);
- pw.println(" mPendingRequestLocked=" + mPendingRequestLocked);
- pw.println(" mPendingRequestChangedLocked=" + mPendingRequestChangedLocked);
- pw.println(" mPendingUpdatePowerStateLocked=" + mPendingUpdatePowerStateLocked);
- }
-
- pw.println();
- pw.println("Display Power Controller Configuration:");
- pw.println(" mScreenBrightnessDozeConfig=" + mScreenBrightnessDozeConfig);
- pw.println(" mUseSoftwareAutoBrightnessConfig=" + mUseSoftwareAutoBrightnessConfig);
- pw.println(" mSkipScreenOnBrightnessRamp=" + mSkipScreenOnBrightnessRamp);
- pw.println(" mColorFadeFadesConfig=" + mColorFadeFadesConfig);
- pw.println(" mColorFadeEnabled=" + mColorFadeEnabled);
- pw.println(" mIsDisplayInternal=" + mIsDisplayInternal);
- synchronized (mCachedBrightnessInfo) {
- pw.println(" mCachedBrightnessInfo.brightness="
- + mCachedBrightnessInfo.brightness.value);
- pw.println(" mCachedBrightnessInfo.adjustedBrightness="
- + mCachedBrightnessInfo.adjustedBrightness.value);
- pw.println(" mCachedBrightnessInfo.brightnessMin="
- + mCachedBrightnessInfo.brightnessMin.value);
- pw.println(" mCachedBrightnessInfo.brightnessMax="
- + mCachedBrightnessInfo.brightnessMax.value);
- pw.println(" mCachedBrightnessInfo.hbmMode=" + mCachedBrightnessInfo.hbmMode.value);
- pw.println(" mCachedBrightnessInfo.hbmTransitionPoint="
- + mCachedBrightnessInfo.hbmTransitionPoint.value);
- pw.println(" mCachedBrightnessInfo.brightnessMaxReason ="
- + mCachedBrightnessInfo.brightnessMaxReason.value);
- }
- pw.println(" mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig);
- pw.println(" mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig);
- mHandler.runWithScissors(() -> dumpLocal(pw), 1000);
- }
-
- private void dumpLocal(PrintWriter pw) {
- pw.println();
- pw.println("Display Power Controller Thread State:");
- pw.println(" mPowerRequest=" + mPowerRequest);
- pw.println(" mBrightnessReason=" + mBrightnessReason);
- pw.println(" mAppliedDimming=" + mAppliedDimming);
- pw.println(" mAppliedThrottling=" + mAppliedThrottling);
- pw.println(" mDozing=" + mDozing);
- pw.println(" mSkipRampState=" + skipRampStateToString(mSkipRampState));
- pw.println(" mScreenOnBlockStartRealTime=" + mScreenOnBlockStartRealTime);
- pw.println(" mScreenOffBlockStartRealTime=" + mScreenOffBlockStartRealTime);
- pw.println(" mPendingScreenOnUnblocker=" + mPendingScreenOnUnblocker);
- pw.println(" mPendingScreenOffUnblocker=" + mPendingScreenOffUnblocker);
- pw.println(" mPendingScreenOff=" + mPendingScreenOff);
- pw.println(" mReportedToPolicy="
- + reportedToPolicyToString(mReportedScreenStateToPolicy));
- pw.println(" mIsRbcActive=" + mIsRbcActive);
- IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
- mAutomaticBrightnessStrategy.dump(ipw);
-
- if (mScreenBrightnessRampAnimator != null) {
- pw.println(" mScreenBrightnessRampAnimator.isAnimating()="
- + mScreenBrightnessRampAnimator.isAnimating());
- }
-
- if (mColorFadeOnAnimator != null) {
- pw.println(" mColorFadeOnAnimator.isStarted()="
- + mColorFadeOnAnimator.isStarted());
- }
- if (mColorFadeOffAnimator != null) {
- pw.println(" mColorFadeOffAnimator.isStarted()="
- + mColorFadeOffAnimator.isStarted());
- }
-
- if (mPowerState != null) {
- mPowerState.dump(pw);
- }
-
- if (mAutomaticBrightnessController != null) {
- mAutomaticBrightnessController.dump(pw);
- dumpBrightnessEvents(pw);
- }
-
- dumpRbcEvents(pw);
-
- if (mScreenOffBrightnessSensorController != null) {
- mScreenOffBrightnessSensorController.dump(pw);
- }
-
- if (mBrightnessRangeController != null) {
- mBrightnessRangeController.dump(pw);
- }
-
- if (mBrightnessThrottler != null) {
- mBrightnessThrottler.dump(pw);
- }
-
- pw.println();
- if (mDisplayWhiteBalanceController != null) {
- mDisplayWhiteBalanceController.dump(pw);
- mDisplayWhiteBalanceSettings.dump(pw);
- }
-
- pw.println();
-
- if (mWakelockController != null) {
- mWakelockController.dumpLocal(pw);
- }
-
- pw.println();
- if (mDisplayBrightnessController != null) {
- mDisplayBrightnessController.dump(pw);
- }
-
- pw.println();
- if (mDisplayStateController != null) {
- mDisplayStateController.dumpsys(pw);
- }
-
- pw.println();
- if (mBrightnessClamperController != null) {
- mBrightnessClamperController.dump(ipw);
- }
- }
-
-
- private static String reportedToPolicyToString(int state) {
- switch (state) {
- case REPORTED_TO_POLICY_SCREEN_OFF:
- return "REPORTED_TO_POLICY_SCREEN_OFF";
- case REPORTED_TO_POLICY_SCREEN_TURNING_ON:
- return "REPORTED_TO_POLICY_SCREEN_TURNING_ON";
- case REPORTED_TO_POLICY_SCREEN_ON:
- return "REPORTED_TO_POLICY_SCREEN_ON";
- default:
- return Integer.toString(state);
- }
- }
-
- private static String skipRampStateToString(int state) {
- switch (state) {
- case RAMP_STATE_SKIP_NONE:
- return "RAMP_STATE_SKIP_NONE";
- case RAMP_STATE_SKIP_INITIAL:
- return "RAMP_STATE_SKIP_INITIAL";
- case RAMP_STATE_SKIP_AUTOBRIGHT:
- return "RAMP_STATE_SKIP_AUTOBRIGHT";
- default:
- return Integer.toString(state);
- }
- }
-
- private void dumpBrightnessEvents(PrintWriter pw) {
- int size = mBrightnessEventRingBuffer.size();
- if (size < 1) {
- pw.println("No Automatic Brightness Adjustments");
- return;
- }
-
- pw.println("Automatic Brightness Adjustments Last " + size + " Events: ");
- BrightnessEvent[] eventArray = mBrightnessEventRingBuffer.toArray();
- for (int i = 0; i < mBrightnessEventRingBuffer.size(); i++) {
- pw.println(" " + eventArray[i].toString());
- }
- }
-
- private void dumpRbcEvents(PrintWriter pw) {
- int size = mRbcEventRingBuffer.size();
- if (size < 1) {
- pw.println("No Reduce Bright Colors Adjustments");
- return;
- }
-
- pw.println("Reduce Bright Colors Adjustments Last " + size + " Events: ");
- BrightnessEvent[] eventArray = mRbcEventRingBuffer.toArray();
- for (int i = 0; i < mRbcEventRingBuffer.size(); i++) {
- pw.println(" " + eventArray[i]);
- }
- }
-
-
- private void noteScreenState(int screenState) {
- // Log screen state change with display id
- FrameworkStatsLog.write(FrameworkStatsLog.SCREEN_STATE_CHANGED_V2,
- screenState, mDisplayStatsId);
- if (mBatteryStats != null) {
- try {
- // TODO(multi-display): make this multi-display
- mBatteryStats.noteScreenState(screenState);
- } catch (RemoteException e) {
- // same process
- }
- }
- }
-
- @SuppressLint("AndroidFrameworkRequiresPermission")
- private void noteScreenBrightness(float brightness) {
- if (mBatteryStats != null) {
- try {
- // TODO(brightnessfloat): change BatteryStats to use float
- int brightnessInt = mFlags.isBrightnessIntRangeUserPerceptionEnabled()
- ? BrightnessSynchronizer.brightnessFloatToIntSetting(mContext, brightness)
- : BrightnessSynchronizer.brightnessFloatToInt(brightness);
- mBatteryStats.noteScreenBrightness(brightnessInt);
- } catch (RemoteException e) {
- // same process
- }
- }
- }
-
- private void reportStats(float brightness) {
- if (mLastStatsBrightness == brightness) {
- return;
- }
-
- float hbmTransitionPoint = PowerManager.BRIGHTNESS_MAX;
- synchronized (mCachedBrightnessInfo) {
- if (mCachedBrightnessInfo.hbmTransitionPoint == null) {
- return;
- }
- hbmTransitionPoint = mCachedBrightnessInfo.hbmTransitionPoint.value;
- }
-
- final boolean aboveTransition = brightness > hbmTransitionPoint;
- final boolean oldAboveTransition = mLastStatsBrightness > hbmTransitionPoint;
-
- if (aboveTransition || oldAboveTransition) {
- mLastStatsBrightness = brightness;
- mHandler.removeMessages(MSG_STATSD_HBM_BRIGHTNESS);
- if (aboveTransition != oldAboveTransition) {
- // report immediately
- logHbmBrightnessStats(brightness, mDisplayStatsId);
- } else {
- // delay for rate limiting
- Message msg = mHandler.obtainMessage();
- msg.what = MSG_STATSD_HBM_BRIGHTNESS;
- msg.arg1 = Float.floatToIntBits(brightness);
- msg.arg2 = mDisplayStatsId;
- mHandler.sendMessageAtTime(msg, mClock.uptimeMillis()
- + BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS);
- }
- }
- }
-
- private void logHbmBrightnessStats(float brightness, int displayStatsId) {
- synchronized (mHandler) {
- FrameworkStatsLog.write(
- FrameworkStatsLog.DISPLAY_HBM_BRIGHTNESS_CHANGED, displayStatsId, brightness);
- }
- }
-
- // Return bucket index of range_[left]_[right] where
- // left <= nits < right
- private int nitsToRangeIndex(float nits) {
- for (int i = 0; i < BRIGHTNESS_RANGE_BOUNDARIES.length; i++) {
- if (nits < BRIGHTNESS_RANGE_BOUNDARIES[i]) {
- return BRIGHTNESS_RANGE_INDEX[i];
- }
- }
- return FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_3000_INF;
- }
-
- private int convertBrightnessReasonToStatsEnum(int brightnessReason) {
- switch(brightnessReason) {
- case BrightnessReason.REASON_UNKNOWN:
- return FrameworkStatsLog
- .DISPLAY_BRIGHTNESS_CHANGED__ENTIRE_REASON__REASON_UNKNOWN;
- case BrightnessReason.REASON_MANUAL:
- return FrameworkStatsLog
- .DISPLAY_BRIGHTNESS_CHANGED__ENTIRE_REASON__REASON_MANUAL;
- case BrightnessReason.REASON_DOZE:
- return FrameworkStatsLog
- .DISPLAY_BRIGHTNESS_CHANGED__ENTIRE_REASON__REASON_DOZE;
- case BrightnessReason.REASON_DOZE_DEFAULT:
- return FrameworkStatsLog
- .DISPLAY_BRIGHTNESS_CHANGED__ENTIRE_REASON__REASON_DOZE_DEFAULT;
- case BrightnessReason.REASON_AUTOMATIC:
- return FrameworkStatsLog
- .DISPLAY_BRIGHTNESS_CHANGED__ENTIRE_REASON__REASON_AUTOMATIC;
- case BrightnessReason.REASON_SCREEN_OFF:
- return FrameworkStatsLog
- .DISPLAY_BRIGHTNESS_CHANGED__ENTIRE_REASON__REASON_SCREEN_OFF;
- case BrightnessReason.REASON_OVERRIDE:
- return FrameworkStatsLog
- .DISPLAY_BRIGHTNESS_CHANGED__ENTIRE_REASON__REASON_OVERRIDE;
- case BrightnessReason.REASON_TEMPORARY:
- return FrameworkStatsLog
- .DISPLAY_BRIGHTNESS_CHANGED__ENTIRE_REASON__REASON_TEMPORARY;
- case BrightnessReason.REASON_BOOST:
- return FrameworkStatsLog
- .DISPLAY_BRIGHTNESS_CHANGED__ENTIRE_REASON__REASON_BOOST;
- case BrightnessReason.REASON_SCREEN_OFF_BRIGHTNESS_SENSOR:
- return FrameworkStatsLog
- .DISPLAY_BRIGHTNESS_CHANGED__ENTIRE_REASON__REASON_SCREEN_OFF_BRIGHTNESS_SENSOR;
- case BrightnessReason.REASON_FOLLOWER:
- return FrameworkStatsLog
- .DISPLAY_BRIGHTNESS_CHANGED__ENTIRE_REASON__REASON_FOLLOWER;
- }
- return FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__ENTIRE_REASON__REASON_UNKNOWN;
- }
-
- private void logBrightnessEvent(BrightnessEvent event, float unmodifiedBrightness) {
- int modifier = event.getReason().getModifier();
- int flags = event.getFlags();
- // It's easier to check if the brightness is at maximum level using the brightness
- // value untouched by any modifiers
- boolean brightnessIsMax = unmodifiedBrightness == event.getHbmMax();
- float brightnessInNits =
- mDisplayBrightnessController.convertToAdjustedNits(event.getBrightness());
- float appliedLowPowerMode = event.isLowPowerModeSet() ? event.getPowerFactor() : -1f;
- int appliedRbcStrength = event.isRbcEnabled() ? event.getRbcStrength() : -1;
- float appliedHbmMaxNits =
- event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF
- ? -1f : mDisplayBrightnessController.convertToAdjustedNits(event.getHbmMax());
- // thermalCapNits set to -1 if not currently capping max brightness
- float appliedThermalCapNits =
- event.getThermalMax() == PowerManager.BRIGHTNESS_MAX
- ? -1f : mDisplayBrightnessController.convertToAdjustedNits(event.getThermalMax());
- if (mIsDisplayInternal) {
- FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
- mDisplayBrightnessController
- .convertToAdjustedNits(event.getInitialBrightness()),
- brightnessInNits,
- event.getLux(),
- event.getPhysicalDisplayId(),
- event.wasShortTermModelActive(),
- appliedLowPowerMode,
- appliedRbcStrength,
- appliedHbmMaxNits,
- appliedThermalCapNits,
- event.isAutomaticBrightnessEnabled(),
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__REASON__REASON_MANUAL,
- convertBrightnessReasonToStatsEnum(event.getReason().getReason()),
- nitsToRangeIndex(brightnessInNits),
- brightnessIsMax,
- event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
- event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR,
- (modifier & BrightnessReason.MODIFIER_LOW_POWER) > 0,
- mBrightnessClamperController.getBrightnessMaxReason(),
- // TODO: (flc) add brightnessMinReason here too.
- (modifier & BrightnessReason.MODIFIER_DIMMED) > 0,
- event.isRbcEnabled(),
- (flags & BrightnessEvent.FLAG_INVALID_LUX) > 0,
- (flags & BrightnessEvent.FLAG_DOZE_SCALE) > 0,
- (flags & BrightnessEvent.FLAG_USER_SET) > 0,
- (flags & BrightnessEvent.FLAG_IDLE_CURVE) > 0,
- (flags & BrightnessEvent.FLAG_LOW_POWER_MODE) > 0);
- }
- }
-
- /**
- * Indicates whether the display state is ready to update. If this is the default display, we
- * want to update it right away so that we can draw the boot animation on it. If it is not
- * the default display, drawing the boot animation on it would look incorrect, so we need
- * to wait until boot is completed.
- * @return True if the display state is ready to update
- */
- private boolean readyToUpdateDisplayState() {
- return mDisplayId == Display.DEFAULT_DISPLAY || mBootCompleted;
- }
-
- private final class DisplayControllerHandler extends Handler {
- DisplayControllerHandler(Looper looper) {
- super(looper, null, true /*async*/);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_UPDATE_POWER_STATE:
- updatePowerState();
- break;
-
- case MSG_SCREEN_ON_UNBLOCKED:
- if (mPendingScreenOnUnblocker == msg.obj) {
- unblockScreenOn();
- updatePowerState();
- }
- break;
- case MSG_SCREEN_OFF_UNBLOCKED:
- if (mPendingScreenOffUnblocker == msg.obj) {
- unblockScreenOff();
- updatePowerState();
- }
- break;
- case MSG_OFFLOADING_SCREEN_ON_UNBLOCKED:
- if (mDisplayOffloadSession == msg.obj) {
- unblockScreenOnByDisplayOffload();
- updatePowerState();
- }
- break;
- case MSG_CONFIGURE_BRIGHTNESS:
- BrightnessConfiguration brightnessConfiguration =
- (BrightnessConfiguration) msg.obj;
- mAutomaticBrightnessStrategy.setBrightnessConfiguration(brightnessConfiguration,
- msg.arg1 == 1);
- if (mBrightnessTracker != null) {
- mBrightnessTracker
- .setShouldCollectColorSample(brightnessConfiguration != null
- && brightnessConfiguration.shouldCollectColorSamples());
- }
- updatePowerState();
- break;
-
- case MSG_SET_TEMPORARY_BRIGHTNESS:
- // TODO: Should we have a a timeout for the temporary brightness?
- mDisplayBrightnessController
- .setTemporaryBrightness(Float.intBitsToFloat(msg.arg1));
- updatePowerState();
- break;
-
- case MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT:
- mAutomaticBrightnessStrategy
- .setTemporaryAutoBrightnessAdjustment(Float.intBitsToFloat(msg.arg1));
- updatePowerState();
- break;
-
- case MSG_STOP:
- cleanupHandlerThreadAfterStop();
- break;
-
- case MSG_UPDATE_BRIGHTNESS:
- if (mStopped) {
- return;
- }
- handleSettingsChange(false /*userSwitch*/);
- break;
-
- case MSG_UPDATE_RBC:
- handleRbcChanged();
- break;
-
- case MSG_BRIGHTNESS_RAMP_DONE:
- if (mPowerState != null) {
- final float brightness = mPowerState.getScreenBrightness();
- reportStats(brightness);
- }
- break;
-
- case MSG_STATSD_HBM_BRIGHTNESS:
- logHbmBrightnessStats(Float.intBitsToFloat(msg.arg1), msg.arg2);
- break;
-
- case MSG_SWITCH_USER:
- handleOnSwitchUser(msg.arg1);
- break;
-
- case MSG_BOOT_COMPLETED:
- mBootCompleted = true;
- updatePowerState();
- break;
-
- case MSG_SET_DWBC_STRONG_MODE:
- setDwbcStrongMode(msg.arg1);
- break;
-
- case MSG_SET_DWBC_COLOR_OVERRIDE:
- final float cct = Float.intBitsToFloat(msg.arg1);
- setDwbcOverride(cct);
- break;
-
- case MSG_SET_DWBC_LOGGING_ENABLED:
- setDwbcLoggingEnabled(msg.arg1);
- break;
- case MSG_SET_BRIGHTNESS_FROM_OFFLOAD:
- mDisplayBrightnessController.setBrightnessFromOffload(
- Float.intBitsToFloat(msg.arg1));
- updatePowerState();
- break;
- }
- }
- }
-
-
- private final class SettingsObserver extends ContentObserver {
- SettingsObserver(Handler handler) {
- super(handler);
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- if (uri.equals(Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE))) {
- handleBrightnessModeChange();
- } else if (uri.equals(Settings.System.getUriFor(
- Settings.System.SCREEN_BRIGHTNESS_FOR_ALS))) {
- int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
- Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL,
- UserHandle.USER_CURRENT);
- Slog.i(mTag, "Setting up auto-brightness for preset "
- + autoBrightnessPresetToString(preset));
- setUpAutoBrightness(mContext, mHandler);
- sendUpdatePowerState();
- } else {
- handleSettingsChange(false /* userSwitch */);
- }
- }
- }
-
- private final class ScreenOnUnblocker implements WindowManagerPolicy.ScreenOnListener {
- @Override
- public void onScreenOn() {
- Message msg = mHandler.obtainMessage(MSG_SCREEN_ON_UNBLOCKED, this);
- mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
- }
- }
-
- private final class ScreenOffUnblocker implements WindowManagerPolicy.ScreenOffListener {
- @Override
- public void onScreenOff() {
- Message msg = mHandler.obtainMessage(MSG_SCREEN_OFF_UNBLOCKED, this);
- mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
- }
- }
-
- @Override
- public void setAutoBrightnessLoggingEnabled(boolean enabled) {
- if (mAutomaticBrightnessController != null) {
- mAutomaticBrightnessController.setLoggingEnabled(enabled);
- }
- }
-
- @Override // DisplayWhiteBalanceController.Callbacks
- public void updateWhiteBalance() {
- sendUpdatePowerState();
- }
-
- @Override
- public void setDisplayWhiteBalanceLoggingEnabled(boolean enabled) {
- Message msg = mHandler.obtainMessage();
- msg.what = MSG_SET_DWBC_LOGGING_ENABLED;
- msg.arg1 = enabled ? 1 : 0;
- msg.sendToTarget();
- }
-
- @Override
- public void setAmbientColorTemperatureOverride(float cct) {
- Message msg = mHandler.obtainMessage();
- msg.what = MSG_SET_DWBC_COLOR_OVERRIDE;
- msg.arg1 = Float.floatToIntBits(cct);
- msg.sendToTarget();
- }
-
- /** Functional interface for providing time. */
- @VisibleForTesting
- interface Clock {
- /**
- * Returns current time in milliseconds since boot, not counting time spent in deep sleep.
- */
- long uptimeMillis();
- }
-
- @VisibleForTesting
- static class Injector {
- Clock getClock() {
- return SystemClock::uptimeMillis;
- }
-
- DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade,
- int displayId, int displayState) {
- return new DisplayPowerState(blanker, colorFade, displayId, displayState);
- }
-
- DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps,
- FloatProperty<DisplayPowerState> firstProperty,
- FloatProperty<DisplayPowerState> secondProperty) {
- return new DualRampAnimator(dps, firstProperty, secondProperty);
- }
-
- WakelockController getWakelockController(int displayId,
- DisplayPowerCallbacks displayPowerCallbacks) {
- return new WakelockController(displayId, displayPowerCallbacks);
- }
-
- DisplayPowerProximityStateController getDisplayPowerProximityStateController(
- WakelockController wakelockController, DisplayDeviceConfig displayDeviceConfig,
- Looper looper, Runnable nudgeUpdatePowerState,
- int displayId, SensorManager sensorManager) {
- return new DisplayPowerProximityStateController(wakelockController, displayDeviceConfig,
- looper, nudgeUpdatePowerState,
- displayId, sensorManager, /* injector= */ null);
- }
-
- AutomaticBrightnessController getAutomaticBrightnessController(
- AutomaticBrightnessController.Callbacks callbacks, Looper looper,
- SensorManager sensorManager, Sensor lightSensor,
- SparseArray<BrightnessMappingStrategy> brightnessMappingStrategyMap,
- int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
- float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
- long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
- long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle,
- boolean resetAmbientLuxAfterWarmUpConfig,
- HysteresisLevels ambientBrightnessThresholds,
- HysteresisLevels screenBrightnessThresholds,
- HysteresisLevels ambientBrightnessThresholdsIdle,
- HysteresisLevels screenBrightnessThresholdsIdle, Context context,
- BrightnessRangeController brightnessModeController,
- BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort,
- int ambientLightHorizonLong, float userLux, float userNits) {
- return new AutomaticBrightnessController(callbacks, looper, sensorManager, lightSensor,
- brightnessMappingStrategyMap, lightSensorWarmUpTime, brightnessMin,
- brightnessMax, dozeScaleFactor, lightSensorRate, initialLightSensorRate,
- brighteningLightDebounceConfig, darkeningLightDebounceConfig,
- brighteningLightDebounceConfigIdle, darkeningLightDebounceConfigIdle,
- resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds,
- screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
- screenBrightnessThresholdsIdle, context, brightnessModeController,
- brightnessThrottler, ambientLightHorizonShort, ambientLightHorizonLong, userLux,
- userNits);
- }
-
- BrightnessMappingStrategy getDefaultModeBrightnessMapper(Context context,
- DisplayDeviceConfig displayDeviceConfig,
- DisplayWhiteBalanceController displayWhiteBalanceController) {
- return BrightnessMappingStrategy.create(context, displayDeviceConfig,
- AUTO_BRIGHTNESS_MODE_DEFAULT, displayWhiteBalanceController);
- }
-
- HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
- float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
- float[] darkeningThresholdLevels, float minDarkeningThreshold,
- float minBrighteningThreshold) {
- return new HysteresisLevels(brighteningThresholdsPercentages,
- darkeningThresholdsPercentages, brighteningThresholdLevels,
- darkeningThresholdLevels, minDarkeningThreshold, minBrighteningThreshold);
- }
-
- HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
- float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
- float[] darkeningThresholdLevels, float minDarkeningThreshold,
- float minBrighteningThreshold, boolean potentialOldBrightnessRange) {
- return new HysteresisLevels(brighteningThresholdsPercentages,
- darkeningThresholdsPercentages, brighteningThresholdLevels,
- darkeningThresholdLevels, minDarkeningThreshold, minBrighteningThreshold,
- potentialOldBrightnessRange);
- }
-
- ScreenOffBrightnessSensorController getScreenOffBrightnessSensorController(
- SensorManager sensorManager,
- Sensor lightSensor,
- Handler handler,
- ScreenOffBrightnessSensorController.Clock clock,
- int[] sensorValueToLux,
- BrightnessMappingStrategy brightnessMapper) {
- return new ScreenOffBrightnessSensorController(
- sensorManager,
- lightSensor,
- handler,
- clock,
- sensorValueToLux,
- brightnessMapper
- );
- }
-
- HighBrightnessModeController getHighBrightnessModeController(Handler handler, int width,
- int height, IBinder displayToken, String displayUniqueId, float brightnessMin,
- float brightnessMax, DisplayDeviceConfig.HighBrightnessModeData hbmData,
- HighBrightnessModeController.HdrBrightnessDeviceConfig hdrBrightnessCfg,
- Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata,
- Context context) {
- return new HighBrightnessModeController(handler, width, height, displayToken,
- displayUniqueId, brightnessMin, brightnessMax, hbmData, hdrBrightnessCfg,
- hbmChangeCallback, hbmMetadata, context);
- }
-
- BrightnessRangeController getBrightnessRangeController(
- HighBrightnessModeController hbmController, Runnable modeChangeCallback,
- DisplayDeviceConfig displayDeviceConfig, Handler handler,
- DisplayManagerFlags flags, IBinder displayToken, DisplayDeviceInfo info) {
- return new BrightnessRangeController(hbmController,
- modeChangeCallback, displayDeviceConfig, handler, flags, displayToken, info);
- }
-
- BrightnessClamperController getBrightnessClamperController(Handler handler,
- BrightnessClamperController.ClamperChangeListener clamperChangeListener,
- BrightnessClamperController.DisplayDeviceData data, Context context,
- DisplayManagerFlags flags) {
-
- return new BrightnessClamperController(handler, clamperChangeListener, data, context,
- flags);
- }
-
- DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
- SensorManager sensorManager, Resources resources) {
- return DisplayWhiteBalanceFactory.create(handler,
- sensorManager, resources);
- }
-
- boolean isColorFadeEnabled() {
- return !ActivityManager.isLowRamDeviceStatic();
- }
- }
-
- static class CachedBrightnessInfo {
- public MutableFloat brightness = new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT);
- public MutableFloat adjustedBrightness =
- new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT);
- public MutableFloat brightnessMin =
- new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT);
- public MutableFloat brightnessMax =
- new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT);
- public MutableInt hbmMode = new MutableInt(BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
- public MutableFloat hbmTransitionPoint =
- new MutableFloat(HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID);
- public MutableInt brightnessMaxReason =
- new MutableInt(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
-
- public boolean checkAndSetFloat(MutableFloat mf, float f) {
- if (mf.value != f) {
- mf.value = f;
- return true;
- }
- return false;
- }
-
- public boolean checkAndSetInt(MutableInt mi, int i) {
- if (mi.value != i) {
- mi.value = i;
- return true;
- }
- return false;
- }
- }
-}
diff --git a/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java b/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java
index 544f490..e0bdda5 100644
--- a/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java
+++ b/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java
@@ -165,8 +165,15 @@
*/
public float[] getLuxArray(@AutomaticBrightnessController.AutomaticBrightnessMode int mode,
int preset) {
- return mBrightnessLevelsLuxMap.get(
+ float[] luxArray = mBrightnessLevelsLuxMap.get(
autoBrightnessModeToString(mode) + "_" + autoBrightnessPresetToString(preset));
+ if (luxArray != null) {
+ return luxArray;
+ }
+
+ // No array for this preset, fall back to the normal preset
+ return mBrightnessLevelsLuxMap.get(autoBrightnessModeToString(mode) + "_"
+ + AutoBrightnessSettingName.normal.getRawName());
}
/**
@@ -184,8 +191,15 @@
*/
public float[] getBrightnessArray(
@AutomaticBrightnessController.AutomaticBrightnessMode int mode, int preset) {
- return mBrightnessLevelsMap.get(
+ float[] brightnessArray = mBrightnessLevelsMap.get(
autoBrightnessModeToString(mode) + "_" + autoBrightnessPresetToString(preset));
+ if (brightnessArray != null) {
+ return brightnessArray;
+ }
+
+ // No array for this preset, fall back to the normal preset
+ return mBrightnessLevelsMap.get(autoBrightnessModeToString(mode) + "_"
+ + AutoBrightnessSettingName.normal.getRawName());
}
@Override
diff --git a/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java
index 465584c..403dfbe 100644
--- a/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java
+++ b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java
@@ -41,13 +41,6 @@
mDeviceConfig = deviceConfig;
}
- // feature: revamping_display_power_controller_feature
- // parameter: use_newly_structured_display_power_controller
- public boolean isNewPowerControllerFeatureEnabled() {
- return mDeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
- DisplayManager.DeviceConfig.KEY_NEW_POWER_CONTROLLER, true);
- }
-
// feature: hdr_output_control
// parameter: enable_hdr_output_control
public boolean isHdrOutputControlFeatureEnabled() {
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 be48eb4..1ae2559 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -100,6 +100,10 @@
Flags.FLAG_ENABLE_VSYNC_LOW_POWER_VOTE,
Flags::enableVsyncLowPowerVote);
+ private final FlagState mVsyncLowLightVote = new FlagState(
+ Flags.FLAG_ENABLE_VSYNC_LOW_LIGHT_VOTE,
+ Flags::enableVsyncLowLightVote);
+
private final FlagState mBrightnessWearBedtimeModeClamperFlagState = new FlagState(
Flags.FLAG_BRIGHTNESS_WEAR_BEDTIME_MODE_CLAMPER,
Flags::brightnessWearBedtimeModeClamper);
@@ -220,6 +224,10 @@
return mVsyncLowPowerVote.isEnabled();
}
+ public boolean isVsyncLowLightVoteEnabled() {
+ return mVsyncLowLightVote.isEnabled();
+ }
+
public boolean isBrightnessWearBedtimeModeClamperEnabled() {
return mBrightnessWearBedtimeModeClamperFlagState.isEnabled();
}
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 c9569cb..c2f52b5 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
@@ -109,7 +109,7 @@
name: "back_up_smooth_display_and_force_peak_refresh_rate"
namespace: "display_manager"
description: "Feature flag for backing up Smooth Display and Force Peak Refresh Rate"
- bug: "211737588"
+ bug: "299552529"
is_fixed_read_only: true
}
@@ -125,7 +125,7 @@
name: "brightness_int_range_user_perception"
namespace: "display_manager"
description: "Feature flag for converting the brightness integer range to the user perception scale"
- bug: "183655602"
+ bug: "319236956"
is_fixed_read_only: true
}
@@ -146,6 +146,14 @@
}
flag {
+ name: "enable_vsync_low_light_vote"
+ namespace: "display_manager"
+ description: "Feature flag for vsync low light vote"
+ bug: "314921657"
+ is_fixed_read_only: true
+}
+
+flag {
name: "brightness_wear_bedtime_mode_clamper"
namespace: "display_manager"
description: "Feature flag for the Wear Bedtime mode brightness clamper"
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 50e9533..8707000 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -22,7 +22,6 @@
import static android.view.Display.Mode.INVALID_MODE_ID;
import static com.android.server.display.DisplayDeviceConfig.DEFAULT_LOW_REFRESH_RATE;
-import static com.android.internal.display.RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay;
import android.annotation.IntegerRes;
import android.annotation.NonNull;
@@ -216,7 +215,8 @@
mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings();
mSettingsObserver = new SettingsObserver(context, handler, mDvrrSupported,
displayManagerFlags);
- mBrightnessObserver = new BrightnessObserver(context, handler, injector);
+ mBrightnessObserver = new BrightnessObserver(context, handler, injector, mDvrrSupported,
+ displayManagerFlags);
mDefaultDisplayDeviceConfig = null;
mUdfpsObserver = new UdfpsObserver();
mVotesStorage = new VotesStorage(this::notifyDesiredDisplayModeSpecsChangedLocked,
@@ -238,8 +238,11 @@
* is ready.
*/
public void start(SensorManager sensorManager) {
- mSettingsObserver.observe();
+ // This has to be called first to read the supported display modes that will be used by
+ // other observers
mDisplayObserver.observe();
+
+ mSettingsObserver.observe();
mBrightnessObserver.observe(sensorManager);
mSensorObserver.observe();
mHbmObserver.observe();
@@ -620,11 +623,16 @@
}
@VisibleForTesting
+ DisplayObserver getDisplayObserver() {
+ return mDisplayObserver;
+ }
+
+ @VisibleForTesting
DesiredDisplayModeSpecs getDesiredDisplayModeSpecsWithInjectedFpsSettings(
float minRefreshRate, float peakRefreshRate, float defaultRefreshRate) {
synchronized (mLock) {
- mSettingsObserver.updateRefreshRateSettingLocked(
- minRefreshRate, peakRefreshRate, defaultRefreshRate);
+ mSettingsObserver.updateRefreshRateSettingLocked(minRefreshRate, peakRefreshRate,
+ defaultRefreshRate, Display.DEFAULT_DISPLAY);
return getDesiredDisplayModeSpecs(Display.DEFAULT_DISPLAY);
}
}
@@ -897,19 +905,17 @@
if (defaultPeakRefreshRate == null) {
setDefaultPeakRefreshRate(mDefaultDisplayDeviceConfig,
/* attemptReadFromFeatureParams= */ false);
- updateRefreshRateSettingLocked();
} else if (mDefaultPeakRefreshRate != defaultPeakRefreshRate) {
mDefaultPeakRefreshRate = defaultPeakRefreshRate;
- updateRefreshRateSettingLocked();
}
+ updateRefreshRateSettingLocked();
}
}
@Override
public void onChange(boolean selfChange, Uri uri, int userId) {
synchronized (mLock) {
- if (mPeakRefreshRateSetting.equals(uri)
- || mMinRefreshRateSetting.equals(uri)) {
+ if (mPeakRefreshRateSetting.equals(uri) || mMinRefreshRateSetting.equals(uri)) {
updateRefreshRateSettingLocked();
} else if (mLowPowerModeSetting.equals(uri)) {
updateLowPowerModeSettingLocked();
@@ -969,9 +975,29 @@
mBrightnessObserver.onLowPowerModeEnabledLocked(inLowPowerMode);
}
+ /**
+ * Update refresh rate settings for all displays
+ */
+ @GuardedBy("mLock")
private void updateRefreshRateSettingLocked() {
+ for (int i = 0; i < mSupportedModesByDisplay.size(); i++) {
+ updateRefreshRateSettingLocked(mSupportedModesByDisplay.keyAt(i));
+ }
+ }
+
+ /**
+ * Update refresh rate settings for a specific display
+ * @param displayId The display ID
+ */
+ @GuardedBy("mLock")
+ private void updateRefreshRateSettingLocked(int displayId) {
final ContentResolver cr = mContext.getContentResolver();
- float highestRefreshRate = findHighestRefreshRateForDefaultDisplay(mContext);
+ if (!mSupportedModesByDisplay.contains(displayId)) {
+ Slog.e(TAG, "Cannot update refresh rate setting: no supported modes for display "
+ + displayId);
+ return;
+ }
+ float highestRefreshRate = getMaxRefreshRateLocked(displayId);
float minRefreshRate = Settings.System.getFloatForUser(cr,
Settings.System.MIN_REFRESH_RATE, 0f, cr.getUserId());
@@ -1009,11 +1035,13 @@
Float.POSITIVE_INFINITY, cr.getUserId());
}
- updateRefreshRateSettingLocked(minRefreshRate, peakRefreshRate, mDefaultRefreshRate);
+ updateRefreshRateSettingLocked(minRefreshRate, peakRefreshRate, mDefaultRefreshRate,
+ displayId);
}
- private void updateRefreshRateSettingLocked(
- float minRefreshRate, float peakRefreshRate, float defaultRefreshRate) {
+ @GuardedBy("mLock")
+ private void updateRefreshRateSettingLocked(float minRefreshRate, float peakRefreshRate,
+ float defaultRefreshRate, int displayId) {
// TODO(b/156304339): The logic in here, aside from updating the refresh rate votes, is
// used to predict if we're going to be doing frequent refresh rate switching, and if
// so, enable the brightness observer. The logic here is more complicated and fragile
@@ -1021,9 +1049,9 @@
Vote peakVote = peakRefreshRate == 0f
? null
: Vote.forRenderFrameRates(0f, Math.max(minRefreshRate, peakRefreshRate));
- mVotesStorage.updateGlobalVote(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
+ mVotesStorage.updateVote(displayId, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
peakVote);
- mVotesStorage.updateGlobalVote(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
+ mVotesStorage.updateVote(displayId, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
Vote.forRenderFrameRates(minRefreshRate, Float.POSITIVE_INFINITY));
Vote defaultVote =
defaultRefreshRate == 0f
@@ -1050,6 +1078,14 @@
mBrightnessObserver.onRefreshRateSettingChangedLocked(minRefreshRate, maxRefreshRate);
}
+ private void removeRefreshRateSetting(int displayId) {
+ mVotesStorage.updateVote(displayId, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
+ null);
+ mVotesStorage.updateVote(displayId, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
+ null);
+ mVotesStorage.updateVote(displayId, Vote.PRIORITY_DEFAULT_RENDER_FRAME_RATE, null);
+ }
+
private void updateModeSwitchingTypeSettingLocked() {
final ContentResolver cr = mContext.getContentResolver();
int switchingType = Settings.Secure.getIntForUser(
@@ -1180,7 +1216,8 @@
}
}
- private final class DisplayObserver implements DisplayManager.DisplayListener {
+ @VisibleForTesting
+ public final class DisplayObserver implements DisplayManager.DisplayListener {
// Note that we can never call into DisplayManager or any of the non-POD classes it
// returns, while holding mLock since it may call into DMS, which might be simultaneously
// calling into us already holding its own lock.
@@ -1227,11 +1264,10 @@
// Populate existing displays
SparseArray<Display.Mode[]> modes = new SparseArray<>();
SparseArray<Display.Mode> defaultModes = new SparseArray<>();
- DisplayInfo info = new DisplayInfo();
Display[] displays = mInjector.getDisplays();
for (Display d : displays) {
final int displayId = d.getDisplayId();
- d.getDisplayInfo(info);
+ DisplayInfo info = getDisplayInfo(displayId);
modes.put(displayId, info.supportedModes);
defaultModes.put(displayId, info.getDefaultMode());
}
@@ -1259,6 +1295,7 @@
synchronized (mLock) {
mSupportedModesByDisplay.remove(displayId);
mDefaultModeByDisplay.remove(displayId);
+ mSettingsObserver.removeRefreshRateSetting(displayId);
}
updateLayoutLimitedFrameRate(displayId, null);
removeUserSettingDisplayPreferredSize(displayId);
@@ -1409,6 +1446,7 @@
}
if (changed) {
notifyDesiredDisplayModeSpecsChangedLocked();
+ mSettingsObserver.updateRefreshRateSettingLocked(displayId);
}
}
}
@@ -1473,6 +1511,8 @@
private final Injector mInjector;
private final Handler mHandler;
+ private final boolean mVsyncLowLightBlockingVoteEnabled;
+
private final IThermalEventListener.Stub mThermalListener =
new IThermalEventListener.Stub() {
@Override
@@ -1507,7 +1547,8 @@
@GuardedBy("mLock")
private @Temperature.ThrottlingStatus int mThermalStatus = Temperature.THROTTLING_NONE;
- BrightnessObserver(Context context, Handler handler, Injector injector) {
+ BrightnessObserver(Context context, Handler handler, Injector injector,
+ boolean dvrrSupported , DisplayManagerFlags flags) {
mContext = context;
mHandler = handler;
mInjector = injector;
@@ -1515,6 +1556,7 @@
/* attemptReadFromFeatureParams= */ false);
mRefreshRateInHighZone = context.getResources().getInteger(
R.integer.config_fixedRefreshRateInHighZone);
+ mVsyncLowLightBlockingVoteEnabled = dvrrSupported && flags.isVsyncLowLightVoteEnabled();
}
/**
@@ -2094,7 +2136,17 @@
Vote.forPhysicalRefreshRates(range.min, range.max);
}
}
- refreshRateSwitchingVote = Vote.forDisableRefreshRateSwitching();
+
+ if (mVsyncLowLightBlockingVoteEnabled) {
+ refreshRateSwitchingVote = Vote.forSupportedModesAndDisableRefreshRateSwitching(
+ List.of(
+ new SupportedModesVote.SupportedMode(
+ /* peakRefreshRate= */ 60f, /* vsyncRate= */ 60f),
+ new SupportedModesVote.SupportedMode(
+ /* peakRefreshRate= */120f, /* vsyncRate= */ 120f)));
+ } else {
+ refreshRateSwitchingVote = Vote.forDisableRefreshRateSwitching();
+ }
}
boolean insideHighZone = hasValidHighZone()
diff --git a/services/core/java/com/android/server/display/mode/Vote.java b/services/core/java/com/android/server/display/mode/Vote.java
index 8f39570..e8d5a19 100644
--- a/services/core/java/com/android/server/display/mode/Vote.java
+++ b/services/core/java/com/android/server/display/mode/Vote.java
@@ -170,6 +170,13 @@
return new SupportedModesVote(supportedModes);
}
+
+ static Vote forSupportedModesAndDisableRefreshRateSwitching(
+ List<SupportedModesVote.SupportedMode> supportedModes) {
+ return new CombinedVote(
+ List.of(forDisableRefreshRateSwitching(), forSupportedModes(supportedModes)));
+ }
+
static String priorityToString(int priority) {
switch (priority) {
case PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE:
diff --git a/services/core/java/com/android/server/display/mode/VotesStatsReporter.java b/services/core/java/com/android/server/display/mode/VotesStatsReporter.java
index a30c4d2..e80b9451 100644
--- a/services/core/java/com/android/server/display/mode/VotesStatsReporter.java
+++ b/services/core/java/com/android/server/display/mode/VotesStatsReporter.java
@@ -16,12 +16,19 @@
package com.android.server.display.mode;
+import static com.android.internal.util.FrameworkStatsLog.DISPLAY_MODE_DIRECTOR_VOTE_CHANGED;
+import static com.android.internal.util.FrameworkStatsLog.DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_ACTIVE;
+import static com.android.internal.util.FrameworkStatsLog.DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_ADDED;
+import static com.android.internal.util.FrameworkStatsLog.DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_REMOVED;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Trace;
import android.util.SparseArray;
import android.view.Display;
+import com.android.internal.util.FrameworkStatsLog;
+
/**
* The VotesStatsReporter is responsible for collecting and sending Vote related statistics
*/
@@ -31,42 +38,77 @@
private final boolean mIgnoredRenderRate;
private final boolean mFrameworkStatsLogReportingEnabled;
+ private int mLastMinPriorityReported = Vote.MAX_PRIORITY + 1;
+
public VotesStatsReporter(boolean ignoreRenderRate, boolean refreshRateVotingTelemetryEnabled) {
mIgnoredRenderRate = ignoreRenderRate;
mFrameworkStatsLogReportingEnabled = refreshRateVotingTelemetryEnabled;
}
- void reportVoteAdded(int displayId, int priority, @NonNull Vote vote) {
+ void reportVoteChanged(int displayId, int priority, @Nullable Vote vote) {
+ if (vote == null) {
+ reportVoteRemoved(displayId, priority);
+ } else {
+ reportVoteAdded(displayId, priority, vote);
+ }
+ }
+
+ private void reportVoteAdded(int displayId, int priority, @NonNull Vote vote) {
int maxRefreshRate = getMaxRefreshRate(vote, mIgnoredRenderRate);
Trace.traceCounter(Trace.TRACE_TAG_POWER,
TAG + "." + displayId + ":" + Vote.priorityToString(priority), maxRefreshRate);
- // if ( mFrameworkStatsLogReportingEnabled) {
- // FrameworkStatsLog.write(VOTE_CHANGED, displayID, priority, ADDED, maxRefreshRate, -1);
- // }
+ if (mFrameworkStatsLogReportingEnabled) {
+ FrameworkStatsLog.write(
+ DISPLAY_MODE_DIRECTOR_VOTE_CHANGED, displayId, priority,
+ DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_ADDED,
+ maxRefreshRate, -1);
+ }
}
- void reportVoteRemoved(int displayId, int priority) {
+ private void reportVoteRemoved(int displayId, int priority) {
Trace.traceCounter(Trace.TRACE_TAG_POWER,
TAG + "." + displayId + ":" + Vote.priorityToString(priority), -1);
- // if ( mFrameworkStatsLogReportingEnabled) {
- // FrameworkStatsLog.write(VOTE_CHANGED, displayID, priority, REMOVED, -1, -1);
- // }
+ if (mFrameworkStatsLogReportingEnabled) {
+ FrameworkStatsLog.write(
+ DISPLAY_MODE_DIRECTOR_VOTE_CHANGED, displayId, priority,
+ DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_REMOVED, -1, -1);
+ }
}
void reportVotesActivated(int displayId, int minPriority, @Nullable Display.Mode baseMode,
SparseArray<Vote> votes) {
-// if (!mFrameworkStatsLogReportingEnabled) {
-// return;
-// }
-// int selectedRefreshRate = baseMode != null ? (int) baseMode.getRefreshRate() : -1;
-// for (int priority = minPriority; priority <= Vote.MAX_PRIORITY; priority ++) {
-// Vote vote = votes.get(priority);
-// if (vote != null) {
-// int maxRefreshRate = getMaxRefreshRate(vote, mIgnoredRenderRate);
-// FrameworkStatsLog.write(VOTE_CHANGED, displayId, priority,
-// ACTIVE, maxRefreshRate, selectedRefreshRate);
-// }
-// }
+ if (!mFrameworkStatsLogReportingEnabled) {
+ return;
+ }
+ int selectedRefreshRate = baseMode != null ? (int) baseMode.getRefreshRate() : -1;
+ for (int priority = Vote.MIN_PRIORITY; priority <= Vote.MAX_PRIORITY; priority++) {
+ if (priority < mLastMinPriorityReported && priority < minPriority) {
+ continue;
+ }
+ Vote vote = votes.get(priority);
+ if (vote == null) {
+ continue;
+ }
+
+ // Was previously reported ACTIVE, changed to ADDED
+ if (priority >= mLastMinPriorityReported && priority < minPriority) {
+ int maxRefreshRate = getMaxRefreshRate(vote, mIgnoredRenderRate);
+ FrameworkStatsLog.write(
+ DISPLAY_MODE_DIRECTOR_VOTE_CHANGED, displayId, priority,
+ DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_ADDED,
+ maxRefreshRate, selectedRefreshRate);
+ }
+ // Was previously reported ADDED, changed to ACTIVE
+ if (priority >= minPriority && priority < mLastMinPriorityReported) {
+ int maxRefreshRate = getMaxRefreshRate(vote, mIgnoredRenderRate);
+ FrameworkStatsLog.write(
+ DISPLAY_MODE_DIRECTOR_VOTE_CHANGED, displayId, priority,
+ DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_ACTIVE,
+ maxRefreshRate, selectedRefreshRate);
+ }
+
+ mLastMinPriorityReported = minPriority;
+ }
}
private static int getMaxRefreshRate(@NonNull Vote vote, boolean ignoreRenderRate) {
diff --git a/services/core/java/com/android/server/display/mode/VotesStorage.java b/services/core/java/com/android/server/display/mode/VotesStorage.java
index 7a1f7e9..56c7c18 100644
--- a/services/core/java/com/android/server/display/mode/VotesStorage.java
+++ b/services/core/java/com/android/server/display/mode/VotesStorage.java
@@ -117,22 +117,13 @@
Slog.i(TAG, "Updated votes for display=" + displayId + " votes=" + votes);
}
if (changed) {
- reportVoteStats(displayId, priority, vote);
+ if (mVotesStatsReporter != null) {
+ mVotesStatsReporter.reportVoteChanged(displayId, priority, vote);
+ }
mListener.onChanged();
}
}
- private void reportVoteStats(int displayId, int priority, @Nullable Vote vote) {
- if (mVotesStatsReporter == null) {
- return;
- }
- if (vote == null) {
- mVotesStatsReporter.reportVoteRemoved(displayId, priority);
- } else {
- mVotesStatsReporter.reportVoteAdded(displayId, priority, vote);
- }
- }
-
/** dump class values, for debugging */
void dump(@NonNull PrintWriter pw) {
SparseArray<SparseArray<Vote>> votesByDisplayLocal = new SparseArray<>();
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
index 1f59b57..c260f10 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
@@ -17,6 +17,7 @@
package com.android.server.grammaticalinflection;
import android.annotation.Nullable;
+import android.content.res.Configuration;
/**
* System-server internal interface to the {@link android.app.GrammaticalInflectionManager}.
@@ -37,5 +38,14 @@
* at the time this is called, to be referenced later when the app is installed.
*/
public abstract void stageAndApplyRestoredPayload(byte[] payload, int userId);
+
+ /**
+ * Get the current system grammatical gender of privileged application.
+ *
+ * @return the value of grammatical gender
+ *
+ * @see Configuration#getGrammaticalGender
+ */
+ public abstract @Configuration.GrammaticalGender int getSystemGrammaticalGender(int userId);
}
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
index 68848a2..6eb7e95 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -19,9 +19,12 @@
import static android.app.Flags.systemTermsOfAddressEnabled;
import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
+import static com.android.server.grammaticalinflection.GrammaticalInflectionUtils.checkSystemGrammaticalGenderPermission;
+
import android.annotation.Nullable;
import android.app.GrammaticalInflectionManager;
import android.app.IGrammaticalInflectionManager;
+import android.content.AttributionSource;
import android.content.Context;
import android.content.pm.PackageManagerInternal;
import android.os.Binder;
@@ -30,6 +33,7 @@
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.SystemProperties;
+import android.permission.PermissionManager;
import android.util.AtomicFile;
import android.util.Log;
import android.util.SparseIntArray;
@@ -75,6 +79,8 @@
private PackageManagerInternal mPackageManagerInternal;
private GrammaticalInflectionService.GrammaticalInflectionBinderService mBinderService;
+ private PermissionManager mPermissionManager;
+ private Context mContext;
/**
* Initializes the system service.
@@ -88,11 +94,12 @@
*/
public GrammaticalInflectionService(Context context) {
super(context);
+ mContext = context;
mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
- mBackupHelper = new GrammaticalInflectionBackupHelper(
- this, context.getPackageManager());
+ mBackupHelper = new GrammaticalInflectionBackupHelper(this, context.getPackageManager());
mBinderService = new GrammaticalInflectionBinderService();
+ mPermissionManager = context.getSystemService(PermissionManager.class);
}
@Override
@@ -112,7 +119,7 @@
}
@Override
- public void setSystemWideGrammaticalGender(int userId, int grammaticalGender) {
+ public void setSystemWideGrammaticalGender(int grammaticalGender, int userId) {
checkCallerIsSystem();
checkSystemTermsOfAddressIsEnabled();
GrammaticalInflectionService.this.setSystemWideGrammaticalGender(grammaticalGender,
@@ -120,16 +127,17 @@
}
@Override
- public int getSystemGrammaticalGender(int userId) {
+ public int getSystemGrammaticalGender(AttributionSource attributionSource, int userId) {
checkSystemTermsOfAddressIsEnabled();
- return GrammaticalInflectionService.this.getSystemGrammaticalGender(userId);
+ return GrammaticalInflectionService.this.getSystemGrammaticalGender(attributionSource,
+ userId);
}
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
- (new GrammaticalInflectionShellCommand(mBinderService))
+ (new GrammaticalInflectionShellCommand(mBinderService, mContext.getAttributionSource()))
.exec(this, in, out, err, args, callback, resultReceiver);
}
};
@@ -148,6 +156,13 @@
public void stageAndApplyRestoredPayload(byte[] payload, int userId) {
mBackupHelper.stageAndApplyRestoredPayload(payload, userId);
}
+
+ @Override
+ public int getSystemGrammaticalGender(int userId) {
+ checkCallerIsSystem();
+ return GrammaticalInflectionService.this.getSystemGrammaticalGender(
+ mContext.getAttributionSource(), userId);
+ }
}
protected int getApplicationGrammaticalGender(String appPackageName, int userId) {
@@ -211,9 +226,24 @@
}
}
- // TODO(b/298591009): Add a new AppOp value for the apps that want to access the grammatical
- // gender.
- public int getSystemGrammaticalGender(int userId) {
+ public int getSystemGrammaticalGender(AttributionSource attributionSource, int userId) {
+ String packageName = attributionSource.getPackageName();
+ if (packageName == null) {
+ Log.d(TAG, "Package name is null.");
+ return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+ }
+
+ int callingUid = Binder.getCallingUid();
+ if (mPackageManagerInternal.getPackageUid(packageName, 0, userId) != callingUid) {
+ Log.d(TAG,
+ "Package " + packageName + " does not belong to the calling uid " + callingUid);
+ return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+ }
+
+ if (!checkSystemGrammaticalGenderPermission(mPermissionManager, attributionSource)) {
+ return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+ }
+
synchronized (mLock) {
final File file = getGrammaticalGenderFile(userId);
if (!file.exists()) {
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java
index d223728..cdda692 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java
@@ -21,6 +21,7 @@
import android.app.ActivityManager;
import android.app.GrammaticalInflectionManager;
import android.app.IGrammaticalInflectionManager;
+import android.content.AttributionSource;
import android.content.res.Configuration;
import android.os.RemoteException;
import android.os.ShellCommand;
@@ -44,9 +45,12 @@
}
private final IGrammaticalInflectionManager mBinderService;
+ private AttributionSource mAttributionSource;
- GrammaticalInflectionShellCommand(IGrammaticalInflectionManager grammaticalInflectionManager) {
+ GrammaticalInflectionShellCommand(IGrammaticalInflectionManager grammaticalInflectionManager,
+ AttributionSource attributionSource) {
mBinderService = grammaticalInflectionManager;
+ mAttributionSource = attributionSource;
}
@Override
@@ -115,7 +119,7 @@
} while (true);
try {
- mBinderService.setSystemWideGrammaticalGender(userId, grammaticalGender);
+ mBinderService.setSystemWideGrammaticalGender(grammaticalGender, userId);
} catch (RemoteException e) {
getOutPrintWriter().println("Remote Exception: " + e);
}
@@ -141,7 +145,8 @@
} while (true);
try {
- int grammaticalGender = mBinderService.getSystemGrammaticalGender(userId);
+ int grammaticalGender = mBinderService.getSystemGrammaticalGender(mAttributionSource,
+ userId);
getOutPrintWriter().println(GRAMMATICAL_GENDER_MAP.get(grammaticalGender));
} catch (RemoteException e) {
getOutPrintWriter().println("Remote Exception: " + e);
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionUtils.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionUtils.java
new file mode 100644
index 0000000..f056561
--- /dev/null
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionUtils.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.grammaticalinflection;
+
+import static android.Manifest.permission.READ_SYSTEM_GRAMMATICAL_GENDER;
+
+import android.annotation.NonNull;
+import android.content.AttributionSource;
+import android.permission.PermissionManager;
+import android.util.Log;
+
+/**
+ * Utility methods for system grammatical gender.
+ */
+public class GrammaticalInflectionUtils {
+
+ private static final String TAG = "GrammaticalInflectionUtils";
+
+ public static boolean checkSystemGrammaticalGenderPermission(
+ @NonNull PermissionManager permissionManager,
+ @NonNull AttributionSource attributionSource) {
+ int permissionCheckResult = permissionManager.checkPermissionForDataDelivery(
+ READ_SYSTEM_GRAMMATICAL_GENDER,
+ attributionSource, /* message= */ null);
+ if (permissionCheckResult != PermissionManager.PERMISSION_GRANTED) {
+ Log.v(TAG, "AttributionSource: " + attributionSource
+ + " does not have READ_SYSTEM_GRAMMATICAL_GENDER permission.");
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
index e827866..10c0186 100755
--- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
+++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
@@ -140,7 +140,13 @@
wrapUpAndFinish();
return;
}
-
+ // Check if the action was finished before the callback was called.
+ // See {@link HdmiCecFeatureAction#finish}.
+ if (mState == STATE_NONE) {
+ Slog.v(TAG, "Action was already finished before the callback was called.");
+ wrapUpAndFinish();
+ return;
+ }
Slog.v(TAG, "Device detected: " + ackedAddress);
allocateDevices(ackedAddress);
if (mDelayPeriod > 0) {
@@ -453,7 +459,6 @@
wrapUpAndFinish();
return;
}
-
// If finished current stage, move on to next stage.
if (mProcessedDeviceCount == mDevices.size()) {
mProcessedDeviceCount = 0;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index 9087354..a79fb88 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -160,6 +160,9 @@
// This variable is used for testing, in order to delay the logical address allocation.
private long mLogicalAddressAllocationDelay = 0;
+ // This variable is used for testing, in order to delay polling devices.
+ private long mPollDevicesDelay = 0;
+
// Private constructor. Use HdmiCecController.create().
private HdmiCecController(
HdmiControlService service, NativeWrapper nativeWrapper, HdmiCecAtomWriter atomWriter) {
@@ -463,6 +466,14 @@
}
/**
+ * This method is used for testing, in order to delay polling devices.
+ */
+ @VisibleForTesting
+ void setPollDevicesDelay(long delay) {
+ mPollDevicesDelay = delay;
+ }
+
+ /**
* Returns true if the language code is well-formed.
*/
@VisibleForTesting static boolean isLanguage(String language) {
@@ -523,7 +534,10 @@
// Extract polling candidates. No need to poll against local devices.
List<Integer> pollingCandidates = pickPollCandidates(pickStrategy);
ArrayList<Integer> allocated = new ArrayList<>();
- runDevicePolling(sourceAddress, pollingCandidates, retryCount, callback, allocated);
+ mControlHandler.postDelayed(
+ () -> runDevicePolling(
+ sourceAddress, pollingCandidates, retryCount, callback, allocated),
+ mPollDevicesDelay);
}
private List<Integer> pickPollCandidates(int pickStrategy) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 1cd267d..d34661d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -1290,15 +1290,19 @@
mService.getHdmiCecNetwork().removeCecSwitches(portId);
}
- // Turning System Audio Mode off when the AVR is unlugged or standby.
- // When the device is not unplugged but reawaken from standby, we check if the System
- // Audio Control Feature is enabled or not then decide if turning SAM on/off accordingly.
- if (getAvrDeviceInfo() != null && portId == getAvrDeviceInfo().getPortId()) {
- HdmiLogger.debug("Port ID:%d, 5v=%b", portId, connected);
- if (!connected) {
- setSystemAudioMode(false);
- } else {
- onNewAvrAdded(getAvrDeviceInfo());
+ if (!mService.isEarcEnabled() || !mService.isEarcSupported()) {
+ HdmiDeviceInfo avr = getAvrDeviceInfo();
+ if (avr != null
+ && portId == avr.getPortId()
+ && isConnectedToArcPort(avr.getPhysicalAddress())) {
+ HdmiLogger.debug("Port ID:%d, 5v=%b", portId, connected);
+ if (connected) {
+ if (mArcEstablished) {
+ enableAudioReturnChannel(true);
+ }
+ } else {
+ enableAudioReturnChannel(false);
+ }
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index eaf754d..e0e825d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -3617,7 +3617,7 @@
}
}
- @VisibleForTesting
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
protected boolean isEarcSupported() {
synchronized (mLock) {
return mEarcSupported;
diff --git a/services/core/java/com/android/server/hdmi/NewDeviceAction.java b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
index f3532e5..b6c0e5d 100644
--- a/services/core/java/com/android/server/hdmi/NewDeviceAction.java
+++ b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
@@ -53,6 +53,7 @@
private int mVendorId;
private String mDisplayName;
private int mTimeoutRetry;
+ private HdmiDeviceInfo mOldDeviceInfo;
/**
* Constructor.
@@ -73,6 +74,38 @@
@Override
public boolean start() {
+ mOldDeviceInfo =
+ localDevice().mService.getHdmiCecNetwork().getCecDeviceInfo(mDeviceLogicalAddress);
+ // If there's deviceInfo with same (logical address, physical address) set
+ // Then addCecDevice should be delayed until system information process is finished
+ if (mOldDeviceInfo != null
+ && mOldDeviceInfo.getPhysicalAddress() == mDevicePhysicalAddress) {
+ Slog.d(TAG, "Start NewDeviceAction with old deviceInfo:["
+ + mOldDeviceInfo.toString() + "]");
+ } else {
+ // Add the device ahead with default information to handle <Active Source>
+ // promptly, rather than waiting till the new device action is finished.
+ Slog.d(TAG, "Start NewDeviceAction with default deviceInfo");
+ HdmiDeviceInfo deviceInfo = HdmiDeviceInfo.cecDeviceBuilder()
+ .setLogicalAddress(mDeviceLogicalAddress)
+ .setPhysicalAddress(mDevicePhysicalAddress)
+ .setPortId(tv().getPortId(mDevicePhysicalAddress))
+ .setDeviceType(mDeviceType)
+ .setVendorId(Constants.VENDOR_ID_UNKNOWN)
+ .build();
+ // If a deviceInfo with same logical address but different physical address exists
+ // We should remove the old deviceInfo first
+ // This will happen if the interval between unplugging and plugging device is too short
+ // and HotplugDetection Action fails to remove the old deviceInfo, or when the newly
+ // plugged device violates HDMI Spec and uses an occupied logical address
+ if (mOldDeviceInfo != null) {
+ Slog.d(TAG, "Remove device by NewDeviceAction, logical address conflicts: "
+ + mDevicePhysicalAddress);
+ localDevice().mService.getHdmiCecNetwork().removeCecDevice(
+ localDevice(), mDeviceLogicalAddress);
+ }
+ localDevice().mService.getHdmiCecNetwork().addCecDevice(deviceInfo);
+ }
requestOsdName(true);
return true;
}
@@ -182,14 +215,30 @@
.setVendorId(mVendorId)
.setDisplayName(mDisplayName)
.build();
- localDevice().mService.getHdmiCecNetwork().updateCecDevice(deviceInfo);
- // Consume CEC messages we already got for this newly found device.
- tv().processDelayedMessages(mDeviceLogicalAddress);
+ // Check if oldDevice is same as newDevice
+ // If so, don't add newDevice info, preventing ARC or HDMI source re-connection
+ if (mOldDeviceInfo != null
+ && mOldDeviceInfo.getLogicalAddress() == mDeviceLogicalAddress
+ && mOldDeviceInfo.getPhysicalAddress() == mDevicePhysicalAddress
+ && mOldDeviceInfo.getDeviceType() == mDeviceType
+ && mOldDeviceInfo.getVendorId() == mVendorId
+ && mOldDeviceInfo.getDisplayName().equals(mDisplayName)) {
+ // Consume CEC messages we already got for this newly found device.
+ tv().processDelayedMessages(mDeviceLogicalAddress);
+ Slog.d(TAG, "Ignore NewDevice, deviceInfo is same as current device");
+ Slog.d(TAG, "Old:[" + mOldDeviceInfo.toString()
+ + "]; New:[" + deviceInfo.toString() + "]");
+ } else {
+ Slog.d(TAG, "Add NewDevice:[" + deviceInfo.toString() + "]");
+ localDevice().mService.getHdmiCecNetwork().addCecDevice(deviceInfo);
- if (HdmiUtils.isEligibleAddressForDevice(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM,
- mDeviceLogicalAddress)) {
- tv().onNewAvrAdded(deviceInfo);
+ // Consume CEC messages we already got for this newly found device.
+ tv().processDelayedMessages(mDeviceLogicalAddress);
+ if (HdmiUtils.isEligibleAddressForDevice(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM,
+ mDeviceLogicalAddress)) {
+ tv().onNewAvrAdded(deviceInfo);
+ }
}
}
diff --git a/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java b/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java
index 1153cc3..8a3a56c 100644
--- a/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java
+++ b/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java
@@ -16,6 +16,8 @@
package com.android.server.health;
+import static android.os.Flags.batteryPartStatusApi;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.health.BatteryHealthData;
@@ -150,6 +152,18 @@
healthData = service.getBatteryHealthData();
prop.setLong(healthData.batteryStateOfHealth);
break;
+ case BatteryManager.BATTERY_PROPERTY_SERIAL_NUMBER:
+ if (batteryPartStatusApi()) {
+ healthData = service.getBatteryHealthData();
+ prop.setString(healthData.batterySerialNumber);
+ }
+ break;
+ case BatteryManager.BATTERY_PROPERTY_PART_STATUS:
+ if (batteryPartStatusApi()) {
+ healthData = service.getBatteryHealthData();
+ prop.setLong(healthData.batteryPartStatus);
+ }
+ break;
}
} catch (UnsupportedOperationException e) {
// Leave prop untouched.
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 8580b96..46668de 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -76,6 +76,8 @@
import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.util.XmlUtils;
+import com.android.server.LocalServices;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.input.KeyboardMetricsCollector.KeyboardConfigurationEvent;
import com.android.server.input.KeyboardMetricsCollector.LayoutSelectionCriteria;
import com.android.server.inputmethod.InputMethodManagerInternal;
@@ -197,7 +199,7 @@
final KeyboardIdentifier keyboardIdentifier = new KeyboardIdentifier(inputDevice);
KeyboardConfiguration config = mConfiguredKeyboards.get(deviceId);
if (config == null) {
- config = new KeyboardConfiguration();
+ config = new KeyboardConfiguration(deviceId);
mConfiguredKeyboards.put(deviceId, config);
}
@@ -1093,19 +1095,26 @@
@MainThread
private void maybeUpdateNotification() {
- if (mConfiguredKeyboards.size() == 0) {
- hideKeyboardLayoutNotification();
- return;
- }
+ List<KeyboardConfiguration> configurations = new ArrayList<>();
for (int i = 0; i < mConfiguredKeyboards.size(); i++) {
+ int deviceId = mConfiguredKeyboards.keyAt(i);
+ KeyboardConfiguration config = mConfiguredKeyboards.valueAt(i);
+ if (isVirtualDevice(deviceId)) {
+ continue;
+ }
// If we have a keyboard with no selected layouts, we should always show missing
// layout notification even if there are other keyboards that are configured properly.
- if (!mConfiguredKeyboards.valueAt(i).hasConfiguredLayouts()) {
+ if (!config.hasConfiguredLayouts()) {
showMissingKeyboardLayoutNotification();
return;
}
+ configurations.add(config);
}
- showConfiguredKeyboardLayoutNotification();
+ if (configurations.size() == 0) {
+ hideKeyboardLayoutNotification();
+ return;
+ }
+ showConfiguredKeyboardLayoutNotification(configurations);
}
@MainThread
@@ -1185,10 +1194,11 @@
}
@MainThread
- private void showConfiguredKeyboardLayoutNotification() {
+ private void showConfiguredKeyboardLayoutNotification(
+ List<KeyboardConfiguration> configurations) {
final Resources r = mContext.getResources();
- if (mConfiguredKeyboards.size() != 1) {
+ if (configurations.size() != 1) {
showKeyboardLayoutNotification(
r.getString(R.string.keyboard_layout_notification_multiple_selected_title),
r.getString(R.string.keyboard_layout_notification_multiple_selected_message),
@@ -1196,8 +1206,8 @@
return;
}
- final InputDevice inputDevice = getInputDevice(mConfiguredKeyboards.keyAt(0));
- final KeyboardConfiguration config = mConfiguredKeyboards.valueAt(0);
+ final KeyboardConfiguration config = configurations.get(0);
+ final InputDevice inputDevice = getInputDevice(config.getDeviceId());
if (inputDevice == null || !config.hasConfiguredLayouts()) {
return;
}
@@ -1356,6 +1366,13 @@
return false;
}
+ @VisibleForTesting
+ public boolean isVirtualDevice(int deviceId) {
+ VirtualDeviceManagerInternal vdm = LocalServices.getService(
+ VirtualDeviceManagerInternal.class);
+ return vdm != null && vdm.isInputDeviceOwnedByVirtualDevice(deviceId);
+ }
+
private static int[] getScriptCodes(@Nullable Locale locale) {
if (locale == null) {
return new int[0];
@@ -1430,11 +1447,22 @@
}
private static class KeyboardConfiguration {
+
// If null or empty, it means no layout is configured for the device. And user needs to
// manually set up the device.
@Nullable
private Set<String> mConfiguredLayouts;
+ private final int mDeviceId;
+
+ private KeyboardConfiguration(int deviceId) {
+ mDeviceId = deviceId;
+ }
+
+ private int getDeviceId() {
+ return mDeviceId;
+ }
+
private boolean hasConfiguredLayouts() {
return mConfiguredLayouts != null && !mConfiguredLayouts.isEmpty();
}
diff --git a/services/core/java/com/android/server/inputmethod/ClientController.java b/services/core/java/com/android/server/inputmethod/ClientController.java
index 2934640..21b952b 100644
--- a/services/core/java/com/android/server/inputmethod/ClientController.java
+++ b/services/core/java/com/android/server/inputmethod/ClientController.java
@@ -25,9 +25,13 @@
import android.view.inputmethod.InputBinding;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.inputmethod.IInputMethodClient;
import com.android.internal.inputmethod.IRemoteInputConnection;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Store and manage {@link InputMethodManagerService} clients. This class was designed to be a
* singleton in {@link InputMethodManagerService} since it stores information about all clients,
@@ -37,9 +41,7 @@
* As part of the re-architecture plan (described in go/imms-rearchitecture-plan), the following
* fields and methods will be moved out from IMMS and placed here:
* <ul>
- * <li>mCurClient (ClientState)</li>
* <li>mClients (ArrayMap of ClientState indexed by IBinder)</li>
- * <li>mLastSwitchUserId</li>
* </ul>
* <p>
* Nested Classes (to move from IMMS):
@@ -54,7 +56,6 @@
* <li>removeClient</li>
* <li>verifyClientAndPackageMatch</li>
* <li>setImeTraceEnabledForAllClients (make it reactive)</li>
- * <li>unbindCurrentClient</li>
* </ul>
*/
// TODO(b/314150112): Update the Javadoc above, by removing the re-architecture steps, once this
@@ -65,18 +66,32 @@
@GuardedBy("ImfLock.class")
final ArrayMap<IBinder, ClientState> mClients = new ArrayMap<>();
+ @GuardedBy("ImfLock.class")
+ private final List<ClientControllerCallback> mCallbacks = new ArrayList<>();
+
private final PackageManagerInternal mPackageManagerInternal;
+ interface ClientControllerCallback {
+
+ void onClientRemoved(ClientState client);
+ }
+
ClientController(PackageManagerInternal packageManagerInternal) {
mPackageManagerInternal = packageManagerInternal;
}
@GuardedBy("ImfLock.class")
- void addClient(IInputMethodClientInvoker clientInvoker,
- IRemoteInputConnection inputConnection,
- int selfReportedDisplayId, IBinder.DeathRecipient deathRecipient, int callerUid,
+ ClientState addClient(IInputMethodClientInvoker clientInvoker,
+ IRemoteInputConnection inputConnection, int selfReportedDisplayId, int callerUid,
int callerPid) {
- // TODO: Optimize this linear search.
+ final IBinder.DeathRecipient deathRecipient = () -> {
+ // Exceptionally holding ImfLock here since this is a internal lambda expression.
+ synchronized (ImfLock.class) {
+ removeClientAsBinder(clientInvoker.asBinder());
+ }
+ };
+
+ // TODO(b/319457906): Optimize this linear search.
final int numClients = mClients.size();
for (int i = 0; i < numClients; ++i) {
final ClientState state = mClients.valueAt(i);
@@ -101,14 +116,40 @@
// have the client crash. Thus we do not verify the display ID at all here. Instead we
// later check the display ID every time the client needs to interact with the specified
// display.
- mClients.put(clientInvoker.asBinder(), new ClientState(clientInvoker, inputConnection,
- callerUid, callerPid, selfReportedDisplayId, deathRecipient));
+ final ClientState cs = new ClientState(clientInvoker, inputConnection,
+ callerUid, callerPid, selfReportedDisplayId, deathRecipient);
+ mClients.put(clientInvoker.asBinder(), cs);
+ return cs;
+ }
+
+ @VisibleForTesting
+ @GuardedBy("ImfLock.class")
+ boolean removeClient(IInputMethodClient client) {
+ return removeClientAsBinder(client.asBinder());
+ }
+
+ @GuardedBy("ImfLock.class")
+ private boolean removeClientAsBinder(IBinder binder) {
+ final ClientState cs = mClients.remove(binder);
+ if (cs == null) {
+ return false;
+ }
+ binder.unlinkToDeath(cs.mClientDeathRecipient, 0 /* flags */);
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).onClientRemoved(cs);
+ }
+ return true;
+ }
+
+ @GuardedBy("ImfLock.class")
+ void addClientControllerCallback(ClientControllerCallback callback) {
+ mCallbacks.add(callback);
}
@GuardedBy("ImfLock.class")
boolean verifyClientAndPackageMatch(
@NonNull IInputMethodClient client, @NonNull String packageName) {
- ClientState cs = mClients.get(client.asBinder());
+ final ClientState cs = mClients.get(client.asBinder());
if (cs == null) {
throw new IllegalArgumentException("unknown client " + client.asBinder());
}
diff --git a/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java b/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
index 898d5a5..ad27c52 100644
--- a/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
+++ b/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
@@ -53,8 +53,7 @@
@GuardedBy("ImfLock.class")
void reset(@NonNull ArrayMap<String, InputMethodInfo> methodMap) {
mSubtypeHandles.clear();
- final InputMethodUtils.InputMethodSettings settings =
- new InputMethodUtils.InputMethodSettings(methodMap, mUserId);
+ final InputMethodSettings settings = new InputMethodSettings(methodMap, mUserId);
for (final InputMethodInfo imi : settings.getEnabledInputMethodListLocked()) {
if (!imi.shouldShowInInputMethodPicker()) {
continue;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java
index 2957935..a763251 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java
@@ -77,8 +77,6 @@
return this;
}
- // TODO: The behavior of InputMethodSubtype#overridesImplicitlyEnabledSubtype() should be
- // documented more clearly.
InputMethodListBuilder fillAuxiliaryImes(ArrayList<InputMethodInfo> imis, Context context) {
// If one or more auxiliary input methods are available, OK to stop populating the list.
for (final InputMethodInfo imi : mInputMethodSet) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 25ec683..8448fc2 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -183,7 +183,6 @@
import com.android.server.input.InputManagerInternal;
import com.android.server.inputmethod.InputMethodManagerInternal.InputMethodListListener;
import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
-import com.android.server.inputmethod.InputMethodUtils.InputMethodSettings;
import com.android.server.pm.UserManagerInternal;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.utils.PriorityDump;
@@ -479,7 +478,6 @@
/**
* The client that is currently bound to an input method.
*/
- // TODO(b/314150112): Move this to ClientController.
@Nullable
private ClientState mCurClient;
@@ -1677,7 +1675,11 @@
mVisibilityStateComputer = new ImeVisibilityStateComputer(this);
mVisibilityApplier = new DefaultImeVisibilityApplier(this);
+
mClientController = new ClientController(mPackageManagerInternal);
+ synchronized (ImfLock.class) {
+ mClientController.addClientControllerCallback(c -> onClientRemoved(c));
+ }
mPreventImeStartupUnlessTextEditor = mRes.getBoolean(
com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor);
@@ -2169,47 +2171,41 @@
// actually running.
final int callerUid = Binder.getCallingUid();
final int callerPid = Binder.getCallingPid();
-
- // TODO(b/314150112): Move the death recipient logic to ClientController when moving
- // removeClient method.
- final IBinder.DeathRecipient deathRecipient = () -> removeClient(client);
final IInputMethodClientInvoker clientInvoker =
IInputMethodClientInvoker.create(client, mHandler);
synchronized (ImfLock.class) {
mClientController.addClient(clientInvoker, inputConnection, selfReportedDisplayId,
- deathRecipient, callerUid, callerPid);
+ callerUid, callerPid);
}
}
- // TODO(b/314150112): Move this to ClientController.
- void removeClient(IInputMethodClient client) {
+ // TODO(b/314150112): Move this method to InputMethodBindingController
+ /**
+ * Hide the IME if the removed user is the current user.
+ */
+ private void onClientRemoved(ClientController.ClientState client) {
synchronized (ImfLock.class) {
- ClientState cs = mClientController.mClients.remove(client.asBinder());
- if (cs != null) {
- client.asBinder().unlinkToDeath(cs.mClientDeathRecipient, 0 /* flags */);
- clearClientSessionLocked(cs);
- clearClientSessionForAccessibilityLocked(cs);
-
- if (mCurClient == cs) {
- hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
- null /* resultReceiver */, SoftInputShowHideReason.HIDE_REMOVE_CLIENT);
- if (mBoundToMethod) {
- mBoundToMethod = false;
- IInputMethodInvoker curMethod = getCurMethodLocked();
- if (curMethod != null) {
- // When we unbind input, we are unbinding the client, so we always
- // unbind ime and a11y together.
- curMethod.unbindInput();
- AccessibilityManagerInternal.get().unbindInput();
- }
+ clearClientSessionLocked(client);
+ clearClientSessionForAccessibilityLocked(client);
+ if (mCurClient == client) {
+ hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
+ null /* resultReceiver */, SoftInputShowHideReason.HIDE_REMOVE_CLIENT);
+ if (mBoundToMethod) {
+ mBoundToMethod = false;
+ IInputMethodInvoker curMethod = getCurMethodLocked();
+ if (curMethod != null) {
+ // When we unbind input, we are unbinding the client, so we always
+ // unbind ime and a11y together.
+ curMethod.unbindInput();
+ AccessibilityManagerInternal.get().unbindInput();
}
- mBoundToAccessibility = false;
- mCurClient = null;
}
- if (mCurFocusedWindowClient == cs) {
- mCurFocusedWindowClient = null;
- mCurFocusedWindowEditorInfo = null;
- }
+ mBoundToAccessibility = false;
+ mCurClient = null;
+ }
+ if (mCurFocusedWindowClient == client) {
+ mCurFocusedWindowClient = null;
+ mCurFocusedWindowEditorInfo = null;
}
}
}
@@ -2219,8 +2215,7 @@
void unbindCurrentClientLocked(@UnbindReason int unbindClientReason) {
if (mCurClient != null) {
if (DEBUG) {
- Slog.v(TAG, "unbindCurrentInputLocked: client="
- + mCurClient.mClient.asBinder());
+ Slog.v(TAG, "unbindCurrentInputLocked: client=" + mCurClient.mClient.asBinder());
}
if (mBoundToMethod) {
mBoundToMethod = false;
@@ -2313,7 +2308,8 @@
final StartInputInfo info = new StartInputInfo(mSettings.getCurrentUserId(),
getCurTokenLocked(),
mCurTokenDisplayId, getCurIdLocked(), startInputReason, restarting,
- UserHandle.getUserId(mCurClient.mUid), mCurClient.mSelfReportedDisplayId,
+ UserHandle.getUserId(mCurClient.mUid),
+ mCurClient.mSelfReportedDisplayId,
mCurFocusedWindow, mCurEditorInfo, mCurFocusedWindowSoftInputMode,
getSequenceNumberLocked());
mImeTargetWindowMap.put(startInputToken, mCurFocusedWindow);
@@ -2324,14 +2320,14 @@
// same-user scenarios.
// That said ignoring cross-user scenario will never affect IMEs that do not have
// INTERACT_ACROSS_USERS(_FULL) permissions, which is actually almost always the case.
- if (mSettings.getCurrentUserId() == UserHandle.getUserId(mCurClient.mUid)) {
+ if (mSettings.getCurrentUserId() == UserHandle.getUserId(
+ mCurClient.mUid)) {
mPackageManagerInternal.grantImplicitAccess(mSettings.getCurrentUserId(),
null /* intent */, UserHandle.getAppId(getCurMethodUidLocked()),
mCurClient.mUid, true /* direct */);
}
- @InputMethodNavButtonFlags
- final int navButtonFlags = getInputMethodNavButtonFlagsLocked();
+ @InputMethodNavButtonFlags final int navButtonFlags = getInputMethodNavButtonFlagsLocked();
final SessionState session = mCurClient.mCurSession;
setEnabledSessionLocked(session);
session.mMethod.startInput(startInputToken, mCurInputConnection, mCurEditorInfo, restarting,
@@ -2751,8 +2747,8 @@
&& curMethod.asBinder() == method.asBinder()) {
if (mCurClient != null) {
clearClientSessionLocked(mCurClient);
- mCurClient.mCurSession = new SessionState(mCurClient,
- method, session, channel);
+ mCurClient.mCurSession = new SessionState(
+ mCurClient, method, session, channel);
InputBindResult res = attachNewInputLocked(
StartInputReason.SESSION_CREATED_BY_IME, true);
attachNewAccessibilityLocked(StartInputReason.SESSION_CREATED_BY_IME, true);
@@ -4869,8 +4865,8 @@
final List<ImeSubtypeListItem> imList = InputMethodSubtypeSwitchingController
.getSortedInputMethodAndSubtypeList(
- showAuxSubtypes, isScreenLocked, false, mContext,
- mMethodMap, mSettings.getCurrentUserId());
+ showAuxSubtypes, isScreenLocked, true /* forImeMenu */,
+ mContext, mMethodMap, mSettings.getCurrentUserId());
mMenuController.showInputMethodMenuLocked(showAuxSubtypes, displayId,
lastInputMethodId, lastInputMethodSubtypeId, imList);
}
@@ -5777,8 +5773,10 @@
// TODO(b/305829876): Implement user ID verification
if (mCurClient != null) {
clearClientSessionForAccessibilityLocked(mCurClient, accessibilityConnectionId);
- mCurClient.mAccessibilitySessions.put(accessibilityConnectionId,
- new AccessibilitySessionState(mCurClient, accessibilityConnectionId,
+ mCurClient.mAccessibilitySessions.put(
+ accessibilityConnectionId,
+ new AccessibilitySessionState(mCurClient,
+ accessibilityConnectionId,
session));
attachNewAccessibilityLocked(StartInputReason.SESSION_CREATED_BY_ACCESSIBILITY,
@@ -5812,7 +5810,8 @@
}
// A11yManagerService unbinds the disabled accessibility service. We don't need
// to do it here.
- mCurClient.mClient.onUnbindAccessibilityService(getSequenceNumberLocked(),
+ mCurClient.mClient.onUnbindAccessibilityService(
+ getSequenceNumberLocked(),
accessibilityConnectionId);
}
// We only have sessions when we bound to an input method. Remove this session
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
new file mode 100644
index 0000000..4c7d755
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
@@ -0,0 +1,686 @@
+/*
+ * 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.pm.PackageManagerInternal;
+import android.os.LocaleList;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.IntArray;
+import android.util.Pair;
+import android.util.Printer;
+import android.util.Slog;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Predicate;
+
+/**
+ * Utility class for putting and getting settings for InputMethod.
+ *
+ * <p>This is used in two ways:</p>
+ * <ul>
+ * <li>Singleton instance in {@link InputMethodManagerService}, which is updated on
+ * user-switch to follow the current user.</li>
+ * <li>On-demand instances when we need settings for non-current users.</li>
+ * </ul>
+ */
+final class InputMethodSettings {
+ public static final boolean DEBUG = false;
+ private static final String TAG = "InputMethodSettings";
+
+ private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
+ private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID);
+ private static final char INPUT_METHOD_SEPARATOR = InputMethodUtils.INPUT_METHOD_SEPARATOR;
+ private static final char INPUT_METHOD_SUBTYPE_SEPARATOR =
+ InputMethodUtils.INPUT_METHOD_SUBTYPE_SEPARATOR;
+
+ private final ArrayMap<String, InputMethodInfo> mMethodMap;
+ @UserIdInt
+ private final int mCurrentUserId;
+
+ private static void buildEnabledInputMethodsSettingString(
+ StringBuilder builder, Pair<String, ArrayList<String>> ime) {
+ builder.append(ime.first);
+ // Inputmethod and subtypes are saved in the settings as follows:
+ // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
+ for (String subtypeId : ime.second) {
+ builder.append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(subtypeId);
+ }
+ }
+
+ InputMethodSettings(ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId) {
+ mMethodMap = methodMap;
+ mCurrentUserId = userId;
+ String ime = getSelectedInputMethod();
+ String defaultDeviceIme = getSelectedDefaultDeviceInputMethod();
+ if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
+ putSelectedInputMethod(defaultDeviceIme);
+ putSelectedDefaultDeviceInputMethod(null);
+ }
+ }
+
+ private void putString(@NonNull String key, @Nullable String str) {
+ SecureSettingsWrapper.putString(key, str, mCurrentUserId);
+ }
+
+ @Nullable
+ private String getString(@NonNull String key, @Nullable String defaultValue) {
+ return SecureSettingsWrapper.getString(key, defaultValue, mCurrentUserId);
+ }
+
+ private void putInt(String key, int value) {
+ SecureSettingsWrapper.putInt(key, value, mCurrentUserId);
+ }
+
+ private int getInt(String key, int defaultValue) {
+ return SecureSettingsWrapper.getInt(key, defaultValue, mCurrentUserId);
+ }
+
+ ArrayList<InputMethodInfo> getEnabledInputMethodListLocked() {
+ return getEnabledInputMethodListWithFilterLocked(null /* matchingCondition */);
+ }
+
+ @NonNull
+ ArrayList<InputMethodInfo> getEnabledInputMethodListWithFilterLocked(
+ @Nullable Predicate<InputMethodInfo> matchingCondition) {
+ return createEnabledInputMethodListLocked(
+ getEnabledInputMethodsAndSubtypeListLocked(), matchingCondition);
+ }
+
+ List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
+ InputMethodInfo imi, boolean allowsImplicitlyEnabledSubtypes) {
+ List<InputMethodSubtype> enabledSubtypes =
+ getEnabledInputMethodSubtypeListLocked(imi);
+ if (allowsImplicitlyEnabledSubtypes && enabledSubtypes.isEmpty()) {
+ enabledSubtypes = SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
+ SystemLocaleWrapper.get(mCurrentUserId), imi);
+ }
+ return InputMethodSubtype.sort(imi, enabledSubtypes);
+ }
+
+ List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(InputMethodInfo imi) {
+ List<Pair<String, ArrayList<String>>> imsList =
+ getEnabledInputMethodsAndSubtypeListLocked();
+ ArrayList<InputMethodSubtype> enabledSubtypes = new ArrayList<>();
+ if (imi != null) {
+ for (Pair<String, ArrayList<String>> imsPair : imsList) {
+ InputMethodInfo info = mMethodMap.get(imsPair.first);
+ if (info != null && info.getId().equals(imi.getId())) {
+ final int subtypeCount = info.getSubtypeCount();
+ for (int i = 0; i < subtypeCount; ++i) {
+ InputMethodSubtype ims = info.getSubtypeAt(i);
+ for (String s : imsPair.second) {
+ if (String.valueOf(ims.hashCode()).equals(s)) {
+ enabledSubtypes.add(ims);
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ return enabledSubtypes;
+ }
+
+ List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
+ final String enabledInputMethodsStr = getEnabledInputMethodsStr();
+ final TextUtils.SimpleStringSplitter inputMethodSplitter =
+ new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
+ final TextUtils.SimpleStringSplitter subtypeSplitter =
+ new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
+ final ArrayList<Pair<String, ArrayList<String>>> imsList = new ArrayList<>();
+ if (TextUtils.isEmpty(enabledInputMethodsStr)) {
+ return imsList;
+ }
+ inputMethodSplitter.setString(enabledInputMethodsStr);
+ while (inputMethodSplitter.hasNext()) {
+ String nextImsStr = inputMethodSplitter.next();
+ subtypeSplitter.setString(nextImsStr);
+ if (subtypeSplitter.hasNext()) {
+ ArrayList<String> subtypeHashes = new ArrayList<>();
+ // The first element is ime id.
+ String imeId = subtypeSplitter.next();
+ while (subtypeSplitter.hasNext()) {
+ subtypeHashes.add(subtypeSplitter.next());
+ }
+ imsList.add(new Pair<>(imeId, subtypeHashes));
+ }
+ }
+ return imsList;
+ }
+
+ /**
+ * Build and put a string of EnabledInputMethods with removing specified Id.
+ *
+ * @return the specified id was removed or not.
+ */
+ boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
+ StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
+ boolean isRemoved = false;
+ boolean needsAppendSeparator = false;
+ for (Pair<String, ArrayList<String>> ims : imsList) {
+ String curId = ims.first;
+ if (curId.equals(id)) {
+ // We are disabling this input method, and it is
+ // currently enabled. Skip it to remove from the
+ // new list.
+ isRemoved = true;
+ } else {
+ if (needsAppendSeparator) {
+ builder.append(INPUT_METHOD_SEPARATOR);
+ } else {
+ needsAppendSeparator = true;
+ }
+ buildEnabledInputMethodsSettingString(builder, ims);
+ }
+ }
+ if (isRemoved) {
+ // Update the setting with the new list of input methods.
+ putEnabledInputMethodsStr(builder.toString());
+ }
+ return isRemoved;
+ }
+
+ private ArrayList<InputMethodInfo> createEnabledInputMethodListLocked(
+ List<Pair<String, ArrayList<String>>> imsList,
+ Predicate<InputMethodInfo> matchingCondition) {
+ final ArrayList<InputMethodInfo> res = new ArrayList<>();
+ for (Pair<String, ArrayList<String>> ims : imsList) {
+ InputMethodInfo info = mMethodMap.get(ims.first);
+ if (info != null && !info.isVrOnly()
+ && (matchingCondition == null || matchingCondition.test(info))) {
+ res.add(info);
+ }
+ }
+ return res;
+ }
+
+ void putEnabledInputMethodsStr(@Nullable String str) {
+ if (DEBUG) {
+ Slog.d(TAG, "putEnabledInputMethodStr: " + str);
+ }
+ if (TextUtils.isEmpty(str)) {
+ // OK to coalesce to null, since getEnabledInputMethodsStr() can take care of the
+ // empty data scenario.
+ putString(Settings.Secure.ENABLED_INPUT_METHODS, null);
+ } else {
+ putString(Settings.Secure.ENABLED_INPUT_METHODS, str);
+ }
+ }
+
+ @NonNull
+ String getEnabledInputMethodsStr() {
+ return getString(Settings.Secure.ENABLED_INPUT_METHODS, "");
+ }
+
+ private void saveSubtypeHistory(
+ List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) {
+ StringBuilder builder = new StringBuilder();
+ boolean isImeAdded = false;
+ if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) {
+ builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(
+ newSubtypeId);
+ isImeAdded = true;
+ }
+ for (Pair<String, String> ime : savedImes) {
+ String imeId = ime.first;
+ String subtypeId = ime.second;
+ if (TextUtils.isEmpty(subtypeId)) {
+ subtypeId = NOT_A_SUBTYPE_ID_STR;
+ }
+ if (isImeAdded) {
+ builder.append(INPUT_METHOD_SEPARATOR);
+ } else {
+ isImeAdded = true;
+ }
+ builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(
+ subtypeId);
+ }
+ // Remove the last INPUT_METHOD_SEPARATOR
+ putSubtypeHistoryStr(builder.toString());
+ }
+
+ private void addSubtypeToHistory(String imeId, String subtypeId) {
+ List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
+ for (Pair<String, String> ime : subtypeHistory) {
+ if (ime.first.equals(imeId)) {
+ if (DEBUG) {
+ Slog.v(TAG, "Subtype found in the history: " + imeId + ", "
+ + ime.second);
+ }
+ // We should break here
+ subtypeHistory.remove(ime);
+ break;
+ }
+ }
+ if (DEBUG) {
+ Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId);
+ }
+ saveSubtypeHistory(subtypeHistory, imeId, subtypeId);
+ }
+
+ private void putSubtypeHistoryStr(@NonNull String str) {
+ if (DEBUG) {
+ Slog.d(TAG, "putSubtypeHistoryStr: " + str);
+ }
+ if (TextUtils.isEmpty(str)) {
+ // OK to coalesce to null, since getSubtypeHistoryStr() can take care of the empty
+ // data scenario.
+ putString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, null);
+ } else {
+ putString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str);
+ }
+ }
+
+ Pair<String, String> getLastInputMethodAndSubtypeLocked() {
+ // Gets the first one from the history
+ return getLastSubtypeForInputMethodLockedInternal(null);
+ }
+
+ @Nullable
+ InputMethodSubtype getLastInputMethodSubtypeLocked() {
+ final Pair<String, String> lastIme = getLastInputMethodAndSubtypeLocked();
+ // TODO: Handle the case of the last IME with no subtypes
+ if (lastIme == null || TextUtils.isEmpty(lastIme.first)
+ || TextUtils.isEmpty(lastIme.second)) {
+ return null;
+ }
+ final InputMethodInfo lastImi = mMethodMap.get(lastIme.first);
+ if (lastImi == null) return null;
+ try {
+ final int lastSubtypeHash = Integer.parseInt(lastIme.second);
+ final int lastSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(lastImi,
+ lastSubtypeHash);
+ if (lastSubtypeId < 0 || lastSubtypeId >= lastImi.getSubtypeCount()) {
+ return null;
+ }
+ return lastImi.getSubtypeAt(lastSubtypeId);
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }
+
+ String getLastSubtypeForInputMethodLocked(String imeId) {
+ Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId);
+ if (ime != null) {
+ return ime.second;
+ } else {
+ return null;
+ }
+ }
+
+ private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) {
+ List<Pair<String, ArrayList<String>>> enabledImes =
+ getEnabledInputMethodsAndSubtypeListLocked();
+ List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
+ for (Pair<String, String> imeAndSubtype : subtypeHistory) {
+ final String imeInTheHistory = imeAndSubtype.first;
+ // If imeId is empty, returns the first IME and subtype in the history
+ if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) {
+ final String subtypeInTheHistory = imeAndSubtype.second;
+ final String subtypeHashCode =
+ getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(
+ enabledImes, imeInTheHistory, subtypeInTheHistory);
+ if (!TextUtils.isEmpty(subtypeHashCode)) {
+ if (DEBUG) {
+ Slog.d(TAG,
+ "Enabled subtype found in the history: " + subtypeHashCode);
+ }
+ return new Pair<>(imeInTheHistory, subtypeHashCode);
+ }
+ }
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "No enabled IME found in the history");
+ }
+ return null;
+ }
+
+ private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String,
+ ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) {
+ final LocaleList localeList = SystemLocaleWrapper.get(mCurrentUserId);
+ for (Pair<String, ArrayList<String>> enabledIme : enabledImes) {
+ if (enabledIme.first.equals(imeId)) {
+ final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second;
+ final InputMethodInfo imi = mMethodMap.get(imeId);
+ if (explicitlyEnabledSubtypes.size() == 0) {
+ // If there are no explicitly enabled subtypes, applicable subtypes are
+ // enabled implicitly.
+ // If IME is enabled and no subtypes are enabled, applicable subtypes
+ // are enabled implicitly, so needs to treat them to be enabled.
+ if (imi != null && imi.getSubtypeCount() > 0) {
+ List<InputMethodSubtype> implicitlyEnabledSubtypes =
+ SubtypeUtils.getImplicitlyApplicableSubtypesLocked(localeList,
+ imi);
+ final int numSubtypes = implicitlyEnabledSubtypes.size();
+ for (int i = 0; i < numSubtypes; ++i) {
+ final InputMethodSubtype st = implicitlyEnabledSubtypes.get(i);
+ if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) {
+ return subtypeHashCode;
+ }
+ }
+ }
+ } else {
+ for (String s : explicitlyEnabledSubtypes) {
+ if (s.equals(subtypeHashCode)) {
+ // If both imeId and subtypeId are enabled, return subtypeId.
+ try {
+ final int hashCode = Integer.parseInt(subtypeHashCode);
+ // Check whether the subtype id is valid or not
+ if (SubtypeUtils.isValidSubtypeId(imi, hashCode)) {
+ return s;
+ } else {
+ return NOT_A_SUBTYPE_ID_STR;
+ }
+ } catch (NumberFormatException e) {
+ return NOT_A_SUBTYPE_ID_STR;
+ }
+ }
+ }
+ }
+ // If imeId was enabled but subtypeId was disabled.
+ return NOT_A_SUBTYPE_ID_STR;
+ }
+ }
+ // If both imeId and subtypeId are disabled, return null
+ return null;
+ }
+
+ private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() {
+ ArrayList<Pair<String, String>> imsList = new ArrayList<>();
+ final String subtypeHistoryStr = getSubtypeHistoryStr();
+ if (TextUtils.isEmpty(subtypeHistoryStr)) {
+ return imsList;
+ }
+ final TextUtils.SimpleStringSplitter inputMethodSplitter =
+ new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
+ final TextUtils.SimpleStringSplitter subtypeSplitter =
+ new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
+ inputMethodSplitter.setString(subtypeHistoryStr);
+ while (inputMethodSplitter.hasNext()) {
+ String nextImsStr = inputMethodSplitter.next();
+ subtypeSplitter.setString(nextImsStr);
+ if (subtypeSplitter.hasNext()) {
+ String subtypeId = NOT_A_SUBTYPE_ID_STR;
+ // The first element is ime id.
+ String imeId = subtypeSplitter.next();
+ while (subtypeSplitter.hasNext()) {
+ subtypeId = subtypeSplitter.next();
+ break;
+ }
+ imsList.add(new Pair<>(imeId, subtypeId));
+ }
+ }
+ return imsList;
+ }
+
+ @NonNull
+ private String getSubtypeHistoryStr() {
+ final String history = getString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, "");
+ if (DEBUG) {
+ Slog.d(TAG, "getSubtypeHistoryStr: " + history);
+ }
+ return history;
+ }
+
+ void putSelectedInputMethod(String imeId) {
+ if (DEBUG) {
+ Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", "
+ + mCurrentUserId);
+ }
+ putString(Settings.Secure.DEFAULT_INPUT_METHOD, imeId);
+ }
+
+ void putSelectedSubtype(int subtypeId) {
+ if (DEBUG) {
+ Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", "
+ + mCurrentUserId);
+ }
+ putInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId);
+ }
+
+ @Nullable
+ String getSelectedInputMethod() {
+ final String imi = getString(Settings.Secure.DEFAULT_INPUT_METHOD, null);
+ if (DEBUG) {
+ Slog.d(TAG, "getSelectedInputMethodStr: " + imi);
+ }
+ return imi;
+ }
+
+ @Nullable
+ String getSelectedDefaultDeviceInputMethod() {
+ final String imi = getString(Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null);
+ if (DEBUG) {
+ Slog.d(TAG, "getSelectedDefaultDeviceInputMethodStr: " + imi + ", "
+ + mCurrentUserId);
+ }
+ return imi;
+ }
+
+ void putSelectedDefaultDeviceInputMethod(String imeId) {
+ if (DEBUG) {
+ Slog.d(TAG, "putSelectedDefaultDeviceInputMethodStr: " + imeId + ", "
+ + mCurrentUserId);
+ }
+ putString(Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, imeId);
+ }
+
+ void putDefaultVoiceInputMethod(String imeId) {
+ if (DEBUG) {
+ Slog.d(TAG,
+ "putDefaultVoiceInputMethodStr: " + imeId + ", " + mCurrentUserId);
+ }
+ putString(Settings.Secure.DEFAULT_VOICE_INPUT_METHOD, imeId);
+ }
+
+ @Nullable
+ String getDefaultVoiceInputMethod() {
+ final String imi = getString(Settings.Secure.DEFAULT_VOICE_INPUT_METHOD, null);
+ if (DEBUG) {
+ Slog.d(TAG, "getDefaultVoiceInputMethodStr: " + imi);
+ }
+ return imi;
+ }
+
+ boolean isSubtypeSelected() {
+ return getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID;
+ }
+
+ private int getSelectedInputMethodSubtypeHashCode() {
+ return getInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE,
+ NOT_A_SUBTYPE_ID);
+ }
+
+ @UserIdInt
+ public int getCurrentUserId() {
+ return mCurrentUserId;
+ }
+
+ int getSelectedInputMethodSubtypeId(String selectedImiId) {
+ final InputMethodInfo imi = mMethodMap.get(selectedImiId);
+ if (imi == null) {
+ return NOT_A_SUBTYPE_ID;
+ }
+ final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
+ return SubtypeUtils.getSubtypeIdFromHashCode(imi, subtypeHashCode);
+ }
+
+ void saveCurrentInputMethodAndSubtypeToHistory(String curMethodId,
+ InputMethodSubtype currentSubtype) {
+ String subtypeId = NOT_A_SUBTYPE_ID_STR;
+ if (currentSubtype != null) {
+ subtypeId = String.valueOf(currentSubtype.hashCode());
+ }
+ if (InputMethodUtils.canAddToLastInputMethod(currentSubtype)) {
+ addSubtypeToHistory(curMethodId, subtypeId);
+ }
+ }
+
+ /**
+ * A variant of {@link InputMethodManagerService#getCurrentInputMethodSubtypeLocked()} for
+ * non-current users.
+ *
+ * <p>TODO: Address code duplication between this and
+ * {@link InputMethodManagerService#getCurrentInputMethodSubtypeLocked()}.</p>
+ *
+ * @return {@link InputMethodSubtype} if exists. {@code null} otherwise.
+ */
+ @Nullable
+ InputMethodSubtype getCurrentInputMethodSubtypeForNonCurrentUsers() {
+ final String selectedMethodId = getSelectedInputMethod();
+ if (selectedMethodId == null) {
+ return null;
+ }
+ final InputMethodInfo imi = mMethodMap.get(selectedMethodId);
+ if (imi == null || imi.getSubtypeCount() == 0) {
+ return null;
+ }
+
+ final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
+ if (subtypeHashCode != NOT_A_SUBTYPE_ID) {
+ final int subtypeIndex = SubtypeUtils.getSubtypeIdFromHashCode(imi,
+ subtypeHashCode);
+ if (subtypeIndex >= 0) {
+ return imi.getSubtypeAt(subtypeIndex);
+ }
+ }
+
+ // If there are no selected subtypes, the framework will try to find the most applicable
+ // subtype from explicitly or implicitly enabled subtypes.
+ final List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
+ getEnabledInputMethodSubtypeListLocked(imi, true);
+ // If there is only one explicitly or implicitly enabled subtype, just returns it.
+ if (explicitlyOrImplicitlyEnabledSubtypes.isEmpty()) {
+ return null;
+ }
+ if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
+ return explicitlyOrImplicitlyEnabledSubtypes.get(0);
+ }
+ final String locale = SystemLocaleWrapper.get(mCurrentUserId).get(0).toString();
+ final InputMethodSubtype subtype = SubtypeUtils.findLastResortApplicableSubtypeLocked(
+ explicitlyOrImplicitlyEnabledSubtypes, SubtypeUtils.SUBTYPE_MODE_KEYBOARD,
+ locale, true);
+ if (subtype != null) {
+ return subtype;
+ }
+ return SubtypeUtils.findLastResortApplicableSubtypeLocked(
+ explicitlyOrImplicitlyEnabledSubtypes, null, locale, true);
+ }
+
+ boolean setAdditionalInputMethodSubtypes(@NonNull String imeId,
+ @NonNull ArrayList<InputMethodSubtype> subtypes,
+ @NonNull ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap,
+ @NonNull PackageManagerInternal packageManagerInternal, int callingUid) {
+ final InputMethodInfo imi = mMethodMap.get(imeId);
+ if (imi == null) {
+ return false;
+ }
+ if (!InputMethodUtils.checkIfPackageBelongsToUid(packageManagerInternal, callingUid,
+ imi.getPackageName())) {
+ return false;
+ }
+
+ if (subtypes.isEmpty()) {
+ additionalSubtypeMap.remove(imi.getId());
+ } else {
+ additionalSubtypeMap.put(imi.getId(), subtypes);
+ }
+ AdditionalSubtypeUtils.save(additionalSubtypeMap, mMethodMap, getCurrentUserId());
+ return true;
+ }
+
+ boolean setEnabledInputMethodSubtypes(@NonNull String imeId,
+ @NonNull int[] subtypeHashCodes) {
+ final InputMethodInfo imi = mMethodMap.get(imeId);
+ if (imi == null) {
+ return false;
+ }
+
+ final IntArray validSubtypeHashCodes = new IntArray(subtypeHashCodes.length);
+ for (int subtypeHashCode : subtypeHashCodes) {
+ if (subtypeHashCode == NOT_A_SUBTYPE_ID) {
+ continue; // NOT_A_SUBTYPE_ID must not be saved
+ }
+ if (!SubtypeUtils.isValidSubtypeId(imi, subtypeHashCode)) {
+ continue; // this subtype does not exist in InputMethodInfo.
+ }
+ if (validSubtypeHashCodes.indexOf(subtypeHashCode) >= 0) {
+ continue; // The entry is already added. No need to add anymore.
+ }
+ validSubtypeHashCodes.add(subtypeHashCode);
+ }
+
+ final String originalEnabledImesString = getEnabledInputMethodsStr();
+ final String updatedEnabledImesString = updateEnabledImeString(
+ originalEnabledImesString, imi.getId(), validSubtypeHashCodes);
+ if (TextUtils.equals(originalEnabledImesString, updatedEnabledImesString)) {
+ return false;
+ }
+
+ putEnabledInputMethodsStr(updatedEnabledImesString);
+ return true;
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ static String updateEnabledImeString(@NonNull String enabledImesString,
+ @NonNull String imeId, @NonNull IntArray enabledSubtypeHashCodes) {
+ final TextUtils.SimpleStringSplitter imeSplitter =
+ new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
+ final TextUtils.SimpleStringSplitter imeSubtypeSplitter =
+ new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
+
+ final StringBuilder sb = new StringBuilder();
+
+ imeSplitter.setString(enabledImesString);
+ boolean needsImeSeparator = false;
+ while (imeSplitter.hasNext()) {
+ final String nextImsStr = imeSplitter.next();
+ imeSubtypeSplitter.setString(nextImsStr);
+ if (imeSubtypeSplitter.hasNext()) {
+ if (needsImeSeparator) {
+ sb.append(INPUT_METHOD_SEPARATOR);
+ }
+ if (TextUtils.equals(imeId, imeSubtypeSplitter.next())) {
+ sb.append(imeId);
+ for (int i = 0; i < enabledSubtypeHashCodes.size(); ++i) {
+ sb.append(INPUT_METHOD_SUBTYPE_SEPARATOR);
+ sb.append(enabledSubtypeHashCodes.get(i));
+ }
+ } else {
+ sb.append(nextImsStr);
+ }
+ needsImeSeparator = true;
+ }
+ }
+ return sb.toString();
+ }
+
+ void dumpLocked(final Printer pw, final String prefix) {
+ pw.println(prefix + "mCurrentUserId=" + mCurrentUserId);
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
index 4439b06..834ba20 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -31,7 +31,6 @@
import android.view.inputmethod.InputMethodSubtype;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.inputmethod.InputMethodUtils.InputMethodSettings;
import java.util.ArrayList;
import java.util.Collections;
@@ -169,7 +168,7 @@
final ArrayList<InputMethodInfo> imis = settings.getEnabledInputMethodListLocked();
if (imis.isEmpty()) {
- return Collections.emptyList();
+ return new ArrayList<>();
}
if (isScreenLocked && includeAuxiliarySubtypes) {
if (DEBUG) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index fb57c09..361cdbb 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -28,15 +28,10 @@
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.os.Build;
-import android.os.LocaleList;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
-import android.util.ArrayMap;
import android.util.ArraySet;
-import android.util.IntArray;
-import android.util.Pair;
-import android.util.Printer;
import android.util.Slog;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
@@ -51,10 +46,8 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
-import java.util.Objects;
import java.util.StringJoiner;
import java.util.function.Consumer;
-import java.util.function.Predicate;
/**
* This class provides random static utility methods for {@link InputMethodManagerService} and its
@@ -68,12 +61,11 @@
public static final boolean DEBUG = false;
static final int NOT_A_SUBTYPE_ID = -1;
private static final String TAG = "InputMethodUtils";
- private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID);
// The string for enabled input method is saved as follows:
// example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0")
- private static final char INPUT_METHOD_SEPARATOR = ':';
- private static final char INPUT_METHOD_SUBTYPE_SEPARATOR = ';';
+ static final char INPUT_METHOD_SEPARATOR = ':';
+ static final char INPUT_METHOD_SUBTYPE_SEPARATOR = ';';
private InputMethodUtils() {
// This utility class is not publicly instantiable.
@@ -200,648 +192,6 @@
UserHandle.getUserId(uid));
}
- /**
- * Utility class for putting and getting settings for InputMethod.
- *
- * This is used in two ways:
- * - Singleton instance in {@link InputMethodManagerService}, which is updated on user-switch to
- * follow the current user.
- * - On-demand instances when we need settings for non-current users.
- *
- * TODO: Move all putters and getters of settings to this class.
- */
- @UserHandleAware
- public static class InputMethodSettings {
- private final ArrayMap<String, InputMethodInfo> mMethodMap;
-
- @UserIdInt
- private final int mCurrentUserId;
-
- private static void buildEnabledInputMethodsSettingString(
- StringBuilder builder, Pair<String, ArrayList<String>> ime) {
- builder.append(ime.first);
- // Inputmethod and subtypes are saved in the settings as follows:
- // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
- for (String subtypeId: ime.second) {
- builder.append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(subtypeId);
- }
- }
-
- InputMethodSettings(ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId) {
- mMethodMap = methodMap;
- mCurrentUserId = userId;
- String ime = getSelectedInputMethod();
- String defaultDeviceIme = getSelectedDefaultDeviceInputMethod();
- if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
- putSelectedInputMethod(defaultDeviceIme);
- putSelectedDefaultDeviceInputMethod(null);
- }
- }
-
- private void putString(@NonNull String key, @Nullable String str) {
- SecureSettingsWrapper.putString(key, str, mCurrentUserId);
- }
-
- @Nullable
- private String getString(@NonNull String key, @Nullable String defaultValue) {
- return SecureSettingsWrapper.getString(key, defaultValue, mCurrentUserId);
- }
-
- private void putInt(String key, int value) {
- SecureSettingsWrapper.putInt(key, value, mCurrentUserId);
- }
-
- private int getInt(String key, int defaultValue) {
- return SecureSettingsWrapper.getInt(key, defaultValue, mCurrentUserId);
- }
-
- private void putBoolean(String key, boolean value) {
- SecureSettingsWrapper.putBoolean(key, value, mCurrentUserId);
- }
-
- private boolean getBoolean(String key, boolean defaultValue) {
- return SecureSettingsWrapper.getBoolean(key, defaultValue, mCurrentUserId);
- }
-
- ArrayList<InputMethodInfo> getEnabledInputMethodListLocked() {
- return getEnabledInputMethodListWithFilterLocked(null /* matchingCondition */);
- }
-
- @NonNull
- ArrayList<InputMethodInfo> getEnabledInputMethodListWithFilterLocked(
- @Nullable Predicate<InputMethodInfo> matchingCondition) {
- return createEnabledInputMethodListLocked(
- getEnabledInputMethodsAndSubtypeListLocked(), matchingCondition);
- }
-
- List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
- InputMethodInfo imi, boolean allowsImplicitlyEnabledSubtypes) {
- List<InputMethodSubtype> enabledSubtypes =
- getEnabledInputMethodSubtypeListLocked(imi);
- if (allowsImplicitlyEnabledSubtypes && enabledSubtypes.isEmpty()) {
- enabledSubtypes = SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
- SystemLocaleWrapper.get(mCurrentUserId), imi);
- }
- return InputMethodSubtype.sort(imi, enabledSubtypes);
- }
-
- List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(InputMethodInfo imi) {
- List<Pair<String, ArrayList<String>>> imsList =
- getEnabledInputMethodsAndSubtypeListLocked();
- ArrayList<InputMethodSubtype> enabledSubtypes = new ArrayList<>();
- if (imi != null) {
- for (Pair<String, ArrayList<String>> imsPair : imsList) {
- InputMethodInfo info = mMethodMap.get(imsPair.first);
- if (info != null && info.getId().equals(imi.getId())) {
- final int subtypeCount = info.getSubtypeCount();
- for (int i = 0; i < subtypeCount; ++i) {
- InputMethodSubtype ims = info.getSubtypeAt(i);
- for (String s: imsPair.second) {
- if (String.valueOf(ims.hashCode()).equals(s)) {
- enabledSubtypes.add(ims);
- }
- }
- }
- break;
- }
- }
- }
- return enabledSubtypes;
- }
-
- List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
- final String enabledInputMethodsStr = getEnabledInputMethodsStr();
- final TextUtils.SimpleStringSplitter inputMethodSplitter =
- new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
- final TextUtils.SimpleStringSplitter subtypeSplitter =
- new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
- final ArrayList<Pair<String, ArrayList<String>>> imsList = new ArrayList<>();
- if (TextUtils.isEmpty(enabledInputMethodsStr)) {
- return imsList;
- }
- inputMethodSplitter.setString(enabledInputMethodsStr);
- while (inputMethodSplitter.hasNext()) {
- String nextImsStr = inputMethodSplitter.next();
- subtypeSplitter.setString(nextImsStr);
- if (subtypeSplitter.hasNext()) {
- ArrayList<String> subtypeHashes = new ArrayList<>();
- // The first element is ime id.
- String imeId = subtypeSplitter.next();
- while (subtypeSplitter.hasNext()) {
- subtypeHashes.add(subtypeSplitter.next());
- }
- imsList.add(new Pair<>(imeId, subtypeHashes));
- }
- }
- return imsList;
- }
-
- /**
- * Build and put a string of EnabledInputMethods with removing specified Id.
- * @return the specified id was removed or not.
- */
- boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
- StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
- boolean isRemoved = false;
- boolean needsAppendSeparator = false;
- for (Pair<String, ArrayList<String>> ims: imsList) {
- String curId = ims.first;
- if (curId.equals(id)) {
- // We are disabling this input method, and it is
- // currently enabled. Skip it to remove from the
- // new list.
- isRemoved = true;
- } else {
- if (needsAppendSeparator) {
- builder.append(INPUT_METHOD_SEPARATOR);
- } else {
- needsAppendSeparator = true;
- }
- buildEnabledInputMethodsSettingString(builder, ims);
- }
- }
- if (isRemoved) {
- // Update the setting with the new list of input methods.
- putEnabledInputMethodsStr(builder.toString());
- }
- return isRemoved;
- }
-
- private ArrayList<InputMethodInfo> createEnabledInputMethodListLocked(
- List<Pair<String, ArrayList<String>>> imsList,
- Predicate<InputMethodInfo> matchingCondition) {
- final ArrayList<InputMethodInfo> res = new ArrayList<>();
- for (Pair<String, ArrayList<String>> ims: imsList) {
- InputMethodInfo info = mMethodMap.get(ims.first);
- if (info != null && !info.isVrOnly()
- && (matchingCondition == null || matchingCondition.test(info))) {
- res.add(info);
- }
- }
- return res;
- }
-
- void putEnabledInputMethodsStr(@Nullable String str) {
- if (DEBUG) {
- Slog.d(TAG, "putEnabledInputMethodStr: " + str);
- }
- if (TextUtils.isEmpty(str)) {
- // OK to coalesce to null, since getEnabledInputMethodsStr() can take care of the
- // empty data scenario.
- putString(Settings.Secure.ENABLED_INPUT_METHODS, null);
- } else {
- putString(Settings.Secure.ENABLED_INPUT_METHODS, str);
- }
- }
-
- @NonNull
- String getEnabledInputMethodsStr() {
- return getString(Settings.Secure.ENABLED_INPUT_METHODS, "");
- }
-
- private void saveSubtypeHistory(
- List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) {
- StringBuilder builder = new StringBuilder();
- boolean isImeAdded = false;
- if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) {
- builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(
- newSubtypeId);
- isImeAdded = true;
- }
- for (Pair<String, String> ime: savedImes) {
- String imeId = ime.first;
- String subtypeId = ime.second;
- if (TextUtils.isEmpty(subtypeId)) {
- subtypeId = NOT_A_SUBTYPE_ID_STR;
- }
- if (isImeAdded) {
- builder.append(INPUT_METHOD_SEPARATOR);
- } else {
- isImeAdded = true;
- }
- builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(
- subtypeId);
- }
- // Remove the last INPUT_METHOD_SEPARATOR
- putSubtypeHistoryStr(builder.toString());
- }
-
- private void addSubtypeToHistory(String imeId, String subtypeId) {
- List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
- for (Pair<String, String> ime: subtypeHistory) {
- if (ime.first.equals(imeId)) {
- if (DEBUG) {
- Slog.v(TAG, "Subtype found in the history: " + imeId + ", "
- + ime.second);
- }
- // We should break here
- subtypeHistory.remove(ime);
- break;
- }
- }
- if (DEBUG) {
- Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId);
- }
- saveSubtypeHistory(subtypeHistory, imeId, subtypeId);
- }
-
- private void putSubtypeHistoryStr(@NonNull String str) {
- if (DEBUG) {
- Slog.d(TAG, "putSubtypeHistoryStr: " + str);
- }
- if (TextUtils.isEmpty(str)) {
- // OK to coalesce to null, since getSubtypeHistoryStr() can take care of the empty
- // data scenario.
- putString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, null);
- } else {
- putString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str);
- }
- }
-
- Pair<String, String> getLastInputMethodAndSubtypeLocked() {
- // Gets the first one from the history
- return getLastSubtypeForInputMethodLockedInternal(null);
- }
-
- @Nullable
- InputMethodSubtype getLastInputMethodSubtypeLocked() {
- final Pair<String, String> lastIme = getLastInputMethodAndSubtypeLocked();
- // TODO: Handle the case of the last IME with no subtypes
- if (lastIme == null || TextUtils.isEmpty(lastIme.first)
- || TextUtils.isEmpty(lastIme.second)) return null;
- final InputMethodInfo lastImi = mMethodMap.get(lastIme.first);
- if (lastImi == null) return null;
- try {
- final int lastSubtypeHash = Integer.parseInt(lastIme.second);
- final int lastSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(lastImi,
- lastSubtypeHash);
- if (lastSubtypeId < 0 || lastSubtypeId >= lastImi.getSubtypeCount()) {
- return null;
- }
- return lastImi.getSubtypeAt(lastSubtypeId);
- } catch (NumberFormatException e) {
- return null;
- }
- }
-
- String getLastSubtypeForInputMethodLocked(String imeId) {
- Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId);
- if (ime != null) {
- return ime.second;
- } else {
- return null;
- }
- }
-
- private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) {
- List<Pair<String, ArrayList<String>>> enabledImes =
- getEnabledInputMethodsAndSubtypeListLocked();
- List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
- for (Pair<String, String> imeAndSubtype : subtypeHistory) {
- final String imeInTheHistory = imeAndSubtype.first;
- // If imeId is empty, returns the first IME and subtype in the history
- if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) {
- final String subtypeInTheHistory = imeAndSubtype.second;
- final String subtypeHashCode =
- getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(
- enabledImes, imeInTheHistory, subtypeInTheHistory);
- if (!TextUtils.isEmpty(subtypeHashCode)) {
- if (DEBUG) {
- Slog.d(TAG, "Enabled subtype found in the history: " + subtypeHashCode);
- }
- return new Pair<>(imeInTheHistory, subtypeHashCode);
- }
- }
- }
- if (DEBUG) {
- Slog.d(TAG, "No enabled IME found in the history");
- }
- return null;
- }
-
- private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String,
- ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) {
- final LocaleList localeList = SystemLocaleWrapper.get(mCurrentUserId);
- for (Pair<String, ArrayList<String>> enabledIme: enabledImes) {
- if (enabledIme.first.equals(imeId)) {
- final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second;
- final InputMethodInfo imi = mMethodMap.get(imeId);
- if (explicitlyEnabledSubtypes.size() == 0) {
- // If there are no explicitly enabled subtypes, applicable subtypes are
- // enabled implicitly.
- // If IME is enabled and no subtypes are enabled, applicable subtypes
- // are enabled implicitly, so needs to treat them to be enabled.
- if (imi != null && imi.getSubtypeCount() > 0) {
- List<InputMethodSubtype> implicitlyEnabledSubtypes =
- SubtypeUtils.getImplicitlyApplicableSubtypesLocked(localeList,
- imi);
- final int numSubtypes = implicitlyEnabledSubtypes.size();
- for (int i = 0; i < numSubtypes; ++i) {
- final InputMethodSubtype st = implicitlyEnabledSubtypes.get(i);
- if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) {
- return subtypeHashCode;
- }
- }
- }
- } else {
- for (String s: explicitlyEnabledSubtypes) {
- if (s.equals(subtypeHashCode)) {
- // If both imeId and subtypeId are enabled, return subtypeId.
- try {
- final int hashCode = Integer.parseInt(subtypeHashCode);
- // Check whether the subtype id is valid or not
- if (SubtypeUtils.isValidSubtypeId(imi, hashCode)) {
- return s;
- } else {
- return NOT_A_SUBTYPE_ID_STR;
- }
- } catch (NumberFormatException e) {
- return NOT_A_SUBTYPE_ID_STR;
- }
- }
- }
- }
- // If imeId was enabled but subtypeId was disabled.
- return NOT_A_SUBTYPE_ID_STR;
- }
- }
- // If both imeId and subtypeId are disabled, return null
- return null;
- }
-
- private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() {
- ArrayList<Pair<String, String>> imsList = new ArrayList<>();
- final String subtypeHistoryStr = getSubtypeHistoryStr();
- if (TextUtils.isEmpty(subtypeHistoryStr)) {
- return imsList;
- }
- final TextUtils.SimpleStringSplitter inputMethodSplitter =
- new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
- final TextUtils.SimpleStringSplitter subtypeSplitter =
- new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
- inputMethodSplitter.setString(subtypeHistoryStr);
- while (inputMethodSplitter.hasNext()) {
- String nextImsStr = inputMethodSplitter.next();
- subtypeSplitter.setString(nextImsStr);
- if (subtypeSplitter.hasNext()) {
- String subtypeId = NOT_A_SUBTYPE_ID_STR;
- // The first element is ime id.
- String imeId = subtypeSplitter.next();
- while (subtypeSplitter.hasNext()) {
- subtypeId = subtypeSplitter.next();
- break;
- }
- imsList.add(new Pair<>(imeId, subtypeId));
- }
- }
- return imsList;
- }
-
- @NonNull
- private String getSubtypeHistoryStr() {
- final String history = getString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, "");
- if (DEBUG) {
- Slog.d(TAG, "getSubtypeHistoryStr: " + history);
- }
- return history;
- }
-
- void putSelectedInputMethod(String imeId) {
- if (DEBUG) {
- Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", "
- + mCurrentUserId);
- }
- putString(Settings.Secure.DEFAULT_INPUT_METHOD, imeId);
- }
-
- void putSelectedSubtype(int subtypeId) {
- if (DEBUG) {
- Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", "
- + mCurrentUserId);
- }
- putInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId);
- }
-
- @Nullable
- String getSelectedInputMethod() {
- final String imi = getString(Settings.Secure.DEFAULT_INPUT_METHOD, null);
- if (DEBUG) {
- Slog.d(TAG, "getSelectedInputMethodStr: " + imi);
- }
- return imi;
- }
-
- @Nullable
- String getSelectedDefaultDeviceInputMethod() {
- final String imi = getString(Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null);
- if (DEBUG) {
- Slog.d(TAG, "getSelectedDefaultDeviceInputMethodStr: " + imi + ", "
- + mCurrentUserId);
- }
- return imi;
- }
-
- void putSelectedDefaultDeviceInputMethod(String imeId) {
- if (DEBUG) {
- Slog.d(TAG, "putSelectedDefaultDeviceInputMethodStr: " + imeId + ", "
- + mCurrentUserId);
- }
- putString(Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, imeId);
- }
-
- void putDefaultVoiceInputMethod(String imeId) {
- if (DEBUG) {
- Slog.d(TAG, "putDefaultVoiceInputMethodStr: " + imeId + ", " + mCurrentUserId);
- }
- putString(Settings.Secure.DEFAULT_VOICE_INPUT_METHOD, imeId);
- }
-
- @Nullable
- String getDefaultVoiceInputMethod() {
- final String imi = getString(Settings.Secure.DEFAULT_VOICE_INPUT_METHOD, null);
- if (DEBUG) {
- Slog.d(TAG, "getDefaultVoiceInputMethodStr: " + imi);
- }
- return imi;
- }
-
- boolean isSubtypeSelected() {
- return getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID;
- }
-
- private int getSelectedInputMethodSubtypeHashCode() {
- return getInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, NOT_A_SUBTYPE_ID);
- }
-
- @UserIdInt
- public int getCurrentUserId() {
- return mCurrentUserId;
- }
-
- int getSelectedInputMethodSubtypeId(String selectedImiId) {
- final InputMethodInfo imi = mMethodMap.get(selectedImiId);
- if (imi == null) {
- return NOT_A_SUBTYPE_ID;
- }
- final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
- return SubtypeUtils.getSubtypeIdFromHashCode(imi, subtypeHashCode);
- }
-
- void saveCurrentInputMethodAndSubtypeToHistory(String curMethodId,
- InputMethodSubtype currentSubtype) {
- String subtypeId = NOT_A_SUBTYPE_ID_STR;
- if (currentSubtype != null) {
- subtypeId = String.valueOf(currentSubtype.hashCode());
- }
- if (canAddToLastInputMethod(currentSubtype)) {
- addSubtypeToHistory(curMethodId, subtypeId);
- }
- }
-
- /**
- * A variant of {@link InputMethodManagerService#getCurrentInputMethodSubtypeLocked()} for
- * non-current users.
- *
- * <p>TODO: Address code duplication between this and
- * {@link InputMethodManagerService#getCurrentInputMethodSubtypeLocked()}.</p>
- *
- * @return {@link InputMethodSubtype} if exists. {@code null} otherwise.
- */
- @Nullable
- InputMethodSubtype getCurrentInputMethodSubtypeForNonCurrentUsers() {
- final String selectedMethodId = getSelectedInputMethod();
- if (selectedMethodId == null) {
- return null;
- }
- final InputMethodInfo imi = mMethodMap.get(selectedMethodId);
- if (imi == null || imi.getSubtypeCount() == 0) {
- return null;
- }
-
- final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
- if (subtypeHashCode != InputMethodUtils.NOT_A_SUBTYPE_ID) {
- final int subtypeIndex = SubtypeUtils.getSubtypeIdFromHashCode(imi,
- subtypeHashCode);
- if (subtypeIndex >= 0) {
- return imi.getSubtypeAt(subtypeIndex);
- }
- }
-
- // If there are no selected subtypes, the framework will try to find the most applicable
- // subtype from explicitly or implicitly enabled subtypes.
- final List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
- getEnabledInputMethodSubtypeListLocked(imi, true);
- // If there is only one explicitly or implicitly enabled subtype, just returns it.
- if (explicitlyOrImplicitlyEnabledSubtypes.isEmpty()) {
- return null;
- }
- if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
- return explicitlyOrImplicitlyEnabledSubtypes.get(0);
- }
- final String locale = SystemLocaleWrapper.get(mCurrentUserId).get(0).toString();
- final InputMethodSubtype subtype = SubtypeUtils.findLastResortApplicableSubtypeLocked(
- explicitlyOrImplicitlyEnabledSubtypes, SubtypeUtils.SUBTYPE_MODE_KEYBOARD,
- locale, true);
- if (subtype != null) {
- return subtype;
- }
- return SubtypeUtils.findLastResortApplicableSubtypeLocked(
- explicitlyOrImplicitlyEnabledSubtypes, null, locale, true);
- }
-
- boolean setAdditionalInputMethodSubtypes(@NonNull String imeId,
- @NonNull ArrayList<InputMethodSubtype> subtypes,
- @NonNull ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap,
- @NonNull PackageManagerInternal packageManagerInternal, int callingUid) {
- final InputMethodInfo imi = mMethodMap.get(imeId);
- if (imi == null) {
- return false;
- }
- if (!InputMethodUtils.checkIfPackageBelongsToUid(packageManagerInternal, callingUid,
- imi.getPackageName())) {
- return false;
- }
-
- if (subtypes.isEmpty()) {
- additionalSubtypeMap.remove(imi.getId());
- } else {
- additionalSubtypeMap.put(imi.getId(), subtypes);
- }
- AdditionalSubtypeUtils.save(additionalSubtypeMap, mMethodMap, getCurrentUserId());
- return true;
- }
-
- boolean setEnabledInputMethodSubtypes(@NonNull String imeId,
- @NonNull int[] subtypeHashCodes) {
- final InputMethodInfo imi = mMethodMap.get(imeId);
- if (imi == null) {
- return false;
- }
-
- final IntArray validSubtypeHashCodes = new IntArray(subtypeHashCodes.length);
- for (int subtypeHashCode : subtypeHashCodes) {
- if (subtypeHashCode == NOT_A_SUBTYPE_ID) {
- continue; // NOT_A_SUBTYPE_ID must not be saved
- }
- if (!SubtypeUtils.isValidSubtypeId(imi, subtypeHashCode)) {
- continue; // this subtype does not exist in InputMethodInfo.
- }
- if (validSubtypeHashCodes.indexOf(subtypeHashCode) >= 0) {
- continue; // The entry is already added. No need to add anymore.
- }
- validSubtypeHashCodes.add(subtypeHashCode);
- }
-
- final String originalEnabledImesString = getEnabledInputMethodsStr();
- final String updatedEnabledImesString = updateEnabledImeString(
- originalEnabledImesString, imi.getId(), validSubtypeHashCodes);
- if (TextUtils.equals(originalEnabledImesString, updatedEnabledImesString)) {
- return false;
- }
-
- putEnabledInputMethodsStr(updatedEnabledImesString);
- return true;
- }
-
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
- static String updateEnabledImeString(@NonNull String enabledImesString,
- @NonNull String imeId, @NonNull IntArray enabledSubtypeHashCodes) {
- final TextUtils.SimpleStringSplitter imeSplitter =
- new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
- final TextUtils.SimpleStringSplitter imeSubtypeSplitter =
- new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
-
- final StringBuilder sb = new StringBuilder();
-
- imeSplitter.setString(enabledImesString);
- boolean needsImeSeparator = false;
- while (imeSplitter.hasNext()) {
- final String nextImsStr = imeSplitter.next();
- imeSubtypeSplitter.setString(nextImsStr);
- if (imeSubtypeSplitter.hasNext()) {
- if (needsImeSeparator) {
- sb.append(INPUT_METHOD_SEPARATOR);
- }
- if (TextUtils.equals(imeId, imeSubtypeSplitter.next())) {
- sb.append(imeId);
- for (int i = 0; i < enabledSubtypeHashCodes.size(); ++i) {
- sb.append(INPUT_METHOD_SUBTYPE_SEPARATOR);
- sb.append(enabledSubtypeHashCodes.get(i));
- }
- } else {
- sb.append(nextImsStr);
- }
- needsImeSeparator = true;
- }
- }
- return sb.toString();
- }
-
- public void dumpLocked(final Printer pw, final String prefix) {
- pw.println(prefix + "mCurrentUserId=" + mCurrentUserId);
- }
- }
-
static boolean isSoftInputModeStateVisibleAllowed(int targetSdkVersion,
@StartInputFlags int startInputFlags) {
if (targetSdkVersion < Build.VERSION_CODES.P) {
diff --git a/services/core/java/com/android/server/inputmethod/OWNERS b/services/core/java/com/android/server/inputmethod/OWNERS
index aa638aa..e507c6b 100644
--- a/services/core/java/com/android/server/inputmethod/OWNERS
+++ b/services/core/java/com/android/server/inputmethod/OWNERS
@@ -6,5 +6,8 @@
[email protected]
[email protected]
+# Automotive
[email protected]
+
[email protected] #{LAST_RESORT_SUGGESTION}
[email protected] #{LAST_RESORT_SUGGESTION}
diff --git a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
index d02b6f4..171fbb6 100644
--- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
@@ -25,6 +25,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
+import android.location.GnssMeasurement;
import android.location.GnssMeasurementRequest;
import android.location.GnssMeasurementsEvent;
import android.location.IGnssMeasurementsListener;
@@ -33,6 +34,7 @@
import android.stats.location.LocationStatsEnums;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
import com.android.server.location.gnss.GnssConfiguration.HalInterfaceVersion;
import com.android.server.location.gnss.hal.GnssNative;
import com.android.server.location.injector.AppOpsHelper;
@@ -40,6 +42,8 @@
import com.android.server.location.injector.LocationUsageLogger;
import com.android.server.location.injector.SettingsHelper;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.Collection;
/**
@@ -91,6 +95,9 @@
private final LocationUsageLogger mLogger;
private final GnssNative mGnssNative;
+ @GuardedBy("mMultiplexerLock")
+ private GnssMeasurementsEvent mLastGnssMeasurementsEvent;
+
public GnssMeasurementsProvider(Injector injector, GnssNative gnssNative) {
super(injector);
mAppOpsHelper = injector.getAppOpsHelper();
@@ -264,5 +271,46 @@
return null;
}
});
+ synchronized (mMultiplexerLock) {
+ mLastGnssMeasurementsEvent = event;
+ }
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ super.dump(fd, pw, args);
+ pw.print("last measurements=");
+ pw.println(getLastMeasurementEventSummary());
+ }
+
+ /**
+ * Returns a string of GnssMeasurementsEvent summary including received time, satellite count
+ * and average baseband C/No.
+ */
+ private String getLastMeasurementEventSummary() {
+ synchronized (mMultiplexerLock) {
+ if (mLastGnssMeasurementsEvent == null) {
+ return null;
+ }
+ StringBuilder builder = new StringBuilder("[");
+ builder.append("elapsedRealtimeNs=").append(
+ mLastGnssMeasurementsEvent.getClock().getElapsedRealtimeNanos());
+ builder.append(" measurementCount=").append(
+ mLastGnssMeasurementsEvent.getMeasurements().size());
+
+ float sumBasebandCn0 = 0;
+ int countBasebandCn0 = 0;
+ for (GnssMeasurement measurement : mLastGnssMeasurementsEvent.getMeasurements()) {
+ if (measurement.hasBasebandCn0DbHz()) {
+ sumBasebandCn0 += measurement.getBasebandCn0DbHz();
+ countBasebandCn0++;
+ }
+ }
+ if (countBasebandCn0 > 0) {
+ builder.append(" avgBasebandCn0=").append(sumBasebandCn0 / countBasebandCn0);
+ }
+ builder.append("]");
+ return builder.toString();
+ }
}
}
diff --git a/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java b/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java
index c772e08..5df0de8 100644
--- a/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java
@@ -22,12 +22,14 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.os.SystemClock;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
import android.util.Log;
import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.flags.Flags;
import com.android.server.FgThread;
import java.util.Objects;
@@ -104,10 +106,26 @@
boolean isInExtensionTime = mEmergencyCallEndRealtimeMs != Long.MIN_VALUE
&& (SystemClock.elapsedRealtime() - mEmergencyCallEndRealtimeMs) < extensionTimeMs;
- return mIsInEmergencyCall
- || isInExtensionTime
- || mTelephonyManager.getEmergencyCallbackMode()
- || mTelephonyManager.isInEmergencySmsMode();
+ if (!Flags.enforceTelephonyFeatureMapping()) {
+ return mIsInEmergencyCall
+ || isInExtensionTime
+ || mTelephonyManager.getEmergencyCallbackMode()
+ || mTelephonyManager.isInEmergencySmsMode();
+ } else {
+ boolean emergencyCallbackMode = false;
+ boolean emergencySmsMode = false;
+ PackageManager pm = mContext.getPackageManager();
+ if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CALLING)) {
+ emergencyCallbackMode = mTelephonyManager.getEmergencyCallbackMode();
+ }
+ if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)) {
+ emergencySmsMode = mTelephonyManager.isInEmergencySmsMode();
+ }
+ return mIsInEmergencyCall
+ || isInExtensionTime
+ || emergencyCallbackMode
+ || emergencySmsMode;
+ }
}
private class EmergencyCallTelephonyCallback extends TelephonyCallback implements
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index ad09082..542b3b0 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -52,6 +52,7 @@
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.IActivityManager;
@@ -74,6 +75,7 @@
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.database.sqlite.SQLiteDatabase;
@@ -303,7 +305,7 @@
private boolean mThirdPartyAppsStarted;
// Current password metrics for all secured users on the device. Updated when user unlocks the
- // device or changes password. Removed when user is stopped.
+ // device or changes password. Removed if user is stopped with its CE key evicted.
@GuardedBy("this")
private final SparseArray<PasswordMetrics> mUserPasswordMetrics = new SparseArray<>();
@VisibleForTesting
@@ -793,13 +795,33 @@
}
@VisibleForTesting
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.QUERY_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
void onUserStopped(int userId) {
hideEncryptionNotification(new UserHandle(userId));
- // User is stopped with its CE key evicted. Restore strong auth requirement to the default
- // flags after boot since stopping and restarting a user later is equivalent to rebooting
- // the device.
+
+ // Normally, CE storage is locked when a user is stopped, and restarting the user requires
+ // strong auth. Therefore, reset the user's strong auth flags. The exception is users that
+ // allow delayed locking; under some circumstances, biometric authentication is allowed to
+ // restart such users. Don't reset the strong auth flags for such users.
+ //
+ // TODO(b/319142556): It might make more sense to reset the strong auth flags when CE
+ // storage is locked, instead of when the user is stopped. This would ensure the flags get
+ // reset if CE storage is locked later for a user that allows delayed locking.
+ if (android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()) {
+ UserProperties userProperties = mUserManager.getUserProperties(UserHandle.of(userId));
+ if (userProperties != null && userProperties.getAllowStoppingUserWithDelayedLocking()) {
+ return;
+ }
+ }
int strongAuthRequired = LockPatternUtils.StrongAuthTracker.getDefaultFlags(mContext);
requireStrongAuth(strongAuthRequired, userId);
+
+ // Don't keep the password metrics in memory for a stopped user that will require strong
+ // auth to start again, since strong auth will make the password metrics available again.
synchronized (this) {
mUserPasswordMetrics.remove(userId);
}
diff --git a/services/core/java/com/android/server/media/MediaFeatureFlagManager.java b/services/core/java/com/android/server/media/MediaFeatureFlagManager.java
deleted file mode 100644
index f90f64a..0000000
--- a/services/core/java/com/android/server/media/MediaFeatureFlagManager.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.media;
-
-import android.annotation.StringDef;
-import android.app.ActivityThread;
-import android.app.Application;
-import android.provider.DeviceConfig;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/* package */ class MediaFeatureFlagManager {
-
- /**
- * Namespace for media better together features.
- */
- private static final String NAMESPACE_MEDIA_BETTER_TOGETHER = "media_better_together";
-
- @StringDef(
- prefix = "FEATURE_",
- value = {
- FEATURE_SCANNING_MINIMUM_PACKAGE_IMPORTANCE
- })
- @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
- @Retention(RetentionPolicy.SOURCE)
- /* package */ @interface MediaFeatureFlag {}
-
- /**
- * Whether to use IMPORTANCE_FOREGROUND (i.e. 100) or IMPORTANCE_FOREGROUND_SERVICE (i.e. 125)
- * as the minimum package importance for scanning.
- */
- /* package */ static final @MediaFeatureFlag String
- FEATURE_SCANNING_MINIMUM_PACKAGE_IMPORTANCE = "scanning_package_minimum_importance";
-
- private static final MediaFeatureFlagManager sInstance = new MediaFeatureFlagManager();
-
- private MediaFeatureFlagManager() {
- // Empty to prevent instantiation.
- }
-
- /* package */ static MediaFeatureFlagManager getInstance() {
- return sInstance;
- }
-
- /**
- * Returns a boolean value from {@link DeviceConfig} from the system_time namespace, or
- * {@code defaultValue} if there is no explicit value set.
- */
- public boolean getBoolean(@MediaFeatureFlag String key, boolean defaultValue) {
- return DeviceConfig.getBoolean(NAMESPACE_MEDIA_BETTER_TOGETHER, key, defaultValue);
- }
-
- /**
- * Returns an int value from {@link DeviceConfig} from the system_time namespace, or {@code
- * defaultValue} if there is no explicit value set.
- */
- public int getInt(@MediaFeatureFlag String key, int defaultValue) {
- return DeviceConfig.getInt(NAMESPACE_MEDIA_BETTER_TOGETHER, key, defaultValue);
- }
-
- /**
- * Adds a listener to react for changes in media feature flags values. Future calls to this
- * method with the same listener will replace the old namespace and executor.
- *
- * @param onPropertiesChangedListener The listener to add.
- */
- public void addOnPropertiesChangedListener(
- DeviceConfig.OnPropertiesChangedListener onPropertiesChangedListener) {
- Application currentApplication = ActivityThread.currentApplication();
- if (currentApplication != null) {
- DeviceConfig.addOnPropertiesChangedListener(
- NAMESPACE_MEDIA_BETTER_TOGETHER,
- currentApplication.getMainExecutor(),
- onPropertiesChangedListener);
- }
- }
-}
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index 21e7bef..0ae6036 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -44,6 +44,7 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.media.flags.Flags;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -318,11 +319,19 @@
@Override
public void onBindingDied(ComponentName name) {
- if (DEBUG) {
- Slog.d(TAG, this + ": Service binding died");
- }
unbind();
- if (shouldBind()) {
+ if (Flags.enablePreventionOfKeepAliveRouteProviders()) {
+ Slog.w(
+ TAG,
+ TextUtils.formatSimple(
+ "Route provider service (%s) binding died, but we did not rebind.",
+ name.toString()));
+ } else if (shouldBind()) {
+ Slog.w(
+ TAG,
+ TextUtils.formatSimple(
+ "Rebound to provider service (%s) after binding died.",
+ name.toString()));
bind();
}
}
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
index bd252e7..3d717a8 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
@@ -33,6 +33,8 @@
import android.util.Log;
import android.util.Slog;
+import com.android.media.flags.Flags;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
@@ -86,7 +88,9 @@
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
- filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
+ if (!Flags.enablePreventionOfKeepAliveRouteProviders()) {
+ filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
+ }
filter.addDataScheme("package");
mContext.registerReceiverAsUser(mScanPackagesReceiver,
new UserHandle(mUserId), filter, null, mHandler);
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 06a8d98..28a1c7a 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -16,7 +16,7 @@
package com.android.server.media;
-import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.content.Intent.ACTION_SCREEN_OFF;
import static android.content.Intent.ACTION_SCREEN_ON;
import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
@@ -24,7 +24,6 @@
import static android.media.MediaRouter2Utils.getProviderId;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-import static com.android.server.media.MediaFeatureFlagManager.FEATURE_SCANNING_MINIMUM_PACKAGE_IMPORTANCE;
import android.Manifest;
import android.annotation.NonNull;
@@ -55,7 +54,6 @@
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.provider.DeviceConfig;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
@@ -97,11 +95,7 @@
// in MediaRouter2, remove this constant and replace the usages with the real request IDs.
private static final long DUMMY_REQUEST_ID = -1;
- private static int sPackageImportanceForScanning =
- MediaFeatureFlagManager.getInstance()
- .getInt(
- FEATURE_SCANNING_MINIMUM_PACKAGE_IMPORTANCE,
- IMPORTANCE_FOREGROUND_SERVICE);
+ private static final int REQUIRED_PACKAGE_IMPORTANCE_FOR_SCANNING = IMPORTANCE_FOREGROUND;
/**
* Contains the list of bluetooth permissions that are required to do system routing.
@@ -159,7 +153,7 @@
mContext = context;
mActivityManager = mContext.getSystemService(ActivityManager.class);
mActivityManager.addOnUidImportanceListener(mOnUidImportanceListener,
- sPackageImportanceForScanning);
+ REQUIRED_PACKAGE_IMPORTANCE_FOR_SCANNING);
mPowerManager = mContext.getSystemService(PowerManager.class);
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
@@ -171,9 +165,6 @@
}
mContext.getPackageManager().addOnPermissionsChangeListener(this::onPermissionsChanged);
-
- MediaFeatureFlagManager.getInstance()
- .addOnPropertiesChangedListener(this::onDeviceConfigChange);
}
/**
@@ -774,7 +765,7 @@
.generateDeviceRouteSelectedSessionInfo(packageName);
} else {
sessionInfos = userRecord.mHandler.mSystemProvider.getSessionInfos();
- if (sessionInfos != null && !sessionInfos.isEmpty()) {
+ if (!sessionInfos.isEmpty()) {
// Return a copy of the current system session with no modification,
// except setting the client package name.
return new RoutingSessionInfo.Builder(sessionInfos.get(0))
@@ -1158,14 +1149,7 @@
} else {
if (route.isSystemRoute()
&& !routerRecord.hasSystemRoutingPermission()
- && !TextUtils.equals(
- route.getId(),
- routerRecord
- .mUserRecord
- .mHandler
- .mSystemProvider
- .getDefaultRoute()
- .getId())) {
+ && !TextUtils.equals(route.getId(), MediaRoute2Info.ROUTE_ID_DEFAULT)) {
Slog.w(TAG, "MODIFY_AUDIO_ROUTING permission is required to transfer to"
+ route);
routerRecord.mUserRecord.mHandler.notifySessionCreationFailedToRouter(
@@ -1252,11 +1236,9 @@
"transferToRouteWithRouter2 | router: %s(id: %d), route: %s",
routerRecord.mPackageName, routerRecord.mRouterId, route.getId()));
- String defaultRouteId =
- routerRecord.mUserRecord.mHandler.mSystemProvider.getDefaultRoute().getId();
if (route.isSystemRoute()
&& !routerRecord.hasSystemRoutingPermission()
- && !TextUtils.equals(route.getId(), defaultRouteId)) {
+ && !TextUtils.equals(route.getId(), MediaRoute2Info.ROUTE_ID_DEFAULT)) {
routerRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::notifySessionCreationFailedToRouter,
routerRecord.mUserRecord.mHandler,
@@ -1452,8 +1434,13 @@
return;
}
- Slog.i(TAG, TextUtils.formatSimple(
- "startScan | manager: %d", managerRecord.mManagerId));
+ Slog.i(
+ TAG,
+ TextUtils.formatSimple(
+ "startScan | manager: %d, ownerPackageName: %s, targetPackageName: %s",
+ managerRecord.mManagerId,
+ managerRecord.mOwnerPackageName,
+ managerRecord.mTargetPackageName));
managerRecord.startScan();
}
@@ -1466,8 +1453,13 @@
return;
}
- Slog.i(TAG, TextUtils.formatSimple(
- "stopScan | manager: %d", managerRecord.mManagerId));
+ Slog.i(
+ TAG,
+ TextUtils.formatSimple(
+ "stopScan | manager: %d, ownerPackageName: %s, targetPackageName: %s",
+ managerRecord.mManagerId,
+ managerRecord.mOwnerPackageName,
+ managerRecord.mTargetPackageName));
managerRecord.stopScan();
}
@@ -1734,13 +1726,6 @@
// End of locked methods that are used by both MediaRouter2 and MediaRouter2Manager.
- private void onDeviceConfigChange(@NonNull DeviceConfig.Properties properties) {
- sPackageImportanceForScanning =
- properties.getInt(
- /* name */ FEATURE_SCANNING_MINIMUM_PACKAGE_IMPORTANCE,
- /* defaultValue */ IMPORTANCE_FOREGROUND_SERVICE);
- }
-
static long toUniqueRequestId(int requesterId, int originalRequestId) {
return ((long) requesterId << 32) | originalRequestId;
}
@@ -2761,11 +2746,10 @@
if (manager != null) {
notifyRequestFailedToManager(
manager.mManager, toOriginalRequestId(uniqueRequestId), reason);
- return;
}
- // Currently, only the manager can get notified of failures.
- // TODO: Notify router too when the related callback is introduced.
+ // Currently, only manager records can get notified of failures.
+ // TODO(b/282936553): Notify regular routers of request failures.
}
private boolean handleSessionCreationRequestFailed(@NonNull MediaRoute2Provider provider,
@@ -2909,11 +2893,9 @@
currentSystemSessionInfo = mSystemProvider.getDefaultSessionInfo();
}
- if (currentRoutes.size() == 0) {
- return;
+ if (!currentRoutes.isEmpty()) {
+ routerRecord.notifyRegistered(currentRoutes, currentSystemSessionInfo);
}
-
- routerRecord.notifyRegistered(currentRoutes, currentSystemSessionInfo);
}
private static void notifyRoutesUpdatedToRouterRecords(
@@ -3182,7 +3164,7 @@
record ->
service.mActivityManager.getPackageImportance(
record.mPackageName)
- <= sPackageImportanceForScanning)
+ <= REQUIRED_PACKAGE_IMPORTANCE_FOR_SCANNING)
.collect(Collectors.toList());
}
@@ -3199,7 +3181,7 @@
manager.mIsScanning
&& service.mActivityManager.getPackageImportance(
manager.mOwnerPackageName)
- <= sPackageImportanceForScanning);
+ <= REQUIRED_PACKAGE_IMPORTANCE_FOR_SCANNING);
}
private MediaRoute2Provider findProvider(@Nullable String providerId) {
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 6deda46..550aed5 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -304,7 +304,7 @@
}
@VisibleForTesting
- void addCallback(final IMediaProjectionWatcherCallback callback) {
+ MediaProjectionInfo addCallback(final IMediaProjectionWatcherCallback callback) {
IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
@@ -314,6 +314,7 @@
synchronized (mLock) {
mCallbackDelegate.add(callback);
linkDeathRecipientLocked(callback, deathRecipient);
+ return mProjectionGrant != null ? mProjectionGrant.getProjectionInfo() : null;
}
}
@@ -653,14 +654,14 @@
}
@Override // Binder call
- public boolean hasProjectionPermission(int uid, String packageName) {
+ public boolean hasProjectionPermission(int processUid, String packageName) {
final long token = Binder.clearCallingIdentity();
boolean hasPermission = false;
try {
hasPermission |= checkPermission(packageName,
android.Manifest.permission.CAPTURE_VIDEO_OUTPUT)
|| mAppOps.noteOpNoThrow(
- AppOpsManager.OP_PROJECT_MEDIA, uid, packageName)
+ AppOpsManager.OP_PROJECT_MEDIA, processUid, packageName)
== AppOpsManager.MODE_ALLOWED;
} finally {
Binder.restoreCallingIdentity(token);
@@ -669,7 +670,7 @@
}
@Override // Binder call
- public IMediaProjection createProjection(int uid, String packageName, int type,
+ public IMediaProjection createProjection(int processUid, String packageName, int type,
boolean isPermanentGrant) {
if (mContext.checkCallingPermission(MANAGE_MEDIA_PROJECTION)
!= PackageManager.PERMISSION_GRANTED) {
@@ -680,13 +681,13 @@
throw new IllegalArgumentException("package name must not be empty");
}
final UserHandle callingUser = Binder.getCallingUserHandle();
- return createProjectionInternal(uid, packageName, type, isPermanentGrant,
+ return createProjectionInternal(processUid, packageName, type, isPermanentGrant,
callingUser);
}
@Override // Binder call
@EnforcePermission(MANAGE_MEDIA_PROJECTION)
- public IMediaProjection getProjection(int uid, String packageName) {
+ public IMediaProjection getProjection(int processUid, String packageName) {
getProjection_enforcePermission();
if (packageName == null || packageName.isEmpty()) {
throw new IllegalArgumentException("package name must not be empty");
@@ -695,7 +696,7 @@
MediaProjection projection;
final long callingToken = Binder.clearCallingIdentity();
try {
- projection = getProjectionInternal(uid, packageName);
+ projection = getProjectionInternal(processUid, packageName);
} finally {
Binder.restoreCallingIdentity(callingToken);
}
@@ -786,11 +787,11 @@
@Override //Binder call
@EnforcePermission(MANAGE_MEDIA_PROJECTION)
- public void addCallback(final IMediaProjectionWatcherCallback callback) {
+ public MediaProjectionInfo addCallback(final IMediaProjectionWatcherCallback callback) {
addCallback_enforcePermission();
final long token = Binder.clearCallingIdentity();
try {
- MediaProjectionManagerService.this.addCallback(callback);
+ return MediaProjectionManagerService.this.addCallback(callback);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -869,12 +870,13 @@
@Override // Binder call
@EnforcePermission(MANAGE_MEDIA_PROJECTION)
- public void notifyPermissionRequestInitiated(int hostUid, int sessionCreationSource) {
+ public void notifyPermissionRequestInitiated(
+ int hostProcessUid, int sessionCreationSource) {
notifyPermissionRequestInitiated_enforcePermission();
final long token = Binder.clearCallingIdentity();
try {
MediaProjectionManagerService.this.notifyPermissionRequestInitiated(
- hostUid, sessionCreationSource);
+ hostProcessUid, sessionCreationSource);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -882,11 +884,11 @@
@Override // Binder call
@EnforcePermission(MANAGE_MEDIA_PROJECTION)
- public void notifyPermissionRequestDisplayed(int hostUid) {
+ public void notifyPermissionRequestDisplayed(int hostProcessUid) {
notifyPermissionRequestDisplayed_enforcePermission();
final long token = Binder.clearCallingIdentity();
try {
- MediaProjectionManagerService.this.notifyPermissionRequestDisplayed(hostUid);
+ MediaProjectionManagerService.this.notifyPermissionRequestDisplayed(hostProcessUid);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -894,11 +896,11 @@
@Override // Binder call
@EnforcePermission(MANAGE_MEDIA_PROJECTION)
- public void notifyPermissionRequestCancelled(int hostUid) {
+ public void notifyPermissionRequestCancelled(int hostProcessUid) {
notifyPermissionRequestCancelled_enforcePermission();
final long token = Binder.clearCallingIdentity();
try {
- MediaProjectionManagerService.this.notifyPermissionRequestCancelled(hostUid);
+ MediaProjectionManagerService.this.notifyPermissionRequestCancelled(hostProcessUid);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -906,11 +908,11 @@
@Override // Binder call
@EnforcePermission(MANAGE_MEDIA_PROJECTION)
- public void notifyAppSelectorDisplayed(int hostUid) {
+ public void notifyAppSelectorDisplayed(int hostProcessUid) {
notifyAppSelectorDisplayed_enforcePermission();
final long token = Binder.clearCallingIdentity();
try {
- MediaProjectionManagerService.this.notifyAppSelectorDisplayed(hostUid);
+ MediaProjectionManagerService.this.notifyAppSelectorDisplayed(hostProcessUid);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -919,12 +921,12 @@
@Override // Binder call
@EnforcePermission(MANAGE_MEDIA_PROJECTION)
public void notifyWindowingModeChanged(
- int contentToRecord, int targetUid, int windowingMode) {
+ int contentToRecord, int targetProcessUid, int windowingMode) {
notifyWindowingModeChanged_enforcePermission();
final long token = Binder.clearCallingIdentity();
try {
MediaProjectionManagerService.this.notifyWindowingModeChanged(
- contentToRecord, targetUid, windowingMode);
+ contentToRecord, targetProcessUid, windowingMode);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -1243,7 +1245,7 @@
}
public MediaProjectionInfo getProjectionInfo() {
- return new MediaProjectionInfo(packageName, userHandle);
+ return new MediaProjectionInfo(packageName, userHandle, mLaunchCookie);
}
boolean requiresForegroundService() {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
index 4d19ead..d7188c7 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
@@ -42,7 +42,6 @@
import android.util.Log;
import android.util.Slog;
-import com.android.internal.annotations.Keep;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.RingBuffer;
import com.android.server.am.ProcessList;
@@ -414,7 +413,7 @@
private static final Date sDate = new Date();
public LogBuffer(int capacity) {
- super(Data.class, capacity);
+ super(Data::new, Data[]::new, capacity);
}
public void uidStateChanged(int uid, int procState, long procStateSeq,
@@ -690,12 +689,8 @@
/**
* Container class for all networkpolicy events data.
- *
- * Note: This class needs to be public for RingBuffer class to be able to create
- * new instances of this.
*/
- @Keep
- public static final class Data {
+ private static final class Data {
public int type;
public long timeStamp;
diff --git a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
index 71a6b5e..ab650af 100644
--- a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
+++ b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
@@ -16,7 +16,8 @@
package com.android.server.notification;
-import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_NIGHT;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
import android.app.UiModeManager;
import android.app.WallpaperManager;
@@ -128,10 +129,9 @@
private void updateNightModeImmediately(boolean useNightMode) {
Binder.withCleanCallingIdentity(() -> {
- // TODO: b/314285749 - Placeholder; use real APIs when available.
- mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME);
- mUiModeManager.setNightModeActivatedForCustomMode(MODE_NIGHT_CUSTOM_TYPE_BEDTIME,
- useNightMode);
+ mUiModeManager.setAttentionModeThemeOverlay(
+ useNightMode ? MODE_ATTENTION_THEME_OVERLAY_NIGHT
+ : MODE_ATTENTION_THEME_OVERLAY_OFF);
});
}
diff --git a/services/core/java/com/android/server/notification/NotificationHistoryManager.java b/services/core/java/com/android/server/notification/NotificationHistoryManager.java
index e3880c3..deb95d8 100644
--- a/services/core/java/com/android/server/notification/NotificationHistoryManager.java
+++ b/services/core/java/com/android/server/notification/NotificationHistoryManager.java
@@ -108,7 +108,7 @@
for (int i = 0; i < pendingPackageRemovals.size(); i++) {
userHistory.onPackageRemoved(pendingPackageRemovals.get(i));
}
- mUserPendingPackageRemovals.put(userId, null);
+ mUserPendingPackageRemovals.remove(userId);
}
// delete history if it was disabled when the user was locked
@@ -133,7 +133,7 @@
synchronized (mLock) {
// Actual data deletion is handled by other parts of the system (the entire directory is
// removed) - we just need clean up our internal state for GC
- mUserPendingPackageRemovals.put(userId, null);
+ mUserPendingPackageRemovals.remove(userId);
mHistoryEnabled.put(userId, false);
mUserPendingHistoryDisables.put(userId, false);
onUserStopped(userId);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 7aa7b7e..2ae040a6 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -215,7 +215,6 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.LauncherApps;
-import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManagerInternal;
@@ -373,6 +372,7 @@
import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
+import java.time.Clock;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
@@ -2466,8 +2466,8 @@
mMetricsLogger = new MetricsLogger();
mRankingHandler = rankingHandler;
mConditionProviders = conditionProviders;
- mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), mConditionProviders,
- flagResolver, new ZenModeEventLogger(mPackageManagerClient));
+ mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), Clock.systemUTC(),
+ mConditionProviders, flagResolver, new ZenModeEventLogger(mPackageManagerClient));
mZenModeHelper.addCallback(new ZenModeHelper.Callback() {
@Override
public void onConfigChanged() {
@@ -5931,8 +5931,7 @@
newVisualEffects, policy.priorityConversationSenders);
if (shouldApplyAsImplicitRule) {
- mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, callingUid, policy,
- origin);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, callingUid, policy);
} else {
ZenLog.traceSetNotificationPolicy(pkg, applicationInfo.targetSdkVersion,
policy);
@@ -12103,6 +12102,7 @@
return true;
}
+ long token = Binder.clearCallingIdentity();
try {
if (mPackageManager.checkUidPermission(RECEIVE_SENSITIVE_NOTIFICATIONS, uid)
== PERMISSION_GRANTED || mPackageManagerInternal.isPlatformSigned(pkg)) {
@@ -12129,6 +12129,8 @@
}
} catch (RemoteException e) {
Slog.e(TAG, "Failed to check trusted status of listener", e);
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
return false;
}
diff --git a/services/core/java/com/android/server/notification/ZenAdapters.java b/services/core/java/com/android/server/notification/ZenAdapters.java
index 91df04c..37b263c 100644
--- a/services/core/java/com/android/server/notification/ZenAdapters.java
+++ b/services/core/java/com/android/server/notification/ZenAdapters.java
@@ -59,9 +59,7 @@
}
if (Flags.modesApi()) {
- zenPolicyBuilder.allowChannels(
- policy.allowPriorityChannels()
- ? ZenPolicy.CHANNEL_TYPE_PRIORITY : ZenPolicy.CHANNEL_TYPE_NONE);
+ zenPolicyBuilder.allowPriorityChannels(policy.allowPriorityChannels());
}
return zenPolicyBuilder.build();
diff --git a/services/core/java/com/android/server/notification/ZenModeEventLogger.java b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
index 0145577..a90efe6 100644
--- a/services/core/java/com/android/server/notification/ZenModeEventLogger.java
+++ b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
@@ -18,6 +18,8 @@
import static android.app.NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND;
import static android.provider.Settings.Global.ZEN_MODE_OFF;
+import static android.service.notification.NotificationServiceProto.CHANNEL_POLICY_PRIORITY;
+import static android.service.notification.NotificationServiceProto.CHANNEL_POLICY_NONE;
import static android.service.notification.NotificationServiceProto.RULE_TYPE_AUTOMATIC;
import static android.service.notification.NotificationServiceProto.RULE_TYPE_MANUAL;
import static android.service.notification.NotificationServiceProto.RULE_TYPE_UNKNOWN;
@@ -551,8 +553,8 @@
if (Flags.modesApi()) {
proto.write(DNDPolicyProto.ALLOW_CHANNELS,
mNewPolicy.allowPriorityChannels()
- ? ZenPolicy.CHANNEL_TYPE_PRIORITY
- : ZenPolicy.CHANNEL_TYPE_NONE);
+ ? CHANNEL_POLICY_PRIORITY
+ : CHANNEL_POLICY_NONE);
}
} else {
Log.wtf(TAG, "attempted to write zen mode log event with null policy");
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 911643b..bc4781a 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -32,6 +32,7 @@
import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER;
import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
+import static com.android.internal.util.Preconditions.checkArgument;
import android.annotation.DrawableRes;
import android.annotation.NonNull;
@@ -117,6 +118,9 @@
import java.io.IOException;
import java.io.PrintWriter;
+import java.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -130,9 +134,12 @@
static final String TAG = "ZenModeHelper";
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final String PACKAGE_ANDROID = "android";
+
// The amount of time rules instances can exist without their owning app being installed.
private static final int RULE_INSTANCE_GRACE_PERIOD = 1000 * 60 * 60 * 72;
static final int RULE_LIMIT_PER_PACKAGE = 100;
+ private static final Duration DELETED_RULE_KEPT_FOR = Duration.ofDays(30);
private static final String IMPLICIT_RULE_ID_PREFIX = "implicit_"; // + pkg_name
@@ -148,6 +155,7 @@
private final Context mContext;
private final H mHandler;
+ private final Clock mClock;
private final SettingsObserver mSettingsObserver;
private final AppOpsManager mAppOps;
private final NotificationManager mNotificationManager;
@@ -189,11 +197,13 @@
private String[] mPriorityOnlyDndExemptPackages;
- public ZenModeHelper(Context context, Looper looper, ConditionProviders conditionProviders,
+ public ZenModeHelper(Context context, Looper looper, Clock clock,
+ ConditionProviders conditionProviders,
SystemUiSystemPropertiesFlags.FlagResolver flagResolver,
ZenModeEventLogger zenModeEventLogger) {
mContext = context;
mHandler = new H(looper);
+ mClock = clock;
addCallback(mMetrics);
mAppOps = context.getSystemService(AppOpsManager.class);
mNotificationManager = context.getSystemService(NotificationManager.class);
@@ -418,6 +428,7 @@
public String addAutomaticZenRule(String pkg, AutomaticZenRule automaticZenRule,
@ConfigChangeOrigin int origin, String reason, int callingUid) {
+ requirePublicOrigin("addAutomaticZenRule", origin);
if (!ZenModeConfig.SYSTEM_AUTHORITY.equals(pkg)) {
PackageItemInfo component = getServiceInfo(automaticZenRule.getOwner());
if (component == null) {
@@ -452,6 +463,7 @@
newConfig = mConfig.copy();
ZenRule rule = new ZenRule();
populateZenRule(pkg, automaticZenRule, rule, origin, /* isNew= */ true);
+ rule = maybeRestoreRemovedRule(newConfig, rule, automaticZenRule, origin);
newConfig.automaticRules.put(rule.id, rule);
maybeReplaceDefaultRule(newConfig, automaticZenRule);
@@ -463,6 +475,37 @@
}
}
+ private ZenRule maybeRestoreRemovedRule(ZenModeConfig config, ZenRule ruleToAdd,
+ AutomaticZenRule azrToAdd, @ConfigChangeOrigin int origin) {
+ if (!Flags.modesApi()) {
+ return ruleToAdd;
+ }
+ String deletedKey = ZenModeConfig.deletedRuleKey(ruleToAdd);
+ if (deletedKey == null) {
+ // Couldn't calculate the deletedRuleKey (condition or pkg null?). This should
+ // never happen for an app-provided rule because NMS validates both.
+ return ruleToAdd;
+ }
+ ZenRule ruleToRestore = config.deletedRules.get(deletedKey);
+ if (ruleToRestore == null) {
+ return ruleToAdd; // Cannot restore.
+ }
+
+ // We have found a previous rule to maybe restore. Whether we do that or not, we don't need
+ // to keep it around (if not restored now, it won't be in future calls either).
+ config.deletedRules.remove(deletedKey);
+ ruleToRestore.deletionInstant = null;
+
+ if (origin != UPDATE_ORIGIN_APP) {
+ return ruleToAdd; // Okay to create anew.
+ }
+
+ // "Preserve" the previous rule by considering the azrToAdd an update instead.
+ // Only app-modifiable fields will actually be modified.
+ populateZenRule(ruleToRestore.pkg, azrToAdd, ruleToRestore, origin, /* isNew= */ false);
+ return ruleToRestore;
+ }
+
private static void maybeReplaceDefaultRule(ZenModeConfig config, AutomaticZenRule addedRule) {
if (!Flags.modesApi()) {
return;
@@ -484,6 +527,7 @@
public boolean updateAutomaticZenRule(String ruleId, AutomaticZenRule automaticZenRule,
@ConfigChangeOrigin int origin, String reason, int callingUid) {
+ requirePublicOrigin("updateAutomaticZenRule", origin);
ZenModeConfig newConfig;
synchronized (mConfigLock) {
if (mConfig == null) return false;
@@ -561,7 +605,11 @@
rule = newImplicitZenRule(callingPkg);
newConfig.automaticRules.put(rule.id, rule);
}
- rule.zenMode = zenMode;
+ // If the user has changed the rule's *zenMode*, then don't let app overwrite it.
+ // We allow the update if the user has only changed other aspects of the rule.
+ if ((rule.userModifiedFields & AutomaticZenRule.FIELD_INTERRUPTION_FILTER) == 0) {
+ rule.zenMode = zenMode;
+ }
rule.snoozing = false;
rule.condition = new Condition(rule.conditionId,
mContext.getString(R.string.zen_mode_implicit_activated),
@@ -584,7 +632,7 @@
* {@link Global#ZEN_MODE_IMPORTANT_INTERRUPTIONS}.
*/
void applyGlobalPolicyAsImplicitZenRule(String callingPkg, int callingUid,
- NotificationManager.Policy policy, @ConfigChangeOrigin int origin) {
+ NotificationManager.Policy policy) {
if (!android.app.Flags.modesApi()) {
Log.wtf(TAG, "applyGlobalPolicyAsImplicitZenRule called with flag off!");
return;
@@ -600,10 +648,17 @@
rule.zenMode = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
newConfig.automaticRules.put(rule.id, rule);
}
- // TODO: b/308673679 - Keep user customization of this rule!
- rule.zenPolicy = ZenAdapters.notificationPolicyToZenPolicy(policy);
- setConfigLocked(newConfig, /* triggeringComponent= */ null, origin,
- "applyGlobalPolicyAsImplicitZenRule", callingUid);
+ // If the user has changed the rule's *ZenPolicy*, then don't let app overwrite it.
+ // We allow the update if the user has only changed other aspects of the rule.
+ if (rule.zenPolicyUserModifiedFields == 0) {
+ updatePolicy(
+ rule,
+ ZenAdapters.notificationPolicyToZenPolicy(policy),
+ /* updateBitmask= */ false);
+
+ setConfigLocked(newConfig, /* triggeringComponent= */ null, UPDATE_ORIGIN_APP,
+ "applyGlobalPolicyAsImplicitZenRule", callingUid);
+ }
}
}
@@ -644,7 +699,7 @@
ZenRule rule = new ZenRule();
rule.id = implicitRuleId(pkg);
rule.pkg = pkg;
- rule.creationTime = System.currentTimeMillis();
+ rule.creationTime = mClock.millis();
Binder.withCleanCallingIdentity(() -> {
try {
@@ -664,7 +719,7 @@
rule.condition = null;
rule.conditionId = new Uri.Builder()
.scheme(Condition.SCHEME)
- .authority("android")
+ .authority(PACKAGE_ANDROID)
.appendPath("implicit")
.appendPath(pkg)
.build();
@@ -685,6 +740,7 @@
boolean removeAutomaticZenRule(String id, @ConfigChangeOrigin int origin, String reason,
int callingUid) {
+ requirePublicOrigin("removeAutomaticZenRule", origin);
ZenModeConfig newConfig;
synchronized (mConfigLock) {
if (mConfig == null) return false;
@@ -693,7 +749,9 @@
if (ruleToRemove == null) return false;
if (canManageAutomaticZenRule(ruleToRemove)) {
newConfig.automaticRules.remove(id);
- if (ruleToRemove.getPkg() != null && !"android".equals(ruleToRemove.getPkg())) {
+ maybePreserveRemovedRule(newConfig, ruleToRemove, origin);
+ if (ruleToRemove.getPkg() != null
+ && !PACKAGE_ANDROID.equals(ruleToRemove.getPkg())) {
for (ZenRule currRule : newConfig.automaticRules.values()) {
if (currRule.getPkg() != null
&& currRule.getPkg().equals(ruleToRemove.getPkg())) {
@@ -715,6 +773,7 @@
boolean removeAutomaticZenRules(String packageName, @ConfigChangeOrigin int origin,
String reason, int callingUid) {
+ requirePublicOrigin("removeAutomaticZenRules", origin);
ZenModeConfig newConfig;
synchronized (mConfigLock) {
if (mConfig == null) return false;
@@ -723,14 +782,47 @@
ZenRule rule = newConfig.automaticRules.get(newConfig.automaticRules.keyAt(i));
if (Objects.equals(rule.getPkg(), packageName) && canManageAutomaticZenRule(rule)) {
newConfig.automaticRules.removeAt(i);
+ maybePreserveRemovedRule(newConfig, rule, origin);
+ }
+ }
+ // If the system is clearing all rules this means DND access is revoked or the package
+ // was uninstalled, so also clear the preserved-deleted rules.
+ if (origin == UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI) {
+ for (int i = newConfig.deletedRules.size() - 1; i >= 0; i--) {
+ ZenRule rule = newConfig.deletedRules.get(newConfig.deletedRules.keyAt(i));
+ if (Objects.equals(rule.getPkg(), packageName)) {
+ newConfig.deletedRules.removeAt(i);
+ }
}
}
return setConfigLocked(newConfig, origin, reason, null, true, callingUid);
}
}
+ private void maybePreserveRemovedRule(ZenModeConfig config, ZenRule ruleToRemove,
+ @ConfigChangeOrigin int origin) {
+ if (!Flags.modesApi()) {
+ return;
+ }
+ // If an app deletes a previously customized rule, keep it around to preserve
+ // the user's customization when/if it's recreated later.
+ // We don't try to preserve system-owned rules because their conditionIds (used as
+ // deletedRuleKey) are not stable. This is almost moot anyway because an app cannot
+ // delete a system-owned rule.
+ if (origin == UPDATE_ORIGIN_APP && !ruleToRemove.canBeUpdatedByApp()
+ && !PACKAGE_ANDROID.equals(ruleToRemove.pkg)) {
+ String deletedKey = ZenModeConfig.deletedRuleKey(ruleToRemove);
+ if (deletedKey != null) {
+ ruleToRemove.deletionInstant = Instant.now(mClock);
+ // Overwrites a previously-deleted rule with the same conditionId, but that's okay.
+ config.deletedRules.put(deletedKey, ruleToRemove);
+ }
+ }
+ }
+
void setAutomaticZenRuleState(String id, Condition condition, @ConfigChangeOrigin int origin,
int callingUid) {
+ requirePublicOrigin("setAutomaticZenRuleState", origin);
ZenModeConfig newConfig;
synchronized (mConfigLock) {
if (mConfig == null) return;
@@ -744,6 +836,7 @@
void setAutomaticZenRuleState(Uri ruleDefinition, Condition condition,
@ConfigChangeOrigin int origin, int callingUid) {
+ requirePublicOrigin("setAutomaticZenRuleState", origin);
ZenModeConfig newConfig;
synchronized (mConfigLock) {
if (mConfig == null) return;
@@ -913,13 +1006,13 @@
return null;
}
- void populateZenRule(String pkg, AutomaticZenRule automaticZenRule, ZenRule rule,
+ private void populateZenRule(String pkg, AutomaticZenRule automaticZenRule, ZenRule rule,
@ConfigChangeOrigin int origin, boolean isNew) {
if (Flags.modesApi()) {
// These values can always be edited by the app, so we apply changes immediately.
if (isNew) {
rule.id = ZenModeConfig.newRuleId();
- rule.creationTime = System.currentTimeMillis();
+ rule.creationTime = mClock.millis();
rule.component = automaticZenRule.getOwner();
rule.pkg = pkg;
}
@@ -978,11 +1071,9 @@
rule.zenMode = newZenMode;
// Updates the bitmask and values for all policy fields, based on the origin.
- rule.zenPolicy = updatePolicy(rule.zenPolicy, automaticZenRule.getZenPolicy(),
- updateBitmask);
+ updatePolicy(rule, automaticZenRule.getZenPolicy(), updateBitmask);
// Updates the bitmask and values for all device effect fields, based on the origin.
- rule.zenDeviceEffects = updateZenDeviceEffects(
- rule.zenDeviceEffects, automaticZenRule.getDeviceEffects(),
+ updateZenDeviceEffects(rule, automaticZenRule.getDeviceEffects(),
origin == UPDATE_ORIGIN_APP, updateBitmask);
} else {
if (rule.enabled != automaticZenRule.isEnabled()) {
@@ -994,13 +1085,6 @@
rule.enabled = automaticZenRule.isEnabled();
rule.modified = automaticZenRule.isModified();
rule.zenPolicy = automaticZenRule.getZenPolicy();
- if (Flags.modesApi()) {
- rule.zenDeviceEffects = updateZenDeviceEffects(
- rule.zenDeviceEffects,
- automaticZenRule.getDeviceEffects(),
- origin == UPDATE_ORIGIN_APP,
- origin == UPDATE_ORIGIN_USER);
- }
rule.zenMode = NotificationManager.zenModeFromInterruptionFilter(
automaticZenRule.getInterruptionFilter(), Global.ZEN_MODE_OFF);
rule.configurationActivity = automaticZenRule.getConfigurationActivity();
@@ -1024,28 +1108,28 @@
}
/**
- * Modifies {@link ZenPolicy} that is being stored as part of a new or updated ZenRule.
- * Returns a policy based on {@code oldPolicy}, but with fields updated to match
- * {@code newPolicy} where they differ, and updating the internal user-modified bitmask to
- * track these changes, if applicable based on {@code origin}.
+ * Modifies the {@link ZenPolicy} associated to a new or updated ZenRule.
+ *
+ * <p>The new policy is {@code newPolicy}, while the user-modified bitmask is updated to reflect
+ * the changes being applied (if applicable, i.e. if the update is from the user).
*/
- @Nullable
- private ZenPolicy updatePolicy(@Nullable ZenPolicy oldPolicy, @Nullable ZenPolicy newPolicy,
- boolean updateBitmask) {
- // If the update is to make the policy null, we don't need to update the bitmask,
- // because it won't be stored anywhere anyway.
+ private void updatePolicy(ZenRule zenRule, @Nullable ZenPolicy newPolicy,
+ boolean updateBitmask) {
if (newPolicy == null) {
- return null;
+ // TODO: b/319242206 - Treat as newPolicy == default policy and continue below.
+ zenRule.zenPolicy = null;
+ return;
}
// If oldPolicy is null, we compare against the default policy when determining which
// fields in the bitmask should be marked as updated.
- if (oldPolicy == null) {
- oldPolicy = mDefaultConfig.toZenPolicy();
- }
+ ZenPolicy oldPolicy =
+ zenRule.zenPolicy != null ? zenRule.zenPolicy : mDefaultConfig.toZenPolicy();
- int userModifiedFields = oldPolicy.getUserModifiedFields();
+ zenRule.zenPolicy = newPolicy;
+
if (updateBitmask) {
+ int userModifiedFields = zenRule.zenPolicyUserModifiedFields;
if (oldPolicy.getPriorityMessageSenders() != newPolicy.getPriorityMessageSenders()) {
userModifiedFields |= ZenPolicy.FIELD_MESSAGES;
}
@@ -1056,7 +1140,7 @@
!= newPolicy.getPriorityConversationSenders()) {
userModifiedFields |= ZenPolicy.FIELD_CONVERSATIONS;
}
- if (oldPolicy.getAllowedChannels() != newPolicy.getAllowedChannels()) {
+ if (oldPolicy.getPriorityChannels() != newPolicy.getPriorityChannels()) {
userModifiedFields |= ZenPolicy.FIELD_ALLOW_CHANNELS;
}
if (oldPolicy.getPriorityCategoryReminders()
@@ -1103,66 +1187,47 @@
!= newPolicy.getVisualEffectNotificationList()) {
userModifiedFields |= ZenPolicy.FIELD_VISUAL_EFFECT_NOTIFICATION_LIST;
}
+ zenRule.zenPolicyUserModifiedFields = userModifiedFields;
}
-
- // After all bitmask changes have been made, sets the bitmask.
- return new ZenPolicy.Builder(newPolicy).setUserModifiedFields(userModifiedFields).build();
}
/**
- * Modifies {@link ZenDeviceEffects} that are being stored as part of a new or updated ZenRule.
- * Returns a {@link ZenDeviceEffects} based on {@code oldEffects}, but with fields updated to
- * match {@code newEffects} where they differ, and updating the internal user-modified bitmask
- * to track these changes, if applicable based on {@code origin}.
- * <ul>
- * <li> Apps cannot turn on hidden effects (those tagged as {@code @hide}) since they are
- * intended for platform-specific rules (e.g. wearables). If it's a new rule, we blank them
- * out; if it's an update, we preserve the previous values.
- * </ul>
+ * Modifies the {@link ZenDeviceEffects} associated to a new or updated ZenRule.
+ *
+ * <p>The new value is {@code newEffects}, while the user-modified bitmask is updated to reflect
+ * the changes being applied (if applicable, i.e. if the update is from the user).
+ *
+ * <p>Apps cannot turn on hidden effects (those tagged as {@code @hide}), so those fields are
+ * treated especially: for a new rule, they are blanked out; for an updated rule, previous
+ * values are preserved.
*/
- @Nullable
- private static ZenDeviceEffects updateZenDeviceEffects(@Nullable ZenDeviceEffects oldEffects,
- @Nullable ZenDeviceEffects newEffects,
- boolean isFromApp,
- boolean updateBitmask) {
+ private static void updateZenDeviceEffects(ZenRule zenRule,
+ @Nullable ZenDeviceEffects newEffects, boolean isFromApp, boolean updateBitmask) {
if (newEffects == null) {
- return null;
+ zenRule.zenDeviceEffects = null;
+ return;
}
- // Since newEffects is not null, we want to adopt all the new provided device effects.
- ZenDeviceEffects.Builder builder = new ZenDeviceEffects.Builder(newEffects);
+ ZenDeviceEffects oldEffects = zenRule.zenDeviceEffects != null
+ ? zenRule.zenDeviceEffects
+ : new ZenDeviceEffects.Builder().build();
if (isFromApp) {
- if (oldEffects != null) {
- // We can do this because we know we don't need to update the bitmask FROM_APP.
- return builder
- .setShouldDisableAutoBrightness(oldEffects.shouldDisableAutoBrightness())
- .setShouldDisableTapToWake(oldEffects.shouldDisableTapToWake())
- .setShouldDisableTiltToWake(oldEffects.shouldDisableTiltToWake())
- .setShouldDisableTouch(oldEffects.shouldDisableTouch())
- .setShouldMinimizeRadioUsage(oldEffects.shouldMinimizeRadioUsage())
- .setShouldMaximizeDoze(oldEffects.shouldMaximizeDoze())
- .build();
- } else {
- return builder
- .setShouldDisableAutoBrightness(false)
- .setShouldDisableTapToWake(false)
- .setShouldDisableTiltToWake(false)
- .setShouldDisableTouch(false)
- .setShouldMinimizeRadioUsage(false)
- .setShouldMaximizeDoze(false)
- .build();
- }
+ // Don't allow apps to toggle hidden effects.
+ newEffects = new ZenDeviceEffects.Builder(newEffects)
+ .setShouldDisableAutoBrightness(oldEffects.shouldDisableAutoBrightness())
+ .setShouldDisableTapToWake(oldEffects.shouldDisableTapToWake())
+ .setShouldDisableTiltToWake(oldEffects.shouldDisableTiltToWake())
+ .setShouldDisableTouch(oldEffects.shouldDisableTouch())
+ .setShouldMinimizeRadioUsage(oldEffects.shouldMinimizeRadioUsage())
+ .setShouldMaximizeDoze(oldEffects.shouldMaximizeDoze())
+ .build();
}
- // If oldEffects is null, we compare against the default device effects object when
- // determining which fields in the bitmask should be marked as updated.
- if (oldEffects == null) {
- oldEffects = new ZenDeviceEffects.Builder().build();
- }
+ zenRule.zenDeviceEffects = newEffects;
- int userModifiedFields = oldEffects.getUserModifiedFields();
if (updateBitmask) {
+ int userModifiedFields = zenRule.zenDeviceEffectsUserModifiedFields;
if (oldEffects.shouldDisplayGrayscale() != newEffects.shouldDisplayGrayscale()) {
userModifiedFields |= ZenDeviceEffects.FIELD_GRAYSCALE;
}
@@ -1195,11 +1260,8 @@
if (oldEffects.shouldMaximizeDoze() != newEffects.shouldMaximizeDoze()) {
userModifiedFields |= ZenDeviceEffects.FIELD_MAXIMIZE_DOZE;
}
+ zenRule.zenDeviceEffectsUserModifiedFields = userModifiedFields;
}
-
- // Since newEffects is not null, we want to adopt all the new provided device effects.
- // Set the usermodifiedFields value separately, to reflect the updated bitmask.
- return builder.setUserModifiedFields(userModifiedFields).build();
}
private AutomaticZenRule zenRuleToAutomaticZenRule(ZenRule rule) {
@@ -1218,7 +1280,6 @@
.setOwner(rule.component)
.setConfigurationActivity(rule.configurationActivity)
.setTriggerDescription(rule.triggerDescription)
- .setUserModifiedFields(rule.userModifiedFields)
.build();
} else {
azr = new AutomaticZenRule(rule.name, rule.component,
@@ -1379,7 +1440,7 @@
boolean hasDefaultRules = config.automaticRules.containsAll(
ZenModeConfig.DEFAULT_RULE_IDS);
- long time = System.currentTimeMillis();
+ long time = Flags.modesApi() ? mClock.millis() : System.currentTimeMillis();
if (config.automaticRules != null && config.automaticRules.size() > 0) {
for (ZenRule automaticRule : config.automaticRules.values()) {
if (forRestore) {
@@ -1419,6 +1480,12 @@
Settings.Secure.putIntForUser(mContext.getContentResolver(),
Settings.Secure.ZEN_SETTINGS_UPDATED, 1, userId);
}
+
+ if (Flags.modesApi() && forRestore) {
+ // Note: forBackup doesn't write deletedRules, but just in case.
+ config.deletedRules.clear();
+ }
+
if (DEBUG) Log.d(TAG, reason);
synchronized (mConfigLock) {
setConfigLocked(config, null,
@@ -1436,7 +1503,7 @@
if (forBackup && mConfigs.keyAt(i) != userId) {
continue;
}
- mConfigs.valueAt(i).writeXml(out, version);
+ mConfigs.valueAt(i).writeXml(out, version, forBackup);
}
}
}
@@ -1468,28 +1535,51 @@
}
/**
- * Removes old rule instances whose owner is not installed.
+ * Cleans up obsolete rules:
+ * <ul>
+ * <li>Rule instances whose owner is not installed.
+ * <li>Deleted rules that were deleted more than 30 days ago.
+ * </ul>
*/
private void cleanUpZenRules() {
- long currentTime = System.currentTimeMillis();
+ Instant keptRuleThreshold = mClock.instant().minus(DELETED_RULE_KEPT_FOR);
synchronized (mConfigLock) {
final ZenModeConfig newConfig = mConfig.copy();
- if (newConfig.automaticRules != null) {
- for (int i = newConfig.automaticRules.size() - 1; i >= 0; i--) {
- ZenRule rule = newConfig.automaticRules.get(newConfig.automaticRules.keyAt(i));
- if (RULE_INSTANCE_GRACE_PERIOD < (currentTime - rule.creationTime)) {
- try {
- if (rule.getPkg() != null) {
- mPm.getPackageInfo(rule.getPkg(), PackageManager.MATCH_ANY_USER);
- }
- } catch (PackageManager.NameNotFoundException e) {
- newConfig.automaticRules.removeAt(i);
- }
+
+ deleteRulesWithoutOwner(newConfig.automaticRules);
+ if (Flags.modesApi()) {
+ deleteRulesWithoutOwner(newConfig.deletedRules);
+ for (int i = newConfig.deletedRules.size() - 1; i >= 0; i--) {
+ ZenRule deletedRule = newConfig.deletedRules.valueAt(i);
+ if (deletedRule.deletionInstant == null
+ || deletedRule.deletionInstant.isBefore(keptRuleThreshold)) {
+ newConfig.deletedRules.removeAt(i);
}
}
}
- setConfigLocked(newConfig, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "cleanUpZenRules",
- Process.SYSTEM_UID);
+
+ if (!newConfig.equals(mConfig)) {
+ setConfigLocked(newConfig, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+ "cleanUpZenRules", Process.SYSTEM_UID);
+ }
+ }
+ }
+
+ private void deleteRulesWithoutOwner(ArrayMap<String, ZenRule> ruleList) {
+ long currentTime = Flags.modesApi() ? mClock.millis() : System.currentTimeMillis();
+ if (ruleList != null) {
+ for (int i = ruleList.size() - 1; i >= 0; i--) {
+ ZenRule rule = ruleList.valueAt(i);
+ if (RULE_INSTANCE_GRACE_PERIOD < (currentTime - rule.creationTime)) {
+ try {
+ if (rule.getPkg() != null) {
+ mPm.getPackageInfo(rule.getPkg(), PackageManager.MATCH_ANY_USER);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ ruleList.removeAt(i);
+ }
+ }
+ }
}
}
@@ -1961,7 +2051,10 @@
/* optional LoggedZenMode zen_mode = 4 */ ROOT_CONFIG,
/* optional string id = 5 */ "", // empty for root config
/* optional int32 uid = 6 */ Process.SYSTEM_UID, // system owns root config
- /* optional DNDPolicyProto policy = 7 */ config.toZenPolicy().toProto()));
+ /* optional DNDPolicyProto policy = 7 */ config.toZenPolicy().toProto(),
+ /* optional int32 rule_modified_fields = 8 */ 0,
+ /* optional int32 policy_modified_fields = 9 */ 0,
+ /* optional int32 device_effects_modified_fields = 10 */ 0));
if (config.manualRule != null) {
ruleToProtoLocked(user, config.manualRule, true, events);
}
@@ -2003,7 +2096,11 @@
/* optional android.stats.dnd.ZenMode zen_mode = 4 */ rule.zenMode,
/* optional string id = 5 */ id,
/* optional int32 uid = 6 */ getPackageUid(pkg, user),
- /* optional DNDPolicyProto policy = 7 */ policyProto));
+ /* optional DNDPolicyProto policy = 7 */ policyProto,
+ /* optional int32 rule_modified_fields = 8 */ rule.userModifiedFields,
+ /* optional int32 policy_modified_fields = 9 */ rule.zenPolicyUserModifiedFields,
+ /* optional int32 device_effects_modified_fields = 10 */
+ rule.zenDeviceEffectsUserModifiedFields));
}
private int getPackageUid(String pkg, int user) {
@@ -2265,6 +2362,19 @@
return null;
}
}
+
+ /** Checks that the {@code origin} supplied to a ZenModeHelper "API" method makes sense. */
+ private static void requirePublicOrigin(String method, @ConfigChangeOrigin int origin) {
+ if (!Flags.modesApi()) {
+ return;
+ }
+ checkArgument(origin == UPDATE_ORIGIN_APP || origin == UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+ || origin == UPDATE_ORIGIN_USER,
+ "Expected one of UPDATE_ORIGIN_APP, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, or "
+ + "UPDATE_ORIGIN_USER for %s, but received '%s'.",
+ method, origin);
+ }
+
private final class Metrics extends Callback {
private static final String COUNTER_MODE_PREFIX = "dnd_mode_";
private static final String COUNTER_TYPE_PREFIX = "dnd_type_";
diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
index 9ce3cb3..f6e7ef3 100644
--- a/services/core/java/com/android/server/os/NativeTombstoneManager.java
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -41,13 +41,14 @@
import android.system.StructStat;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoParseException;
import com.android.internal.annotations.GuardedBy;
import com.android.server.BootReceiver;
import com.android.server.ServiceThread;
import com.android.server.os.TombstoneProtos.Cause;
import com.android.server.os.TombstoneProtos.Tombstone;
-import com.android.server.os.protobuf.CodedInputStream;
import libcore.io.IoUtils;
@@ -129,21 +130,18 @@
return;
}
+ String processName = "UNKNOWN";
final boolean isProtoFile = filename.endsWith(".pb");
- if (!isProtoFile) {
- return;
- }
+ File protoPath = isProtoFile ? path : new File(path.getAbsolutePath() + ".pb");
- Optional<ParsedTombstone> parsedTombstone = handleProtoTombstone(path, true);
+ Optional<TombstoneFile> parsedTombstone = handleProtoTombstone(protoPath, isProtoFile);
if (parsedTombstone.isPresent()) {
- BootReceiver.addTombstoneToDropBox(
- mContext, path, parsedTombstone.get().getTombstone(),
- parsedTombstone.get().getProcessName(), mTmpFileLock);
+ processName = parsedTombstone.get().getProcessName();
}
+ BootReceiver.addTombstoneToDropBox(mContext, path, isProtoFile, processName, mTmpFileLock);
}
- private Optional<ParsedTombstone> handleProtoTombstone(
- File path, boolean addToList) {
+ private Optional<TombstoneFile> handleProtoTombstone(File path, boolean addToList) {
final String filename = path.getName();
if (!filename.endsWith(".pb")) {
Slog.w(TAG, "unexpected tombstone name: " + path);
@@ -173,7 +171,7 @@
return Optional.empty();
}
- final Optional<ParsedTombstone> parsedTombstone = TombstoneFile.parse(pfd);
+ final Optional<TombstoneFile> parsedTombstone = TombstoneFile.parse(pfd);
if (!parsedTombstone.isPresent()) {
IoUtils.closeQuietly(pfd);
return Optional.empty();
@@ -186,7 +184,7 @@
previous.dispose();
}
- mTombstones.put(number, parsedTombstone.get().getTombstoneFile());
+ mTombstones.put(number, parsedTombstone.get());
}
}
@@ -334,27 +332,6 @@
}
}
- static class ParsedTombstone {
- TombstoneFile mTombstoneFile;
- Tombstone mTombstone;
- ParsedTombstone(TombstoneFile tombstoneFile, Tombstone tombstone) {
- mTombstoneFile = tombstoneFile;
- mTombstone = tombstone;
- }
-
- public String getProcessName() {
- return mTombstoneFile.getProcessName();
- }
-
- public TombstoneFile getTombstoneFile() {
- return mTombstoneFile;
- }
-
- public Tombstone getTombstone() {
- return mTombstone;
- }
- }
-
static class TombstoneFile {
final ParcelFileDescriptor mPfd;
@@ -437,21 +414,67 @@
}
}
- static Optional<ParsedTombstone> parse(ParcelFileDescriptor pfd) {
- Tombstone tombstoneProto;
- try (FileInputStream is = new FileInputStream(pfd.getFileDescriptor())) {
- final byte[] tombstoneBytes = is.readAllBytes();
+ static Optional<TombstoneFile> parse(ParcelFileDescriptor pfd) {
+ final FileInputStream is = new FileInputStream(pfd.getFileDescriptor());
+ final ProtoInputStream stream = new ProtoInputStream(is);
- tombstoneProto = Tombstone.parseFrom(
- CodedInputStream.newInstance(tombstoneBytes));
- } catch (IOException ex) {
+ int pid = 0;
+ int uid = 0;
+ String processName = null;
+ String crashReason = "";
+ String selinuxLabel = "";
+
+ try {
+ while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (stream.getFieldNumber()) {
+ case (int) Tombstone.PID:
+ pid = stream.readInt(Tombstone.PID);
+ break;
+
+ case (int) Tombstone.UID:
+ uid = stream.readInt(Tombstone.UID);
+ break;
+
+ case (int) Tombstone.COMMAND_LINE:
+ if (processName == null) {
+ processName = stream.readString(Tombstone.COMMAND_LINE);
+ }
+ break;
+
+ case (int) Tombstone.CAUSES:
+ if (!crashReason.equals("")) {
+ // Causes appear in decreasing order of likelihood. For now we only
+ // want the most likely crash reason here, so ignore all others.
+ break;
+ }
+ long token = stream.start(Tombstone.CAUSES);
+ cause:
+ while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (stream.getFieldNumber()) {
+ case (int) Cause.HUMAN_READABLE:
+ crashReason = stream.readString(Cause.HUMAN_READABLE);
+ break cause;
+
+ default:
+ break;
+ }
+ }
+ stream.end(token);
+ break;
+
+ case (int) Tombstone.SELINUX_LABEL:
+ selinuxLabel = stream.readString(Tombstone.SELINUX_LABEL);
+ break;
+
+ default:
+ break;
+ }
+ }
+ } catch (IOException | ProtoParseException ex) {
Slog.e(TAG, "Failed to parse tombstone", ex);
return Optional.empty();
}
- int pid = tombstoneProto.getPid();
- int uid = tombstoneProto.getUid();
-
if (!UserHandle.isApp(uid)) {
Slog.e(TAG, "Tombstone's UID (" + uid + ") not an app, ignoring");
return Optional.empty();
@@ -468,7 +491,6 @@
final int userId = UserHandle.getUserId(uid);
final int appId = UserHandle.getAppId(uid);
- String selinuxLabel = tombstoneProto.getSelinuxLabel();
if (!selinuxLabel.startsWith("u:r:untrusted_app")) {
Slog.e(TAG, "Tombstone has invalid selinux label (" + selinuxLabel + "), ignoring");
return Optional.empty();
@@ -480,30 +502,11 @@
result.mAppId = appId;
result.mPid = pid;
result.mUid = uid;
- result.mProcessName = getCmdLineProcessName(tombstoneProto);
+ result.mProcessName = processName == null ? "" : processName;
result.mTimestampMs = timestampMs;
- result.mCrashReason = getCrashReason(tombstoneProto);
+ result.mCrashReason = crashReason;
- return Optional.of(new ParsedTombstone(result, tombstoneProto));
- }
-
- private static String getCmdLineProcessName(Tombstone tombstoneProto) {
- for (String cmdline : tombstoneProto.getCommandLineList()) {
- if (cmdline != null) {
- return cmdline;
- }
- }
- return "";
- }
-
- private static String getCrashReason(Tombstone tombstoneProto) {
- for (Cause cause : tombstoneProto.getCausesList()) {
- if (cause.getHumanReadable() != null
- && !cause.getHumanReadable().equals("")) {
- return cause.getHumanReadable();
- }
- }
- return "";
+ return Optional.of(result);
}
public IParcelFileDescriptorRetriever getPfdRetriever() {
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 659c36c5..5d71439e 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -276,7 +276,8 @@
* Returns list of {@code packageName} of apks inside the given apex.
* @param apexPackageName Package name of the apk container of apex
*/
- abstract List<String> getApksInApex(String apexPackageName);
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public abstract List<String> getApksInApex(String apexPackageName);
/**
* Returns the apex module name for the given package name, if the package is an APEX. Otherwise
@@ -751,8 +752,9 @@
}
}
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
@Override
- List<String> getApksInApex(String apexPackageName) {
+ public List<String> getApksInApex(String apexPackageName) {
synchronized (mLock) {
Preconditions.checkState(mPackageNameToApexModuleName != null,
"APEX packages have not been scanned");
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
index 7f0aadc..c110fb6 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -75,11 +75,9 @@
private static final int MSG_PACKAGE_ADDED = 1;
private static final int MSG_PACKAGE_REMOVED = 2;
- private final Context mContext;
private final BinderService mBinderService;
private final PackageManager mPackageManager;
private final PackageManagerInternal mPackageManagerInternal;
- private final UsageStatsManagerInternal mUsageStatsManagerInternal;
private final PermissionManagerServiceInternal mPermissionManager;
private final Handler mHandler;
private final File mDiskFile;
@@ -99,14 +97,14 @@
@VisibleForTesting
BackgroundInstallControlService(@NonNull Injector injector) {
super(injector.getContext());
- mContext = injector.getContext();
mPackageManager = injector.getPackageManager();
mPackageManagerInternal = injector.getPackageManagerInternal();
mPermissionManager = injector.getPermissionManager();
mHandler = new EventHandler(injector.getLooper(), this);
mDiskFile = injector.getDiskFile();
- mUsageStatsManagerInternal = injector.getUsageStatsManagerInternal();
- mUsageStatsManagerInternal.registerListener(
+ UsageStatsManagerInternal usageStatsManagerInternal =
+ injector.getUsageStatsManagerInternal();
+ usageStatsManagerInternal.registerListener(
(userId, event) ->
mHandler.obtainMessage(MSG_USAGE_EVENT_RECEIVED,
userId,
diff --git a/services/core/java/com/android/server/pm/DataLoaderManagerService.java b/services/core/java/com/android/server/pm/DataLoaderManagerService.java
index 888e1c2..c25cea6 100644
--- a/services/core/java/com/android/server/pm/DataLoaderManagerService.java
+++ b/services/core/java/com/android/server/pm/DataLoaderManagerService.java
@@ -47,7 +47,6 @@
public class DataLoaderManagerService extends SystemService {
private static final String TAG = "DataLoaderManager";
private final Context mContext;
- private final HandlerThread mThread;
private final Handler mHandler;
private final DataLoaderManagerBinderService mBinderService;
private final SparseArray<DataLoaderServiceConnection> mServiceConnections =
@@ -57,10 +56,10 @@
super(context);
mContext = context;
- mThread = new HandlerThread(TAG);
- mThread.start();
+ HandlerThread thread = new HandlerThread(TAG);
+ thread.start();
- mHandler = new Handler(mThread.getLooper());
+ mHandler = new Handler(thread.getLooper());
mBinderService = new DataLoaderManagerBinderService();
}
diff --git a/services/core/java/com/android/server/pm/IPackageManagerBase.java b/services/core/java/com/android/server/pm/IPackageManagerBase.java
index e3bbd2d..f987d4a 100644
--- a/services/core/java/com/android/server/pm/IPackageManagerBase.java
+++ b/services/core/java/com/android/server/pm/IPackageManagerBase.java
@@ -107,9 +107,6 @@
@Nullable
private final ComponentName mInstantAppResolverSettingsComponent;
- @NonNull
- private final String mRequiredSupplementalProcessPackage;
-
@Nullable
private final String mServicesExtensionPackageName;
@@ -125,7 +122,6 @@
@NonNull PackageInstallerService installerService,
@NonNull PackageProperty packageProperty, @NonNull ComponentName resolveComponentName,
@Nullable ComponentName instantAppResolverSettingsComponent,
- @NonNull String requiredSupplementalProcessPackage,
@Nullable String servicesExtensionPackageName,
@Nullable String sharedSystemSharedLibraryPackageName) {
mService = service;
@@ -140,7 +136,6 @@
mPackageProperty = packageProperty;
mResolveComponentName = resolveComponentName;
mInstantAppResolverSettingsComponent = instantAppResolverSettingsComponent;
- mRequiredSupplementalProcessPackage = requiredSupplementalProcessPackage;
mServicesExtensionPackageName = servicesExtensionPackageName;
mSharedSystemSharedLibraryPackageName = sharedSystemSharedLibraryPackageName;
}
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index d5471cb0..34903d1 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -1183,8 +1183,7 @@
* Returns an auth token for the provided writable FD.
*
* @param authFd a file descriptor to proof that the caller can write to the file.
- * @param appUid uid of the calling app.
- * @param userId id of the user whose app file to enable fs-verity.
+ * @param uid uid of the calling app.
*
* @return authToken, or null if a remote call shouldn't be continued. See {@link
* #checkBeforeRemote}.
@@ -1192,13 +1191,12 @@
* @throws InstallerException if the remote call failed.
*/
public IInstalld.IFsveritySetupAuthToken createFsveritySetupAuthToken(
- ParcelFileDescriptor authFd, int appUid, @UserIdInt int userId)
- throws InstallerException {
+ ParcelFileDescriptor authFd, int uid) throws InstallerException {
if (!checkBeforeRemote()) {
return null;
}
try {
- return mInstalld.createFsveritySetupAuthToken(authFd, appUid, userId);
+ return mInstalld.createFsveritySetupAuthToken(authFd, uid);
} catch (Exception e) {
throw InstallerException.from(e);
}
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 127bf49..ac826af 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -32,6 +32,8 @@
import static android.content.pm.LauncherApps.FLAG_CACHE_NOTIFICATION_SHORTCUTS;
import static android.content.pm.LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS;
+import static com.android.server.pm.PackageArchiver.isArchivingEnabled;
+
import android.annotation.AppIdInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -56,7 +58,6 @@
import android.content.LocusId;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
-import android.content.pm.Flags;
import android.content.pm.ILauncherApps;
import android.content.pm.IOnAppsChangedListener;
import android.content.pm.IPackageInstallerCallback;
@@ -507,7 +508,8 @@
if (!canAccessProfile(userId, "cannot get shouldHideFromSuggestions")) {
return false;
}
- if (Flags.archiving() && packageName != null && isPackageArchived(packageName, user)) {
+ if (isArchivingEnabled() && packageName != null
+ && isPackageArchived(packageName, user)) {
return true;
}
if (mPackageManagerInternal.filterAppAccess(
@@ -530,7 +532,7 @@
.addCategory(Intent.CATEGORY_LAUNCHER)
.setPackage(packageName),
user);
- if (Flags.archiving()) {
+ if (isArchivingEnabled()) {
launcherActivities =
getActivitiesForArchivedApp(packageName, user, launcherActivities);
}
@@ -701,7 +703,7 @@
callingUid,
user.getIdentifier());
if (activityInfo == null) {
- if (Flags.archiving()) {
+ if (isArchivingEnabled()) {
return getMatchingArchivedAppActivityInfo(component, user);
}
return null;
@@ -984,7 +986,7 @@
long callingFlag =
PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
- if (Flags.archiving()) {
+ if (isArchivingEnabled()) {
callingFlag |= PackageManager.MATCH_ARCHIVED_PACKAGES;
}
final PackageInfo info =
@@ -1457,7 +1459,7 @@
if (!canAccessProfile(user.getIdentifier(), "Cannot check component")) {
return false;
}
- if (Flags.archiving() && component != null && component.getPackageName() != null) {
+ if (isArchivingEnabled() && component != null && component.getPackageName() != null) {
List<LauncherActivityInfoInternal> archiveActivities =
generateLauncherActivitiesForArchivedApp(component.getPackageName(), user);
if (!archiveActivities.isEmpty()) {
@@ -1610,21 +1612,117 @@
"Can't access AppMarketActivity for another user")) {
return null;
}
+ final int callingUser = getCallingUserId();
final long identity = Binder.clearCallingIdentity();
+
try {
- // TODO(b/316118005): Add code to launch the app installer for the packageName.
- Intent appMarketIntent = new Intent(Intent.ACTION_MAIN);
- appMarketIntent.addCategory(Intent.CATEGORY_APP_MARKET);
- final PendingIntent pi = PendingIntent.getActivityAsUser(
- mContext, /* requestCode */ 0, appMarketIntent, PendingIntent.FLAG_ONE_SHOT
- | PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_CANCEL_CURRENT,
- /* options */ null, user);
- return pi == null ? null : pi.getIntentSender();
+ if (packageName == null) {
+ return buildAppMarketIntentSenderForUser(user);
+ }
+
+ String installerPackageName = getInstallerPackage(packageName, callingUser);
+ if (installerPackageName == null
+ || mPackageManagerInternal.getPackageUid(
+ installerPackageName, /* flags= */ 0, user.getIdentifier())
+ < 0) {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "Can't find installer for "
+ + packageName
+ + " in user: "
+ + user.getIdentifier());
+ }
+ return buildAppMarketIntentSenderForUser(user);
+ }
+
+ Intent packageInfoIntent =
+ buildMarketPackageInfoIntent(
+ packageName, installerPackageName, callingPackage);
+ if (mPackageManagerInternal
+ .queryIntentActivities(
+ packageInfoIntent,
+ packageInfoIntent.resolveTypeIfNeeded(
+ mContext.getContentResolver()),
+ PackageManager.MATCH_ALL,
+ Process.myUid(),
+ user.getIdentifier())
+ .isEmpty()) {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "Can't resolve package info intent for package "
+ + packageName
+ + " and installer: "
+ + installerPackageName);
+ }
+ return buildAppMarketIntentSenderForUser(user);
+ }
+
+ return buildIntentSenderForUser(packageInfoIntent, user);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
+ @Nullable
+ private IntentSender buildAppMarketIntentSenderForUser(@NonNull UserHandle user) {
+ Intent appMarketIntent = new Intent(Intent.ACTION_MAIN);
+ appMarketIntent.addCategory(Intent.CATEGORY_APP_MARKET);
+ return buildIntentSenderForUser(appMarketIntent, user);
+ }
+
+ @Nullable
+ private IntentSender buildIntentSenderForUser(
+ @NonNull Intent intent, @NonNull UserHandle user) {
+ final PendingIntent pi =
+ PendingIntent.getActivityAsUser(
+ mContext,
+ /* requestCode */ 0,
+ intent,
+ PendingIntent.FLAG_ONE_SHOT
+ | PendingIntent.FLAG_IMMUTABLE
+ | PendingIntent.FLAG_CANCEL_CURRENT,
+ /* options */ null,
+ user);
+ return pi == null ? null : pi.getIntentSender();
+ }
+
+ @Nullable
+ private String getInstallerPackage(@NonNull String packageName, int callingUserId) {
+ String installerPackageName = null;
+ try {
+ installerPackageName =
+ mIPM.getInstallSourceInfo(packageName, callingUserId)
+ .getInstallingPackageName();
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Couldn't find installer for " + packageName, re);
+ }
+
+ return installerPackageName;
+ }
+
+ @NonNull
+ private Intent buildMarketPackageInfoIntent(
+ @NonNull String packageName,
+ @NonNull String installerPackageName,
+ @NonNull String callingPackage) {
+ return new Intent(Intent.ACTION_VIEW)
+ .setData(
+ new Uri.Builder()
+ .scheme("market")
+ .authority("details")
+ .appendQueryParameter("id", packageName)
+ .build())
+ .putExtra(
+ Intent.EXTRA_REFERRER,
+ new Uri.Builder()
+ .scheme("android-app")
+ .authority(callingPackage)
+ .build())
+ .setPackage(installerPackageName);
+ }
+
@Override
public void startActivityAsUser(IApplicationThread caller, String callingPackage,
String callingFeatureId, ComponentName component, Rect sourceBounds,
@@ -1692,7 +1790,7 @@
}
if (!canLaunch
&& includeArchivedApps
- && Flags.archiving()
+ && isArchivingEnabled()
&& getMatchingArchivedAppActivityInfo(component, user) != null) {
launchIntent.setPackage(null);
launchIntent.setComponent(component);
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 3e5759a..09a91ed 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -51,12 +51,13 @@
import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
import android.content.pm.ArchivedActivityParcel;
+import android.content.pm.ArchivedPackageInfo;
import android.content.pm.ArchivedPackageParcel;
+import android.content.pm.Flags;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.DeleteFlags;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.pm.VersionedPackage;
@@ -77,6 +78,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.SELinux;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ExceptionUtils;
@@ -172,12 +174,15 @@
return userState.getArchiveState() != null && !userState.isInstalled();
}
+ public static boolean isArchivingEnabled() {
+ return Flags.archiving() || SystemProperties.getBoolean("pm.archiving.enabled", false);
+ }
+
void requestArchive(
@NonNull String packageName,
@NonNull String callerPackageName,
@NonNull IntentSender intentSender,
- @NonNull UserHandle userHandle,
- @DeleteFlags int flags) {
+ @NonNull UserHandle userHandle) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(callerPackageName);
Objects.requireNonNull(intentSender);
@@ -217,7 +222,7 @@
new VersionedPackage(packageName,
PackageManager.VERSION_CODE_HIGHEST),
callerPackageName,
- DELETE_ARCHIVE | DELETE_KEEP_DATA | flags,
+ DELETE_ARCHIVE | DELETE_KEEP_DATA,
intentSender,
userId,
binderUid);
@@ -402,23 +407,30 @@
installerPackage, /* flags= */ 0, userId);
if (installerInfo == null) {
// Should never happen because we just fetched the installerInfo.
- Slog.e(TAG, "Couldnt find installer " + installerPackage);
+ Slog.e(TAG, "Couldn't find installer " + installerPackage);
return null;
}
+ final int iconSize = mContext.getSystemService(
+ ActivityManager.class).getLauncherLargeIconSize();
+
+ var info = new ArchivedPackageInfo(archivedPackage);
try {
- var packageName = archivedPackage.packageName;
- var mainActivities = archivedPackage.archivedActivities;
- List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(mainActivities.length);
- for (int i = 0, size = mainActivities.length; i < size; ++i) {
- var mainActivity = mainActivities[i];
- Path iconPath = storeIconForParcel(packageName, mainActivity, userId, i);
+ var packageName = info.getPackageName();
+ var mainActivities = info.getLauncherActivities();
+ List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(mainActivities.size());
+ for (int i = 0, size = mainActivities.size(); i < size; ++i) {
+ var mainActivity = mainActivities.get(i);
+ Path iconPath = storeDrawable(
+ packageName, mainActivity.getIcon(), userId, i, iconSize);
+ Path monochromePath = storeDrawable(
+ packageName, mainActivity.getMonochromeIcon(), userId, i, iconSize);
ArchiveActivityInfo activityInfo =
new ArchiveActivityInfo(
- mainActivity.title,
- mainActivity.originalComponentName,
+ mainActivity.getLabel().toString(),
+ mainActivity.getComponentName(),
iconPath,
- null);
+ monochromePath);
archiveActivityInfos.add(activityInfo);
}
@@ -452,21 +464,6 @@
return new ArchiveState(archiveActivityInfos, installerTitle);
}
- // TODO(b/298452477) Handle monochrome icons.
- private static Path storeIconForParcel(String packageName, ArchivedActivityParcel mainActivity,
- @UserIdInt int userId, int index) throws IOException {
- if (mainActivity.iconBitmap == null) {
- return null;
- }
- File iconsDir = createIconsDir(packageName, userId);
- File iconFile = new File(iconsDir, index + ".png");
- try (FileOutputStream out = new FileOutputStream(iconFile)) {
- out.write(mainActivity.iconBitmap);
- out.flush();
- }
- return iconFile.toPath();
- }
-
@VisibleForTesting
Path storeIcon(String packageName, LauncherActivityInfo mainActivity,
@UserIdInt int userId, int index, int iconSize) throws IOException {
@@ -475,9 +472,18 @@
// The app doesn't define an icon. No need to store anything.
return null;
}
+ return storeDrawable(packageName, mainActivity.getIcon(/* density= */ 0), userId, index,
+ iconSize);
+ }
+
+ private static Path storeDrawable(String packageName, @Nullable Drawable iconDrawable,
+ @UserIdInt int userId, int index, int iconSize) throws IOException {
+ if (iconDrawable == null) {
+ return null;
+ }
File iconsDir = createIconsDir(packageName, userId);
File iconFile = new File(iconsDir, index + ".png");
- Bitmap icon = drawableToBitmap(mainActivity.getIcon(/* density= */ 0), iconSize);
+ Bitmap icon = drawableToBitmap(iconDrawable, iconSize);
try (FileOutputStream out = new FileOutputStream(iconFile)) {
// Note: Quality is ignored for PNGs.
if (!icon.compress(Bitmap.CompressFormat.PNG, /* quality= */ 100, out)) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index fdcd28b..7bf9fe7a 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -30,6 +30,7 @@
import static android.os.Process.INVALID_UID;
import static android.os.Process.SYSTEM_UID;
+import static com.android.server.pm.PackageArchiver.isArchivingEnabled;
import static com.android.server.pm.PackageManagerService.SHELL_PACKAGE_NAME;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
@@ -826,7 +827,7 @@
}
params.installFlags &= ~PackageManager.INSTALL_UNARCHIVE;
- if (Flags.archiving() && params.appPackageName != null) {
+ if (isArchivingEnabled() && params.appPackageName != null) {
PackageStateInternal ps = mPm.snapshotComputer().getPackageStateInternal(
params.appPackageName, SYSTEM_UID);
if (ps != null
@@ -1034,7 +1035,7 @@
private int getExistingDraftSessionIdInternal(int installerUid,
SessionParams sessionParams, int userId) {
String appPackageName = sessionParams.appPackageName;
- if (!Flags.archiving() || installerUid == INVALID_UID || appPackageName == null) {
+ if (!isArchivingEnabled() || installerUid == INVALID_UID || appPackageName == null) {
return SessionInfo.INVALID_ID;
}
@@ -1407,14 +1408,11 @@
final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext,
statusReceiver, versionedPackage.getPackageName(),
canSilentlyInstallPackage, userId, mPackageArchiver, flags);
- final boolean shouldShowConfirmationDialog =
- (flags & PackageManager.DELETE_SHOW_DIALOG) != 0;
- if (!shouldShowConfirmationDialog
- && mContext.checkCallingOrSelfPermission(Manifest.permission.DELETE_PACKAGES)
- == PackageManager.PERMISSION_GRANTED) {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.DELETE_PACKAGES)
+ == PackageManager.PERMISSION_GRANTED) {
// Sweet, call straight through!
mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags);
- } else if (!shouldShowConfirmationDialog && canSilentlyInstallPackage) {
+ } else if (canSilentlyInstallPackage) {
// Allow the device owner and affiliated profile owner to silently delete packages
// Need to clear the calling identity to get DELETE_PACKAGES permission
final long ident = Binder.clearCallingIdentity();
@@ -1656,10 +1654,8 @@
@NonNull String packageName,
@NonNull String callerPackageName,
@NonNull IntentSender intentSender,
- @NonNull UserHandle userHandle,
- @DeleteFlags int flags) {
- mPackageArchiver.requestArchive(packageName, callerPackageName, intentSender,
- userHandle, flags);
+ @NonNull UserHandle userHandle) {
+ mPackageArchiver.requestArchive(packageName, callerPackageName, intentSender, userHandle);
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 5225529..c5b5a76 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4659,8 +4659,7 @@
mPreferredActivityHelper, mResolveIntentHelper, mDomainVerificationManager,
mDomainVerificationConnection, mInstallerService, mPackageProperty,
mResolveComponentName, mInstantAppResolverSettingsComponent,
- mRequiredSdkSandboxPackage, mServicesExtensionPackageName,
- mSharedSystemSharedLibraryPackageName);
+ mServicesExtensionPackageName, mSharedSystemSharedLibraryPackageName);
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 5724ee0..ca00c84 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -4645,7 +4645,7 @@
try {
mInterface.getPackageInstaller().requestArchive(packageName,
/* callerPackageName= */ "", receiver.getIntentSender(),
- new UserHandle(translatedUserId), 0);
+ new UserHandle(translatedUserId));
} catch (Exception e) {
pw.println("Failure [" + e.getMessage() + "]");
return 1;
diff --git a/services/core/java/com/android/server/pm/ProtectedPackages.java b/services/core/java/com/android/server/pm/ProtectedPackages.java
index 98533725..524252c 100644
--- a/services/core/java/com/android/server/pm/ProtectedPackages.java
+++ b/services/core/java/com/android/server/pm/ProtectedPackages.java
@@ -57,11 +57,8 @@
@GuardedBy("this")
private final SparseArray<Set<String>> mOwnerProtectedPackages = new SparseArray<>();
- private final Context mContext;
-
public ProtectedPackages(Context context) {
- mContext = context;
- mDeviceProvisioningPackage = mContext.getResources().getString(
+ mDeviceProvisioningPackage = context.getResources().getString(
R.string.config_deviceProvisioningPackage);
}
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 7bd6a43..70352be 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -67,7 +67,6 @@
private final PackageManagerService mPm;
private final IncrementalManager mIncrementalManager;
private final Installer mInstaller;
- private final UserManagerInternal mUserManagerInternal;
private final PermissionManagerServiceInternal mPermissionManager;
private final SharedLibrariesImpl mSharedLibraries;
private final AppDataHelper mAppDataHelper;
@@ -79,7 +78,6 @@
mPm = pm;
mIncrementalManager = mPm.mInjector.getIncrementalManager();
mInstaller = mPm.mInjector.getInstaller();
- mUserManagerInternal = mPm.mInjector.getUserManagerInternal();
mPermissionManager = mPm.mInjector.getPermissionManagerServiceInternal();
mSharedLibraries = mPm.mInjector.getSharedLibrariesImpl();
mAppDataHelper = appDataHelper;
@@ -394,8 +392,7 @@
// Delete from mSettings
final SparseBooleanArray changedUsers = new SparseBooleanArray();
synchronized (mPm.mLock) {
- mPm.mSettings.removePackageLPw(packageName);
- outInfo.mIsAppIdRemoved = true;
+ outInfo.mIsAppIdRemoved = mPm.mSettings.removePackageAndAppIdLPw(packageName);
if (!mPm.mSettings.isDisabledSystemPackageLPr(packageName)) {
final SharedUserSetting sus = mPm.mSettings.getSharedUserSettingLPr(deletedPs);
// If we don't have a disabled system package to reinstall, the package is
@@ -487,8 +484,6 @@
synchronized (mPm.mInstallLock) {
cleanUpResourcesLI(codeFile, instructionSets);
}
- // TODO: open logging to help debug, will delete or add debug flag
- Slog.d(TAG, "cleanUpResources for " + codeFile);
if (packageName == null) {
return;
}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index b286b12..a97652c 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -92,7 +92,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
-import com.android.internal.pm.parsing.pkg.PackageImpl;
import com.android.internal.pm.pkg.component.ParsedComponent;
import com.android.internal.pm.pkg.component.ParsedIntentInfo;
import com.android.internal.pm.pkg.component.ParsedPermission;
@@ -1460,22 +1459,28 @@
return false;
}
- int removePackageLPw(String name) {
+
+ /**
+ * Remove package from mPackages and its corresponding AppId.
+ *
+ * @return True if the AppId has been removed.
+ * False if the app doesn't exist, or if the app has a shared UID and there are other apps that
+ * still use the same shared UID even after the target app is removed.
+ */
+ boolean removePackageAndAppIdLPw(String name) {
final PackageSetting p = mPackages.remove(name);
if (p != null) {
removeInstallerPackageStatus(name);
SharedUserSetting sharedUserSetting = getSharedUserSettingLPr(p);
if (sharedUserSetting != null) {
sharedUserSetting.removePackage(p);
- if (checkAndPruneSharedUserLPw(sharedUserSetting, false)) {
- return sharedUserSetting.mAppId;
- }
+ return checkAndPruneSharedUserLPw(sharedUserSetting, false);
} else {
removeAppIdLPw(p.getAppId());
- return p.getAppId();
+ return true;
}
}
- return -1;
+ return false;
}
/**
diff --git a/services/core/java/com/android/server/pm/ShortcutNonPersistentUser.java b/services/core/java/com/android/server/pm/ShortcutNonPersistentUser.java
index 7f6f684..aa52522 100644
--- a/services/core/java/com/android/server/pm/ShortcutNonPersistentUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutNonPersistentUser.java
@@ -31,7 +31,6 @@
* The access to it must be guarded with the shortcut manager lock.
*/
public class ShortcutNonPersistentUser {
- private final ShortcutService mService;
private final int mUserId;
@@ -49,8 +48,7 @@
*/
private final ArraySet<String> mHostPackageSet = new ArraySet<>();
- public ShortcutNonPersistentUser(ShortcutService service, int userId) {
- mService = service;
+ public ShortcutNonPersistentUser(int userId) {
mUserId = userId;
}
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index e993d9e5..d644235 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -33,6 +33,7 @@
import android.app.appsearch.SearchResults;
import android.app.appsearch.SearchSpec;
import android.app.appsearch.SetSchemaRequest;
+import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
@@ -47,6 +48,7 @@
import android.os.Binder;
import android.os.PersistableBundle;
import android.os.StrictMode;
+import android.os.SystemClock;
import android.text.format.Formatter;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -192,6 +194,9 @@
private long mLastKnownForegroundElapsedTime;
@GuardedBy("mLock")
+ private long mLastReportedTime;
+
+ @GuardedBy("mLock")
private boolean mIsAppSearchSchemaUpToDate;
private ShortcutPackage(ShortcutUser shortcutUser,
@@ -1673,6 +1678,26 @@
return condition[0];
}
+ void reportShortcutUsed(@NonNull final UsageStatsManagerInternal usageStatsManagerInternal,
+ @NonNull final String shortcutId) {
+ synchronized (mLock) {
+ final long currentTS = SystemClock.elapsedRealtime();
+ final ShortcutService s = mShortcutUser.mService;
+ if (currentTS - mLastReportedTime > s.mSaveDelayMillis) {
+ mLastReportedTime = currentTS;
+ } else {
+ return;
+ }
+ final long token = s.injectClearCallingIdentity();
+ try {
+ usageStatsManagerInternal.reportShortcutUsage(getPackageName(), shortcutId,
+ getPackageUserId());
+ } finally {
+ s.injectRestoreCallingIdentity(token);
+ }
+ }
+ }
+
public void dump(@NonNull PrintWriter pw, @NonNull String prefix, DumpFilter filter) {
pw.println();
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 3adeb4b..c23d2ab 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -77,6 +77,7 @@
import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
import android.os.LocaleList;
import android.os.Looper;
@@ -113,7 +114,6 @@
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.infra.AndroidFuture;
import com.android.internal.logging.MetricsLogger;
-import com.android.internal.os.BackgroundThread;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
@@ -371,7 +371,7 @@
private CompressFormat mIconPersistFormat;
private int mIconPersistQuality;
- private int mSaveDelayMillis;
+ int mSaveDelayMillis;
private final IPackageManager mIPackageManager;
private final PackageManagerInternal mPackageManagerInternal;
@@ -485,7 +485,14 @@
}
public ShortcutService(Context context) {
- this(context, BackgroundThread.get().getLooper(), /*onyForPackgeManagerApis*/ false);
+ this(context, getBgLooper(), /*onyForPackgeManagerApis*/ false);
+ }
+
+ private static Looper getBgLooper() {
+ final HandlerThread handlerThread = new HandlerThread("shortcut",
+ android.os.Process.THREAD_PRIORITY_BACKGROUND);
+ handlerThread.start();
+ return handlerThread.getLooper();
}
@VisibleForTesting
@@ -1371,7 +1378,7 @@
ShortcutNonPersistentUser getNonPersistentUserLocked(@UserIdInt int userId) {
ShortcutNonPersistentUser ret = mShortcutNonPersistentUsers.get(userId);
if (ret == null) {
- ret = new ShortcutNonPersistentUser(this, userId);
+ ret = new ShortcutNonPersistentUser(userId);
mShortcutNonPersistentUsers.put(userId, ret);
}
return ret;
@@ -2284,7 +2291,7 @@
packageShortcutsChanged(ps, changedShortcuts, removedShortcuts);
- reportShortcutUsedInternal(packageName, shortcut.getId(), userId);
+ ps.reportShortcutUsed(mUsageStatsManagerInternal, shortcut.getId());
verifyStates();
}
@@ -2688,25 +2695,17 @@
Slog.d(TAG, String.format("reportShortcutUsed: Shortcut %s package %s used on user %d",
shortcutId, packageName, userId));
}
+ final ShortcutPackage ps;
synchronized (mLock) {
throwIfUserLockedL(userId);
- final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
+ ps = getPackageShortcutsForPublisherLocked(packageName, userId);
if (ps.findShortcutById(shortcutId) == null) {
Log.w(TAG, String.format("reportShortcutUsed: package %s doesn't have shortcut %s",
packageName, shortcutId));
return;
}
}
- reportShortcutUsedInternal(packageName, shortcutId, userId);
- }
-
- private void reportShortcutUsedInternal(String packageName, String shortcutId, int userId) {
- final long token = injectClearCallingIdentity();
- try {
- mUsageStatsManagerInternal.reportShortcutUsage(packageName, shortcutId, userId);
- } finally {
- injectRestoreCallingIdentity(token);
- }
+ ps.reportShortcutUsed(mUsageStatsManagerInternal, shortcutId);
}
@Override
@@ -5195,13 +5194,11 @@
}
// Injection point.
- @VisibleForTesting
long injectClearCallingIdentity() {
return Binder.clearCallingIdentity();
}
// Injection point.
- @VisibleForTesting
void injectRestoreCallingIdentity(long token) {
Binder.restoreCallingIdentity(token);
}
diff --git a/services/core/java/com/android/server/pm/UserDataPreparer.java b/services/core/java/com/android/server/pm/UserDataPreparer.java
index 4c42c2d..1d41401 100644
--- a/services/core/java/com/android/server/pm/UserDataPreparer.java
+++ b/services/core/java/com/android/server/pm/UserDataPreparer.java
@@ -141,7 +141,7 @@
// If internal storage of the system user fails to prepare on first boot, then
// things are *really* broken, so we might as well reboot to recovery right away.
try {
- Log.wtf(TAG, "prepareUserData failed for user " + userId, e);
+ Log.e(TAG, "prepareUserData failed for user " + userId, e);
if (isNewUser && userId == UserHandle.USER_SYSTEM && volumeUuid == null) {
RecoverySystem.rebootPromptAndWipeUserData(mContext,
"failed to prepare internal storage for system user");
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 49af4fe..c94111c 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -16,6 +16,8 @@
package com.android.server.pm;
+import static android.content.Intent.ACTION_SCREEN_OFF;
+import static android.content.Intent.ACTION_SCREEN_ON;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.os.UserManager.DEV_CREATE_OVERRIDE_PROPERTY;
@@ -41,11 +43,13 @@
import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.StringRes;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityManagerNative;
+import android.app.ActivityOptions;
import android.app.BroadcastOptions;
import android.app.IActivityManager;
import android.app.IStopUserCallback;
@@ -73,8 +77,10 @@
import android.content.pm.parsing.FrameworkParsingPackageUtils;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.multiuser.Flags;
+import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -82,6 +88,7 @@
import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.IProgressListener;
import android.os.IUserManager;
@@ -90,6 +97,7 @@
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.os.PersistableBundle;
+import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -173,6 +181,8 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
@@ -295,6 +305,12 @@
private static final long BOOT_USER_SET_TIMEOUT_MS = 300_000;
+ /**
+ * The time duration (in milliseconds) post device inactivity after which the private space
+ * should be auto-locked if the corresponding settings option is selected by the user.
+ */
+ private static final long PRIVATE_SPACE_AUTO_LOCK_INACTIVITY_TIMEOUT_MS = 5 * 60 * 1000;
+
// Tron counters
private static final String TRON_GUEST_CREATED = "users_guest_created";
private static final String TRON_USER_CREATED = "users_user_created";
@@ -320,6 +336,8 @@
private final Handler mHandler;
+ private final ThreadPoolExecutor mInternalExecutor;
+
private final File mUsersDir;
private final File mUserListFile;
@@ -521,6 +539,36 @@
private final LockPatternUtils mLockPatternUtils;
+ private KeyguardManager.KeyguardLockedStateListener mKeyguardLockedStateListener;
+
+ /** Token to identify and remove already scheduled private space auto-lock messages */
+ private static final Object PRIVATE_SPACE_AUTO_LOCK_MESSAGE_TOKEN = new Object();
+
+ /** Content observer to get callbacks for privte space autolock settings changes */
+ private final SettingsObserver mPrivateSpaceAutoLockSettingsObserver;
+
+ private final class SettingsObserver extends ContentObserver {
+ SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (isAutoLockForPrivateSpaceEnabled()) {
+ final String path = uri.getLastPathSegment();
+ if (TextUtils.equals(path, Settings.Secure.PRIVATE_SPACE_AUTO_LOCK)) {
+ int autoLockPreference =
+ Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK,
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER,
+ getMainUserIdUnchecked());
+ Slog.i(LOG_TAG, "Auto-lock settings changed to " + autoLockPreference);
+ setOrUpdateAutoLockPreferenceForPrivateProfile(autoLockPreference);
+ }
+ }
+ }
+ }
+
private final String ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK =
"com.android.server.pm.DISABLE_QUIET_MODE_AFTER_UNLOCK";
@@ -533,12 +581,168 @@
final IntentSender target = intent.getParcelableExtra(Intent.EXTRA_INTENT, android.content.IntentSender.class);
final int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_NULL);
final String callingPackage = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
- // Call setQuietModeEnabled on bg thread to avoid ANR
- BackgroundThread.getHandler().post(() ->
- setQuietModeEnabled(userId, false, target, callingPackage));
+ setQuietModeEnabledAsync(userId, false, target, callingPackage);
}
};
+ /** Checks if the device inactivity broadcast receiver is already registered*/
+ private boolean mIsDeviceInactivityBroadcastReceiverRegistered = false;
+
+ private final BroadcastReceiver mDeviceInactivityBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (isAutoLockForPrivateSpaceEnabled()) {
+ if (ACTION_SCREEN_OFF.equals(intent.getAction())) {
+ maybeScheduleMessageToAutoLockPrivateSpace();
+ } else if (ACTION_SCREEN_ON.equals(intent.getAction())) {
+ // Remove any queued messages since the device is interactive again
+ mHandler.removeCallbacksAndMessages(PRIVATE_SPACE_AUTO_LOCK_MESSAGE_TOKEN);
+ }
+ }
+ }
+ };
+
+ @VisibleForTesting
+ void maybeScheduleMessageToAutoLockPrivateSpace() {
+ // No action needed if auto-lock on inactivity not selected
+ int privateSpaceAutoLockPreference =
+ Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK,
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER,
+ getMainUserIdUnchecked());
+ if (privateSpaceAutoLockPreference
+ != Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY) {
+ return;
+ }
+ int privateProfileUserId = getPrivateProfileUserId();
+ if (privateProfileUserId != UserHandle.USER_NULL) {
+ scheduleMessageToAutoLockPrivateSpace(privateProfileUserId,
+ PRIVATE_SPACE_AUTO_LOCK_MESSAGE_TOKEN,
+ PRIVATE_SPACE_AUTO_LOCK_INACTIVITY_TIMEOUT_MS);
+ }
+ }
+
+ @VisibleForTesting
+ void scheduleMessageToAutoLockPrivateSpace(int userId, Object token,
+ long delayInMillis) {
+ mHandler.postDelayed(() -> {
+ final PowerManager powerManager = mContext.getSystemService(PowerManager.class);
+ if (powerManager != null && !powerManager.isInteractive()) {
+ Slog.i(LOG_TAG, "Auto-locking private space with user-id " + userId);
+ setQuietModeEnabledAsync(userId, true,
+ /* target */ null, mContext.getPackageName());
+ } else {
+ Slog.i(LOG_TAG, "Device is interactive, skipping auto-lock");
+ }
+ }, token, delayInMillis);
+ }
+
+ @RequiresPermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE)
+ private void initializeAndRegisterKeyguardLockedStateListener() {
+ mKeyguardLockedStateListener = this::tryAutoLockingPrivateSpaceOnKeyguardChanged;
+ // Register with keyguard to send locked state events to the listener initialized above
+ try {
+ final KeyguardManager keyguardManager =
+ mContext.getSystemService(KeyguardManager.class);
+ Slog.i(LOG_TAG, "Adding keyguard locked state listener");
+ keyguardManager.addKeyguardLockedStateListener(new HandlerExecutor(mHandler),
+ mKeyguardLockedStateListener);
+ } catch (Exception e) {
+ Slog.e(LOG_TAG, "Error adding keyguard locked listener ", e);
+ }
+ }
+
+ @VisibleForTesting
+ @RequiresPermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE)
+ void setOrUpdateAutoLockPreferenceForPrivateProfile(
+ @Settings.Secure.PrivateSpaceAutoLockOption int autoLockPreference) {
+ int privateProfileUserId = getPrivateProfileUserId();
+ if (privateProfileUserId == UserHandle.USER_NULL) {
+ Slog.e(LOG_TAG, "Auto-lock preference updated but private space user not found");
+ return;
+ }
+
+ if (autoLockPreference == Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY) {
+ // Register inactivity broadcast
+ if (!mIsDeviceInactivityBroadcastReceiverRegistered) {
+ Slog.i(LOG_TAG, "Registering device inactivity broadcast receivers");
+ mContext.registerReceiver(mDeviceInactivityBroadcastReceiver,
+ new IntentFilter(ACTION_SCREEN_OFF),
+ null, mHandler);
+
+ mContext.registerReceiver(mDeviceInactivityBroadcastReceiver,
+ new IntentFilter(ACTION_SCREEN_ON),
+ null, mHandler);
+
+ mIsDeviceInactivityBroadcastReceiverRegistered = true;
+ }
+ } else {
+ // Unregister device inactivity broadcasts
+ if (mIsDeviceInactivityBroadcastReceiverRegistered) {
+ Slog.i(LOG_TAG, "Removing device inactivity broadcast receivers");
+ mHandler.removeCallbacksAndMessages(PRIVATE_SPACE_AUTO_LOCK_MESSAGE_TOKEN);
+ mContext.unregisterReceiver(mDeviceInactivityBroadcastReceiver);
+ mIsDeviceInactivityBroadcastReceiverRegistered = false;
+ }
+ }
+
+ if (autoLockPreference == Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK) {
+ // Initialize and add keyguard state listener
+ initializeAndRegisterKeyguardLockedStateListener();
+ } else {
+ // Remove keyguard state listener
+ try {
+ final KeyguardManager keyguardManager =
+ mContext.getSystemService(KeyguardManager.class);
+ Slog.i(LOG_TAG, "Removing keyguard locked state listener");
+ keyguardManager.removeKeyguardLockedStateListener(mKeyguardLockedStateListener);
+ } catch (Exception e) {
+ Slog.e(LOG_TAG, "Error adding keyguard locked state listener ", e);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void tryAutoLockingPrivateSpaceOnKeyguardChanged(boolean isKeyguardLocked) {
+ if (isAutoLockForPrivateSpaceEnabled()) {
+ int autoLockPreference = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK,
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER,
+ getMainUserIdUnchecked());
+ boolean isAutoLockOnDeviceLockSelected =
+ autoLockPreference == Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK;
+ if (isKeyguardLocked && isAutoLockOnDeviceLockSelected) {
+ int privateProfileUserId = getPrivateProfileUserId();
+ if (privateProfileUserId != UserHandle.USER_NULL) {
+ Slog.i(LOG_TAG, "Auto-locking private space with user-id "
+ + privateProfileUserId);
+ setQuietModeEnabledAsync(privateProfileUserId,
+ /* enableQuietMode */true, /* target */ null,
+ mContext.getPackageName());
+ }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void setQuietModeEnabledAsync(@UserIdInt int userId, boolean enableQuietMode,
+ IntentSender target, @Nullable String callingPackage) {
+ if (android.multiuser.Flags.moveQuietModeOperationsToSeparateThread()) {
+ // Call setQuietModeEnabled on a separate thread. Calling this operation on the main
+ // thread can cause ANRs, posting on a BackgroundThread can result in delays
+ Slog.d(LOG_TAG, "Calling setQuietModeEnabled for user " + userId
+ + " on a separate thread");
+ mInternalExecutor.execute(() -> setQuietModeEnabled(userId, enableQuietMode, target,
+ callingPackage));
+ } else {
+ // Call setQuietModeEnabled on bg thread to avoid ANR
+ BackgroundThread.getHandler().post(
+ () -> setQuietModeEnabled(userId, enableQuietMode, target,
+ callingPackage)
+ );
+ }
+ }
+
/**
* Cache the owner name string, since it could be read repeatedly on a critical code path
* but hit by slow IO. This could be eliminated once we have the cached UserInfo in place.
@@ -587,7 +791,10 @@
public void onFinished(int id, Bundle extras) {
mHandler.post(() -> {
try {
- mContext.startIntentSender(mTarget, null, 0, 0, 0);
+ ActivityOptions activityOptions =
+ ActivityOptions.makeBasic().setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+ mContext.startIntentSender(mTarget, null, 0, 0, 0, activityOptions.toBundle());
} catch (IntentSender.SendIntentException e) {
Slog.e(LOG_TAG, "Failed to start the target in the callback", e);
}
@@ -762,6 +969,8 @@
mPackagesLock = packagesLock;
mUsers = users != null ? users : new SparseArray<>();
mHandler = new MainHandler();
+ mInternalExecutor = new ThreadPoolExecutor(/* corePoolSize */ 0, /* maximumPoolSize */ 1,
+ /* keepAliveTime */ 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
mUserVisibilityMediator = new UserVisibilityMediator(mHandler);
mUserDataPreparer = userDataPreparer;
mUserTypes = UserTypeFactory.getUserTypes();
@@ -786,9 +995,15 @@
mLockPatternUtils = new LockPatternUtils(mContext);
mUserStates.put(UserHandle.USER_SYSTEM, UserState.STATE_BOOTING);
mUser0Allocations = DBG_ALLOCATION ? new AtomicInteger() : null;
+ mPrivateSpaceAutoLockSettingsObserver = new SettingsObserver(mHandler);
emulateSystemUserModeIfNeeded();
}
+ private static boolean isAutoLockForPrivateSpaceEnabled() {
+ return android.os.Flags.allowPrivateProfile()
+ && Flags.supportAutolockForPrivateSpace();
+ }
+
void systemReady() {
mAppOpsService = IAppOpsService.Stub.asInterface(
ServiceManager.getService(Context.APP_OPS_SERVICE));
@@ -805,6 +1020,22 @@
new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED),
null, mHandler);
+ if (isAutoLockForPrivateSpaceEnabled()) {
+
+ int mainUserId = getMainUserIdUnchecked();
+ if (mainUserId != UserHandle.USER_NULL) {
+ mContext.getContentResolver().registerContentObserverAsUser(
+ Settings.Secure.getUriFor(
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK), false,
+ mPrivateSpaceAutoLockSettingsObserver, UserHandle.of(mainUserId));
+
+ setOrUpdateAutoLockPreferenceForPrivateProfile(
+ Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK,
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER, mainUserId));
+ }
+ }
+
markEphemeralUsersForRemoval();
}
@@ -969,6 +1200,18 @@
return UserHandle.USER_NULL;
}
+ private @UserIdInt int getPrivateProfileUserId() {
+ synchronized (mUsersLock) {
+ for (int userId : getUserIds()) {
+ UserInfo userInfo = getUserInfoLU(userId);
+ if (userInfo != null && userInfo.isPrivateProfile()) {
+ return userInfo.id;
+ }
+ }
+ }
+ return UserHandle.USER_NULL;
+ }
+
@Override
public void setBootUser(@UserIdInt int userId) {
checkCreateUsersPermission("Set boot user");
@@ -2337,8 +2580,18 @@
final long identity = Binder.clearCallingIdentity();
try {
final TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
- if (telecomManager != null && telecomManager.isInCall()) {
- flags |= UserManager.SWITCHABILITY_STATUS_USER_IN_CALL;
+ if (com.android.internal.telephony.flags
+ .Flags.enforceTelephonyFeatureMappingForPublicApis()) {
+ if (mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TELECOM)) {
+ if (telecomManager != null && telecomManager.isInCall()) {
+ flags |= UserManager.SWITCHABILITY_STATUS_USER_IN_CALL;
+ }
+ }
+ } else {
+ if (telecomManager != null && telecomManager.isInCall()) {
+ flags |= UserManager.SWITCHABILITY_STATUS_USER_IN_CALL;
+ }
}
} finally {
Binder.restoreCallingIdentity(identity);
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index c6435ae..f0ff85d 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -356,6 +356,11 @@
if (verifierUser == UserHandle.ALL) {
verifierUser = UserHandle.of(mPm.mUserManager.getCurrentUserId());
}
+ // TODO(b/300965895): Remove when inconsistencies loading classpaths from apex for
+ // user > 1 are fixed.
+ if (pkgLite.isSdkLibrary) {
+ verifierUser = UserHandle.SYSTEM;
+ }
final int verifierUserId = verifierUser.getIdentifier();
List<String> requiredVerifierPackages = new ArrayList<>(
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 5d710d2..40f2264 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -29,6 +29,7 @@
import static android.app.AppOpsManager.OP_BLUETOOTH_CONNECT;
import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISALLOWED;
import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISCOURAGED;
+import static android.permission.flags.Flags.serverSideAttributionRegistration;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
@@ -76,7 +77,6 @@
import com.android.internal.util.function.QuadFunction;
import com.android.internal.util.function.TriFunction;
import com.android.server.LocalServices;
-import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.UserManagerService;
import com.android.server.pm.permission.PermissionManagerServiceInternal.HotwordDetectionServiceProvider;
import com.android.server.pm.pkg.AndroidPackage;
@@ -113,9 +113,6 @@
/** Internal connection to the package manager */
private final PackageManagerInternal mPackageManagerInt;
- /** Internal connection to the user manager */
- private final UserManagerInternal mUserManagerInt;
-
/** Map of OneTimePermissionUserManagers keyed by userId */
@GuardedBy("mLock")
@NonNull
@@ -147,7 +144,6 @@
mContext = context;
mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class);
- mUserManagerInt = LocalServices.getService(UserManagerInternal.class);
mAppOpsManager = context.getSystemService(AppOpsManager.class);
mAttributionSourceRegistry = new AttributionSourceRegistry(context);
@@ -439,10 +435,27 @@
}
}
+ /**
+ * Reference propagation over binder is affected by the ownership of the object. So if
+ * the token is owned by client, references to the token on client side won't be
+ * propagated to the server and the token may still be garbage collected on server side.
+ * But if the token is owned by server, references to the token on client side will now
+ * be propagated to the server since it's a foreign object to the client, and that will
+ * keep the token referenced on the server side as long as the client is alive and
+ * holding it.
+ */
@Override
- public void registerAttributionSource(@NonNull AttributionSourceState source) {
- mAttributionSourceRegistry
- .registerAttributionSource(new AttributionSource(source));
+ public IBinder registerAttributionSource(@NonNull AttributionSourceState source) {
+ if (serverSideAttributionRegistration()) {
+ Binder token = new Binder();
+ mAttributionSourceRegistry
+ .registerAttributionSource(new AttributionSource(source).withToken(token));
+ return token;
+ } else {
+ mAttributionSourceRegistry
+ .registerAttributionSource(new AttributionSource(source));
+ return source.token;
+ }
}
@Override
@@ -1080,12 +1093,10 @@
private static final AtomicInteger sAttributionChainIds = new AtomicInteger(0);
private final @NonNull Context mContext;
- private final @NonNull AppOpsManager mAppOpsManager;
private final @NonNull PermissionManagerServiceInternal mPermissionManagerServiceInternal;
PermissionCheckerService(@NonNull Context context) {
mContext = context;
- mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
mPermissionManagerServiceInternal =
LocalServices.getService(PermissionManagerServiceInternal.class);
}
@@ -1218,7 +1229,6 @@
@Nullable String message, boolean forDataDelivery, boolean startDataDelivery,
boolean fromDatasource, int attributedOp) {
PermissionInfo permissionInfo = sPlatformPermissions.get(permission);
-
if (permissionInfo == null) {
try {
permissionInfo = context.getPackageManager().getPermissionInfo(permission, 0);
@@ -1346,8 +1356,8 @@
// If the call is from a datasource we need to vet only the chain before it. This
// way we can avoid the datasource creating an attribution context for every call.
- if (!(fromDatasource && current.equals(attributionSource))
- && next != null && !current.isTrusted(context)) {
+ boolean isDatasource = fromDatasource && current.equals(attributionSource);
+ if (!isDatasource && next != null && !current.isTrusted(context)) {
return PermissionChecker.PERMISSION_HARD_DENIED;
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 3afba39..6a57362 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -279,7 +279,6 @@
@NonNull
private final int[] mGlobalGids;
- private final HandlerThread mHandlerThread;
private final Handler mHandler;
private final Context mContext;
private final MetricsLogger mMetricsLogger = new MetricsLogger();
@@ -432,10 +431,10 @@
}
}
- mHandlerThread = new ServiceThread(TAG,
+ HandlerThread handlerThread = new ServiceThread(TAG,
Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
- mHandlerThread.start();
- mHandler = new Handler(mHandlerThread.getLooper());
+ handlerThread.start();
+ mHandler = new Handler(handlerThread.getLooper());
Watchdog.getInstance().addThread(mHandler);
SystemConfig systemConfig = SystemConfig.getInstance();
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 2128c991..a172de0 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -139,6 +139,7 @@
import com.android.server.power.batterysaver.BatterySaverPolicy;
import com.android.server.power.batterysaver.BatterySaverStateMachine;
import com.android.server.power.batterysaver.BatterySavingStats;
+import com.android.server.power.feature.PowerManagerFlags;
import dalvik.annotation.optimization.NeverCompile;
@@ -329,6 +330,8 @@
// True if battery saver is supported on this device.
private final boolean mBatterySaverSupported;
+ private final PowerManagerFlags mFeatureFlags;
+
private boolean mDisableScreenWakeLocksWhileCached;
private LightsManager mLightsManager;
@@ -1079,6 +1082,10 @@
DeviceConfigParameterProvider createDeviceConfigParameterProvider() {
return new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
}
+
+ PowerManagerFlags getFlags() {
+ return new PowerManagerFlags();
+ }
}
/** Interface for checking an app op permission */
@@ -1145,6 +1152,7 @@
mNativeWrapper = injector.createNativeWrapper();
mSystemProperties = injector.createSystemPropertiesWrapper();
mClock = injector.createClock();
+ mFeatureFlags = injector.getFlags();
mInjector = injector;
mHandlerThread = new ServiceThread(TAG,
@@ -4405,8 +4413,8 @@
private boolean setPowerModeInternal(int mode, boolean enabled) {
// Maybe filter the event.
- if (mBatterySaverStateMachine == null || (mode == Mode.LAUNCH && enabled
- && mBatterySaverStateMachine.getBatterySaverController().isLaunchBoostDisabled())) {
+ if (mode == Mode.LAUNCH && enabled && mBatterySaverStateMachine != null
+ && mBatterySaverStateMachine.getBatterySaverController().isLaunchBoostDisabled()) {
return false;
}
return mNativeWrapper.nativeSetPowerMode(mode, enabled);
@@ -4802,6 +4810,7 @@
mAmbientDisplaySuppressionController.dump(pw);
mLowPowerStandbyController.dump(pw);
+ mFeatureFlags.dump(pw);
}
private void dumpProto(FileDescriptor fd) {
diff --git a/services/core/java/com/android/server/power/feature/Android.bp b/services/core/java/com/android/server/power/feature/Android.bp
new file mode 100644
index 0000000..2295b41
--- /dev/null
+++ b/services/core/java/com/android/server/power/feature/Android.bp
@@ -0,0 +1,7 @@
+aconfig_declarations {
+ name: "power_flags",
+ package: "com.android.server.power.feature.flags",
+ srcs: [
+ "*.aconfig",
+ ],
+}
diff --git a/services/core/java/com/android/server/power/feature/PowerManagerFlags.java b/services/core/java/com/android/server/power/feature/PowerManagerFlags.java
new file mode 100644
index 0000000..a5a7069
--- /dev/null
+++ b/services/core/java/com/android/server/power/feature/PowerManagerFlags.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power.feature;
+
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.server.power.feature.flags.Flags;
+
+import java.io.PrintWriter;
+import java.util.function.Supplier;
+
+/**
+ * Utility class to read the flags used in the power manager server.
+ */
+public class PowerManagerFlags {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "PowerManagerFlags";
+
+ private final FlagState mEarlyScreenTimeoutDetectorFlagState = new FlagState(
+ Flags.FLAG_ENABLE_EARLY_SCREEN_TIMEOUT_DETECTOR,
+ Flags::enableEarlyScreenTimeoutDetector);
+
+ /** Returns whether early-screen-timeout-detector is enabled on not. */
+ public boolean isEarlyScreenTimeoutDetectorEnabled() {
+ return mEarlyScreenTimeoutDetectorFlagState.isEnabled();
+ }
+
+ /**
+ * dumps all flagstates
+ * @param pw printWriter
+ */
+ public void dump(PrintWriter pw) {
+ pw.println("PowerManagerFlags:");
+ pw.println(" " + mEarlyScreenTimeoutDetectorFlagState);
+ }
+
+ private static class FlagState {
+
+ private final String mName;
+
+ private final Supplier<Boolean> mFlagFunction;
+ private boolean mEnabledSet;
+ private boolean mEnabled;
+
+ private FlagState(String name, Supplier<Boolean> flagFunction) {
+ mName = name;
+ mFlagFunction = flagFunction;
+ }
+
+ private boolean isEnabled() {
+ if (mEnabledSet) {
+ if (DEBUG) {
+ Slog.d(TAG, mName + ": mEnabled. Recall = " + mEnabled);
+ }
+ return mEnabled;
+ }
+ mEnabled = mFlagFunction.get();
+ if (DEBUG) {
+ Slog.d(TAG, mName + ": mEnabled. Flag value = " + mEnabled);
+ }
+ mEnabledSet = true;
+ return mEnabled;
+ }
+
+ @Override
+ public String toString() {
+ // remove com.android.server.power.feature.flags. from the beginning of the name.
+ // align all isEnabled() values.
+ // Adjust lengths if we end up with longer names
+ final int nameLength = mName.length();
+ return TextUtils.substring(mName, 39, nameLength) + ": "
+ + TextUtils.formatSimple("%" + (91 - nameLength) + "s%s", " " , isEnabled())
+ + " (def:" + mFlagFunction.get() + ")";
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/feature/power_flags.aconfig b/services/core/java/com/android/server/power/feature/power_flags.aconfig
new file mode 100644
index 0000000..c8c16db
--- /dev/null
+++ b/services/core/java/com/android/server/power/feature/power_flags.aconfig
@@ -0,0 +1,11 @@
+package: "com.android.server.power.feature.flags"
+
+# Important: Flags must be accessed through PowerManagerFlags.
+
+flag {
+ name: "enable_early_screen_timeout_detector"
+ namespace: "power_manager"
+ description: "Feature flag for Early Screen Timeout detector"
+ bug: "309861917"
+ is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
index 32656b1..c3221e4 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -170,7 +170,11 @@
final double minConsumedPowerThreshold = query.getMinConsumedPowerThreshold();
final BatteryUsageStats.Builder batteryUsageStatsBuilder;
+ long monotonicStartTime, monotonicEndTime;
synchronized (stats) {
+ monotonicStartTime = stats.getMonotonicStartTime();
+ monotonicEndTime = stats.getMonotonicEndTime();
+
batteryUsageStatsBuilder = new BatteryUsageStats.Builder(
stats.getCustomEnergyConsumerNames(), includePowerModels,
includeProcessStateData, minConsumedPowerThreshold);
@@ -195,35 +199,36 @@
UidBatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE,
getProcessForegroundServiceTimeMs(uid, realtimeUs));
}
- }
- final int[] powerComponents = query.getPowerComponents();
- final List<PowerCalculator> powerCalculators = getPowerCalculators();
- for (int i = 0, count = powerCalculators.size(); i < count; i++) {
- PowerCalculator powerCalculator = powerCalculators.get(i);
- if (powerComponents != null) {
- boolean include = false;
- for (int powerComponent : powerComponents) {
- if (powerCalculator.isPowerComponentSupported(powerComponent)) {
- include = true;
- break;
+ final int[] powerComponents = query.getPowerComponents();
+ final List<PowerCalculator> powerCalculators = getPowerCalculators();
+ for (int i = 0, count = powerCalculators.size(); i < count; i++) {
+ PowerCalculator powerCalculator = powerCalculators.get(i);
+ if (powerComponents != null) {
+ boolean include = false;
+ for (int powerComponent : powerComponents) {
+ if (powerCalculator.isPowerComponentSupported(powerComponent)) {
+ include = true;
+ break;
+ }
+ }
+ if (!include) {
+ continue;
}
}
- if (!include) {
- continue;
- }
+ powerCalculator.calculate(batteryUsageStatsBuilder, stats, realtimeUs, uptimeUs,
+ query);
}
- powerCalculator.calculate(batteryUsageStatsBuilder, stats, realtimeUs, uptimeUs, query);
+
+ if ((query.getFlags()
+ & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_HISTORY) != 0) {
+ batteryUsageStatsBuilder.setBatteryHistory(stats.copyHistory());
+ }
}
if (mPowerStatsExporterEnabled) {
mPowerStatsExporter.exportAggregatedPowerStats(batteryUsageStatsBuilder,
- stats.getMonotonicStartTime(), stats.getMonotonicEndTime());
- }
-
- if ((query.getFlags()
- & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_HISTORY) != 0) {
- batteryUsageStatsBuilder.setBatteryHistory(stats.copyHistory());
+ monotonicStartTime, monotonicEndTime);
}
BatteryUsageStats batteryUsageStats = batteryUsageStatsBuilder.build();
diff --git a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
index 375ef61..a4dbce6 100644
--- a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
+++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
@@ -65,6 +65,7 @@
import com.android.internal.widget.RebootEscrowListener;
import com.android.server.LocalServices;
import com.android.server.SystemService;
+import com.android.server.Watchdog;
import com.android.server.pm.ApexManager;
import com.android.server.recoverysystem.hal.BootControlHIDL;
@@ -112,6 +113,9 @@
private static final int SOCKET_CONNECTION_MAX_RETRY = 30;
+ /** How long to pause the watchdog for when rebooting the device. */
+ private static final int REBOOT_WATCHDOG_PAUSE_DURATION_MS = 20_000;
+
static final String REQUEST_LSKF_TIMESTAMP_PREF_SUFFIX = "_request_lskf_timestamp";
static final String REQUEST_LSKF_COUNT_PREF_SUFFIX = "_request_lskf_count";
@@ -899,7 +903,8 @@
// Clear the metrics prefs after a successful RoR reboot.
mInjector.getMetricsPrefs().deletePrefsFile();
-
+ Watchdog.getInstance().pauseWatchingCurrentThreadFor(
+ REBOOT_WATCHDOG_PAUSE_DURATION_MS, "reboot can be slow");
PowerManager pm = mInjector.getPowerManager();
pm.reboot(reason);
return RESUME_ON_REBOOT_REBOOT_ERROR_UNSPECIFIED;
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 8d93408..13f1141 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -1279,8 +1279,8 @@
ipw.println();
}
- PackageWatchdog.getInstance(mContext).dump(ipw);
});
+ PackageWatchdog.getInstance(mContext).dump(ipw);
}
@AnyThread
diff --git a/services/core/java/com/android/server/security/FileIntegrityService.java b/services/core/java/com/android/server/security/FileIntegrityService.java
index a49df50..bb4876b 100644
--- a/services/core/java/com/android/server/security/FileIntegrityService.java
+++ b/services/core/java/com/android/server/security/FileIntegrityService.java
@@ -157,7 +157,7 @@
Objects.requireNonNull(authFd);
try {
var authToken = getStorageManagerInternal().createFsveritySetupAuthToken(authFd,
- Binder.getCallingUid(), Binder.getCallingUserHandle().getIdentifier());
+ Binder.getCallingUid());
// fs-verity setup requires no writable fd to the file. Release the dup now that
// it's passed.
authFd.close();
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index e3eb5b5..e5a8a6d 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -44,6 +44,9 @@
import android.graphics.drawable.Drawable;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricSourceType;
+import android.hardware.biometrics.SensorProperties;
+import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.FingerprintManager;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -155,6 +158,8 @@
private final LockPatternUtils mLockPatternUtils;
private final UserManager mUserManager;
private final ActivityManager mActivityManager;
+ private FingerprintManager mFingerprintManager;
+ private FaceManager mFaceManager;
private VirtualDeviceManagerInternal mVirtualDeviceManager;
private enum TrustState {
@@ -292,6 +297,8 @@
mPackageMonitor.register(mContext, mHandler.getLooper(), UserHandle.ALL, true);
mReceiver.register(mContext);
mLockPatternUtils.registerStrongAuthTracker(mStrongAuthTracker);
+ mFingerprintManager = mContext.getSystemService(FingerprintManager.class);
+ mFaceManager = mContext.getSystemService(FaceManager.class);
} else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
mTrustAgentsCanRun = true;
refreshAgentList(UserHandle.USER_ALL);
@@ -893,7 +900,19 @@
private void notifyKeystoreOfDeviceLockState(int userId, boolean isLocked) {
if (isLocked) {
- Authorization.onDeviceLocked(userId, getBiometricSids(userId));
+ if (android.security.Flags.fixUnlockedDeviceRequiredKeysV2()) {
+ // A profile with unified challenge is unlockable not by its own biometrics and
+ // trust agents, but rather by those of the parent user. Therefore, when protecting
+ // the profile's UnlockedDeviceRequired keys, we must use the parent's list of
+ // biometric SIDs and weak unlock methods, not the profile's.
+ int authUserId = mLockPatternUtils.isProfileWithUnifiedChallenge(userId)
+ ? resolveProfileParent(userId) : userId;
+
+ Authorization.onDeviceLocked(userId, getBiometricSids(authUserId),
+ isWeakUnlockMethodEnabled(authUserId));
+ } else {
+ Authorization.onDeviceLocked(userId, getBiometricSids(userId), false);
+ }
} else {
// Notify Keystore that the device is now unlocked for the user. Note that for unlocks
// with LSKF, this is redundant with the call from LockSettingsService which provides
@@ -1440,16 +1459,59 @@
if (biometricManager == null) {
return new long[0];
}
- if (android.security.Flags.fixUnlockedDeviceRequiredKeysV2()
- && mLockPatternUtils.isProfileWithUnifiedChallenge(userId)) {
- // Profiles with unified challenge have their own set of biometrics, but the device
- // unlock happens via the parent user. In this case Keystore needs to be given the list
- // of biometric SIDs from the parent user, not the profile.
- userId = resolveProfileParent(userId);
- }
return biometricManager.getAuthenticatorIds(userId);
}
+ // Returns whether the device can become unlocked for the specified user via one of that user's
+ // non-strong biometrics or trust agents. This assumes that the device is currently locked, or
+ // is becoming locked, for the user.
+ private boolean isWeakUnlockMethodEnabled(int userId) {
+
+ // Check whether the system currently allows the use of non-strong biometrics for the user,
+ // *and* the user actually has a non-strong biometric enrolled.
+ //
+ // The biometrics framework ostensibly supports multiple sensors per modality. However,
+ // that feature is unused and untested. So, we simply consider one sensor per modality.
+ //
+ // Also, currently we just consider fingerprint and face, matching Keyguard. If Keyguard
+ // starts supporting other biometric modalities, this will need to be updated.
+ if (mStrongAuthTracker.isBiometricAllowedForUser(/* isStrongBiometric= */ false, userId)) {
+ DevicePolicyManager dpm = mLockPatternUtils.getDevicePolicyManager();
+ int disabledFeatures = dpm.getKeyguardDisabledFeatures(null, userId);
+
+ if (mFingerprintManager != null
+ && (disabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT) == 0
+ && mFingerprintManager.hasEnrolledTemplates(userId)
+ && isWeakOrConvenienceSensor(
+ mFingerprintManager.getSensorProperties().get(0))) {
+ Slog.i(TAG, "User is unlockable by non-strong fingerprint auth");
+ return true;
+ }
+
+ if (mFaceManager != null
+ && (disabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_FACE) == 0
+ && mFaceManager.hasEnrolledTemplates(userId)
+ && isWeakOrConvenienceSensor(mFaceManager.getSensorProperties().get(0))) {
+ Slog.i(TAG, "User is unlockable by non-strong face auth");
+ return true;
+ }
+ }
+
+ // Check whether it's possible for the device to be actively unlocked by a trust agent.
+ if (getUserTrustStateInner(userId) == TrustState.TRUSTABLE
+ || (isAutomotive() && isTrustUsuallyManagedInternal(userId))) {
+ Slog.i(TAG, "User is unlockable by trust agent");
+ return true;
+ }
+
+ return false;
+ }
+
+ private static boolean isWeakOrConvenienceSensor(SensorProperties sensor) {
+ return sensor.getSensorStrength() == SensorProperties.STRENGTH_WEAK
+ || sensor.getSensorStrength() == SensorProperties.STRENGTH_CONVENIENCE;
+ }
+
// User lifecycle
@Override
diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
index 58acbe0..b384725 100755
--- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java
+++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
@@ -60,6 +60,7 @@
import android.util.SparseBooleanArray;
import android.view.Surface;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
@@ -88,15 +89,25 @@
private final Context mContext;
private final Listener mListener;
private final TvInputHal mHal = new TvInputHal(this);
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
private final SparseArray<Connection> mConnections = new SparseArray<>();
+ @GuardedBy("mLock")
private final List<TvInputHardwareInfo> mHardwareList = new ArrayList<>();
+ @GuardedBy("mLock")
private final List<HdmiDeviceInfo> mHdmiDeviceList = new ArrayList<>();
/* A map from a device ID to the matching TV input ID. */
+ @GuardedBy("mLock")
private final SparseArray<String> mHardwareInputIdMap = new SparseArray<>();
/* A map from a HDMI logical address to the matching TV input ID. */
+ @GuardedBy("mLock")
private final SparseArray<String> mHdmiInputIdMap = new SparseArray<>();
+ @GuardedBy("mLock")
private final Map<String, TvInputInfo> mInputMap = new ArrayMap<>();
/* A map from a HDMI input parent ID to the related input IDs. */
+ @GuardedBy("mLock")
private final Map<String, List<String>> mHdmiParentInputMap = new ArrayMap<>();
private final AudioManager mAudioManager;
@@ -114,16 +125,16 @@
private int mCurrentIndex = 0;
private int mCurrentMaxIndex = 0;
+ @GuardedBy("mLock")
private final SparseBooleanArray mHdmiStateMap = new SparseBooleanArray();
+ @GuardedBy("mLock")
private final List<Message> mPendingHdmiDeviceEvents = new ArrayList<>();
-
+ @GuardedBy("mLock")
private final List<Message> mPendingTvinputInfoEvents = new ArrayList<>();
// Calls to mListener should happen here.
private final Handler mHandler = new ListenerHandler();
- private final Object mLock = new Object();
-
public TvInputHardwareManager(Context context, Listener listener) {
mContext = context;
mListener = listener;
@@ -141,7 +152,9 @@
hdmiControlService.addDeviceEventListener(mHdmiDeviceEventListener);
hdmiControlService.addSystemAudioModeChangeListener(
mHdmiSystemAudioModeChangeListener);
- mHdmiDeviceList.addAll(hdmiControlService.getInputDevices());
+ synchronized (mLock) {
+ mHdmiDeviceList.addAll(hdmiControlService.getInputDevices());
+ }
} catch (RemoteException e) {
Slog.w(TAG, "Error registering listeners to HdmiControlService:", e);
}
@@ -172,6 +185,7 @@
}
}
+ @GuardedBy("mLock")
private void buildHardwareListLocked() {
mHardwareList.clear();
for (int i = 0; i < mConnections.size(); ++i) {
@@ -301,6 +315,7 @@
}
}
+ @GuardedBy("mLock")
private boolean checkUidChangedLocked(
Connection connection, int callingUid, int resolvedUserId) {
Integer connectionCallingUid = connection.getCallingUidLocked();
@@ -496,6 +511,7 @@
}
}
+ @GuardedBy("mLock")
private TvInputHardwareInfo findHardwareInfoForHdmiPortLocked(int port) {
for (TvInputHardwareInfo hardwareInfo : mHardwareList) {
if (hardwareInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI
@@ -506,6 +522,7 @@
return null;
}
+ @GuardedBy("mLock")
private int findDeviceIdForInputIdLocked(String inputId) {
for (int i = 0; i < mConnections.size(); ++i) {
int key = mConnections.keyAt(i);
@@ -597,6 +614,7 @@
return false;
}
+ @GuardedBy("mLock")
private void processPendingHdmiDeviceEventsLocked() {
for (Iterator<Message> it = mPendingHdmiDeviceEvents.iterator(); it.hasNext(); ) {
Message msg = it.next();
@@ -611,6 +629,7 @@
}
+ @GuardedBy("mLock")
private void processPendingTvInputInfoEventsLocked() {
for (Iterator<Message> it = mPendingTvinputInfoEvents.iterator(); it.hasNext(); ) {
Message msg = it.next();
@@ -748,6 +767,7 @@
// *Locked methods assume TvInputHardwareManager.mLock is held.
+ @GuardedBy("mLock")
public void resetLocked(TvInputHardwareImpl hardware, ITvInputHardwareCallback callback,
TvInputInfo info, Integer callingUid, Integer resolvedUserId,
ResourceClientProfile profile) {
@@ -776,50 +796,62 @@
}
}
+ @GuardedBy("mLock")
public void updateConfigsLocked(TvStreamConfig[] configs) {
mConfigs = configs;
}
+ @GuardedBy("mLock")
public TvInputHardwareInfo getHardwareInfoLocked() {
return mHardwareInfo;
}
+ @GuardedBy("mLock")
public TvInputInfo getInfoLocked() {
return mInfo;
}
+ @GuardedBy("mLock")
public ITvInputHardware getHardwareLocked() {
return mHardware;
}
+ @GuardedBy("mLock")
public TvInputHardwareImpl getHardwareImplLocked() {
return mHardware;
}
+ @GuardedBy("mLock")
public ITvInputHardwareCallback getCallbackLocked() {
return mCallback;
}
+ @GuardedBy("mLock")
public TvStreamConfig[] getConfigsLocked() {
return mConfigs;
}
+ @GuardedBy("mLock")
public Integer getCallingUidLocked() {
return mCallingUid;
}
+ @GuardedBy("mLock")
public Integer getResolvedUserIdLocked() {
return mResolvedUserId;
}
+ @GuardedBy("mLock")
public void setOnFirstFrameCapturedLocked(Runnable runnable) {
mOnFirstFrameCaptured = runnable;
}
+ @GuardedBy("mLock")
public Runnable getOnFirstFrameCapturedLocked() {
return mOnFirstFrameCaptured;
}
+ @GuardedBy("mLock")
public ResourceClientProfile getResourceClientProfileLocked() {
return mResourceClientProfile;
}
@@ -844,6 +876,7 @@
+ " }";
}
+ @GuardedBy("mLock")
public boolean updateCableConnectionStatusLocked(int cableConnectionStatus) {
// Update connection status only if it's not default value
if (cableConnectionStatus != TvInputHardwareInfo.CABLE_CONNECTION_STATUS_UNKNOWN
@@ -855,10 +888,12 @@
return mIsCableConnectionStatusUpdated;
}
+ @GuardedBy("mLock")
private int getConfigsLengthLocked() {
return mConfigs == null ? 0 : mConfigs.length;
}
+ @GuardedBy("mLock")
private int getInputStateLocked() {
int configsLength = getConfigsLengthLocked();
if (configsLength > 0) {
@@ -880,7 +915,6 @@
private class TvInputHardwareImpl extends ITvInputHardware.Stub {
private final TvInputHardwareInfo mInfo;
- private boolean mReleased = false;
private final Object mImplLock = new Object();
private final AudioManager.OnAudioPortUpdateListener mAudioListener =
@@ -909,28 +943,44 @@
}
}
};
+ @GuardedBy("mImplLock")
+ private boolean mReleased = false;
+ @GuardedBy("mImplLock")
private int mOverrideAudioType = AudioManager.DEVICE_NONE;
+ @GuardedBy("mImplLock")
private String mOverrideAudioAddress = "";
+ @GuardedBy("mImplLock")
private AudioDevicePort mAudioSource;
+ @GuardedBy("mImplLock")
private List<AudioDevicePort> mAudioSink = new ArrayList<>();
+ @GuardedBy("mImplLock")
private AudioPatch mAudioPatch = null;
// Set to an invalid value for a volume, so that current volume can be applied at the
// first call to updateAudioConfigLocked().
+ @GuardedBy("mImplLock")
private float mCommittedVolume = -1f;
+ @GuardedBy("mImplLock")
private float mSourceVolume = 0.0f;
+ @GuardedBy("mImplLock")
private TvStreamConfig mActiveConfig = null;
+ @GuardedBy("mImplLock")
private int mDesiredSamplingRate = 0;
+ @GuardedBy("mImplLock")
private int mDesiredChannelMask = AudioFormat.CHANNEL_OUT_DEFAULT;
+ @GuardedBy("mImplLock")
private int mDesiredFormat = AudioFormat.ENCODING_DEFAULT;
public TvInputHardwareImpl(TvInputHardwareInfo info) {
mInfo = info;
mAudioManager.registerAudioPortUpdateListener(mAudioListener);
if (mInfo.getAudioType() != AudioManager.DEVICE_NONE) {
- mAudioSource = findAudioDevicePort(mInfo.getAudioType(), mInfo.getAudioAddress());
- findAudioSinkFromAudioPolicy(mAudioSink);
+ synchronized (mImplLock) {
+ mAudioSource =
+ findAudioDevicePort(mInfo.getAudioType(), mInfo.getAudioAddress());
+ findAudioSinkFromAudioPolicy(mAudioSink);
+ }
}
}
@@ -1025,6 +1075,7 @@
/**
* Update audio configuration (source, sink, patch) all up to current state.
*/
+ @GuardedBy("mImplLock")
private void updateAudioConfigLocked() {
boolean sinkUpdated = updateAudioSinkLocked();
boolean sourceUpdated = updateAudioSourceLocked();
@@ -1204,6 +1255,7 @@
}
}
+ @GuardedBy("mImplLock")
private boolean updateAudioSourceLocked() {
if (mInfo.getAudioType() == AudioManager.DEVICE_NONE) {
return false;
@@ -1214,6 +1266,7 @@
: !mAudioSource.equals(previousSource);
}
+ @GuardedBy("mImplLock")
private boolean updateAudioSinkLocked() {
if (mInfo.getAudioType() == AudioManager.DEVICE_NONE) {
return false;
@@ -1339,16 +1392,22 @@
String inputId = mHardwareInputIdMap.get(deviceId);
if (inputId != null) {
- if (connection.updateCableConnectionStatusLocked(cableConnectionStatus)) {
- if (previousCableConnectionStatus != connection.getInputStateLocked()) {
- mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
- connection.getInputStateLocked(), 0, inputId).sendToTarget();
- }
- } else {
- if ((previousConfigsLength == 0)
- != (connection.getConfigsLengthLocked() == 0)) {
- mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
- connection.getInputStateLocked(), 0, inputId).sendToTarget();
+ synchronized (mLock) {
+ if (connection.updateCableConnectionStatusLocked(
+ cableConnectionStatus)) {
+ if (previousCableConnectionStatus
+ != connection.getInputStateLocked()) {
+ mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
+ connection.getInputStateLocked(), 0, inputId)
+ .sendToTarget();
+ }
+ } else {
+ if ((previousConfigsLength == 0)
+ != (connection.getConfigsLengthLocked() == 0)) {
+ mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
+ connection.getInputStateLocked(), 0, inputId)
+ .sendToTarget();
+ }
}
}
}
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index d903ad4..73fc8e9 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -2280,6 +2280,26 @@
}
@Override
+ public void setVideoFrozen(IBinder sessionToken, boolean isFrozen, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "setVideoFrozen");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .setVideoFrozen(isFrozen);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slog.e(TAG, "error in setVideoFrozen", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void setTvMessageEnabled(IBinder sessionToken, int type, boolean enabled,
int userId) {
final int callingUid = Binder.getCallingUid();
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 0467d0c..7ab075e 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -38,7 +38,16 @@
import android.media.tv.BroadcastInfoResponse;
import android.media.tv.TvRecordingInfo;
import android.media.tv.TvTrackInfo;
+import android.media.tv.ad.ITvAdClient;
import android.media.tv.ad.ITvAdManager;
+import android.media.tv.ad.ITvAdManagerCallback;
+import android.media.tv.ad.ITvAdService;
+import android.media.tv.ad.ITvAdServiceCallback;
+import android.media.tv.ad.ITvAdSession;
+import android.media.tv.ad.ITvAdSessionCallback;
+import android.media.tv.ad.TvAdService;
+import android.media.tv.ad.TvAdServiceInfo;
+import android.media.tv.flags.Flags;
import android.media.tv.interactive.AppLinkInfo;
import android.media.tv.interactive.ITvInteractiveAppClient;
import android.media.tv.interactive.ITvInteractiveAppManager;
@@ -110,6 +119,8 @@
@GuardedBy("mLock")
private boolean mGetServiceListCalled = false;
@GuardedBy("mLock")
+ private boolean mGetAdServiceListCalled = false;
+ @GuardedBy("mLock")
private boolean mGetAppLinkInfoListCalled = false;
private final UserManager mUserManager;
@@ -256,6 +267,141 @@
}
@GuardedBy("mLock")
+ private void buildTvAdServiceListLocked(int userId, String[] updatedPackages) {
+ if (!Flags.enableAdServiceFw()) {
+ return;
+ }
+ UserState userState = getOrCreateUserStateLocked(userId);
+ userState.mPackageSet.clear();
+
+ if (DEBUG) {
+ Slogf.d(TAG, "buildTvAdServiceListLocked");
+ }
+ PackageManager pm = mContext.getPackageManager();
+ List<ResolveInfo> services = pm.queryIntentServicesAsUser(
+ new Intent(TvAdService.SERVICE_INTERFACE),
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
+ userId);
+ List<TvAdServiceInfo> serviceList = new ArrayList<>();
+
+ for (ResolveInfo ri : services) {
+ ServiceInfo si = ri.serviceInfo;
+ if (!android.Manifest.permission.BIND_TV_AD_SERVICE.equals(si.permission)) {
+ Slog.w(TAG, "Skipping TV AD service " + si.name
+ + ": it does not require the permission "
+ + android.Manifest.permission.BIND_TV_AD_SERVICE);
+ continue;
+ }
+
+ ComponentName component = new ComponentName(si.packageName, si.name);
+ try {
+ TvAdServiceInfo info = new TvAdServiceInfo(mContext, component);
+ serviceList.add(info);
+ } catch (Exception e) {
+ Slogf.e(TAG, "failed to load TV AD service " + si.name, e);
+ continue;
+ }
+ userState.mPackageSet.add(si.packageName);
+ }
+
+ // sort the service list by service id
+ Collections.sort(serviceList, Comparator.comparing(TvAdServiceInfo::getId));
+ Map<String, TvAdServiceState> adServiceMap = new HashMap<>();
+ for (TvAdServiceInfo info : serviceList) {
+ String serviceId = info.getId();
+ if (DEBUG) {
+ Slogf.d(TAG, "add " + serviceId);
+ }
+ TvAdServiceState adServiceState = userState.mAdServiceMap.get(serviceId);
+ if (adServiceState == null) {
+ adServiceState = new TvAdServiceState();
+ }
+ adServiceState.mInfo = info;
+ adServiceState.mUid = getAdServiceUid(info);
+ adServiceState.mComponentName = info.getComponent();
+ adServiceMap.put(serviceId, adServiceState);
+ }
+
+ for (String serviceId : adServiceMap.keySet()) {
+ if (!userState.mAdServiceMap.containsKey(serviceId)) {
+ notifyAdServiceAddedLocked(userState, serviceId);
+ } else if (updatedPackages != null) {
+ // Notify the package updates
+ ComponentName component = adServiceMap.get(serviceId).mInfo.getComponent();
+ for (String updatedPackage : updatedPackages) {
+ if (component.getPackageName().equals(updatedPackage)) {
+ updateAdServiceConnectionLocked(component, userId);
+ notifyAdServiceUpdatedLocked(userState, serviceId);
+ break;
+ }
+ }
+ }
+ }
+
+ for (String serviceId : userState.mAdServiceMap.keySet()) {
+ if (!adServiceMap.containsKey(serviceId)) {
+ TvAdServiceInfo info = userState.mAdServiceMap.get(serviceId).mInfo;
+ AdServiceState serviceState = userState.mAdServiceStateMap.get(info.getComponent());
+ if (serviceState != null) {
+ abortPendingCreateAdSessionRequestsLocked(serviceState, serviceId, userId);
+ }
+ notifyAdServiceRemovedLocked(userState, serviceId);
+ }
+ }
+
+ userState.mIAppMap.clear();
+ userState.mAdServiceMap = adServiceMap;
+ }
+
+ @GuardedBy("mLock")
+ private void notifyAdServiceAddedLocked(UserState userState, String serviceId) {
+ if (DEBUG) {
+ Slog.d(TAG, "notifyAdServiceAddedLocked(serviceId=" + serviceId + ")");
+ }
+ int n = userState.mAdCallbacks.beginBroadcast();
+ for (int i = 0; i < n; ++i) {
+ try {
+ userState.mAdCallbacks.getBroadcastItem(i).onAdServiceAdded(serviceId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "failed to report added AD service to callback", e);
+ }
+ }
+ userState.mAdCallbacks.finishBroadcast();
+ }
+
+ @GuardedBy("mLock")
+ private void notifyAdServiceRemovedLocked(UserState userState, String serviceId) {
+ if (DEBUG) {
+ Slog.d(TAG, "notifyAdServiceRemovedLocked(serviceId=" + serviceId + ")");
+ }
+ int n = userState.mAdCallbacks.beginBroadcast();
+ for (int i = 0; i < n; ++i) {
+ try {
+ userState.mAdCallbacks.getBroadcastItem(i).onAdServiceRemoved(serviceId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "failed to report removed AD service to callback", e);
+ }
+ }
+ userState.mAdCallbacks.finishBroadcast();
+ }
+
+ @GuardedBy("mLock")
+ private void notifyAdServiceUpdatedLocked(UserState userState, String serviceId) {
+ if (DEBUG) {
+ Slog.d(TAG, "notifyAdServiceUpdatedLocked(serviceId=" + serviceId + ")");
+ }
+ int n = userState.mAdCallbacks.beginBroadcast();
+ for (int i = 0; i < n; ++i) {
+ try {
+ userState.mAdCallbacks.getBroadcastItem(i).onAdServiceUpdated(serviceId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "failed to report updated AD service to callback", e);
+ }
+ }
+ userState.mAdCallbacks.finishBroadcast();
+ }
+
+ @GuardedBy("mLock")
private void notifyInteractiveAppServiceAddedLocked(UserState userState, String iAppServiceId) {
if (DEBUG) {
Slog.d(TAG, "notifyInteractiveAppServiceAddedLocked(iAppServiceId="
@@ -340,6 +486,16 @@
}
}
+ private int getAdServiceUid(TvAdServiceInfo info) {
+ try {
+ return getContext().getPackageManager().getApplicationInfo(
+ info.getServiceInfo().packageName, 0).uid;
+ } catch (PackageManager.NameNotFoundException e) {
+ Slogf.w(TAG, "Unable to get UID for " + info, e);
+ return Process.INVALID_UID;
+ }
+ }
+
@Override
public void onStart() {
if (DEBUG) {
@@ -357,6 +513,7 @@
synchronized (mLock) {
buildTvInteractiveAppServiceListLocked(mCurrentUserId, null);
buildAppLinkInfoLocked(mCurrentUserId);
+ buildTvAdServiceListLocked(mCurrentUserId, null);
}
}
}
@@ -372,6 +529,14 @@
}
}
}
+ private void buildTvAdServiceList(String[] packages) {
+ int userId = getChangingUserId();
+ synchronized (mLock) {
+ if (mCurrentUserId == userId || mRunningProfiles.contains(userId)) {
+ buildTvAdServiceListLocked(userId, packages);
+ }
+ }
+ }
@Override
public void onPackageUpdateFinished(String packageName, int uid) {
@@ -379,6 +544,7 @@
// This callback is invoked when the TV interactive App service is reinstalled.
// In this case, isReplacing() always returns true.
buildTvInteractiveAppServiceList(new String[] { packageName });
+ buildTvAdServiceList(new String[] { packageName });
}
@Override
@@ -390,6 +556,7 @@
// available.
if (isReplacing()) {
buildTvInteractiveAppServiceList(packages);
+ buildTvAdServiceList(packages);
}
}
@@ -403,6 +570,7 @@
}
if (isReplacing()) {
buildTvInteractiveAppServiceList(packages);
+ buildTvAdServiceList(packages);
}
}
@@ -418,6 +586,7 @@
return;
}
buildTvInteractiveAppServiceList(null);
+ buildTvAdServiceList(null);
}
@Override
@@ -476,6 +645,7 @@
mCurrentUserId = userId;
buildTvInteractiveAppServiceListLocked(userId, null);
buildAppLinkInfoLocked(userId);
+ buildTvAdServiceListLocked(userId, null);
}
}
@@ -562,6 +732,7 @@
mRunningProfiles.add(userId);
buildTvInteractiveAppServiceListLocked(userId, null);
buildAppLinkInfoLocked(userId);
+ buildTvAdServiceListLocked(userId, null);
}
@GuardedBy("mLock")
@@ -619,7 +790,19 @@
Slog.e(TAG, "error in onSessionReleased", e);
}
}
- removeSessionStateLocked(state.mSessionToken, state.mUserId);
+ removeAdSessionStateLocked(state.mSessionToken, state.mUserId);
+ }
+
+ @GuardedBy("mLock")
+ private void clearAdSessionAndNotifyClientLocked(AdSessionState state) {
+ if (state.mClient != null) {
+ try {
+ state.mClient.onSessionReleased(state.mSeq);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in onSessionReleased", e);
+ }
+ }
+ removeAdSessionStateLocked(state.mSessionToken, state.mUserId);
}
private int resolveCallingUserId(int callingPid, int callingUid, int requestedUserId,
@@ -655,6 +838,44 @@
}
@GuardedBy("mLock")
+ private AdSessionState getAdSessionStateLocked(
+ IBinder sessionToken, int callingUid, int userId) {
+ UserState userState = getOrCreateUserStateLocked(userId);
+ return getAdSessionStateLocked(sessionToken, callingUid, userState);
+ }
+
+ @GuardedBy("mLock")
+ private AdSessionState getAdSessionStateLocked(IBinder sessionToken, int callingUid,
+ UserState userState) {
+ AdSessionState sessionState = userState.mAdSessionStateMap.get(sessionToken);
+ if (sessionState == null) {
+ throw new SessionNotFoundException("Session state not found for token " + sessionToken);
+ }
+ // Only the application that requested this session or the system can access it.
+ if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.mCallingUid) {
+ throw new SecurityException("Illegal access to the session with token " + sessionToken
+ + " from uid " + callingUid);
+ }
+ return sessionState;
+ }
+
+ @GuardedBy("mLock")
+ private ITvAdSession getAdSessionLocked(
+ IBinder sessionToken, int callingUid, int userId) {
+ return getAdSessionLocked(getAdSessionStateLocked(sessionToken, callingUid, userId));
+ }
+
+ @GuardedBy("mLock")
+ private ITvAdSession getAdSessionLocked(AdSessionState sessionState) {
+ ITvAdSession session = sessionState.mSession;
+ if (session == null) {
+ throw new IllegalStateException("Session not yet created for token "
+ + sessionState.mSessionToken);
+ }
+ return session;
+ }
+
+ @GuardedBy("mLock")
private SessionState getSessionStateLocked(IBinder sessionToken, int callingUid, int userId) {
UserState userState = getOrCreateUserStateLocked(userId);
return getSessionStateLocked(sessionToken, callingUid, userState);
@@ -691,10 +912,200 @@
return session;
}
private final class TvAdBinderService extends ITvAdManager.Stub {
+
+ @Override
+ public List<TvAdServiceInfo> getTvAdServiceList(int userId) {
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, "getTvAdServiceList");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ if (!mGetAdServiceListCalled) {
+ buildTvAdServiceListLocked(userId, null);
+ mGetAdServiceListCalled = true;
+ }
+ UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+ List<TvAdServiceInfo> adServiceList = new ArrayList<>();
+ for (TvAdServiceState state : userState.mAdServiceMap.values()) {
+ adServiceList.add(state.mInfo);
+ }
+ return adServiceList;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void createSession(final ITvAdClient client, final String serviceId, String type,
+ int seq, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ final int resolvedUserId = resolveCallingUserId(callingPid, callingUid,
+ userId, "createSession");
+ final long identity = Binder.clearCallingIdentity();
+
+ try {
+ synchronized (mLock) {
+ if (userId != mCurrentUserId && !mRunningProfiles.contains(userId)) {
+ // Only current user and its running profiles can create sessions.
+ // Let the client get onConnectionFailed callback for this case.
+ sendAdSessionTokenToClientLocked(client, serviceId, null, null, seq);
+ return;
+ }
+ UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+ TvAdServiceState adState = userState.mAdMap.get(serviceId);
+ if (adState == null) {
+ Slogf.w(TAG, "Failed to find state for serviceId=" + serviceId);
+ sendAdSessionTokenToClientLocked(client, serviceId, null, null, seq);
+ return;
+ }
+ AdServiceState serviceState =
+ userState.mAdServiceStateMap.get(adState.mComponentName);
+ if (serviceState == null) {
+ int tasUid = PackageManager.getApplicationInfoAsUserCached(
+ adState.mComponentName.getPackageName(), 0, resolvedUserId).uid;
+ serviceState = new AdServiceState(
+ adState.mComponentName, serviceId, resolvedUserId);
+ userState.mAdServiceStateMap.put(adState.mComponentName, serviceState);
+ }
+ // Send a null token immediately while reconnecting.
+ if (serviceState.mReconnecting) {
+ sendAdSessionTokenToClientLocked(client, serviceId, null, null, seq);
+ return;
+ }
+
+ // Create a new session token and a session state.
+ IBinder sessionToken = new Binder();
+ AdSessionState sessionState = new AdSessionState(sessionToken, serviceId, type,
+ adState.mComponentName, client, seq, callingUid,
+ callingPid, resolvedUserId);
+
+ // Add them to the global session state map of the current user.
+ userState.mAdSessionStateMap.put(sessionToken, sessionState);
+
+ // Also, add them to the session state map of the current service.
+ serviceState.mSessionTokens.add(sessionToken);
+
+ if (serviceState.mService != null) {
+ if (!createAdSessionInternalLocked(serviceState.mService, sessionToken,
+ resolvedUserId)) {
+ removeAdSessionStateLocked(sessionToken, resolvedUserId);
+ }
+ } else {
+ updateAdServiceConnectionLocked(adState.mComponentName, resolvedUserId);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void releaseSession(IBinder sessionToken, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "releaseSession(sessionToken=" + sessionToken + ")");
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "releaseSession");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void setSurface(IBinder sessionToken, Surface surface, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "setSurface");
+ AdSessionState sessionState = null;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ sessionState = getAdSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getAdSessionLocked(sessionState).setSurface(surface);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in setSurface", e);
+ }
+ }
+ } finally {
+ if (surface != null) {
+ // surface is not used in TvInteractiveAppManagerService.
+ surface.release();
+ }
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void dispatchSurfaceChanged(IBinder sessionToken, int format, int width,
+ int height, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "dispatchSurfaceChanged");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ AdSessionState sessionState = getAdSessionStateLocked(
+ sessionToken, callingUid, resolvedUserId);
+ getAdSessionLocked(sessionState).dispatchSurfaceChanged(format, width,
+ height);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in dispatchSurfaceChanged", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
@Override
public void startAdService(IBinder sessionToken, int userId) {
}
+ @Override
+ public void registerCallback(final ITvAdManagerCallback callback, int userId) {
+ int callingPid = Binder.getCallingPid();
+ int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+ "registerCallback");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ final UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+ if (!userState.mAdCallbacks.register(callback)) {
+ Slog.e(TAG, "client process has already died");
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void unregisterCallback(ITvAdManagerCallback callback, int userId) {
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, "unregisterCallback");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+ userState.mAdCallbacks.unregister(callback);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
}
private final class BinderService extends ITvInteractiveAppManager.Stub {
@@ -927,7 +1338,7 @@
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
+ releaseAdSessionLocked(sessionToken, callingUid, resolvedUserId);
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -1471,6 +1882,32 @@
}
@Override
+ public void sendSelectedTrackInfo(IBinder sessionToken, List<TvTrackInfo> tracks,
+ int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "sendSelectedTrackInfo(tracks=%s)", tracks.toString());
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "sendSelectedTrackInfo");
+ SessionState sessionState = null;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ sessionState = getSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getSessionLocked(sessionState).sendSelectedTrackInfo(tracks);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in sendSelectedTrackInfo", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void sendCurrentTvInputId(IBinder sessionToken, String inputId, int userId) {
if (DEBUG) {
Slogf.d(TAG, "sendCurrentTvInputId(inputId=%s)", inputId);
@@ -2134,6 +2571,17 @@
}
@GuardedBy("mLock")
+ private void sendAdSessionTokenToClientLocked(
+ ITvAdClient client, String serviceId, IBinder sessionToken,
+ InputChannel channel, int seq) {
+ try {
+ client.onSessionCreated(serviceId, sessionToken, channel, seq);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in onSessionCreated", e);
+ }
+ }
+
+ @GuardedBy("mLock")
private boolean createSessionInternalLocked(
ITvInteractiveAppService service, IBinder sessionToken, int userId) {
UserState userState = getOrCreateUserStateLocked(userId);
@@ -2163,6 +2611,58 @@
}
@GuardedBy("mLock")
+ private boolean createAdSessionInternalLocked(
+ 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 + ")");
+ }
+ InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
+
+ // Set up a callback to send the session token.
+ ITvAdSessionCallback callback = new AdSessionCallback(sessionState, channels);
+
+ boolean created = true;
+ // Create a session. When failed, send a null token immediately.
+ try {
+ service.createSession(
+ channels[1], callback, sessionState.mAdServiceId, sessionState.mType);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in createSession", e);
+ sendAdSessionTokenToClientLocked(sessionState.mClient, sessionState.mAdServiceId, null,
+ null, sessionState.mSeq);
+ created = false;
+ }
+ channels[1].dispose();
+ return created;
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ private AdSessionState releaseAdSessionLocked(
+ IBinder sessionToken, int callingUid, int userId) {
+ AdSessionState sessionState = null;
+ try {
+ sessionState = getAdSessionStateLocked(sessionToken, callingUid, userId);
+ UserState userState = getOrCreateUserStateLocked(userId);
+ if (sessionState.mSession != null) {
+ sessionState.mSession.asBinder().unlinkToDeath(sessionState, 0);
+ sessionState.mSession.release();
+ }
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in releaseSession", e);
+ } finally {
+ if (sessionState != null) {
+ sessionState.mSession = null;
+ }
+ }
+ removeAdSessionStateLocked(sessionToken, userId);
+ return sessionState;
+ }
+
+ @GuardedBy("mLock")
@Nullable
private SessionState releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) {
SessionState sessionState = null;
@@ -2215,6 +2715,36 @@
}
@GuardedBy("mLock")
+ private void removeAdSessionStateLocked(IBinder sessionToken, int userId) {
+ UserState userState = getOrCreateUserStateLocked(userId);
+
+ // Remove the session state from the global session state map of the current user.
+ AdSessionState sessionState = userState.mAdSessionStateMap.remove(sessionToken);
+
+ if (sessionState == null) {
+ Slogf.e(TAG, "sessionState null, no more remove session action!");
+ return;
+ }
+
+ // Also remove the session token from the session token list of the current client and
+ // service.
+ ClientState clientState = userState.mClientStateMap.get(sessionState.mClient.asBinder());
+ if (clientState != null) {
+ clientState.mSessionTokens.remove(sessionToken);
+ if (clientState.isEmpty()) {
+ userState.mClientStateMap.remove(sessionState.mClient.asBinder());
+ sessionState.mClient.asBinder().unlinkToDeath(clientState, 0);
+ }
+ }
+
+ AdServiceState serviceState = userState.mAdServiceStateMap.get(sessionState.mComponent);
+ if (serviceState != null) {
+ serviceState.mSessionTokens.remove(sessionToken);
+ }
+ updateAdServiceConnectionLocked(sessionState.mComponent, userId);
+ }
+
+ @GuardedBy("mLock")
private void abortPendingCreateSessionRequestsLocked(ServiceState serviceState,
String iAppServiceId, int userId) {
// Let clients know the create session requests are failed.
@@ -2237,6 +2767,28 @@
}
@GuardedBy("mLock")
+ private void abortPendingCreateAdSessionRequestsLocked(AdServiceState serviceState,
+ String serviceId, int userId) {
+ // Let clients know the create session requests are failed.
+ UserState userState = getOrCreateUserStateLocked(userId);
+ List<AdSessionState> sessionsToAbort = new ArrayList<>();
+ for (IBinder sessionToken : serviceState.mSessionTokens) {
+ AdSessionState sessionState = userState.mAdSessionStateMap.get(sessionToken);
+ if (sessionState.mSession == null
+ && (serviceState == null
+ || sessionState.mAdServiceId.equals(serviceId))) {
+ sessionsToAbort.add(sessionState);
+ }
+ }
+ for (AdSessionState sessionState : sessionsToAbort) {
+ removeAdSessionStateLocked(sessionState.mSessionToken, sessionState.mUserId);
+ sendAdSessionTokenToClientLocked(sessionState.mClient,
+ sessionState.mAdServiceId, null, null, sessionState.mSeq);
+ }
+ updateAdServiceConnectionLocked(serviceState.mComponent, userId);
+ }
+
+ @GuardedBy("mLock")
private void updateServiceConnectionLocked(ComponentName component, int userId) {
UserState userState = getOrCreateUserStateLocked(userId);
ServiceState serviceState = userState.mServiceStateMap.get(component);
@@ -2284,10 +2836,64 @@
}
}
+ @GuardedBy("mLock")
+ private void updateAdServiceConnectionLocked(ComponentName component, int userId) {
+ UserState userState = getOrCreateUserStateLocked(userId);
+ AdServiceState serviceState = userState.mAdServiceStateMap.get(component);
+ if (serviceState == null) {
+ return;
+ }
+ if (serviceState.mReconnecting) {
+ if (!serviceState.mSessionTokens.isEmpty()) {
+ // wait until all the sessions are removed.
+ return;
+ }
+ serviceState.mReconnecting = false;
+ }
+
+ boolean shouldBind = (!serviceState.mSessionTokens.isEmpty())
+ || (!serviceState.mPendingAppLinkCommand.isEmpty());
+
+ if (serviceState.mService == null && shouldBind) {
+ // This means that the service is not yet connected but its state indicates that we
+ // have pending requests. Then, connect the service.
+ if (serviceState.mBound) {
+ // We have already bound to the service so we don't try to bind again until after we
+ // unbind later on.
+ return;
+ }
+ if (DEBUG) {
+ Slogf.d(TAG, "bindServiceAsUser(service=" + component + ", userId=" + userId + ")");
+ }
+
+ Intent i = new Intent(TvAdService.SERVICE_INTERFACE).setComponent(component);
+ serviceState.mBound = mContext.bindServiceAsUser(
+ i, serviceState.mConnection,
+ Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
+ new UserHandle(userId));
+ } else if (serviceState.mService != null && !shouldBind) {
+ // This means that the service is already connected but its state indicates that we have
+ // nothing to do with it. Then, disconnect the service.
+ if (DEBUG) {
+ Slogf.d(TAG, "unbindService(service=" + component + ")");
+ }
+ mContext.unbindService(serviceState.mConnection);
+ userState.mAdServiceStateMap.remove(component);
+ }
+ }
+
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.
+ private final Map<IBinder, AdSessionState> mAdSessionStateMap = new HashMap<>();
// A mapping from the TV Interactive App ID to its TvInteractiveAppState.
private Map<String, TvInteractiveAppState> mIAppMap = new HashMap<>();
+ // A mapping from the TV AD service ID to its TvAdServiceState.
+ private Map<String, TvAdServiceState> mAdServiceMap = new HashMap<>();
// A mapping from the token of a client to its state.
private final Map<IBinder, ClientState> mClientStateMap = new HashMap<>();
// A mapping from the name of a TV Interactive App service to its state.
@@ -2299,6 +2905,8 @@
private final Set<String> mPackageSet = new HashSet<>();
// A list of all app link infos.
private final List<AppLinkInfo> mAppLinkInfoList = new ArrayList<>();
+ private final RemoteCallbackList<ITvAdManagerCallback> mAdCallbacks =
+ new RemoteCallbackList<>();
// A list of callbacks.
private final RemoteCallbackList<ITvInteractiveAppManagerCallback> mCallbacks =
@@ -2317,7 +2925,16 @@
private int mIAppNumber;
}
+ private static final class TvAdServiceState {
+ private String mAdServiceId;
+ private ComponentName mComponentName;
+ private TvAdServiceInfo mInfo;
+ private int mUid;
+ private int mAdNumber;
+ }
+
private final class SessionState implements IBinder.DeathRecipient {
+ // TODO: rename SessionState and reorganize classes / methods of this file
private final IBinder mSessionToken;
private ITvInteractiveAppSession mSession;
private final String mIAppServiceId;
@@ -2359,6 +2976,49 @@
}
}
+ private final class AdSessionState implements IBinder.DeathRecipient {
+ private final IBinder mSessionToken;
+ private ITvAdSession mSession;
+ private final String mAdServiceId;
+
+ private final String mType;
+ private final ITvAdClient mClient;
+ private final int mSeq;
+ private final ComponentName mComponent;
+
+ // The UID of the application that created the session.
+ // The application is usually the TV app.
+ private final int mCallingUid;
+
+ // The PID of the application that created the session.
+ // The application is usually the TV app.
+ private final int mCallingPid;
+
+ private final int mUserId;
+
+ private AdSessionState(IBinder sessionToken, String serviceId, String type,
+ ComponentName componentName, ITvAdClient client, int seq,
+ int callingUid, int callingPid, int userId) {
+ mSessionToken = sessionToken;
+ mAdServiceId = serviceId;
+ mType = type;
+ mComponent = componentName;
+ mClient = client;
+ mSeq = seq;
+ mCallingUid = callingUid;
+ mCallingPid = callingPid;
+ mUserId = userId;
+ }
+
+ @Override
+ public void binderDied() {
+ synchronized (mLock) {
+ mSession = null;
+ clearAdSessionAndNotifyClientLocked(this);
+ }
+ }
+ }
+
private final class ClientState implements IBinder.DeathRecipient {
private final List<IBinder> mSessionTokens = new ArrayList<>();
@@ -2429,6 +3089,29 @@
}
}
+ private final class AdServiceState {
+ private final List<IBinder> mSessionTokens = new ArrayList<>();
+ private final ServiceConnection mConnection;
+ private final ComponentName mComponent;
+ private final String mAdServiceId;
+ private final List<Bundle> mPendingAppLinkCommand = new ArrayList<>();
+
+ private ITvAdService mService;
+ private AdServiceCallback mCallback;
+ private boolean mBound;
+ private boolean mReconnecting;
+
+ private AdServiceState(ComponentName component, String tasId, int userId) {
+ mComponent = component;
+ mConnection = new AdServiceConnection(component, userId);
+ mAdServiceId = tasId;
+ }
+
+ private void addPendingAppLinkCommand(Bundle command) {
+ mPendingAppLinkCommand.add(command);
+ }
+ }
+
private final class InteractiveAppServiceConnection implements ServiceConnection {
private final ComponentName mComponent;
private final int mUserId;
@@ -2542,6 +3225,98 @@
}
}
+ private final class AdServiceConnection implements ServiceConnection {
+ private final ComponentName mComponent;
+ private final int mUserId;
+
+ private AdServiceConnection(ComponentName component, int userId) {
+ mComponent = component;
+ mUserId = userId;
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName component, IBinder service) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onServiceConnected(component=" + component + ")");
+ }
+ synchronized (mLock) {
+ UserState userState = getUserStateLocked(mUserId);
+ if (userState == null) {
+ // The user was removed while connecting.
+ mContext.unbindService(this);
+ return;
+ }
+ AdServiceState serviceState = userState.mAdServiceStateMap.get(mComponent);
+ serviceState.mService = ITvAdService.Stub.asInterface(service);
+
+ // Register a callback, if we need to.
+ if (serviceState.mCallback == null) {
+ serviceState.mCallback = new AdServiceCallback(mComponent, mUserId);
+ try {
+ serviceState.mService.registerCallback(serviceState.mCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in registerCallback", e);
+ }
+ }
+
+ if (!serviceState.mPendingAppLinkCommand.isEmpty()) {
+ for (Iterator<Bundle> it = serviceState.mPendingAppLinkCommand.iterator();
+ it.hasNext(); ) {
+ Bundle command = it.next();
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ serviceState.mService.sendAppLinkCommand(command);
+ it.remove();
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in sendAppLinkCommand(" + command
+ + ") when onServiceConnected", e);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ List<IBinder> tokensToBeRemoved = new ArrayList<>();
+
+ // And create sessions, if any.
+ for (IBinder sessionToken : serviceState.mSessionTokens) {
+ if (!createAdSessionInternalLocked(
+ serviceState.mService, sessionToken, mUserId)) {
+ tokensToBeRemoved.add(sessionToken);
+ }
+ }
+
+ for (IBinder sessionToken : tokensToBeRemoved) {
+ removeAdSessionStateLocked(sessionToken, mUserId);
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName component) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onServiceDisconnected(component=" + component + ")");
+ }
+ if (!mComponent.equals(component)) {
+ throw new IllegalArgumentException("Mismatched ComponentName: "
+ + mComponent + " (expected), " + component + " (actual).");
+ }
+ synchronized (mLock) {
+ UserState userState = getOrCreateUserStateLocked(mUserId);
+ AdServiceState serviceState = userState.mAdServiceStateMap.get(mComponent);
+ if (serviceState != null) {
+ serviceState.mReconnecting = true;
+ serviceState.mBound = false;
+ serviceState.mService = null;
+ serviceState.mCallback = null;
+
+ abortPendingCreateAdSessionRequestsLocked(serviceState, null, mUserId);
+ }
+ }
+ }
+ }
+
+
private final class ServiceCallback extends ITvInteractiveAppServiceCallback.Stub {
private final ComponentName mComponent;
private final int mUserId;
@@ -2567,6 +3342,17 @@
}
}
+
+ private final class AdServiceCallback extends ITvAdServiceCallback.Stub {
+ private final ComponentName mComponent;
+ private final int mUserId;
+
+ AdServiceCallback(ComponentName component, int userId) {
+ mComponent = component;
+ mUserId = userId;
+ }
+ }
+
private final class SessionCallback extends ITvInteractiveAppSessionCallback.Stub {
private final SessionState mSessionState;
private final InputChannel[] mInputChannels;
@@ -2798,6 +3584,23 @@
}
@Override
+ public void onRequestSelectedTrackInfo() {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onRequestSelectedTrackInfo");
+ }
+ if (mSessionState.mSession == null || mSessionState.mClient == null) {
+ return;
+ }
+ try {
+ mSessionState.mClient.onRequestSelectedTrackInfo(mSessionState.mSeq);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in onRequestSelectedTrackInfo", e);
+ }
+ }
+ }
+
+ @Override
public void onRequestCurrentTvInputId() {
synchronized (mLock) {
if (DEBUG) {
@@ -3110,6 +3913,85 @@
}
}
+ private final class AdSessionCallback extends ITvAdSessionCallback.Stub {
+ private final AdSessionState mSessionState;
+ private final InputChannel[] mInputChannels;
+
+ AdSessionCallback(AdSessionState sessionState, InputChannel[] channels) {
+ mSessionState = sessionState;
+ mInputChannels = channels;
+ }
+
+ @Override
+ public void onSessionCreated(ITvAdSession session) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onSessionCreated(adServiceId="
+ + mSessionState.mAdServiceId + ")");
+ }
+ synchronized (mLock) {
+ mSessionState.mSession = session;
+ if (session != null && addAdSessionTokenToClientStateLocked(session)) {
+ sendAdSessionTokenToClientLocked(
+ mSessionState.mClient,
+ mSessionState.mAdServiceId,
+ mSessionState.mSessionToken,
+ mInputChannels[0],
+ mSessionState.mSeq);
+ } else {
+ removeAdSessionStateLocked(mSessionState.mSessionToken, mSessionState.mUserId);
+ sendAdSessionTokenToClientLocked(mSessionState.mClient,
+ mSessionState.mAdServiceId, null, null, mSessionState.mSeq);
+ }
+ mInputChannels[0].dispose();
+ }
+ }
+
+ @Override
+ public void onLayoutSurface(int left, int top, int right, int bottom) {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top
+ + ", right=" + right + ", bottom=" + bottom + ",)");
+ }
+ if (mSessionState.mSession == null || mSessionState.mClient == null) {
+ return;
+ }
+ try {
+ mSessionState.mClient.onLayoutSurface(left, top, right, bottom,
+ mSessionState.mSeq);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in onLayoutSurface", e);
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private boolean addAdSessionTokenToClientStateLocked(ITvAdSession session) {
+ try {
+ session.asBinder().linkToDeath(mSessionState, 0);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "session process has already died", e);
+ return false;
+ }
+
+ IBinder clientToken = mSessionState.mClient.asBinder();
+ UserState userState = getOrCreateUserStateLocked(mSessionState.mUserId);
+ ClientState clientState = userState.mClientStateMap.get(clientToken);
+ if (clientState == null) {
+ clientState = new ClientState(clientToken, mSessionState.mUserId);
+ try {
+ clientToken.linkToDeath(clientState, 0);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "client process has already died", e);
+ return false;
+ }
+ userState.mClientStateMap.put(clientToken, clientState);
+ }
+ clientState.mSessionTokens.add(mSessionState.mSessionToken);
+ return true;
+ }
+ }
+
private static class SessionNotFoundException extends IllegalArgumentException {
SessionNotFoundException(String name) {
super(name);
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java b/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
index e54b40e..03c75e0 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
@@ -37,7 +37,17 @@
void revokeUriPermission(String targetPackage, int callingUid,
GrantUri grantUri, final int modeFlags);
- boolean checkUriPermission(GrantUri grantUri, int uid, final int modeFlags);
+ /**
+ * Check if the uid has permission to the URI in grantUri.
+ *
+ * @param isFullAccessForContentUri If true, the URI has to be a content URI
+ * and the method will consider full access.
+ * Otherwise, the method will only consider
+ * URI grants.
+ */
+ boolean checkUriPermission(GrantUri grantUri, int uid, int modeFlags,
+ boolean isFullAccessForContentUri);
+
int checkGrantUriPermission(
int callingUid, String targetPkg, Uri uri, int modeFlags, int userId);
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
index e501b9d..ce2cbed 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
@@ -25,6 +25,7 @@
import static android.content.Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
import static android.content.pm.PackageManager.MATCH_ANY_USER;
import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
@@ -1103,7 +1104,8 @@
*/
private int checkGrantUriPermissionUnlocked(int callingUid, String targetPkg, GrantUri grantUri,
int modeFlags, int lastTargetUid) {
- if (!Intent.isAccessUriMode(modeFlags)) {
+ if (!isContentUriWithAccessModeFlags(grantUri, modeFlags,
+ /* logAction */ "grant URI permission")) {
return -1;
}
@@ -1111,12 +1113,6 @@
if (DEBUG) Slog.v(TAG, "Checking grant " + targetPkg + " permission to " + grantUri);
}
- // If this is not a content: uri, we can't do anything with it.
- if (!ContentResolver.SCHEME_CONTENT.equals(grantUri.uri.getScheme())) {
- if (DEBUG) Slog.v(TAG, "Can't grant URI permission for non-content URI: " + grantUri);
- return -1;
- }
-
// Bail early if system is trying to hand out permissions directly; it
// must always grant permissions on behalf of someone explicit.
final int callingAppId = UserHandle.getAppId(callingUid);
@@ -1137,7 +1133,7 @@
final String authority = grantUri.uri.getAuthority();
final ProviderInfo pi = getProviderInfo(authority, grantUri.sourceUserId,
- MATCH_DEBUG_TRIAGED_MISSING, callingUid);
+ MATCH_DIRECT_BOOT_AUTO, callingUid);
if (pi == null) {
Slog.w(TAG, "No content provider found for permission check: " +
grantUri.uri.toSafeString());
@@ -1285,6 +1281,65 @@
return targetUid;
}
+ private boolean isContentUriWithAccessModeFlags(GrantUri grantUri, int modeFlags,
+ String logAction) {
+ if (!Intent.isAccessUriMode(modeFlags)) {
+ if (DEBUG) Slog.v(TAG, "Mode flags are not access URI mode flags: " + modeFlags);
+ return false;
+ }
+
+ if (!ContentResolver.SCHEME_CONTENT.equals(grantUri.uri.getScheme())) {
+ if (DEBUG) {
+ Slog.v(TAG, "Can't " + logAction + " on non-content URI: " + grantUri);
+ }
+ return false;
+ }
+
+ return true;
+ }
+
+ /** Check if the uid has permission to the content URI in grantUri. */
+ private boolean checkContentUriPermissionFullUnlocked(GrantUri grantUri, int uid,
+ int modeFlags) {
+ if (uid < 0) {
+ throw new IllegalArgumentException("Uid must be positive for the content URI "
+ + "permission check of " + grantUri.uri.toSafeString());
+ }
+
+ if (!isContentUriWithAccessModeFlags(grantUri, modeFlags,
+ /* logAction */ "check content URI permission")) {
+ throw new IllegalArgumentException("The URI must be a content URI and the mode "
+ + "flags must be at least read and/or write for the content URI permission "
+ + "check of " + grantUri.uri.toSafeString());
+ }
+
+ final int appId = UserHandle.getAppId(uid);
+ if ((appId == SYSTEM_UID) || (appId == ROOT_UID)) {
+ return true;
+ }
+
+ // Retrieve the URI's content provider
+ final String authority = grantUri.uri.getAuthority();
+ ProviderInfo pi = getProviderInfo(authority, grantUri.sourceUserId, MATCH_DIRECT_BOOT_AUTO,
+ uid);
+
+ if (pi == null) {
+ Slog.w(TAG, "No content provider found for content URI permission check: "
+ + grantUri.uri.toSafeString());
+ return false;
+ }
+
+ // Check if it has general permission to the URI's content provider
+ if (checkHoldingPermissionsUnlocked(pi, grantUri, uid, modeFlags)) {
+ return true;
+ }
+
+ // Check if it has explicitly granted permissions to the URI
+ synchronized (mLock) {
+ return checkUriPermissionLocked(grantUri, uid, modeFlags);
+ }
+ }
+
/**
* @param userId The userId in which the uri is to be resolved.
*/
@@ -1482,7 +1537,12 @@
}
@Override
- public boolean checkUriPermission(GrantUri grantUri, int uid, int modeFlags) {
+ public boolean checkUriPermission(GrantUri grantUri, int uid, int modeFlags,
+ boolean isFullAccessForContentUri) {
+ if (isFullAccessForContentUri) {
+ return UriGrantsManagerService.this.checkContentUriPermissionFullUnlocked(grantUri,
+ uid, modeFlags);
+ }
synchronized (mLock) {
return UriGrantsManagerService.this.checkUriPermissionLocked(grantUri, uid,
modeFlags);
diff --git a/services/core/java/com/android/server/utils/AnrTimer.java b/services/core/java/com/android/server/utils/AnrTimer.java
index e3aba0f..743005a 100644
--- a/services/core/java/com/android/server/utils/AnrTimer.java
+++ b/services/core/java/com/android/server/utils/AnrTimer.java
@@ -96,22 +96,16 @@
private static boolean DEBUG = false;
/**
- * The trace tag.
+ * The trace tag is the same usd by ActivityManager.
*/
private static final long TRACE_TAG = Trace.TRACE_TAG_ACTIVITY_MANAGER;
/**
- * Enable tracing from the time a timer expires until it is accepted or discarded. This is
- * used to diagnose long latencies in the client.
- */
- private static final boolean ENABLE_TRACING = false;
-
- /**
* Return true if the feature is enabled. By default, the value is take from the Flags class
* but it can be changed for local testing.
*/
private static boolean anrTimerServiceEnabled() {
- return Flags.anrTimerServiceEnabled();
+ return Flags.anrTimerService();
}
/**
@@ -320,24 +314,21 @@
}
/**
- * Start a trace on the timer. The trace is laid down in the AnrTimerTrack.
+ * Generate a trace point with full timer information. The meaning of milliseconds depends on
+ * the caller.
*/
- private void traceBegin(int timerId, int pid, int uid, String what) {
- if (ENABLE_TRACING) {
- final String label = formatSimple("%s(%d,%d,%s)", what, pid, uid, mLabel);
- final int cookie = timerId;
- Trace.asyncTraceForTrackBegin(TRACE_TAG, TRACK, label, cookie);
- }
+ private void trace(String op, int timerId, int pid, int uid, long milliseconds) {
+ final String label =
+ formatSimple("%s(%d,%d,%d,%s,%d)", op, timerId, pid, uid, mLabel, milliseconds);
+ Trace.instantForTrack(TRACE_TAG, TRACK, label);
}
/**
- * End a trace on the timer.
+ * Generate a trace point with just the timer ID.
*/
- private void traceEnd(int timerId) {
- if (ENABLE_TRACING) {
- final int cookie = timerId;
- Trace.asyncTraceForTrackEnd(TRACE_TAG, TRACK, cookie);
- }
+ private void trace(String op, int timerId) {
+ final String label = formatSimple("%s(%d)", op, timerId);
+ Trace.instantForTrack(TRACE_TAG, TRACK, label);
}
/**
@@ -492,7 +483,7 @@
return false;
}
nativeAnrTimerAccept(mNative, timer);
- traceEnd(timer);
+ trace("accept", timer);
return true;
}
}
@@ -511,7 +502,7 @@
return false;
}
nativeAnrTimerDiscard(mNative, timer);
- traceEnd(timer);
+ trace("discard", timer);
return true;
}
}
@@ -629,13 +620,18 @@
}
/**
- * The notifier that a timer has fired. The timerId and original pid/uid are supplied. This
- * method is called from native code. This method takes mLock so that a timer cannot expire
- * in the middle of another operation (like start or cancel).
+ * The notifier that a timer has fired. The timerId and original pid/uid are supplied. The
+ * elapsed time is the actual time since the timer was scheduled, which may be different from
+ * the original timeout if the timer was extended or if other delays occurred. This method
+ * takes mLock so that a timer cannot expire in the middle of another operation (like start or
+ * cancel).
+ *
+ * This method is called from native code. The function must return true if the expiration
+ * message is delivered to the upper layers and false if it could not be delivered.
*/
@Keep
- private boolean expire(int timerId, int pid, int uid) {
- traceBegin(timerId, pid, uid, "expired");
+ private boolean expire(int timerId, int pid, int uid, long elapsedMs) {
+ trace("expired", timerId, pid, uid, elapsedMs);
V arg = null;
synchronized (mLock) {
arg = mTimerArgMap.get(timerId);
@@ -815,9 +811,4 @@
/** Prod the native library to log a few statistics. */
private static native void nativeAnrTimerDump(long service, boolean verbose);
-
- // This is not a native method but it is a native interface, in the sense that it is called from
- // the native layer to report timer expiration. The function must return true if the expiration
- // message is delivered to the upper layers and false if it could not be delivered.
- // private boolean expire(int timerId, int pid, int uid);
}
diff --git a/services/core/java/com/android/server/utils/flags.aconfig b/services/core/java/com/android/server/utils/flags.aconfig
index 489e21a..163116b 100644
--- a/services/core/java/com/android/server/utils/flags.aconfig
+++ b/services/core/java/com/android/server/utils/flags.aconfig
@@ -1,7 +1,7 @@
package: "com.android.server.utils"
flag {
- name: "anr_timer_service_enabled"
+ name: "anr_timer_service"
namespace: "system_performance"
is_fixed_read_only: true
description: "Feature flag for the ANR timer service"
diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java
index 9213d96..ed04e5f 100644
--- a/services/core/java/com/android/server/vcn/VcnContext.java
+++ b/services/core/java/com/android/server/vcn/VcnContext.java
@@ -34,6 +34,7 @@
@NonNull private final Looper mLooper;
@NonNull private final VcnNetworkProvider mVcnNetworkProvider;
@NonNull private final FeatureFlags mFeatureFlags;
+ @NonNull private final com.android.net.flags.FeatureFlags mCoreNetFeatureFlags;
private final boolean mIsInTestMode;
public VcnContext(
@@ -48,6 +49,7 @@
// Auto-generated class
mFeatureFlags = new FeatureFlagsImpl();
+ mCoreNetFeatureFlags = new com.android.net.flags.FeatureFlagsImpl();
}
@NonNull
@@ -69,11 +71,23 @@
return mIsInTestMode;
}
+ public boolean isFlagNetworkMetricMonitorEnabled() {
+ return mFeatureFlags.networkMetricMonitor();
+ }
+
+ public boolean isFlagIpSecTransformStateEnabled() {
+ return mCoreNetFeatureFlags.ipsecTransformState();
+ }
+
@NonNull
public FeatureFlags getFeatureFlags() {
return mFeatureFlags;
}
+ public boolean isFlagSafeModeTimeoutConfigEnabled() {
+ return mFeatureFlags.safeModeTimeoutConfig();
+ }
+
/**
* Verifies that the caller is running on the VcnContext Thread.
*
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 54c97dd..3094b18 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -915,9 +915,11 @@
// TODO(b/180132994): explore safely removing this Thread check
mVcnContext.ensureRunningOnLooperThread();
- logInfo(
- "Selected underlying network changed: "
- + (underlying == null ? null : underlying.network));
+ if (!UnderlyingNetworkRecord.isSameNetwork(mUnderlying, underlying)) {
+ logInfo(
+ "Selected underlying network changed: "
+ + (underlying == null ? null : underlying.network));
+ }
// TODO(b/179091925): Move the delayed-message handling to BaseState
@@ -1242,9 +1244,28 @@
createScheduledAlarm(
SAFEMODE_TIMEOUT_ALARM,
delayedMessage,
- mVcnContext.isInTestMode()
- ? TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS_TEST_MODE)
- : TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS));
+ getSafeModeTimeoutMs(mVcnContext, mLastSnapshot, mSubscriptionGroup));
+ }
+
+ /** Gets the safe mode timeout */
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public static long getSafeModeTimeoutMs(
+ VcnContext vcnContext, TelephonySubscriptionSnapshot snapshot, ParcelUuid subGrp) {
+ final int defaultSeconds =
+ vcnContext.isInTestMode()
+ ? SAFEMODE_TIMEOUT_SECONDS_TEST_MODE
+ : SAFEMODE_TIMEOUT_SECONDS;
+
+ final PersistableBundleWrapper carrierConfig = snapshot.getCarrierConfigForSubGrp(subGrp);
+ int resultSeconds = defaultSeconds;
+
+ if (vcnContext.isFlagSafeModeTimeoutConfigEnabled() && carrierConfig != null) {
+ resultSeconds =
+ carrierConfig.getInt(
+ VcnManager.VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY, defaultSeconds);
+ }
+
+ return TimeUnit.SECONDS.toMillis(resultSeconds);
}
private void cancelSafeModeAlarm() {
@@ -1889,6 +1910,12 @@
// Transforms do not need to be persisted; the IkeSession will keep them alive
mIpSecManager.applyTunnelModeTransform(tunnelIface, direction, transform);
+ if (direction == IpSecManager.DIRECTION_IN
+ && mVcnContext.isFlagNetworkMetricMonitorEnabled()
+ && mVcnContext.isFlagIpSecTransformStateEnabled()) {
+ mUnderlyingNetworkController.updateInboundTransform(mUnderlying, transform);
+ }
+
// For inbound transforms, additionally allow forwarded traffic to bridge to DUN (as
// needed)
final Set<Integer> exposedCaps = mConnectionConfig.getAllExposedCapabilities();
diff --git a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
new file mode 100644
index 0000000..5f4852f
--- /dev/null
+++ b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.vcn.routeselection;
+
+import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.IpSecTransformState;
+import android.net.Network;
+import android.net.vcn.VcnManager;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.OutcomeReceiver;
+import android.os.PowerManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.server.vcn.VcnContext;
+
+import java.util.BitSet;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * IpSecPacketLossDetector is responsible for continuously monitoring IPsec packet loss
+ *
+ * <p>When the packet loss rate surpass the threshold, IpSecPacketLossDetector will report it to the
+ * caller
+ *
+ * <p>IpSecPacketLossDetector will start monitoring when the network being monitored is selected AND
+ * an inbound IpSecTransform has been applied to this network.
+ *
+ * <p>This class is flag gated by "network_metric_monitor" and "ipsec_tramsform_state"
+ */
+public class IpSecPacketLossDetector extends NetworkMetricMonitor {
+ private static final String TAG = IpSecPacketLossDetector.class.getSimpleName();
+
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ static final int PACKET_LOSS_UNAVALAIBLE = -1;
+
+ // For VoIP, losses between 5% and 10% of the total packet stream will affect the quality
+ // significantly (as per "Computer Networking for LANS to WANS: Hardware, Software and
+ // Security"). For audio and video streaming, above 10-12% packet loss is unacceptable (as per
+ // "ICTP-SDU: About PingER"). Thus choose 12% as a conservative default threshold to declare a
+ // validation failure.
+ private static final int IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DEFAULT = 12;
+
+ private static final int POLL_IPSEC_STATE_INTERVAL_SECONDS_DEFAULT = 20;
+
+ private long mPollIpSecStateIntervalMs;
+ private final int mPacketLossRatePercentThreshold;
+
+ @NonNull private final Handler mHandler;
+ @NonNull private final PowerManager mPowerManager;
+ @NonNull private final Object mCancellationToken = new Object();
+ @NonNull private final PacketLossCalculator mPacketLossCalculator;
+
+ @Nullable private IpSecTransformWrapper mInboundTransform;
+ @Nullable private IpSecTransformState mLastIpSecTransformState;
+
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public IpSecPacketLossDetector(
+ @NonNull VcnContext vcnContext,
+ @NonNull Network network,
+ @Nullable PersistableBundleWrapper carrierConfig,
+ @NonNull NetworkMetricMonitorCallback callback,
+ @NonNull Dependencies deps)
+ throws IllegalAccessException {
+ super(vcnContext, network, carrierConfig, callback);
+
+ Objects.requireNonNull(deps, "Missing deps");
+
+ if (!vcnContext.isFlagIpSecTransformStateEnabled()) {
+ // Caller error
+ logWtf("ipsecTransformState flag disabled");
+ throw new IllegalAccessException("ipsecTransformState flag disabled");
+ }
+
+ mHandler = new Handler(getVcnContext().getLooper());
+
+ mPowerManager = getVcnContext().getContext().getSystemService(PowerManager.class);
+
+ mPacketLossCalculator = deps.getPacketLossCalculator();
+
+ mPollIpSecStateIntervalMs = getPollIpSecStateIntervalMs(carrierConfig);
+ mPacketLossRatePercentThreshold = getPacketLossRatePercentThreshold(carrierConfig);
+
+ // Register for system broadcasts to monitor idle mode change
+ final IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+ getVcnContext()
+ .getContext()
+ .registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(
+ intent.getAction())
+ && mPowerManager.isDeviceIdleMode()) {
+ mLastIpSecTransformState = null;
+ }
+ }
+ },
+ intentFilter,
+ null /* broadcastPermission not required */,
+ mHandler);
+ }
+
+ public IpSecPacketLossDetector(
+ @NonNull VcnContext vcnContext,
+ @NonNull Network network,
+ @Nullable PersistableBundleWrapper carrierConfig,
+ @NonNull NetworkMetricMonitorCallback callback)
+ throws IllegalAccessException {
+ this(vcnContext, network, carrierConfig, callback, new Dependencies());
+ }
+
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public static class Dependencies {
+ public PacketLossCalculator getPacketLossCalculator() {
+ return new PacketLossCalculator();
+ }
+ }
+
+ private static long getPollIpSecStateIntervalMs(
+ @Nullable PersistableBundleWrapper carrierConfig) {
+ final int seconds;
+
+ if (carrierConfig != null) {
+ seconds =
+ carrierConfig.getInt(
+ VcnManager.VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY,
+ POLL_IPSEC_STATE_INTERVAL_SECONDS_DEFAULT);
+ } else {
+ seconds = POLL_IPSEC_STATE_INTERVAL_SECONDS_DEFAULT;
+ }
+
+ return TimeUnit.SECONDS.toMillis(seconds);
+ }
+
+ private static int getPacketLossRatePercentThreshold(
+ @Nullable PersistableBundleWrapper carrierConfig) {
+ if (carrierConfig != null) {
+ return carrierConfig.getInt(
+ VcnManager.VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY,
+ IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DEFAULT);
+ }
+ return IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DEFAULT;
+ }
+
+ @Override
+ protected void onSelectedUnderlyingNetworkChanged() {
+ if (!isSelectedUnderlyingNetwork()) {
+ mInboundTransform = null;
+ stop();
+ }
+
+ // No action when the underlying network got selected. Wait for the inbound transform to
+ // start the monitor
+ }
+
+ @Override
+ public void setInboundTransformInternal(@NonNull IpSecTransformWrapper inboundTransform) {
+ Objects.requireNonNull(inboundTransform, "inboundTransform is null");
+
+ if (Objects.equals(inboundTransform, mInboundTransform)) {
+ return;
+ }
+
+ if (!isSelectedUnderlyingNetwork()) {
+ logWtf("setInboundTransform called but network not selected");
+ return;
+ }
+
+ // When multiple parallel inbound transforms are created, NetworkMetricMonitor will be
+ // enabled on the last one as a sample
+ mInboundTransform = inboundTransform;
+ start();
+ }
+
+ @Override
+ public void setCarrierConfig(@Nullable PersistableBundleWrapper carrierConfig) {
+ // The already scheduled event will not be affected. The followup events will be scheduled
+ // with the new interval
+ mPollIpSecStateIntervalMs = getPollIpSecStateIntervalMs(carrierConfig);
+ }
+
+ @Override
+ protected void start() {
+ super.start();
+ clearTransformStateAndPollingEvents();
+ mHandler.postDelayed(new PollIpSecStateRunnable(), mCancellationToken, 0L);
+ }
+
+ @Override
+ public void stop() {
+ super.stop();
+ clearTransformStateAndPollingEvents();
+ }
+
+ private void clearTransformStateAndPollingEvents() {
+ mHandler.removeCallbacksAndEqualMessages(mCancellationToken);
+ mLastIpSecTransformState = null;
+ }
+
+ @Override
+ public void close() {
+ super.close();
+
+ if (mInboundTransform != null) {
+ mInboundTransform.close();
+ }
+ }
+
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ @Nullable
+ public IpSecTransformState getLastTransformState() {
+ return mLastIpSecTransformState;
+ }
+
+ @VisibleForTesting(visibility = Visibility.PROTECTED)
+ @Nullable
+ public IpSecTransformWrapper getInboundTransformInternal() {
+ return mInboundTransform;
+ }
+
+ private class PollIpSecStateRunnable implements Runnable {
+ @Override
+ public void run() {
+ if (!isStarted()) {
+ logWtf("Monitor stopped but PollIpSecStateRunnable not removed from Handler");
+ return;
+ }
+
+ getInboundTransformInternal()
+ .getIpSecTransformState(
+ new HandlerExecutor(mHandler), new IpSecTransformStateReceiver());
+
+ // Schedule for next poll
+ mHandler.postDelayed(
+ new PollIpSecStateRunnable(), mCancellationToken, mPollIpSecStateIntervalMs);
+ }
+ }
+
+ private class IpSecTransformStateReceiver
+ implements OutcomeReceiver<IpSecTransformState, RuntimeException> {
+ @Override
+ public void onResult(@NonNull IpSecTransformState state) {
+ getVcnContext().ensureRunningOnLooperThread();
+
+ if (!isStarted()) {
+ return;
+ }
+
+ onIpSecTransformStateReceived(state);
+ }
+
+ @Override
+ public void onError(@NonNull RuntimeException error) {
+ getVcnContext().ensureRunningOnLooperThread();
+
+ // Nothing we can do here
+ logW("TransformStateReceiver#onError " + error.toString());
+ }
+ }
+
+ private void onIpSecTransformStateReceived(@NonNull IpSecTransformState state) {
+ if (mLastIpSecTransformState == null) {
+ // This is first time to poll the state
+ mLastIpSecTransformState = state;
+ return;
+ }
+
+ final int packetLossRate =
+ mPacketLossCalculator.getPacketLossRatePercentage(
+ mLastIpSecTransformState, state, getLogPrefix());
+
+ if (packetLossRate == PACKET_LOSS_UNAVALAIBLE) {
+ return;
+ }
+
+ final String logMsg =
+ "packetLossRate: "
+ + packetLossRate
+ + "% in the past "
+ + (state.getTimestamp() - mLastIpSecTransformState.getTimestamp())
+ + "ms";
+
+ mLastIpSecTransformState = state;
+ if (packetLossRate < mPacketLossRatePercentThreshold) {
+ logV(logMsg);
+ onValidationResultReceivedInternal(false /* isFailed */);
+ } else {
+ logInfo(logMsg);
+ onValidationResultReceivedInternal(true /* isFailed */);
+ }
+ }
+
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public static class PacketLossCalculator {
+ /** Calculate the packet loss rate between two timestamps */
+ public int getPacketLossRatePercentage(
+ @NonNull IpSecTransformState oldState,
+ @NonNull IpSecTransformState newState,
+ String logPrefix) {
+ logVIpSecTransform("oldState", oldState, logPrefix);
+ logVIpSecTransform("newState", newState, logPrefix);
+
+ final int replayWindowSize = oldState.getReplayBitmap().length * 8;
+ final long oldSeqHi = oldState.getRxHighestSequenceNumber();
+ final long oldSeqLow = Math.max(0L, oldSeqHi - replayWindowSize + 1);
+ final long newSeqHi = newState.getRxHighestSequenceNumber();
+ final long newSeqLow = Math.max(0L, newSeqHi - replayWindowSize + 1);
+
+ if (oldSeqHi == newSeqHi || newSeqHi < replayWindowSize) {
+ // The replay window did not proceed and all packets might have been delivered out
+ // of order
+ return PACKET_LOSS_UNAVALAIBLE;
+ }
+
+ // Get the expected packet count by assuming there is no packet loss. In this case, SA
+ // should receive all packets whose sequence numbers are smaller than the lower bound of
+ // the replay window AND the packets received within the window.
+ // When the lower bound is 0, it's not possible to tell whether packet with seqNo 0 is
+ // received or not. For simplicity just assume that packet is received.
+ final long newExpectedPktCnt = newSeqLow + getPacketCntInReplayWindow(newState);
+ final long oldExpectedPktCnt = oldSeqLow + getPacketCntInReplayWindow(oldState);
+
+ final long expectedPktCntDiff = newExpectedPktCnt - oldExpectedPktCnt;
+ final long actualPktCntDiff = newState.getPacketCount() - oldState.getPacketCount();
+
+ logV(
+ TAG,
+ logPrefix
+ + " expectedPktCntDiff: "
+ + expectedPktCntDiff
+ + " actualPktCntDiff: "
+ + actualPktCntDiff);
+
+ if (expectedPktCntDiff < 0
+ || expectedPktCntDiff == 0
+ || actualPktCntDiff < 0
+ || actualPktCntDiff > expectedPktCntDiff) {
+ logWtf(TAG, "Impossible values for expectedPktCntDiff or" + " actualPktCntDiff");
+ return PACKET_LOSS_UNAVALAIBLE;
+ }
+
+ return 100 - (int) (actualPktCntDiff * 100 / expectedPktCntDiff);
+ }
+ }
+
+ private static void logVIpSecTransform(
+ String transformTag, IpSecTransformState state, String logPrefix) {
+ final String stateString =
+ " seqNo: "
+ + state.getRxHighestSequenceNumber()
+ + " | pktCnt: "
+ + state.getPacketCount()
+ + " | pktCntInWindow: "
+ + getPacketCntInReplayWindow(state);
+ logV(TAG, logPrefix + " " + transformTag + stateString);
+ }
+
+ /** Get the number of received packets within the replay window */
+ private static long getPacketCntInReplayWindow(@NonNull IpSecTransformState state) {
+ return BitSet.valueOf(state.getReplayBitmap()).cardinality();
+ }
+}
diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java b/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
new file mode 100644
index 0000000..a79f188
--- /dev/null
+++ b/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.vcn.routeselection;
+
+import static com.android.server.VcnManagementService.LOCAL_LOG;
+import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.IpSecTransform;
+import android.net.IpSecTransformState;
+import android.net.Network;
+import android.os.OutcomeReceiver;
+import android.util.CloseGuard;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.server.vcn.VcnContext;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * NetworkMetricMonitor is responsible for managing metric monitoring and tracking validation
+ * results.
+ *
+ * <p>This class is flag gated by "network_metric_monitor"
+ */
+public abstract class NetworkMetricMonitor implements AutoCloseable {
+ private static final String TAG = NetworkMetricMonitor.class.getSimpleName();
+
+ private static final boolean VDBG = false; // STOPSHIP: if true
+
+ @NonNull private final CloseGuard mCloseGuard = new CloseGuard();
+
+ @NonNull private final VcnContext mVcnContext;
+ @NonNull private final Network mNetwork;
+ @NonNull private final NetworkMetricMonitorCallback mCallback;
+
+ private boolean mIsSelectedUnderlyingNetwork;
+ private boolean mIsStarted;
+ private boolean mIsValidationFailed;
+
+ protected NetworkMetricMonitor(
+ @NonNull VcnContext vcnContext,
+ @NonNull Network network,
+ @Nullable PersistableBundleWrapper carrierConfig,
+ @NonNull NetworkMetricMonitorCallback callback)
+ throws IllegalAccessException {
+ if (!vcnContext.isFlagNetworkMetricMonitorEnabled()) {
+ // Caller error
+ logWtf("networkMetricMonitor flag disabled");
+ throw new IllegalAccessException("networkMetricMonitor flag disabled");
+ }
+
+ mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext");
+ mNetwork = Objects.requireNonNull(network, "Missing network");
+ mCallback = Objects.requireNonNull(callback, "Missing callback");
+
+ mIsSelectedUnderlyingNetwork = false;
+ mIsStarted = false;
+ mIsValidationFailed = false;
+ }
+
+ /** Callback to notify caller of the validation result */
+ public interface NetworkMetricMonitorCallback {
+ /** Called when there is a validation result is ready */
+ void onValidationResultReceived();
+ }
+
+ /**
+ * Start monitoring
+ *
+ * <p>This method might be called on a an already started monitor for updating monitor
+ * properties (e.g. IpSecTransform, carrier config)
+ *
+ * <p>Subclasses MUST call super.start() when overriding this method
+ */
+ protected void start() {
+ mIsStarted = true;
+ }
+
+ /**
+ * Stop monitoring
+ *
+ * <p>Subclasses MUST call super.stop() when overriding this method
+ */
+ public void stop() {
+ mIsValidationFailed = false;
+ mIsStarted = false;
+ }
+
+ /** Called by the subclasses when the validation result is ready */
+ protected void onValidationResultReceivedInternal(boolean isFailed) {
+ mIsValidationFailed = isFailed;
+ mCallback.onValidationResultReceived();
+ }
+
+ /** Called when the underlying network changes to selected or unselected */
+ protected abstract void onSelectedUnderlyingNetworkChanged();
+
+ /**
+ * Mark the network being monitored selected or unselected
+ *
+ * <p>Subclasses MUST call super when overriding this method
+ */
+ public void setIsSelectedUnderlyingNetwork(boolean isSelectedUnderlyingNetwork) {
+ if (mIsSelectedUnderlyingNetwork == isSelectedUnderlyingNetwork) {
+ return;
+ }
+
+ mIsSelectedUnderlyingNetwork = isSelectedUnderlyingNetwork;
+ onSelectedUnderlyingNetworkChanged();
+ }
+
+ /** Wrapper that allows injection for testing purposes */
+ @VisibleForTesting(visibility = Visibility.PROTECTED)
+ public static class IpSecTransformWrapper {
+ @NonNull public final IpSecTransform ipSecTransform;
+
+ public IpSecTransformWrapper(@NonNull IpSecTransform ipSecTransform) {
+ this.ipSecTransform = ipSecTransform;
+ }
+
+ /** Poll an IpSecTransformState */
+ public void getIpSecTransformState(
+ @NonNull Executor executor,
+ @NonNull OutcomeReceiver<IpSecTransformState, RuntimeException> callback) {
+ ipSecTransform.getIpSecTransformState(executor, callback);
+ }
+
+ /** Close this instance and release the underlying resources */
+ public void close() {
+ ipSecTransform.close();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(ipSecTransform);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof IpSecTransformWrapper)) {
+ return false;
+ }
+
+ final IpSecTransformWrapper other = (IpSecTransformWrapper) o;
+
+ return Objects.equals(ipSecTransform, other.ipSecTransform);
+ }
+ }
+
+ /** Set the IpSecTransform that applied to the Network being monitored */
+ public void setInboundTransform(@NonNull IpSecTransform inTransform) {
+ setInboundTransformInternal(new IpSecTransformWrapper(inTransform));
+ }
+
+ /**
+ * Set the IpSecTransform that applied to the Network being monitored *
+ *
+ * <p>Subclasses MUST call super when overriding this method
+ */
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public void setInboundTransformInternal(@NonNull IpSecTransformWrapper inTransform) {
+ // Subclasses MUST override it if they care
+ }
+
+ /** Update the carrierconfig */
+ public void setCarrierConfig(@Nullable PersistableBundleWrapper carrierConfig) {
+ // Subclasses MUST override it if they care
+ }
+
+ public boolean isValidationFailed() {
+ return mIsValidationFailed;
+ }
+
+ public boolean isSelectedUnderlyingNetwork() {
+ return mIsSelectedUnderlyingNetwork;
+ }
+
+ public boolean isStarted() {
+ return mIsStarted;
+ }
+
+ @NonNull
+ public VcnContext getVcnContext() {
+ return mVcnContext;
+ }
+
+ // Override methods for AutoCloseable. Subclasses MUST call super when overriding this method
+ @Override
+ public void close() {
+ mCloseGuard.close();
+
+ stop();
+ }
+
+ // Override #finalize() to use closeGuard for flagging that #close() was not called
+ @SuppressWarnings("Finalize")
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private String getClassName() {
+ return this.getClass().getSimpleName();
+ }
+
+ protected String getLogPrefix() {
+ return " [Network " + mNetwork + "] ";
+ }
+
+ protected void logV(String msg) {
+ if (VDBG) {
+ Slog.v(getClassName(), getLogPrefix() + msg);
+ LOCAL_LOG.log("[VERBOSE ] " + getClassName() + getLogPrefix() + msg);
+ }
+ }
+
+ protected void logInfo(String msg) {
+ Slog.i(getClassName(), getLogPrefix() + msg);
+ LOCAL_LOG.log("[INFO ] " + getClassName() + getLogPrefix() + msg);
+ }
+
+ protected void logW(String msg) {
+ Slog.w(getClassName(), getLogPrefix() + msg);
+ LOCAL_LOG.log("[WARN ] " + getClassName() + getLogPrefix() + msg);
+ }
+
+ protected void logWtf(String msg) {
+ Slog.wtf(getClassName(), getLogPrefix() + msg);
+ LOCAL_LOG.log("[WTF ] " + getClassName() + getLogPrefix() + msg);
+ }
+
+ protected static void logV(String className, String msgWithPrefix) {
+ if (VDBG) {
+ Slog.wtf(className, msgWithPrefix);
+ LOCAL_LOG.log("[VERBOSE ] " + className + msgWithPrefix);
+ }
+ }
+
+ protected static void logWtf(String className, String msgWithPrefix) {
+ Slog.wtf(className, msgWithPrefix);
+ LOCAL_LOG.log("[WTF ] " + className + msgWithPrefix);
+ }
+}
diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
index 7f129ea..d32e5cc 100644
--- a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
+++ b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
@@ -47,7 +47,6 @@
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Set;
/** @hide */
@@ -86,7 +85,6 @@
* <p>VCN MUST never select a non-INTERNET network that are unvalidated or fail to match any
* template as the underlying network.
*/
- @VisibleForTesting(visibility = Visibility.PRIVATE)
static final int PRIORITY_INVALID = -1;
/** Gives networks a priority class, based on configured VcnGatewayConnectionConfig */
@@ -96,7 +94,7 @@
List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
ParcelUuid subscriptionGroup,
TelephonySubscriptionSnapshot snapshot,
- UnderlyingNetworkRecord currentlySelected,
+ boolean isSelected,
PersistableBundleWrapper carrierConfig) {
// mRouteSelectionNetworkRequest requires a network be both VALIDATED and NOT_SUSPENDED
@@ -118,7 +116,7 @@
networkRecord,
subscriptionGroup,
snapshot,
- currentlySelected,
+ isSelected,
carrierConfig)) {
return priorityIndex;
}
@@ -140,12 +138,9 @@
UnderlyingNetworkRecord networkRecord,
ParcelUuid subscriptionGroup,
TelephonySubscriptionSnapshot snapshot,
- UnderlyingNetworkRecord currentlySelected,
+ boolean isSelected,
PersistableBundleWrapper carrierConfig) {
final NetworkCapabilities caps = networkRecord.networkCapabilities;
- final boolean isSelectedUnderlyingNetwork =
- currentlySelected != null
- && Objects.equals(currentlySelected.network, networkRecord.network);
final int meteredMatch = networkPriority.getMetered();
final boolean isMetered = !caps.hasCapability(NET_CAPABILITY_NOT_METERED);
@@ -159,7 +154,7 @@
if (caps.getLinkUpstreamBandwidthKbps() < networkPriority.getMinExitUpstreamBandwidthKbps()
|| (caps.getLinkUpstreamBandwidthKbps()
< networkPriority.getMinEntryUpstreamBandwidthKbps()
- && !isSelectedUnderlyingNetwork)) {
+ && !isSelected)) {
return false;
}
@@ -167,7 +162,7 @@
< networkPriority.getMinExitDownstreamBandwidthKbps()
|| (caps.getLinkDownstreamBandwidthKbps()
< networkPriority.getMinEntryDownstreamBandwidthKbps()
- && !isSelectedUnderlyingNetwork)) {
+ && !isSelected)) {
return false;
}
@@ -191,7 +186,7 @@
return checkMatchesWifiPriorityRule(
(VcnWifiUnderlyingNetworkTemplate) networkPriority,
networkRecord,
- currentlySelected,
+ isSelected,
carrierConfig);
}
@@ -214,7 +209,7 @@
public static boolean checkMatchesWifiPriorityRule(
VcnWifiUnderlyingNetworkTemplate networkPriority,
UnderlyingNetworkRecord networkRecord,
- UnderlyingNetworkRecord currentlySelected,
+ boolean isSelected,
PersistableBundleWrapper carrierConfig) {
final NetworkCapabilities caps = networkRecord.networkCapabilities;
@@ -223,7 +218,7 @@
}
// TODO: Move the Network Quality check to the network metric monitor framework.
- if (!isWifiRssiAcceptable(networkRecord, currentlySelected, carrierConfig)) {
+ if (!isWifiRssiAcceptable(networkRecord, isSelected, carrierConfig)) {
return false;
}
@@ -237,15 +232,11 @@
private static boolean isWifiRssiAcceptable(
UnderlyingNetworkRecord networkRecord,
- UnderlyingNetworkRecord currentlySelected,
+ boolean isSelected,
PersistableBundleWrapper carrierConfig) {
final NetworkCapabilities caps = networkRecord.networkCapabilities;
- final boolean isSelectedNetwork =
- currentlySelected != null
- && networkRecord.network.equals(currentlySelected.network);
- if (isSelectedNetwork
- && caps.getSignalStrength() >= getWifiExitRssiThreshold(carrierConfig)) {
+ if (isSelected && caps.getSignalStrength() >= getWifiExitRssiThreshold(carrierConfig)) {
return true;
}
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
index 6afa795..3f8d39e 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
@@ -30,6 +30,7 @@
import android.annotation.Nullable;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
+import android.net.IpSecTransform;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
@@ -48,9 +49,11 @@
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
import com.android.server.vcn.VcnContext;
+import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.NetworkEvaluatorCallback;
import com.android.server.vcn.util.LogUtils;
import java.util.ArrayList;
@@ -83,6 +86,9 @@
@NonNull private final TelephonyCallback mActiveDataSubIdListener =
new VcnActiveDataSubscriptionIdListener();
+ private final Map<Network, UnderlyingNetworkEvaluator> mUnderlyingNetworkRecords =
+ new ArrayMap<>();
+
@NonNull private final List<NetworkCallback> mCellBringupCallbacks = new ArrayList<>();
@Nullable private NetworkCallback mWifiBringupCallback;
@Nullable private NetworkCallback mWifiEntryRssiThresholdCallback;
@@ -105,7 +111,8 @@
this(vcnContext, connectionConfig, subscriptionGroup, snapshot, cb, new Dependencies());
}
- private UnderlyingNetworkController(
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ UnderlyingNetworkController(
@NonNull VcnContext vcnContext,
@NonNull VcnGatewayConnectionConfig connectionConfig,
@NonNull ParcelUuid subscriptionGroup,
@@ -197,6 +204,15 @@
List<NetworkCallback> oldCellCallbacks = new ArrayList<>(mCellBringupCallbacks);
mCellBringupCallbacks.clear();
+ if (mVcnContext.isFlagNetworkMetricMonitorEnabled()
+ && mVcnContext.isFlagIpSecTransformStateEnabled()) {
+ for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) {
+ evaluator.close();
+ }
+ }
+
+ mUnderlyingNetworkRecords.clear();
+
// Register new callbacks. Make-before-break; always register new callbacks before removal
// of old callbacks
if (!mIsQuitting) {
@@ -395,15 +411,58 @@
// Update carrier config
mCarrierConfig = mLastSnapshot.getCarrierConfigForSubGrp(mSubscriptionGroup);
+ // Make sure all evaluators use the same updated TelephonySubscriptionSnapshot and carrier
+ // config to calculate their cached priority classes. For simplicity, the
+ // UnderlyingNetworkController does not listen for changes in VCN-related carrier config
+ // keys, and changes are applied at restart of the VcnGatewayConnection
+ for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) {
+ evaluator.reevaluate(
+ mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
+ mSubscriptionGroup,
+ mLastSnapshot,
+ mCarrierConfig);
+ }
+
// Only trigger re-registration if subIds in this group have changed
if (oldSnapshot
.getAllSubIdsInGroup(mSubscriptionGroup)
.equals(newSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))) {
+
+ if (mVcnContext.isFlagNetworkMetricMonitorEnabled()
+ && mVcnContext.isFlagIpSecTransformStateEnabled()) {
+ reevaluateNetworks();
+ }
return;
}
registerOrUpdateNetworkRequests();
}
+ /**
+ * Pass the IpSecTransform of the VCN to UnderlyingNetworkController for metric monitoring
+ *
+ * <p>Caller MUST call it when IpSecTransforms have been created for VCN creation or migration
+ */
+ public void updateInboundTransform(
+ @NonNull UnderlyingNetworkRecord currentNetwork, @NonNull IpSecTransform transform) {
+ if (!mVcnContext.isFlagNetworkMetricMonitorEnabled()
+ || !mVcnContext.isFlagIpSecTransformStateEnabled()) {
+ logWtf("#updateInboundTransform: unexpected call; flags missing");
+ return;
+ }
+
+ Objects.requireNonNull(currentNetwork, "currentNetwork is null");
+ Objects.requireNonNull(transform, "transform is null");
+
+ if (mCurrentRecord == null
+ || mRouteSelectionCallback == null
+ || !Objects.equals(currentNetwork.network, mCurrentRecord.network)) {
+ // The caller (VcnGatewayConnection) is out-of-dated. Ignore this call.
+ return;
+ }
+
+ mUnderlyingNetworkRecords.get(mCurrentRecord.network).setInboundTransform(transform);
+ }
+
/** Tears down this Tracker, and releases all underlying network requests. */
public void teardown() {
mVcnContext.ensureRunningOnLooperThread();
@@ -418,32 +477,62 @@
.unregisterTelephonyCallback(mActiveDataSubIdListener);
}
+ private TreeSet<UnderlyingNetworkEvaluator> getSortedUnderlyingNetworks() {
+ TreeSet<UnderlyingNetworkEvaluator> sorted =
+ new TreeSet<>(UnderlyingNetworkEvaluator.getComparator(mVcnContext));
+
+ for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) {
+ if (evaluator.getPriorityClass() != NetworkPriorityClassifier.PRIORITY_INVALID) {
+ sorted.add(evaluator);
+ }
+ }
+
+ return sorted;
+ }
+
private void reevaluateNetworks() {
if (mIsQuitting || mRouteSelectionCallback == null) {
return; // UnderlyingNetworkController has quit.
}
- TreeSet<UnderlyingNetworkRecord> sorted =
- mRouteSelectionCallback.getSortedUnderlyingNetworks();
- UnderlyingNetworkRecord candidate = sorted.isEmpty() ? null : sorted.first();
+ TreeSet<UnderlyingNetworkEvaluator> sorted = getSortedUnderlyingNetworks();
+
+ UnderlyingNetworkEvaluator candidateEvaluator = sorted.isEmpty() ? null : sorted.first();
+ UnderlyingNetworkRecord candidate =
+ candidateEvaluator == null ? null : candidateEvaluator.getNetworkRecord();
if (Objects.equals(mCurrentRecord, candidate)) {
return;
}
String allNetworkPriorities = "";
- for (UnderlyingNetworkRecord record : sorted) {
+ for (UnderlyingNetworkEvaluator recordEvaluator : sorted) {
if (!allNetworkPriorities.isEmpty()) {
allNetworkPriorities += ", ";
}
- allNetworkPriorities += record.network + ": " + record.priorityClass;
+ allNetworkPriorities +=
+ recordEvaluator.getNetwork() + ": " + recordEvaluator.getPriorityClass();
}
- logInfo(
- "Selected network changed to "
- + (candidate == null ? null : candidate.network)
- + ", selected from list: "
- + allNetworkPriorities);
+
+ if (!UnderlyingNetworkRecord.isSameNetwork(mCurrentRecord, candidate)) {
+ logInfo(
+ "Selected network changed to "
+ + (candidate == null ? null : candidate.network)
+ + ", selected from list: "
+ + allNetworkPriorities);
+ }
+
mCurrentRecord = candidate;
mCb.onSelectedUnderlyingNetworkChanged(mCurrentRecord);
+
+ // Need to update all evaluators to ensure the previously selected one is unselected
+ for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) {
+ evaluator.setIsSelected(
+ candidateEvaluator == evaluator,
+ mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
+ mSubscriptionGroup,
+ mLastSnapshot,
+ mCarrierConfig);
+ }
}
/**
@@ -463,46 +552,32 @@
*/
@VisibleForTesting
class UnderlyingNetworkListener extends NetworkCallback {
- private final Map<Network, UnderlyingNetworkRecord.Builder>
- mUnderlyingNetworkRecordBuilders = new ArrayMap<>();
-
UnderlyingNetworkListener() {
super(NetworkCallback.FLAG_INCLUDE_LOCATION_INFO);
}
- private TreeSet<UnderlyingNetworkRecord> getSortedUnderlyingNetworks() {
- TreeSet<UnderlyingNetworkRecord> sorted =
- new TreeSet<>(UnderlyingNetworkRecord.getComparator());
-
- for (UnderlyingNetworkRecord.Builder builder :
- mUnderlyingNetworkRecordBuilders.values()) {
- if (builder.isValid()) {
- final UnderlyingNetworkRecord record =
- builder.build(
- mVcnContext,
- mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
- mSubscriptionGroup,
- mLastSnapshot,
- mCurrentRecord,
- mCarrierConfig);
- if (record.priorityClass != NetworkPriorityClassifier.PRIORITY_INVALID) {
- sorted.add(record);
- }
- }
- }
-
- return sorted;
- }
-
@Override
public void onAvailable(@NonNull Network network) {
- mUnderlyingNetworkRecordBuilders.put(
- network, new UnderlyingNetworkRecord.Builder(network));
+ mUnderlyingNetworkRecords.put(
+ network,
+ mDeps.newUnderlyingNetworkEvaluator(
+ mVcnContext,
+ network,
+ mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
+ mSubscriptionGroup,
+ mLastSnapshot,
+ mCarrierConfig,
+ new NetworkEvaluatorCallbackImpl()));
}
@Override
public void onLost(@NonNull Network network) {
- mUnderlyingNetworkRecordBuilders.remove(network);
+ if (mVcnContext.isFlagNetworkMetricMonitorEnabled()
+ && mVcnContext.isFlagIpSecTransformStateEnabled()) {
+ mUnderlyingNetworkRecords.get(network).close();
+ }
+
+ mUnderlyingNetworkRecords.remove(network);
reevaluateNetworks();
}
@@ -510,15 +585,20 @@
@Override
public void onCapabilitiesChanged(
@NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) {
- final UnderlyingNetworkRecord.Builder builder =
- mUnderlyingNetworkRecordBuilders.get(network);
- if (builder == null) {
+ final UnderlyingNetworkEvaluator evaluator = mUnderlyingNetworkRecords.get(network);
+ if (evaluator == null) {
logWtf("Got capabilities change for unknown key: " + network);
return;
}
- builder.setNetworkCapabilities(networkCapabilities);
- if (builder.isValid()) {
+ evaluator.setNetworkCapabilities(
+ networkCapabilities,
+ mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
+ mSubscriptionGroup,
+ mLastSnapshot,
+ mCarrierConfig);
+
+ if (evaluator.isValid()) {
reevaluateNetworks();
}
}
@@ -526,35 +606,60 @@
@Override
public void onLinkPropertiesChanged(
@NonNull Network network, @NonNull LinkProperties linkProperties) {
- final UnderlyingNetworkRecord.Builder builder =
- mUnderlyingNetworkRecordBuilders.get(network);
- if (builder == null) {
+ final UnderlyingNetworkEvaluator evaluator = mUnderlyingNetworkRecords.get(network);
+ if (evaluator == null) {
logWtf("Got link properties change for unknown key: " + network);
return;
}
- builder.setLinkProperties(linkProperties);
- if (builder.isValid()) {
+ evaluator.setLinkProperties(
+ linkProperties,
+ mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
+ mSubscriptionGroup,
+ mLastSnapshot,
+ mCarrierConfig);
+
+ if (evaluator.isValid()) {
reevaluateNetworks();
}
}
@Override
public void onBlockedStatusChanged(@NonNull Network network, boolean isBlocked) {
- final UnderlyingNetworkRecord.Builder builder =
- mUnderlyingNetworkRecordBuilders.get(network);
- if (builder == null) {
+ final UnderlyingNetworkEvaluator evaluator = mUnderlyingNetworkRecords.get(network);
+ if (evaluator == null) {
logWtf("Got blocked status change for unknown key: " + network);
return;
}
- builder.setIsBlocked(isBlocked);
- if (builder.isValid()) {
+ evaluator.setIsBlocked(
+ isBlocked,
+ mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
+ mSubscriptionGroup,
+ mLastSnapshot,
+ mCarrierConfig);
+
+ if (evaluator.isValid()) {
reevaluateNetworks();
}
}
}
+ @VisibleForTesting
+ class NetworkEvaluatorCallbackImpl implements NetworkEvaluatorCallback {
+ @Override
+ public void onEvaluationResultChanged() {
+ if (!mVcnContext.isFlagNetworkMetricMonitorEnabled()
+ || !mVcnContext.isFlagIpSecTransformStateEnabled()) {
+ logWtf("#onEvaluationResultChanged: unexpected call; flags missing");
+ return;
+ }
+
+ mVcnContext.ensureRunningOnLooperThread();
+ reevaluateNetworks();
+ }
+ }
+
private String getLogPrefix() {
return "("
+ LogUtils.getHashedSubscriptionGroup(mSubscriptionGroup)
@@ -614,16 +719,8 @@
pw.println("Underlying networks:");
pw.increaseIndent();
if (mRouteSelectionCallback != null) {
- for (UnderlyingNetworkRecord record :
- mRouteSelectionCallback.getSortedUnderlyingNetworks()) {
- record.dump(
- mVcnContext,
- pw,
- mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
- mSubscriptionGroup,
- mLastSnapshot,
- mCurrentRecord,
- mCarrierConfig);
+ for (UnderlyingNetworkEvaluator recordEvaluator : getSortedUnderlyingNetworks()) {
+ recordEvaluator.dump(pw);
}
}
pw.decreaseIndent();
@@ -653,5 +750,24 @@
@Nullable UnderlyingNetworkRecord underlyingNetworkRecord);
}
- private static class Dependencies {}
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public static class Dependencies {
+ public UnderlyingNetworkEvaluator newUnderlyingNetworkEvaluator(
+ @NonNull VcnContext vcnContext,
+ @NonNull Network network,
+ @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+ @NonNull ParcelUuid subscriptionGroup,
+ @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+ @Nullable PersistableBundleWrapper carrierConfig,
+ @NonNull NetworkEvaluatorCallback evaluatorCallback) {
+ return new UnderlyingNetworkEvaluator(
+ vcnContext,
+ network,
+ underlyingNetworkTemplates,
+ subscriptionGroup,
+ lastSnapshot,
+ carrierConfig,
+ evaluatorCallback);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
new file mode 100644
index 0000000..2f4cf5e
--- /dev/null
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
@@ -0,0 +1,442 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.vcn.routeselection;
+
+import static com.android.server.VcnManagementService.LOCAL_LOG;
+import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.IpSecTransform;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.vcn.VcnManager;
+import android.net.vcn.VcnUnderlyingNetworkTemplate;
+import android.os.Handler;
+import android.os.ParcelUuid;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+import com.android.server.vcn.VcnContext;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * UnderlyingNetworkEvaluator evaluates the quality and priority class of a network candidate for
+ * route selection.
+ *
+ * @hide
+ */
+public class UnderlyingNetworkEvaluator {
+ private static final String TAG = UnderlyingNetworkEvaluator.class.getSimpleName();
+
+ private static final int[] PENALTY_TIMEOUT_MINUTES_DEFAULT = new int[] {5};
+
+ @NonNull private final VcnContext mVcnContext;
+ @NonNull private final Handler mHandler;
+ @NonNull private final Object mCancellationToken = new Object();
+
+ @NonNull private final UnderlyingNetworkRecord.Builder mNetworkRecordBuilder;
+
+ @NonNull private final NetworkEvaluatorCallback mEvaluatorCallback;
+ @NonNull private final List<NetworkMetricMonitor> mMetricMonitors = new ArrayList<>();
+
+ @NonNull private final Dependencies mDependencies;
+
+ // TODO: Support back-off timeouts
+ private long mPenalizedTimeoutMs;
+
+ private boolean mIsSelected;
+ private boolean mIsPenalized;
+ private int mPriorityClass = NetworkPriorityClassifier.PRIORITY_INVALID;
+
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public UnderlyingNetworkEvaluator(
+ @NonNull VcnContext vcnContext,
+ @NonNull Network network,
+ @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+ @NonNull ParcelUuid subscriptionGroup,
+ @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+ @Nullable PersistableBundleWrapper carrierConfig,
+ @NonNull NetworkEvaluatorCallback evaluatorCallback,
+ @NonNull Dependencies dependencies) {
+ mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext");
+ mHandler = new Handler(mVcnContext.getLooper());
+
+ mDependencies = Objects.requireNonNull(dependencies, "Missing dependencies");
+ mEvaluatorCallback = Objects.requireNonNull(evaluatorCallback, "Missing deps");
+
+ Objects.requireNonNull(underlyingNetworkTemplates, "Missing underlyingNetworkTemplates");
+ Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
+ Objects.requireNonNull(lastSnapshot, "Missing lastSnapshot");
+
+ mNetworkRecordBuilder =
+ new UnderlyingNetworkRecord.Builder(
+ Objects.requireNonNull(network, "Missing network"));
+ mIsSelected = false;
+ mIsPenalized = false;
+ mPenalizedTimeoutMs = getPenaltyTimeoutMs(carrierConfig);
+
+ updatePriorityClass(
+ underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
+
+ if (isIpSecPacketLossDetectorEnabled()) {
+ try {
+ mMetricMonitors.add(
+ mDependencies.newIpSecPacketLossDetector(
+ mVcnContext,
+ mNetworkRecordBuilder.getNetwork(),
+ carrierConfig,
+ new MetricMonitorCallbackImpl()));
+ } catch (IllegalAccessException e) {
+ // No action. Do not add anything to mMetricMonitors
+ }
+ }
+ }
+
+ public UnderlyingNetworkEvaluator(
+ @NonNull VcnContext vcnContext,
+ @NonNull Network network,
+ @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+ @NonNull ParcelUuid subscriptionGroup,
+ @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+ @Nullable PersistableBundleWrapper carrierConfig,
+ @NonNull NetworkEvaluatorCallback evaluatorCallback) {
+ this(
+ vcnContext,
+ network,
+ underlyingNetworkTemplates,
+ subscriptionGroup,
+ lastSnapshot,
+ carrierConfig,
+ evaluatorCallback,
+ new Dependencies());
+ }
+
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public static class Dependencies {
+ /** Get an IpSecPacketLossDetector instance */
+ public IpSecPacketLossDetector newIpSecPacketLossDetector(
+ @NonNull VcnContext vcnContext,
+ @NonNull Network network,
+ @Nullable PersistableBundleWrapper carrierConfig,
+ @NonNull NetworkMetricMonitor.NetworkMetricMonitorCallback callback)
+ throws IllegalAccessException {
+ return new IpSecPacketLossDetector(vcnContext, network, carrierConfig, callback);
+ }
+ }
+
+ /** Callback to notify caller to reevaluate network selection */
+ public interface NetworkEvaluatorCallback {
+ /**
+ * Called when mIsPenalized changed
+ *
+ * <p>When receiving this call, UnderlyingNetworkController should reevaluate all network
+ * candidates for VCN underlying network selection
+ */
+ void onEvaluationResultChanged();
+ }
+
+ private class MetricMonitorCallbackImpl
+ implements NetworkMetricMonitor.NetworkMetricMonitorCallback {
+ public void onValidationResultReceived() {
+ mVcnContext.ensureRunningOnLooperThread();
+
+ handleValidationResult();
+ }
+ }
+
+ private void updatePriorityClass(
+ @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+ @NonNull ParcelUuid subscriptionGroup,
+ @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+ @Nullable PersistableBundleWrapper carrierConfig) {
+ if (mNetworkRecordBuilder.isValid()) {
+ mPriorityClass =
+ NetworkPriorityClassifier.calculatePriorityClass(
+ mVcnContext,
+ mNetworkRecordBuilder.build(),
+ underlyingNetworkTemplates,
+ subscriptionGroup,
+ lastSnapshot,
+ mIsSelected,
+ carrierConfig);
+ } else {
+ mPriorityClass = NetworkPriorityClassifier.PRIORITY_INVALID;
+ }
+ }
+
+ private boolean isIpSecPacketLossDetectorEnabled() {
+ return isIpSecPacketLossDetectorEnabled(mVcnContext);
+ }
+
+ private static boolean isIpSecPacketLossDetectorEnabled(VcnContext vcnContext) {
+ return vcnContext.isFlagIpSecTransformStateEnabled()
+ && vcnContext.isFlagNetworkMetricMonitorEnabled();
+ }
+
+ /** Get the comparator for UnderlyingNetworkEvaluator */
+ public static Comparator<UnderlyingNetworkEvaluator> getComparator(VcnContext vcnContext) {
+ return (left, right) -> {
+ if (isIpSecPacketLossDetectorEnabled(vcnContext)) {
+ if (left.mIsPenalized != right.mIsPenalized) {
+ // A penalized network should have lower priority which means a larger index
+ return left.mIsPenalized ? 1 : -1;
+ }
+ }
+
+ final int leftIndex = left.mPriorityClass;
+ final int rightIndex = right.mPriorityClass;
+
+ // In the case of networks in the same priority class, prioritize based on other
+ // criteria (eg. actively selected network, link metrics, etc)
+ if (leftIndex == rightIndex) {
+ // TODO: Improve the strategy of network selection when both UnderlyingNetworkRecord
+ // fall into the same priority class.
+ if (left.mIsSelected) {
+ return -1;
+ }
+ if (right.mIsSelected) {
+ return 1;
+ }
+ }
+ return Integer.compare(leftIndex, rightIndex);
+ };
+ }
+
+ private static long getPenaltyTimeoutMs(@Nullable PersistableBundleWrapper carrierConfig) {
+ final int[] timeoutMinuteList;
+
+ if (carrierConfig != null) {
+ timeoutMinuteList =
+ carrierConfig.getIntArray(
+ VcnManager.VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY,
+ PENALTY_TIMEOUT_MINUTES_DEFAULT);
+ } else {
+ timeoutMinuteList = PENALTY_TIMEOUT_MINUTES_DEFAULT;
+ }
+
+ // TODO: Add the support of back-off timeouts and return the full list
+ return TimeUnit.MINUTES.toMillis(timeoutMinuteList[0]);
+ }
+
+ private void handleValidationResult() {
+ final boolean wasPenalized = mIsPenalized;
+ mIsPenalized = false;
+ for (NetworkMetricMonitor monitor : mMetricMonitors) {
+ mIsPenalized |= monitor.isValidationFailed();
+ }
+
+ if (wasPenalized == mIsPenalized) {
+ return;
+ }
+
+ logInfo(
+ "#handleValidationResult: wasPenalized "
+ + wasPenalized
+ + " mIsPenalized "
+ + mIsPenalized);
+
+ if (mIsPenalized) {
+ mHandler.postDelayed(
+ new ExitPenaltyBoxRunnable(), mCancellationToken, mPenalizedTimeoutMs);
+ } else {
+ // Exit the penalty box
+ mHandler.removeCallbacksAndEqualMessages(mCancellationToken);
+ }
+ mEvaluatorCallback.onEvaluationResultChanged();
+ }
+
+ public class ExitPenaltyBoxRunnable implements Runnable {
+ @Override
+ public void run() {
+ if (!mIsPenalized) {
+ logWtf("Evaluator not being penalized but ExitPenaltyBoxRunnable was scheduled");
+ return;
+ }
+
+ // TODO: There might be a future metric monitor (e.g. ping) that will require the
+ // validation to pass before exiting the penalty box.
+ mIsPenalized = false;
+ mEvaluatorCallback.onEvaluationResultChanged();
+ }
+ }
+
+ /** Set the NetworkCapabilities */
+ public void setNetworkCapabilities(
+ @NonNull NetworkCapabilities nc,
+ @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+ @NonNull ParcelUuid subscriptionGroup,
+ @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+ @Nullable PersistableBundleWrapper carrierConfig) {
+ mNetworkRecordBuilder.setNetworkCapabilities(nc);
+
+ updatePriorityClass(
+ underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
+ }
+
+ /** Set the LinkProperties */
+ public void setLinkProperties(
+ @NonNull LinkProperties lp,
+ @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+ @NonNull ParcelUuid subscriptionGroup,
+ @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+ @Nullable PersistableBundleWrapper carrierConfig) {
+ mNetworkRecordBuilder.setLinkProperties(lp);
+
+ updatePriorityClass(
+ underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
+ }
+
+ /** Set whether the network is blocked */
+ public void setIsBlocked(
+ boolean isBlocked,
+ @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+ @NonNull ParcelUuid subscriptionGroup,
+ @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+ @Nullable PersistableBundleWrapper carrierConfig) {
+ mNetworkRecordBuilder.setIsBlocked(isBlocked);
+
+ updatePriorityClass(
+ underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
+ }
+
+ /** Set whether the network is selected as VCN's underlying network */
+ public void setIsSelected(
+ boolean isSelected,
+ @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+ @NonNull ParcelUuid subscriptionGroup,
+ @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+ @Nullable PersistableBundleWrapper carrierConfig) {
+ mIsSelected = isSelected;
+
+ updatePriorityClass(
+ underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
+
+ for (NetworkMetricMonitor monitor : mMetricMonitors) {
+ monitor.setIsSelectedUnderlyingNetwork(isSelected);
+ }
+ }
+
+ /**
+ * Update the last TelephonySubscriptionSnapshot and carrier config to reevaluate the network
+ */
+ public void reevaluate(
+ @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+ @NonNull ParcelUuid subscriptionGroup,
+ @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+ @Nullable PersistableBundleWrapper carrierConfig) {
+ updatePriorityClass(
+ underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
+
+ // The already scheduled event will not be affected. The followup events will be scheduled
+ // with the new timeout
+ mPenalizedTimeoutMs = getPenaltyTimeoutMs(carrierConfig);
+
+ for (NetworkMetricMonitor monitor : mMetricMonitors) {
+ monitor.setCarrierConfig(carrierConfig);
+ }
+ }
+
+ /** Update the inbound IpSecTransform applied to the network */
+ public void setInboundTransform(@NonNull IpSecTransform transform) {
+ if (!mIsSelected) {
+ logWtf("setInboundTransform on an unselected evaluator");
+ return;
+ }
+
+ for (NetworkMetricMonitor monitor : mMetricMonitors) {
+ monitor.setInboundTransform(transform);
+ }
+ }
+
+ /** Close the evaluator and stop all the underlying network metric monitors */
+ public void close() {
+ mHandler.removeCallbacksAndEqualMessages(mCancellationToken);
+
+ for (NetworkMetricMonitor monitor : mMetricMonitors) {
+ monitor.close();
+ }
+ }
+
+ /** Return whether this network evaluator is valid */
+ public boolean isValid() {
+ return mNetworkRecordBuilder.isValid();
+ }
+
+ /** Return the network */
+ public Network getNetwork() {
+ return mNetworkRecordBuilder.getNetwork();
+ }
+
+ /** Return the network record */
+ public UnderlyingNetworkRecord getNetworkRecord() {
+ return mNetworkRecordBuilder.build();
+ }
+
+ /** Return the priority class for network selection */
+ public int getPriorityClass() {
+ return mPriorityClass;
+ }
+
+ /** Return whether the network is being penalized */
+ public boolean isPenalized() {
+ return mIsPenalized;
+ }
+
+ /** Dump the information of this instance */
+ public void dump(IndentingPrintWriter pw) {
+ pw.println("UnderlyingNetworkEvaluator:");
+ pw.increaseIndent();
+
+ if (mNetworkRecordBuilder.isValid()) {
+ getNetworkRecord().dump(pw);
+ } else {
+ pw.println(
+ "UnderlyingNetworkRecord incomplete: mNetwork: "
+ + mNetworkRecordBuilder.getNetwork());
+ }
+
+ pw.println("mIsSelected: " + mIsSelected);
+ pw.println("mPriorityClass: " + mPriorityClass);
+ pw.println("mIsPenalized: " + mIsPenalized);
+
+ pw.decreaseIndent();
+ }
+
+ private String getLogPrefix() {
+ return "[Network " + mNetworkRecordBuilder.getNetwork() + "] ";
+ }
+
+ private void logInfo(String msg) {
+ Slog.i(TAG, getLogPrefix() + msg);
+ LOCAL_LOG.log("[INFO ] " + TAG + getLogPrefix() + msg);
+ }
+
+ private void logWtf(String msg) {
+ Slog.wtf(TAG, getLogPrefix() + msg);
+ LOCAL_LOG.log("[WTF ] " + TAG + getLogPrefix() + msg);
+ }
+}
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
index aea9f4d..7ab8e55 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
@@ -16,24 +16,17 @@
package com.android.server.vcn.routeselection;
-import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
-import android.net.vcn.VcnUnderlyingNetworkTemplate;
-import android.os.ParcelUuid;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
-import com.android.server.vcn.VcnContext;
-import java.util.Comparator;
-import java.util.List;
import java.util.Objects;
/**
@@ -46,54 +39,17 @@
@NonNull public final NetworkCapabilities networkCapabilities;
@NonNull public final LinkProperties linkProperties;
public final boolean isBlocked;
- public final boolean isSelected;
- public final int priorityClass;
@VisibleForTesting(visibility = Visibility.PRIVATE)
public UnderlyingNetworkRecord(
@NonNull Network network,
@NonNull NetworkCapabilities networkCapabilities,
@NonNull LinkProperties linkProperties,
- boolean isBlocked,
- VcnContext vcnContext,
- List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
- ParcelUuid subscriptionGroup,
- TelephonySubscriptionSnapshot snapshot,
- UnderlyingNetworkRecord currentlySelected,
- PersistableBundleWrapper carrierConfig) {
+ boolean isBlocked) {
this.network = network;
this.networkCapabilities = networkCapabilities;
this.linkProperties = linkProperties;
this.isBlocked = isBlocked;
-
- this.isSelected = isSelected(this.network, currentlySelected);
-
- priorityClass =
- NetworkPriorityClassifier.calculatePriorityClass(
- vcnContext,
- this,
- underlyingNetworkTemplates,
- subscriptionGroup,
- snapshot,
- currentlySelected,
- carrierConfig);
- }
-
- @VisibleForTesting(visibility = Visibility.PRIVATE)
- public UnderlyingNetworkRecord(
- @NonNull Network network,
- @NonNull NetworkCapabilities networkCapabilities,
- @NonNull LinkProperties linkProperties,
- boolean isBlocked,
- boolean isSelected,
- int priorityClass) {
- this.network = network;
- this.networkCapabilities = networkCapabilities;
- this.linkProperties = linkProperties;
- this.isBlocked = isBlocked;
- this.isSelected = isSelected;
-
- this.priorityClass = priorityClass;
}
@Override
@@ -113,64 +69,20 @@
return Objects.hash(network, networkCapabilities, linkProperties, isBlocked);
}
- /** Returns if two records are equal including their priority classes. */
- public static boolean isEqualIncludingPriorities(
- UnderlyingNetworkRecord left, UnderlyingNetworkRecord right) {
- if (left != null && right != null) {
- return left.equals(right)
- && left.isSelected == right.isSelected
- && left.priorityClass == right.priorityClass;
- }
-
- return left == right;
- }
-
- static Comparator<UnderlyingNetworkRecord> getComparator() {
- return (left, right) -> {
- final int leftIndex = left.priorityClass;
- final int rightIndex = right.priorityClass;
-
- // In the case of networks in the same priority class, prioritize based on other
- // criteria (eg. actively selected network, link metrics, etc)
- if (leftIndex == rightIndex) {
- // TODO: Improve the strategy of network selection when both UnderlyingNetworkRecord
- // fall into the same priority class.
- if (left.isSelected) {
- return -1;
- }
- if (right.isSelected) {
- return 1;
- }
- }
- return Integer.compare(leftIndex, rightIndex);
- };
- }
-
- private static boolean isSelected(
- Network networkToCheck, UnderlyingNetworkRecord currentlySelected) {
- if (currentlySelected == null) {
- return false;
- }
- if (currentlySelected.network.equals(networkToCheck)) {
- return true;
- }
- return false;
+ /** Return whether two records represent the same network */
+ public static boolean isSameNetwork(
+ @Nullable UnderlyingNetworkRecord leftRecord,
+ @Nullable UnderlyingNetworkRecord rightRecord) {
+ final Network left = leftRecord == null ? null : leftRecord.network;
+ final Network right = rightRecord == null ? null : rightRecord.network;
+ return Objects.equals(left, right);
}
/** Dumps the state of this record for logging and debugging purposes. */
- void dump(
- VcnContext vcnContext,
- IndentingPrintWriter pw,
- List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
- ParcelUuid subscriptionGroup,
- TelephonySubscriptionSnapshot snapshot,
- UnderlyingNetworkRecord currentlySelected,
- PersistableBundleWrapper carrierConfig) {
+ void dump(IndentingPrintWriter pw) {
pw.println("UnderlyingNetworkRecord:");
pw.increaseIndent();
- pw.println("priorityClass: " + priorityClass);
- pw.println("isSelected: " + isSelected);
pw.println("mNetwork: " + network);
pw.println("mNetworkCapabilities: " + networkCapabilities);
pw.println("mLinkProperties: " + linkProperties);
@@ -218,29 +130,14 @@
return mNetworkCapabilities != null && mLinkProperties != null && mWasIsBlockedSet;
}
- UnderlyingNetworkRecord build(
- VcnContext vcnContext,
- List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
- ParcelUuid subscriptionGroup,
- TelephonySubscriptionSnapshot snapshot,
- UnderlyingNetworkRecord currentlySelected,
- PersistableBundleWrapper carrierConfig) {
+ UnderlyingNetworkRecord build() {
if (!isValid()) {
throw new IllegalArgumentException(
"Called build before UnderlyingNetworkRecord was valid");
}
return new UnderlyingNetworkRecord(
- mNetwork,
- mNetworkCapabilities,
- mLinkProperties,
- mIsBlocked,
- vcnContext,
- underlyingNetworkTemplates,
- subscriptionGroup,
- snapshot,
- currentlySelected,
- carrierConfig);
+ mNetwork, mNetworkCapabilities, mLinkProperties, mIsBlocked);
}
}
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index b773ade..51acc8e0 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -16,21 +16,28 @@
package com.android.server.wallpaper;
+import static android.app.WallpaperManager.getOrientation;
+import static android.app.WallpaperManager.getRotatedOrientation;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.server.wallpaper.WallpaperUtils.RECORD_FILE;
import static com.android.server.wallpaper.WallpaperUtils.RECORD_LOCK_FILE;
import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER;
import static com.android.server.wallpaper.WallpaperUtils.getWallpaperDir;
+import static com.android.window.flags.Flags.multiCrop;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageDecoder;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.FileUtils;
import android.os.SELinux;
+import android.text.TextUtils;
import android.util.Slog;
+import android.util.SparseArray;
import android.view.DisplayInfo;
+import android.view.View;
import com.android.server.utils.TimingsTraceAndSlog;
@@ -39,28 +46,334 @@
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
/**
* Helper file for wallpaper cropping
- * Meant to have a single instance, only used by the WallpaperManagerService
+ * Meant to have a single instance, only used internally by system_server
+ * @hide
*/
-class WallpaperCropper {
+public class WallpaperCropper {
private static final String TAG = WallpaperCropper.class.getSimpleName();
private static final boolean DEBUG = false;
private static final boolean DEBUG_CROP = true;
+ /**
+ * Maximum acceptable parallax.
+ * A value of 1 means "the additional width for parallax is at most 100% of the screen width"
+ */
+ private static final float MAX_PARALLAX = 1f;
+
+ /**
+ * We define three ways to adjust a crop. These modes are used depending on the situation:
+ * - When going from unfolded to folded, we want to remove content
+ * - When going from folded to unfolded, we want to add content
+ * - For a screen rotation, we want to keep the same amount of content
+ */
+ private static final int ADD = 1;
+ private static final int REMOVE = 2;
+ private static final int BALANCE = 3;
+
+
private final WallpaperDisplayHelper mWallpaperDisplayHelper;
+ /**
+ * Helpers exposed to the window manager part (WallpaperController)
+ */
+ public interface WallpaperCropUtils {
+
+ /**
+ * Equivalent to {@link #getCrop(Point, Point, SparseArray, boolean)}
+ */
+ Rect getCrop(Point displaySize, Point bitmapSize,
+ SparseArray<Rect> suggestedCrops, boolean rtl);
+ }
+
WallpaperCropper(WallpaperDisplayHelper wallpaperDisplayHelper) {
mWallpaperDisplayHelper = wallpaperDisplayHelper;
}
/**
- * Once a new wallpaper has been written via setWallpaper(...), it needs to be cropped
- * for display.
+ * Given the dimensions of the original wallpaper image, some optional suggested crops
+ * (either defined by the user, or coming from a backup), and whether the device is RTL,
+ * generate a crop for the current display. This is done through the following process:
+ * <ul>
+ * <li> If no suggested crops are provided, center the full image on the display. </li>
+ * <li> If there is a suggested crop the given displaySize, reuse the suggested crop and
+ * adjust it using {@link #getAdjustedCrop}. </li>
+ * <li> If there are suggested crops, but not for the orientation of the given displaySize,
+ * reuse one of the suggested crop for another orientation and adjust if using
+ * {@link #getAdjustedCrop}. </li>
+ * </ul>
*
- * This will generate the crop and write it in the file
+ * @param displaySize The dimensions of the surface where we want to render the wallpaper
+ * @param bitmapSize The dimensions of the wallpaper bitmap
+ * @param rtl Whether the device is right-to-left
+ * @param suggestedCrops An optional list of user-defined crops for some orientations.
+ * If there is a suggested crop for
+ *
+ * @return A Rect indicating how to crop the bitmap for the current display.
+ */
+ public Rect getCrop(Point displaySize, Point bitmapSize,
+ SparseArray<Rect> suggestedCrops, boolean rtl) {
+
+ // Case 1: if no crops are provided, center align the full image
+ if (suggestedCrops == null || suggestedCrops.size() == 0) {
+ Rect crop = new Rect(0, 0, displaySize.x, displaySize.y);
+ float scale = Math.min(
+ ((float) bitmapSize.x) / displaySize.x,
+ ((float) bitmapSize.y) / displaySize.y);
+ crop.scale(scale);
+ crop.offset((bitmapSize.x - crop.width()) / 2,
+ (bitmapSize.y - crop.height()) / 2);
+ return crop;
+ }
+ int orientation = getOrientation(displaySize);
+
+ // Case 2: if the orientation exists in the suggested crops, adjust the suggested crop
+ Rect suggestedCrop = suggestedCrops.get(orientation);
+ if (suggestedCrop != null) {
+ if (suggestedCrop.left < 0 || suggestedCrop.top < 0
+ || suggestedCrop.right > bitmapSize.x || suggestedCrop.bottom > bitmapSize.y) {
+ Slog.w(TAG, "invalid suggested crop: " + suggestedCrop);
+ Rect fullImage = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+ return getAdjustedCrop(fullImage, bitmapSize, displaySize, true, rtl, ADD);
+ } else {
+ return getAdjustedCrop(suggestedCrop, bitmapSize, displaySize, true, rtl, ADD);
+ }
+ }
+
+ // Case 3: if we have the 90° rotated orientation in the suggested crops, reuse it and
+ // trying to preserve the zoom level and the center of the image
+ SparseArray<Point> defaultDisplaySizes = mWallpaperDisplayHelper.getDefaultDisplaySizes();
+ int rotatedOrientation = getRotatedOrientation(orientation);
+ suggestedCrop = suggestedCrops.get(rotatedOrientation);
+ Point suggestedDisplaySize = defaultDisplaySizes.get(rotatedOrientation);
+ if (suggestedCrop != null) {
+ // only keep the visible part (without parallax)
+ Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl);
+ return getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, BALANCE);
+ }
+
+ // Case 4: if the device is a foldable, if we're looking for a folded orientation and have
+ // the suggested crop of the relative unfolded orientation, reuse it by removing content.
+ int unfoldedOrientation = mWallpaperDisplayHelper.getUnfoldedOrientation(orientation);
+ suggestedCrop = suggestedCrops.get(unfoldedOrientation);
+ suggestedDisplaySize = defaultDisplaySizes.get(unfoldedOrientation);
+ if (suggestedCrop != null) {
+ // only keep the visible part (without parallax)
+ Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl);
+ return getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, REMOVE);
+ }
+
+ // Case 5: if the device is a foldable, if we're looking for an unfolded orientation and
+ // have the suggested crop of the relative folded orientation, reuse it by adding content.
+ int foldedOrientation = mWallpaperDisplayHelper.getFoldedOrientation(orientation);
+ suggestedCrop = suggestedCrops.get(foldedOrientation);
+ suggestedDisplaySize = defaultDisplaySizes.get(foldedOrientation);
+ if (suggestedCrop != null) {
+ // only keep the visible part (without parallax)
+ Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl);
+ return getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, ADD);
+ }
+
+ // Case 6: for a foldable device, try to combine case 3 + case 4 or 5:
+ // rotate, then fold or unfold
+ Point rotatedDisplaySize = defaultDisplaySizes.get(rotatedOrientation);
+ if (rotatedDisplaySize != null) {
+ int rotatedFolded = mWallpaperDisplayHelper.getFoldedOrientation(rotatedOrientation);
+ int rotateUnfolded = mWallpaperDisplayHelper.getUnfoldedOrientation(rotatedOrientation);
+ for (int suggestedOrientation : new int[]{rotatedFolded, rotateUnfolded}) {
+ suggestedCrop = suggestedCrops.get(suggestedOrientation);
+ if (suggestedCrop != null) {
+ Rect rotatedCrop = getCrop(rotatedDisplaySize, bitmapSize, suggestedCrops, rtl);
+ SparseArray<Rect> rotatedCropMap = new SparseArray<>();
+ rotatedCropMap.put(rotatedOrientation, rotatedCrop);
+ return getCrop(displaySize, bitmapSize, rotatedCropMap, rtl);
+ }
+ }
+ }
+
+ // Case 7: could not properly reuse the suggested crops. Fall back to case 1.
+ Slog.w(TAG, "Could not find a proper default crop for display: " + displaySize
+ + ", bitmap size: " + bitmapSize + ", suggested crops: " + suggestedCrops
+ + ", orientation: " + orientation + ", rtl: " + rtl
+ + ", defaultDisplaySizes: " + defaultDisplaySizes);
+ return getCrop(displaySize, bitmapSize, new SparseArray<>(), rtl);
+ }
+
+ /**
+ * Given a crop, a displaySize for the orientation of that crop, compute the visible part of the
+ * crop. This removes any additional width used for parallax. No-op if displaySize == null.
+ */
+ private static Rect noParallax(Rect crop, Point displaySize, Point bitmapSize, boolean rtl) {
+ if (displaySize == null) return crop;
+ Rect adjustedCrop = getAdjustedCrop(crop, bitmapSize, displaySize, true, rtl, ADD);
+ // only keep the visible part (without parallax)
+ float suggestedDisplayRatio = 1f * displaySize.x / displaySize.y;
+ int widthToRemove = (int) (adjustedCrop.width()
+ - (((float) adjustedCrop.height()) * suggestedDisplayRatio) + 0.5f);
+ if (rtl) {
+ adjustedCrop.left += widthToRemove;
+ } else {
+ adjustedCrop.right -= widthToRemove;
+ }
+ return adjustedCrop;
+ }
+
+ /**
+ * Adjust a given crop:
+ * <ul>
+ * <li>If parallax = true, make sure we have a parallax of at most {@link #MAX_PARALLAX},
+ * by removing content from the right (or left if RTL) if necessary.
+ * </li>
+ * <li>If parallax = false, make sure we do not have additional width for parallax. If we
+ * have additional width for parallax, remove half of the additional width on both sides.
+ * </li>
+ * <li>Make sure the crop fills the screen, i.e. that the width/height ratio of the crop
+ * is at least the width/height ratio of the screen. If it is less, add width to the crop
+ * (if possible on both sides) to fill the screen. If not enough width available, remove
+ * height to the crop.
+ * </li>
+ * </ul>
+ */
+ private static Rect getAdjustedCrop(Rect crop, Point bitmapSize, Point screenSize,
+ boolean parallax, boolean rtl, int mode) {
+ Rect adjustedCrop = new Rect(crop);
+ float cropRatio = ((float) crop.width()) / crop.height();
+ float screenRatio = ((float) screenSize.x) / screenSize.y;
+ if (cropRatio >= screenRatio) {
+ if (!parallax) {
+ // rotate everything 90 degrees clockwise, compute the result, and rotate back
+ int newLeft = bitmapSize.y - crop.bottom;
+ int newRight = newLeft + crop.height();
+ int newTop = crop.left;
+ int newBottom = newTop + crop.width();
+ Rect rotatedCrop = new Rect(newLeft, newTop, newRight, newBottom);
+ Point rotatedBitmap = new Point(bitmapSize.y, bitmapSize.x);
+ Point rotatedScreen = new Point(screenSize.y, screenSize.x);
+ Rect rect = getAdjustedCrop(rotatedCrop, rotatedBitmap, rotatedScreen, false, rtl,
+ mode);
+ int resultLeft = rect.top;
+ int resultRight = resultLeft + rect.height();
+ int resultTop = rotatedBitmap.x - rect.right;
+ int resultBottom = resultTop + rect.width();
+ return new Rect(resultLeft, resultTop, resultRight, resultBottom);
+ }
+ float additionalWidthForParallax = cropRatio / screenRatio - 1f;
+ if (additionalWidthForParallax > MAX_PARALLAX) {
+ int widthToRemove = (int) Math.ceil(
+ (additionalWidthForParallax - MAX_PARALLAX) * screenRatio * crop.height());
+ if (rtl) {
+ adjustedCrop.left += widthToRemove;
+ } else {
+ adjustedCrop.right -= widthToRemove;
+ }
+ }
+ } else {
+ int widthToAdd = mode == REMOVE ? 0
+ : mode == ADD ? (int) (0.5 + crop.height() * screenRatio - crop.width())
+ : (int) (0.5 + crop.height() - crop.width());
+ int availableWidth = bitmapSize.x - crop.width();
+ if (availableWidth >= widthToAdd) {
+ int widthToAddLeft = widthToAdd / 2;
+ int widthToAddRight = widthToAdd / 2 + widthToAdd % 2;
+
+ if (crop.left < widthToAddLeft) {
+ widthToAddRight += (widthToAddLeft - crop.left);
+ widthToAddLeft = crop.left;
+ } else if (bitmapSize.x - crop.right < widthToAddRight) {
+ widthToAddLeft += (widthToAddRight - (bitmapSize.x - crop.right));
+ widthToAddRight = bitmapSize.x - crop.right;
+ }
+ adjustedCrop.left -= widthToAddLeft;
+ adjustedCrop.right += widthToAddRight;
+ } else {
+ adjustedCrop.left = 0;
+ adjustedCrop.right = bitmapSize.x;
+ }
+ int heightToRemove = (int) (crop.height() - (adjustedCrop.width() / screenRatio));
+ adjustedCrop.top += heightToRemove / 2 + heightToRemove % 2;
+ adjustedCrop.bottom -= heightToRemove / 2;
+ }
+ return adjustedCrop;
+ }
+
+ /**
+ * To find the smallest sub-image that contains all the given crops.
+ * This is used in {@link #generateCrop(WallpaperData)}
+ * to determine how the file from {@link WallpaperData#getCropFile()} needs to be cropped.
+ *
+ * @param crops a list of rectangles
+ * @return the smallest rectangle that contains them all.
+ */
+ public static Rect getTotalCrop(SparseArray<Rect> crops) {
+ int left = Integer.MAX_VALUE, top = Integer.MAX_VALUE;
+ int right = Integer.MIN_VALUE, bottom = Integer.MIN_VALUE;
+ for (int i = 0; i < crops.size(); i++) {
+ Rect rect = crops.valueAt(i);
+ left = Math.min(left, rect.left);
+ top = Math.min(top, rect.top);
+ right = Math.max(right, rect.right);
+ bottom = Math.max(bottom, rect.bottom);
+ }
+ return new Rect(left, top, right, bottom);
+ }
+
+ /**
+ * The crops stored in {@link WallpaperData#mCropHints} are relative to the original image.
+ * This computes the crops relative to the sub-image that will actually be rendered on a window.
+ */
+ SparseArray<Rect> getRelativeCropHints(WallpaperData wallpaper) {
+ SparseArray<Rect> result = new SparseArray<>();
+ for (int i = 0; i < wallpaper.mCropHints.size(); i++) {
+ Rect adjustedRect = new Rect(wallpaper.mCropHints.valueAt(i));
+ adjustedRect.offset(-wallpaper.cropHint.left, -wallpaper.cropHint.top);
+ adjustedRect.scale(1f / wallpaper.mSampleSize);
+ result.put(wallpaper.mCropHints.keyAt(i), adjustedRect);
+ }
+ return result;
+ }
+
+ /**
+ * Inverse operation of {@link #getRelativeCropHints}
+ */
+ static List<Rect> getOriginalCropHints(
+ WallpaperData wallpaper, List<Rect> relativeCropHints) {
+ List<Rect> result = new ArrayList<>();
+ for (Rect crop : relativeCropHints) {
+ Rect originalRect = new Rect(crop);
+ originalRect.scale(wallpaper.mSampleSize);
+ originalRect.offset(wallpaper.cropHint.left, wallpaper.cropHint.right);
+ result.add(originalRect);
+ }
+ return result;
+ }
+
+ /**
+ * Given some suggested crops, find cropHints for all orientations of the default display.
+ */
+ SparseArray<Rect> getDefaultCrops(SparseArray<Rect> suggestedCrops, Point bitmapSize) {
+ SparseArray<Rect> result = new SparseArray<>();
+ // add missing cropHints for all orientation of the default display
+ SparseArray<Point> defaultDisplaySizes = mWallpaperDisplayHelper.getDefaultDisplaySizes();
+ boolean rtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault())
+ == View.LAYOUT_DIRECTION_RTL;
+ for (int i = 0; i < defaultDisplaySizes.size(); i++) {
+ int orientation = defaultDisplaySizes.keyAt(i);
+ Point displaySize = defaultDisplaySizes.valueAt(i);
+ Rect newCrop = getCrop(displaySize, bitmapSize, suggestedCrops, rtl);
+ result.put(orientation, newCrop);
+ }
+ return result;
+ }
+
+ /**
+ * Once a new wallpaper has been written via setWallpaper(...), it needs to be cropped
+ * for display. This will generate the crop and write it in the file.
*/
void generateCrop(WallpaperData wallpaper) {
TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
@@ -75,27 +388,47 @@
// Only generate crop for default display.
final WallpaperDisplayHelper.DisplayData wpData =
mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
- final Rect cropHint = new Rect(wallpaper.cropHint);
final DisplayInfo displayInfo = mWallpaperDisplayHelper.getDisplayInfo(DEFAULT_DISPLAY);
- if (DEBUG) {
- Slog.v(TAG, "Generating crop for new wallpaper(s): 0x"
- + Integer.toHexString(wallpaper.mWhich)
- + " to " + wallpaper.getCropFile().getName()
- + " crop=(" + cropHint.width() + 'x' + cropHint.height()
- + ") dim=(" + wpData.mWidth + 'x' + wpData.mHeight + ')');
- }
-
// Analyse the source; needed in multiple cases
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(wallpaper.getWallpaperFile().getAbsolutePath(), options);
if (options.outWidth <= 0 || options.outHeight <= 0) {
Slog.w(TAG, "Invalid wallpaper data");
- success = false;
} else {
boolean needCrop = false;
boolean needScale;
+ boolean multiCrop = multiCrop() && wallpaper.mSupportsMultiCrop;
+
+ Point bitmapSize = new Point(options.outWidth, options.outHeight);
+
+ final Rect cropHint;
+ if (multiCrop) {
+ SparseArray<Rect> defaultDisplayCrops =
+ getDefaultCrops(wallpaper.mCropHints, bitmapSize);
+ // adapt the entries in wallpaper.mCropHints for the actual display
+ SparseArray<Rect> updatedCropHints = new SparseArray<>();
+ for (int i = 0; i < wallpaper.mCropHints.size(); i++) {
+ Rect defaultCrop = defaultDisplayCrops.valueAt(i);
+ if (defaultCrop != null) {
+ updatedCropHints.put(defaultDisplayCrops.keyAt(i), defaultCrop);
+ }
+ }
+ wallpaper.mCropHints = updatedCropHints;
+ cropHint = getTotalCrop(defaultDisplayCrops);
+ wallpaper.cropHint.set(cropHint);
+ } else {
+ cropHint = new Rect(wallpaper.cropHint);
+ }
+
+ if (DEBUG) {
+ Slog.v(TAG, "Generating crop for new wallpaper(s): 0x"
+ + Integer.toHexString(wallpaper.mWhich)
+ + " to " + wallpaper.getCropFile().getName()
+ + " crop=(" + cropHint.width() + 'x' + cropHint.height()
+ + ") dim=(" + wpData.mWidth + 'x' + wpData.mHeight + ')');
+ }
// Empty crop means use the full image
if (cropHint.isEmpty()) {
@@ -128,7 +461,7 @@
|| cropHint.width() > GLHelper.getMaxTextureSize();
//make sure screen aspect ratio is preserved if width is scaled under screen size
- if (needScale) {
+ if (needScale && !multiCrop) {
final float scaleByHeight = (float) wpData.mHeight / (float) cropHint.height();
final int newWidth = (int) (cropHint.width() * scaleByHeight);
if (newWidth < displayInfo.logicalWidth) {
@@ -171,7 +504,7 @@
BufferedOutputStream bos = null;
try {
// This actually downsamples only by powers of two, but that's okay; we do
- // a proper scaling blit later. This is to minimize transient RAM use.
+ // a proper scaling a bit later. This is to minimize transient RAM use.
// We calculate the largest power-of-two under the actual ratio rather than
// just let the decode take care of it because we also want to remap where the
// cropHint rectangle lies in the decoded [super]rect.
@@ -185,19 +518,31 @@
final Rect estimateCrop = new Rect(cropHint);
estimateCrop.scale(1f / options.inSampleSize);
- final float hRatio = (float) wpData.mHeight / estimateCrop.height();
+ float hRatio = (float) wpData.mHeight / estimateCrop.height();
+ if (multiCrop) {
+ // make sure the crop height is at most the display largest dimension
+ hRatio = (float) mWallpaperDisplayHelper.getDefaultDisplayLargestDimension()
+ / estimateCrop.height();
+ hRatio = Math.min(hRatio, 1f);
+ }
final int destHeight = (int) (estimateCrop.height() * hRatio);
final int destWidth = (int) (estimateCrop.width() * hRatio);
// We estimated an invalid crop, try to adjust the cropHint to get a valid one.
if (destWidth > GLHelper.getMaxTextureSize()) {
+ if (DEBUG) {
+ Slog.w(TAG, "Invalid crop dimensions, trying to adjust.");
+ }
+ if (multiCrop) {
+ // clear custom crop guidelines, fallback to system default
+ wallpaper.mCropHints.clear();
+ generateCropInternal(wallpaper);
+ return;
+ }
+
int newHeight = (int) (wpData.mHeight / hRatio);
int newWidth = (int) (wpData.mWidth / hRatio);
- if (DEBUG) {
- Slog.v(TAG, "Invalid crop dimensions, trying to adjust.");
- }
-
estimateCrop.set(cropHint);
estimateCrop.left += (cropHint.width() - newWidth) / 2;
estimateCrop.top += (cropHint.height() - newHeight) / 2;
@@ -210,8 +555,8 @@
// We've got the safe cropHint; now we want to scale it properly to
// the desired rectangle.
// That's a height-biased operation: make it fit the hinted height.
- final int safeHeight = (int) (estimateCrop.height() * hRatio);
- final int safeWidth = (int) (estimateCrop.width() * hRatio);
+ final int safeHeight = (int) (estimateCrop.height() * hRatio + 0.5f);
+ final int safeWidth = (int) (estimateCrop.width() * hRatio + 0.5f);
if (DEBUG_CROP) {
Slog.v(TAG, "Decode parameters:");
@@ -248,6 +593,12 @@
// We are safe to create final crop with safe dimensions now.
final Bitmap finalCrop = Bitmap.createScaledBitmap(cropped,
safeWidth, safeHeight, true);
+
+ if (multiCrop) {
+ wallpaper.mSampleSize =
+ ((float) cropHint.height()) / finalCrop.getHeight();
+ }
+
if (DEBUG) {
Slog.v(TAG, "Final extract:");
Slog.v(TAG, " dims: w=" + wpData.mWidth
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperData.java b/services/core/java/com/android/server/wallpaper/WallpaperData.java
index 5c86701..02594d2 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperData.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperData.java
@@ -17,6 +17,7 @@
package com.android.server.wallpaper;
import static android.app.WallpaperManager.FLAG_LOCK;
+import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER;
import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_CROP;
@@ -26,6 +27,7 @@
import android.app.IWallpaperManagerCallback;
import android.app.WallpaperColors;
+import android.app.WallpaperManager.ScreenOrientation;
import android.app.WallpaperManager.SetWallpaperFlags;
import android.content.ComponentName;
import android.graphics.Rect;
@@ -126,10 +128,16 @@
RemoteCallbackList<IWallpaperManagerCallback> callbacks = new RemoteCallbackList<>();
/**
- * The crop hint supplied for displaying a subset of the source image
+ * Defines which part of the {@link #getWallpaperFile()} image is in the {@link #getCropFile()}.
*/
final Rect cropHint = new Rect(0, 0, 0, 0);
+ /**
+ * How much the crop is sub-sampled. A value > 1 means that the image quality was reduced.
+ * This is the ratio between the cropHint height and the actual {@link #getCropFile()} height.
+ */
+ float mSampleSize = 1f;
+
// Describes the context of a call to WallpaperManagerService#bindWallpaperComponentLocked
enum BindSource {
UNKNOWN,
@@ -156,6 +164,23 @@
private final SparseArray<File> mWallpaperFiles = new SparseArray<>();
private final SparseArray<File> mCropFiles = new SparseArray<>();
+ /**
+ * Mapping of {@link ScreenOrientation} -> crop hint. The crop hints are relative to the
+ * original image stored in {@link #getWallpaperFile()}.
+ * Only used when multi crop flag is enabled.
+ */
+ SparseArray<Rect> mCropHints = new SparseArray<>();
+
+ /**
+ * cropHints will be ignored if this flag is false
+ */
+ boolean mSupportsMultiCrop;
+
+ /**
+ * The phone orientation when the wallpaper was set. Only relevant for image wallpapers
+ */
+ int mOrientationWhenSet = ORIENTATION_UNKNOWN;
+
WallpaperData(int userId, @SetWallpaperFlags int wallpaperType) {
this.userId = userId;
this.mWhich = wallpaperType;
@@ -176,6 +201,10 @@
this.mWhich = source.mWhich;
this.wallpaperId = source.wallpaperId;
this.cropHint.set(source.cropHint);
+ if (source.mCropHints != null) {
+ this.mCropHints = source.mCropHints.clone();
+ }
+ this.mSupportsMultiCrop = source.mSupportsMultiCrop;
this.allowBackup = source.allowBackup;
this.primaryColors = source.primaryColors;
this.mWallpaperDimAmount = source.mWallpaperDimAmount;
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index de98df5..88e9672 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
@@ -18,6 +18,7 @@
import static android.app.WallpaperManager.FLAG_LOCK;
import static android.app.WallpaperManager.FLAG_SYSTEM;
+import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.server.wallpaper.WallpaperDisplayHelper.DisplayData;
@@ -26,9 +27,11 @@
import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_INFO;
import static com.android.server.wallpaper.WallpaperUtils.getWallpaperDir;
import static com.android.server.wallpaper.WallpaperUtils.makeWallpaperIdLocked;
+import static com.android.window.flags.Flags.multiCrop;
import android.annotation.Nullable;
import android.app.WallpaperColors;
+import android.app.WallpaperManager;
import android.app.WallpaperManager.SetWallpaperFlags;
import android.app.backup.WallpaperBackupHelper;
import android.content.ComponentName;
@@ -36,7 +39,9 @@
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Color;
+import android.graphics.Rect;
import android.os.FileUtils;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.Xml;
@@ -60,14 +65,16 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
/**
* Helper for the wallpaper loading / saving / xml parsing
* Only meant to be used lock held by WallpaperManagerService
* Only meant to be instantiated once by WallpaperManagerService
+ * @hide
*/
-class WallpaperDataParser {
+public class WallpaperDataParser {
private static final String TAG = WallpaperDataParser.class.getSimpleName();
private static final boolean DEBUG = false;
@@ -132,6 +139,7 @@
*/
public WallpaperLoadingResult loadSettingsLocked(int userId, boolean keepDimensionHints,
boolean migrateFromOld, @SetWallpaperFlags int which) {
+ // TODO(b/270726737) remove the "keepDimensionHints" arg when removing the multi crop flag
JournaledFile journal = makeJournaledFile(userId);
FileInputStream stream = null;
File file = journal.chooseForRead();
@@ -174,7 +182,9 @@
WallpaperData wallpaperToParse =
"wp".equals(tag) ? wallpaper : lockWallpaper;
- parseWallpaperAttributes(parser, wallpaperToParse, keepDimensionHints);
+ if (!multiCrop()) {
+ parseWallpaperAttributes(parser, wallpaperToParse, keepDimensionHints);
+ }
String comp = parser.getAttributeValue(null, "component");
wallpaperToParse.nextWallpaperComponent = comp != null
@@ -186,6 +196,10 @@
wallpaperToParse.nextWallpaperComponent = mImageWallpaper;
}
+ if (multiCrop()) {
+ parseWallpaperAttributes(parser, wallpaperToParse, keepDimensionHints);
+ }
+
if (DEBUG) {
Slog.v(TAG, "mWidth:" + wpdData.mWidth);
Slog.v(TAG, "mHeight:" + wpdData.mHeight);
@@ -300,20 +314,48 @@
wallpaper.wallpaperId = makeWallpaperIdLocked();
}
- final DisplayData wpData = mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
-
- if (!keepDimensionHints) {
- wpData.mWidth = parser.getAttributeInt(null, "width");
- wpData.mHeight = parser.getAttributeInt(null, "height");
+ Rect totalCropHint = new Rect(
+ getAttributeInt(parser, "totalCropLeft", 0),
+ getAttributeInt(parser, "totalCropTop", 0),
+ getAttributeInt(parser, "totalCropRight", 0),
+ getAttributeInt(parser, "totalCropBottom", 0));
+ wallpaper.mSupportsMultiCrop = multiCrop() && (
+ parser.getAttributeBoolean(null, "supportsMultiCrop", false)
+ || mImageWallpaper.equals(wallpaper.wallpaperComponent));
+ if (wallpaper.mSupportsMultiCrop) {
+ wallpaper.mCropHints = new SparseArray<>();
+ for (Pair<Integer, String> pair: screenDimensionPairs()) {
+ Rect cropHint = new Rect(
+ parser.getAttributeInt(null, "cropLeft" + pair.second, 0),
+ parser.getAttributeInt(null, "cropTop" + pair.second, 0),
+ parser.getAttributeInt(null, "cropRight" + pair.second, 0),
+ parser.getAttributeInt(null, "cropBottom" + pair.second, 0));
+ if (!cropHint.isEmpty()) wallpaper.mCropHints.put(pair.first, cropHint);
+ }
+ if (wallpaper.mCropHints.size() == 0) {
+ // 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()
+ ? WallpaperManager.PORTRAIT : WallpaperManager.LANDSCAPE;
+ if (!totalCropHint.isEmpty()) wallpaper.mCropHints.put(orientation, totalCropHint);
+ } else {
+ wallpaper.cropHint.set(totalCropHint);
+ }
+ } else {
+ wallpaper.cropHint.set(totalCropHint);
}
- wallpaper.cropHint.left = getAttributeInt(parser, "cropLeft", 0);
- wallpaper.cropHint.top = getAttributeInt(parser, "cropTop", 0);
- wallpaper.cropHint.right = getAttributeInt(parser, "cropRight", 0);
- wallpaper.cropHint.bottom = getAttributeInt(parser, "cropBottom", 0);
- wpData.mPadding.left = getAttributeInt(parser, "paddingLeft", 0);
- wpData.mPadding.top = getAttributeInt(parser, "paddingTop", 0);
- wpData.mPadding.right = getAttributeInt(parser, "paddingRight", 0);
- wpData.mPadding.bottom = getAttributeInt(parser, "paddingBottom", 0);
+ final DisplayData wpData = mWallpaperDisplayHelper
+ .getDisplayDataOrCreate(DEFAULT_DISPLAY);
+ if (!keepDimensionHints && !multiCrop()) {
+ wpData.mWidth = parser.getAttributeInt(null, "width", 0);
+ wpData.mHeight = parser.getAttributeInt(null, "height", 0);
+ }
+ if (!multiCrop()) {
+ wpData.mPadding.left = getAttributeInt(parser, "paddingLeft", 0);
+ wpData.mPadding.top = getAttributeInt(parser, "paddingTop", 0);
+ wpData.mPadding.right = getAttributeInt(parser, "paddingRight", 0);
+ wpData.mPadding.bottom = getAttributeInt(parser, "paddingBottom", 0);
+ }
wallpaper.mWallpaperDimAmount = getAttributeFloat(parser, "dimAmount", 0f);
BindSource bindSource;
try {
@@ -365,11 +407,11 @@
wallpaper.allowBackup = parser.getAttributeBoolean(null, "backup", false);
}
- private int getAttributeInt(TypedXmlPullParser parser, String name, int defValue) {
+ private static int getAttributeInt(TypedXmlPullParser parser, String name, int defValue) {
return parser.getAttributeInt(null, name, defValue);
}
- private float getAttributeFloat(TypedXmlPullParser parser, String name, float defValue) {
+ private static float getAttributeFloat(TypedXmlPullParser parser, String name, float defValue) {
return parser.getAttributeFloat(null, name, defValue);
}
@@ -412,28 +454,66 @@
if (DEBUG) {
Slog.v(TAG, "writeWallpaperAttributes id=" + wallpaper.wallpaperId);
}
- final DisplayData wpdData = mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
out.startTag(null, tag);
out.attributeInt(null, "id", wallpaper.wallpaperId);
- out.attributeInt(null, "width", wpdData.mWidth);
- out.attributeInt(null, "height", wpdData.mHeight);
- out.attributeInt(null, "cropLeft", wallpaper.cropHint.left);
- out.attributeInt(null, "cropTop", wallpaper.cropHint.top);
- out.attributeInt(null, "cropRight", wallpaper.cropHint.right);
- out.attributeInt(null, "cropBottom", wallpaper.cropHint.bottom);
+ out.attributeBoolean(null, "supportsMultiCrop", wallpaper.mSupportsMultiCrop);
- if (wpdData.mPadding.left != 0) {
- out.attributeInt(null, "paddingLeft", wpdData.mPadding.left);
- }
- if (wpdData.mPadding.top != 0) {
- out.attributeInt(null, "paddingTop", wpdData.mPadding.top);
- }
- if (wpdData.mPadding.right != 0) {
- out.attributeInt(null, "paddingRight", wpdData.mPadding.right);
- }
- if (wpdData.mPadding.bottom != 0) {
- out.attributeInt(null, "paddingBottom", wpdData.mPadding.bottom);
+ if (multiCrop() && wallpaper.mSupportsMultiCrop) {
+ if (wallpaper.mCropHints == null) {
+ Slog.e(TAG, "cropHints should not be null when saved");
+ wallpaper.mCropHints = new SparseArray<>();
+ }
+ for (Pair<Integer, String> pair : screenDimensionPairs()) {
+ Rect cropHint = wallpaper.mCropHints.get(pair.first);
+ if (cropHint == null) continue;
+ out.attributeInt(null, "cropLeft" + pair.second, cropHint.left);
+ out.attributeInt(null, "cropTop" + pair.second, cropHint.top);
+ out.attributeInt(null, "cropRight" + pair.second, cropHint.right);
+ out.attributeInt(null, "cropBottom" + pair.second, cropHint.bottom);
+
+ // to support back compatibility in B&R, save the crops for one orientation in the
+ // legacy "cropLeft", "cropTop", "cropRight", "cropBottom" entries
+ int orientationToPutInLegacyCrop = wallpaper.mOrientationWhenSet;
+ if (mWallpaperDisplayHelper.isFoldable()) {
+ int unfoldedOrientation = mWallpaperDisplayHelper
+ .getUnfoldedOrientation(orientationToPutInLegacyCrop);
+ if (unfoldedOrientation != ORIENTATION_UNKNOWN) {
+ orientationToPutInLegacyCrop = unfoldedOrientation;
+ }
+ }
+ if (pair.first == orientationToPutInLegacyCrop) {
+ out.attributeInt(null, "cropLeft", cropHint.left);
+ out.attributeInt(null, "cropTop", cropHint.top);
+ out.attributeInt(null, "cropRight", cropHint.right);
+ out.attributeInt(null, "cropBottom", cropHint.bottom);
+ }
+ }
+ out.attributeInt(null, "totalCropLeft", wallpaper.cropHint.left);
+ out.attributeInt(null, "totalCropTop", wallpaper.cropHint.top);
+ out.attributeInt(null, "totalCropRight", wallpaper.cropHint.right);
+ out.attributeInt(null, "totalCropBottom", wallpaper.cropHint.bottom);
+ } else if (!multiCrop()) {
+ final DisplayData wpdData =
+ mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
+ out.attributeInt(null, "width", wpdData.mWidth);
+ out.attributeInt(null, "height", wpdData.mHeight);
+ out.attributeInt(null, "cropLeft", wallpaper.cropHint.left);
+ out.attributeInt(null, "cropTop", wallpaper.cropHint.top);
+ out.attributeInt(null, "cropRight", wallpaper.cropHint.right);
+ out.attributeInt(null, "cropBottom", wallpaper.cropHint.bottom);
+ if (wpdData.mPadding.left != 0) {
+ out.attributeInt(null, "paddingLeft", wpdData.mPadding.left);
+ }
+ if (wpdData.mPadding.top != 0) {
+ out.attributeInt(null, "paddingTop", wpdData.mPadding.top);
+ }
+ if (wpdData.mPadding.right != 0) {
+ out.attributeInt(null, "paddingRight", wpdData.mPadding.right);
+ }
+ if (wpdData.mPadding.bottom != 0) {
+ out.attributeInt(null, "paddingBottom", wpdData.mPadding.bottom);
+ }
}
out.attributeFloat(null, "dimAmount", wallpaper.mWallpaperDimAmount);
@@ -564,4 +644,12 @@
}
return false;
}
+
+ private static List<Pair<Integer, String>> screenDimensionPairs() {
+ return List.of(
+ new Pair<>(WallpaperManager.PORTRAIT, "Portrait"),
+ new Pair<>(WallpaperManager.LANDSCAPE, "Landscape"),
+ new Pair<>(WallpaperManager.SQUARE_PORTRAIT, "SquarePortrait"),
+ new Pair<>(WallpaperManager.SQUARE_LANDSCAPE, "SquareLandscape"));
+ }
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java b/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
index f48178c..19fd9a9 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
@@ -16,19 +16,31 @@
package com.android.server.wallpaper;
+import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
+import static android.app.WallpaperManager.getRotatedOrientation;
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.window.flags.Flags.multiCrop;
+
+import android.app.WallpaperManager;
+import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.os.Binder;
import android.os.Debug;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayInfo;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
import com.android.server.wm.WindowManagerInternal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
import java.util.function.Consumer;
/**
@@ -50,12 +62,55 @@
private final SparseArray<DisplayData> mDisplayDatas = new SparseArray<>();
private final DisplayManager mDisplayManager;
private final WindowManagerInternal mWindowManagerInternal;
+ private final SparseArray<Point> mDefaultDisplaySizes = new SparseArray<>();
+
+ // related orientations pairs for foldable (folded orientation, unfolded orientation)
+ private final List<Pair<Integer, Integer>> mFoldableOrientationPairs = new ArrayList<>();
+
+ private boolean mIsFoldable;
WallpaperDisplayHelper(
DisplayManager displayManager,
- WindowManagerInternal windowManagerInternal) {
+ WindowManager windowManager,
+ WindowManagerInternal windowManagerInternal,
+ boolean isFoldable) {
mDisplayManager = displayManager;
mWindowManagerInternal = windowManagerInternal;
+ mIsFoldable = isFoldable;
+ if (!multiCrop()) return;
+ Set<WindowMetrics> metrics = windowManager.getPossibleMaximumWindowMetrics(DEFAULT_DISPLAY);
+ boolean populateOrientationPairs = isFoldable && metrics.size() == 2;
+ float surface = 0;
+ int firstOrientation = -1;
+ for (WindowMetrics metric: metrics) {
+ Rect bounds = metric.getBounds();
+ Point displaySize = new Point(bounds.width(), bounds.height());
+ Point reversedDisplaySize = new Point(displaySize.y, displaySize.x);
+ for (Point point : List.of(displaySize, reversedDisplaySize)) {
+ int orientation = WallpaperManager.getOrientation(point);
+ // don't add an entry if there is already a larger display of the same orientation
+ Point display = mDefaultDisplaySizes.get(orientation);
+ if (display == null || display.x * display.y < point.x * point.y) {
+ mDefaultDisplaySizes.put(orientation, point);
+ }
+ }
+ if (populateOrientationPairs) {
+ int orientation = WallpaperManager.getOrientation(displaySize);
+ float newSurface = displaySize.x * displaySize.y * metric.getDensity();
+ if (surface <= 0) {
+ surface = newSurface;
+ firstOrientation = orientation;
+ } else {
+ Pair<Integer, Integer> pair = (newSurface > surface)
+ ? new Pair<>(firstOrientation, orientation)
+ : new Pair<>(orientation, firstOrientation);
+ Pair<Integer, Integer> rotatedPair = new Pair<>(
+ getRotatedOrientation(pair.first), getRotatedOrientation(pair.second));
+ mFoldableOrientationPairs.add(pair);
+ mFoldableOrientationPairs.add(rotatedPair);
+ }
+ }
+ }
}
DisplayData getDisplayDataOrCreate(int displayId) {
@@ -68,6 +123,12 @@
return wpdData;
}
+ int getDefaultDisplayCurrentOrientation() {
+ Point displaySize = new Point();
+ mDisplayManager.getDisplay(DEFAULT_DISPLAY).getSize(displaySize);
+ return WallpaperManager.getOrientation(displaySize);
+ }
+
void removeDisplayData(int displayId) {
mDisplayDatas.remove(displayId);
}
@@ -133,4 +194,46 @@
boolean isValidDisplay(int displayId) {
return mDisplayManager.getDisplay(displayId) != null;
}
+
+ SparseArray<Point> getDefaultDisplaySizes() {
+ return mDefaultDisplaySizes;
+ }
+
+ /** Return the number of pixel of the largest dimension of the default display */
+ int getDefaultDisplayLargestDimension() {
+ int result = -1;
+ for (int i = 0; i < mDefaultDisplaySizes.size(); i++) {
+ Point size = mDefaultDisplaySizes.valueAt(i);
+ result = Math.max(result, Math.max(size.x, size.y));
+ }
+ return result;
+ }
+
+ boolean isFoldable() {
+ return mIsFoldable;
+ }
+
+ /**
+ * If a given orientation corresponds to an unfolded orientation on foldable, return the
+ * corresponding folded orientation. Otherwise, return UNKNOWN. Always return UNKNOWN if the
+ * device is not a foldable.
+ */
+ int getFoldedOrientation(int orientation) {
+ for (Pair<Integer, Integer> pair : mFoldableOrientationPairs) {
+ if (pair.second.equals(orientation)) return pair.first;
+ }
+ return ORIENTATION_UNKNOWN;
+ }
+
+ /**
+ * If a given orientation corresponds to a folded orientation on foldable, return the
+ * corresponding unfolded orientation. Otherwise, return UNKNOWN. Always return UNKNOWN if the
+ * device is not a foldable.
+ */
+ int getUnfoldedOrientation(int orientation) {
+ for (Pair<Integer, Integer> pair : mFoldableOrientationPairs) {
+ if (pair.first.equals(orientation)) return pair.second;
+ }
+ return ORIENTATION_UNKNOWN;
+ }
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 3782b42..8c27bb8 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -22,6 +22,7 @@
import static android.app.WallpaperManager.COMMAND_REAPPLY;
import static android.app.WallpaperManager.FLAG_LOCK;
import static android.app.WallpaperManager.FLAG_SYSTEM;
+import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.ParcelFileDescriptor.MODE_CREATE;
@@ -74,6 +75,7 @@
import android.content.pm.UserInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.hardware.display.DisplayManager;
@@ -103,12 +105,15 @@
import android.service.wallpaper.WallpaperService;
import android.system.ErrnoException;
import android.system.Os;
+import android.text.TextUtils;
import android.util.EventLog;
import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.Display;
+import android.view.View;
+import android.view.WindowManager;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
@@ -137,6 +142,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -189,8 +195,6 @@
}
private final Object mLock = new Object();
- /** True to support different crops for different display dimensions */
- private final boolean mIsMultiCropEnabled;
/** Tracks wallpaper being migrated from system+lock to lock when setting static wp. */
WallpaperDestinationChangeHandler mPendingMigrationViaStatic;
@@ -804,6 +808,12 @@
null /* options */);
mWindowManagerInternal.setWallpaperShowWhenLocked(
mToken, (wallpaper.mWhich & FLAG_LOCK) != 0);
+ if (multiCrop() && wallpaper.mSupportsMultiCrop) {
+ mWindowManagerInternal.setWallpaperCropHints(mToken,
+ mWallpaperCropper.getRelativeCropHints(wallpaper));
+ } else {
+ mWindowManagerInternal.setWallpaperCropHints(mToken, new SparseArray<>());
+ }
final DisplayData wpdData =
mWallpaperDisplayHelper.getDisplayDataOrCreate(mDisplayId);
try {
@@ -1256,7 +1266,7 @@
// migrateStaticSystemToLockWallpaperLocked() and added to the lock wp map
// if successful.
WallpaperData lockWp = mLockWallpaperMap.get(mNewWallpaper.userId);
- if (lockWp != null) {
+ if (lockWp != null && mOriginalSystem.connection != null) {
// Successful rename, set old system+lock to the pending lock wp
if (DEBUG) {
Slog.v(TAG, "static system+lock to system success");
@@ -1479,10 +1489,15 @@
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mIPackageManager = AppGlobals.getPackageManager();
mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
- DisplayManager dm = mContext.getSystemService(DisplayManager.class);
- dm.registerDisplayListener(mDisplayListener, null /* handler */);
- mWallpaperDisplayHelper = new WallpaperDisplayHelper(dm, mWindowManagerInternal);
+ DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+ displayManager.registerDisplayListener(mDisplayListener, null /* handler */);
+ WindowManager windowManager = mContext.getSystemService(WindowManager.class);
+ boolean isFoldable = mContext.getResources()
+ .getIntArray(R.array.config_foldedDeviceStates).length > 0;
+ mWallpaperDisplayHelper = new WallpaperDisplayHelper(
+ displayManager, windowManager, mWindowManagerInternal, isFoldable);
mWallpaperCropper = new WallpaperCropper(mWallpaperDisplayHelper);
+ mWindowManagerInternal.setWallpaperCropUtils(mWallpaperCropper::getCrop);
mActivityManager = mContext.getSystemService(ActivityManager.class);
if (mContext.getResources().getBoolean(
@@ -1522,7 +1537,6 @@
mColorsChangedListeners = new SparseArray<>();
mWallpaperDataParser = new WallpaperDataParser(mContext, mWallpaperDisplayHelper,
mWallpaperCropper);
- mIsMultiCropEnabled = multiCrop();
LocalServices.addService(WallpaperManagerInternal.class, new LocalService());
}
@@ -2199,6 +2213,66 @@
}
}
+ @Override
+ public List<Rect> getBitmapCrops(List<Point> displaySizes, @SetWallpaperFlags int which,
+ boolean originalBitmap, int userId) {
+ userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, false, true, "getBitmapCrop", null);
+ synchronized (mLock) {
+ checkPermission(READ_WALLPAPER_INTERNAL);
+ WallpaperData wallpaper = (which == FLAG_LOCK) ? mLockWallpaperMap.get(userId)
+ : mWallpaperMap.get(userId);
+ if (wallpaper == null || !wallpaper.mSupportsMultiCrop) return null;
+ SparseArray<Rect> relativeSuggestedCrops =
+ mWallpaperCropper.getRelativeCropHints(wallpaper);
+ Point croppedBitmapSize =
+ new Point(wallpaper.cropHint.width(), wallpaper.cropHint.height());
+ SparseArray<Rect> relativeDefaultCrops =
+ mWallpaperCropper.getDefaultCrops(relativeSuggestedCrops, croppedBitmapSize);
+ SparseArray<Rect> adjustedRelativeSuggestedCrops = new SparseArray<>();
+ for (int i = 0; i < relativeDefaultCrops.size(); i++) {
+ int key = relativeDefaultCrops.keyAt(i);
+ if (relativeSuggestedCrops.contains(key)) {
+ adjustedRelativeSuggestedCrops.put(key, relativeDefaultCrops.get(key));
+ }
+ }
+ List<Rect> result = new ArrayList<>();
+ boolean rtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault())
+ == View.LAYOUT_DIRECTION_RTL;
+ for (Point displaySize : displaySizes) {
+ result.add(mWallpaperCropper.getCrop(
+ displaySize, croppedBitmapSize, adjustedRelativeSuggestedCrops, rtl));
+ }
+ if (originalBitmap) result = WallpaperCropper.getOriginalCropHints(wallpaper, result);
+ return result;
+ }
+ }
+
+ @Override
+ public List<Rect> getFutureBitmapCrops(Point bitmapSize, List<Point> displaySizes,
+ int[] screenOrientations, List<Rect> crops) {
+ SparseArray<Rect> cropMap = getCropMap(screenOrientations, crops, ORIENTATION_UNKNOWN);
+ SparseArray<Rect> defaultCrops = mWallpaperCropper.getDefaultCrops(cropMap, bitmapSize);
+ List<Rect> result = new ArrayList<>();
+ boolean rtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault())
+ == View.LAYOUT_DIRECTION_RTL;
+ for (Point displaySize : displaySizes) {
+ result.add(mWallpaperCropper.getCrop(displaySize, bitmapSize, defaultCrops, rtl));
+ }
+ return result;
+ }
+
+ @Override
+ public Rect getBitmapCrop(Point bitmapSize, int[] screenOrientations, List<Rect> crops) {
+ if (!multiCrop()) {
+ throw new UnsupportedOperationException(
+ "This method should only be called with the multi crop flag enabled");
+ }
+ SparseArray<Rect> cropMap = getCropMap(screenOrientations, crops, ORIENTATION_UNKNOWN);
+ SparseArray<Rect> defaultCrops = mWallpaperCropper.getDefaultCrops(cropMap, bitmapSize);
+ return WallpaperCropper.getTotalCrop(defaultCrops);
+ }
+
private boolean hasPermission(String permission) {
return mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED;
}
@@ -2755,8 +2829,18 @@
@Override
public ParcelFileDescriptor setWallpaper(String name, String callingPackage,
- Rect cropHint, boolean allowBackup, Bundle extras, int which,
- IWallpaperManagerCallback completion, int userId) {
+ int[] screenOrientations, List<Rect> crops, boolean allowBackup,
+ Bundle extras, int which, IWallpaperManagerCallback completion, int userId) {
+
+ if (DEBUG) {
+ Slog.d(TAG, "setWallpaper: name = " + name + ", callingPackage = " + callingPackage
+ + ", screenOrientations = "
+ + (screenOrientations == null ? null
+ : Arrays.stream(screenOrientations).boxed().toList())
+ + ", crops = " + crops
+ + ", allowBackup = " + allowBackup);
+ }
+
userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,
false /* all */, true /* full */, "changing wallpaper", null /* pkg */);
checkPermission(android.Manifest.permission.SET_WALLPAPER);
@@ -2771,10 +2855,17 @@
return null;
}
+ int currentOrientation = mWallpaperDisplayHelper.getDefaultDisplayCurrentOrientation();
+ SparseArray<Rect> cropMap = !multiCrop() ? null
+ : getCropMap(screenOrientations, crops, currentOrientation);
+ Rect cropHint = multiCrop() || crops == null ? null : crops.get(0);
+ final boolean fromForegroundApp = !multiCrop() ? false
+ : isFromForegroundApp(callingPackage);
+
// "null" means the no-op crop, preserving the full input image
- if (cropHint == null) {
+ if (cropHint == null && !multiCrop()) {
cropHint = new Rect(0, 0, 0, 0);
- } else {
+ } else if (!multiCrop()) {
if (cropHint.width() < 0 || cropHint.height() < 0
|| cropHint.left < 0
|| cropHint.top < 0) {
@@ -2814,10 +2905,14 @@
wallpaper.mSystemWasBoth = systemIsBoth;
wallpaper.mWhich = which;
wallpaper.setComplete = completion;
- wallpaper.fromForegroundApp = isFromForegroundApp(callingPackage);
- wallpaper.cropHint.set(cropHint);
+ wallpaper.fromForegroundApp = multiCrop() ? fromForegroundApp
+ : isFromForegroundApp(callingPackage);
+ if (!multiCrop()) wallpaper.cropHint.set(cropHint);
+ if (multiCrop()) wallpaper.mSupportsMultiCrop = true;
+ if (multiCrop()) wallpaper.mCropHints = cropMap;
wallpaper.allowBackup = allowBackup;
wallpaper.mWallpaperDimAmount = getWallpaperDimAmount();
+ wallpaper.mOrientationWhenSet = currentOrientation;
}
return pfd;
} finally {
@@ -2826,11 +2921,47 @@
}
}
+ private SparseArray<Rect> getCropMap(int[] screenOrientations, List<Rect> crops,
+ int currentOrientation) {
+ if ((crops == null ^ screenOrientations == null)
+ || (crops != null && crops.size() != screenOrientations.length)) {
+ throw new IllegalArgumentException(
+ "Illegal crops/orientations lists: must both be null, or both the same size");
+ }
+ SparseArray<Rect> cropMap = new SparseArray<>();
+ boolean unknown = false;
+ if (crops != null && crops.size() != 0) {
+ for (int i = 0; i < crops.size(); i++) {
+ Rect crop = crops.get(i);
+ int width = crop.width(), height = crop.height();
+ if (width < 0 || height < 0 || crop.left < 0 || crop.top < 0) {
+ throw new IllegalArgumentException("Invalid crop rect supplied: " + crop);
+ }
+ int orientation = screenOrientations[i];
+ if (orientation == ORIENTATION_UNKNOWN) {
+ if (currentOrientation == ORIENTATION_UNKNOWN) {
+ throw new IllegalArgumentException(
+ "Invalid orientation: " + ORIENTATION_UNKNOWN);
+ }
+ unknown = true;
+ orientation = currentOrientation;
+ }
+ cropMap.put(orientation, crop);
+ }
+ }
+ if (unknown && cropMap.size() > 1) {
+ throw new IllegalArgumentException("Invalid crops supplied: the UNKNOWN screen "
+ + "orientation should only be used in a singleton map (in which case it"
+ + "represents the current orientation of the default display)");
+ }
+ return cropMap;
+ }
+
private void migrateStaticSystemToLockWallpaperLocked(int userId) {
WallpaperData sysWP = mWallpaperMap.get(userId);
if (sysWP == null) {
if (DEBUG) {
- Slog.i(TAG, "No system wallpaper? Not tracking for lock-only");
+ Slog.i(TAG, "No system wallpaper? Not tracking for lock-only");
}
return;
}
@@ -2839,6 +2970,10 @@
WallpaperData lockWP = new WallpaperData(userId, FLAG_LOCK);
lockWP.wallpaperId = sysWP.wallpaperId;
lockWP.cropHint.set(sysWP.cropHint);
+ lockWP.mSupportsMultiCrop = sysWP.mSupportsMultiCrop;
+ if (sysWP.mCropHints != null) {
+ lockWP.mCropHints = sysWP.mCropHints.clone();
+ }
lockWP.allowBackup = sysWP.allowBackup;
lockWP.primaryColors = sysWP.primaryColors;
lockWP.mWallpaperDimAmount = sysWP.mWallpaperDimAmount;
@@ -2956,6 +3091,7 @@
final long ident = Binder.clearCallingIdentity();
try {
+ newWallpaper.mSupportsMultiCrop = mImageWallpaper.equals(name);
newWallpaper.imageWallpaperPending = false;
newWallpaper.mWhich = which;
newWallpaper.mSystemWasBoth = systemIsBoth;
@@ -3428,11 +3564,6 @@
return (wallpaper != null) ? wallpaper.allowBackup : false;
}
- @Override
- public boolean isMultiCropEnabled() {
- return mIsMultiCropEnabled;
- }
-
private void onDisplayReadyInternal(int displayId) {
synchronized (mLock) {
if (mLastWallpaper == null) {
diff --git a/services/core/java/com/android/server/wearable/OWNERS b/services/core/java/com/android/server/wearable/OWNERS
index 073e2d7..eca48b7 100644
--- a/services/core/java/com/android/server/wearable/OWNERS
+++ b/services/core/java/com/android/server/wearable/OWNERS
@@ -1,3 +1 @@
[email protected]
[email protected]
[email protected]
\ No newline at end of file
+include /core/java/android/app/wearable/OWNERS
\ No newline at end of file
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
index cd48f5d..106be5f 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
@@ -218,7 +218,7 @@
PersistableBundle data,
SharedMemory sharedMemory,
RemoteCallback callback) {
- Slog.i(TAG, "WearableSensingManagerInternal provideData.");
+ Slog.d(TAG, "WearableSensingManagerInternal provideData.");
Objects.requireNonNull(data);
Objects.requireNonNull(callback);
mContext.enforceCallingOrSelfPermission(
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
index 60dc4ff..d95b431 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
@@ -348,6 +348,10 @@
private void pinWebviewIfRequired(ApplicationInfo appInfo) {
PinnerService pinnerService = LocalServices.getService(PinnerService.class);
+ if (pinnerService == null) {
+ // This happens in unit tests which do not have services.
+ return;
+ }
int webviewPinQuota = pinnerService.getWebviewPinQuota();
if (webviewPinQuota <= 0) {
return;
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
index 29782d9..f4fb1a1 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
@@ -159,11 +159,28 @@
}
}
+ private boolean shouldTriggerRepairLocked() {
+ if (mCurrentWebViewPackage == null) {
+ return true;
+ }
+ WebViewProviderInfo defaultProvider = getDefaultWebViewPackage();
+ if (mCurrentWebViewPackage.packageName.equals(defaultProvider.packageName)) {
+ List<UserPackage> userPackages =
+ mSystemInterface.getPackageInfoForProviderAllUsers(
+ mContext, defaultProvider);
+ return !isInstalledAndEnabledForAllUsers(userPackages);
+ } else {
+ return false;
+ }
+ }
+
@Override
public void prepareWebViewInSystemServer() {
try {
+ boolean repairNeeded = true;
synchronized (mLock) {
mCurrentWebViewPackage = findPreferredWebViewPackage();
+ repairNeeded = shouldTriggerRepairLocked();
String userSetting = mSystemInterface.getUserChosenWebViewProvider(mContext);
if (userSetting != null
&& !userSetting.equals(mCurrentWebViewPackage.packageName)) {
@@ -177,26 +194,25 @@
}
onWebViewProviderChanged(mCurrentWebViewPackage);
}
+
+ if (repairNeeded) {
+ // We didn't find a valid WebView implementation. Try explicitly re-enabling the
+ // default package for all users in case it was disabled, even if we already did the
+ // one-time migration before. If this actually changes the state, we will see the
+ // PackageManager broadcast shortly and try again.
+ WebViewProviderInfo defaultProvider = getDefaultWebViewPackage();
+ Slog.w(
+ TAG,
+ "No provider available for all users, trying to enable "
+ + defaultProvider.packageName);
+ mSystemInterface.enablePackageForAllUsers(
+ mContext, defaultProvider.packageName, true);
+ }
+
} catch (Throwable t) {
// Log and discard errors at this stage as we must not crash the system server.
Slog.e(TAG, "error preparing webview provider from system server", t);
}
-
- if (getCurrentWebViewPackage() == null) {
- // We didn't find a valid WebView implementation. Try explicitly re-enabling the
- // fallback package for all users in case it was disabled, even if we already did the
- // one-time migration before. If this actually changes the state, we will see the
- // PackageManager broadcast shortly and try again.
- WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages();
- WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders);
- if (fallbackProvider != null) {
- Slog.w(TAG, "No valid provider, trying to enable " + fallbackProvider.packageName);
- mSystemInterface.enablePackageForAllUsers(mContext, fallbackProvider.packageName,
- true);
- } else {
- Slog.e(TAG, "No valid provider and no fallback available.");
- }
- }
}
private void startZygoteWhenReady() {
@@ -421,42 +437,43 @@
/**
* Returns either the package info of the WebView provider determined in the following way:
- * If the user has chosen a provider then use that if it is valid,
- * otherwise use the first package in the webview priority list that is valid.
- *
+ * If the user has chosen a provider then use that if it is valid, enabled and installed
+ * for all users, otherwise use the default provider.
*/
private PackageInfo findPreferredWebViewPackage() throws WebViewPackageMissingException {
- ProviderAndPackageInfo[] providers = getValidWebViewPackagesAndInfos();
-
- String userChosenProvider = mSystemInterface.getUserChosenWebViewProvider(mContext);
-
// If the user has chosen provider, use that (if it's installed and enabled for all
// users).
- for (ProviderAndPackageInfo providerAndPackage : providers) {
- if (providerAndPackage.provider.packageName.equals(userChosenProvider)) {
- // userPackages can contain null objects.
- List<UserPackage> userPackages =
- mSystemInterface.getPackageInfoForProviderAllUsers(mContext,
- providerAndPackage.provider);
- if (isInstalledAndEnabledForAllUsers(userPackages)) {
- return providerAndPackage.packageInfo;
+ String userChosenPackageName = mSystemInterface.getUserChosenWebViewProvider(mContext);
+ WebViewProviderInfo userChosenProvider =
+ getWebViewProviderForPackage(userChosenPackageName);
+ if (userChosenProvider != null) {
+ try {
+ PackageInfo packageInfo =
+ mSystemInterface.getPackageInfoForProvider(userChosenProvider);
+ if (validityResult(userChosenProvider, packageInfo) == VALIDITY_OK) {
+ List<UserPackage> userPackages =
+ mSystemInterface.getPackageInfoForProviderAllUsers(
+ mContext, userChosenProvider);
+ if (isInstalledAndEnabledForAllUsers(userPackages)) {
+ return packageInfo;
+ }
}
+ } catch (NameNotFoundException e) {
+ Slog.w(TAG, "User chosen WebView package (" + userChosenPackageName
+ + ") not found");
}
}
- // User did not choose, or the choice failed; use the most stable provider that is
- // installed and enabled for all users, and available by default (not through
- // user choice).
- for (ProviderAndPackageInfo providerAndPackage : providers) {
- if (providerAndPackage.provider.availableByDefault) {
- // userPackages can contain null objects.
- List<UserPackage> userPackages =
- mSystemInterface.getPackageInfoForProviderAllUsers(mContext,
- providerAndPackage.provider);
- if (isInstalledAndEnabledForAllUsers(userPackages)) {
- return providerAndPackage.packageInfo;
- }
+ // User did not choose, or the choice failed; return the default provider even if it is not
+ // installed or enabled for all users.
+ WebViewProviderInfo defaultProvider = getDefaultWebViewPackage();
+ try {
+ PackageInfo packageInfo = mSystemInterface.getPackageInfoForProvider(defaultProvider);
+ if (validityResult(defaultProvider, packageInfo) == VALIDITY_OK) {
+ return packageInfo;
}
+ } catch (NameNotFoundException e) {
+ Slog.w(TAG, "Default WebView package (" + defaultProvider.packageName + ") not found");
}
// This should never happen during normal operation (only with modified system images).
@@ -464,6 +481,16 @@
throw new WebViewPackageMissingException("Could not find a loadable WebView package");
}
+ private WebViewProviderInfo getWebViewProviderForPackage(String packageName) {
+ WebViewProviderInfo[] allProviders = getWebViewPackages();
+ for (int n = 0; n < allProviders.length; n++) {
+ if (allProviders[n].packageName.equals(packageName)) {
+ return allProviders[n];
+ }
+ }
+ return null;
+ }
+
/**
* Return true iff {@param packageInfos} point to only installed and enabled packages.
* The given packages {@param packageInfos} should all be pointing to the same package, but each
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 676203b..2e0546e 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -778,17 +778,22 @@
try {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
+ if (r == null) {
+ return false;
+ }
// Create a transition if the activity is playing in case the below activity didn't
// commit invisible. That's because if any activity below this one has changed its
// visibility while playing transition, there won't able to commit visibility until
// the running transition finish.
- final Transition transition = r != null
- && r.mTransitionController.inPlayingTransition(r)
+ final Transition transition = r.mTransitionController.isShellTransitionsEnabled()
&& !r.mTransitionController.isCollecting()
? r.mTransitionController.createTransition(TRANSIT_TO_BACK) : null;
- final boolean changed = r != null && r.setOccludesParent(true);
+ final boolean changed = r.setOccludesParent(true);
if (transition != null) {
if (changed) {
+ // Always set as scene transition because it expects to be a jump-cut.
+ transition.setOverrideAnimation(TransitionInfo.AnimationOptions
+ .makeSceneTransitionAnimOptions(), null, null);
r.mTransitionController.requestStartTransition(transition,
null /*startTask */, null /* remoteTransition */,
null /* displayChange */);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 3a792d0..9b1f9c8 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -168,6 +168,7 @@
import static com.android.server.wm.ActivityRecordProto.FRONT_OF_TASK;
import static com.android.server.wm.ActivityRecordProto.IN_SIZE_COMPAT_MODE;
import static com.android.server.wm.ActivityRecordProto.IS_ANIMATING;
+import static com.android.server.wm.ActivityRecordProto.IS_USER_FULLSCREEN_OVERRIDE_ENABLED;
import static com.android.server.wm.ActivityRecordProto.IS_WAITING_FOR_TRANSITION_START;
import static com.android.server.wm.ActivityRecordProto.LAST_ALL_DRAWN;
import static com.android.server.wm.ActivityRecordProto.LAST_DROP_INPUT_MODE;
@@ -182,6 +183,7 @@
import static com.android.server.wm.ActivityRecordProto.PROVIDES_MAX_BOUNDS;
import static com.android.server.wm.ActivityRecordProto.REPORTED_DRAWN;
import static com.android.server.wm.ActivityRecordProto.REPORTED_VISIBLE;
+import static com.android.server.wm.ActivityRecordProto.SHOULD_ENABLE_USER_ASPECT_RATIO_SETTINGS;
import static com.android.server.wm.ActivityRecordProto.SHOULD_FORCE_ROTATE_FOR_CAMERA_COMPAT;
import static com.android.server.wm.ActivityRecordProto.SHOULD_IGNORE_ORIENTATION_REQUEST_LOOP;
import static com.android.server.wm.ActivityRecordProto.SHOULD_OVERRIDE_FORCE_RESIZE_APP;
@@ -3091,7 +3093,6 @@
final boolean changed = occludesParent != mOccludesParent;
mOccludesParent = occludesParent;
setMainWindowOpaque(occludesParent);
- mWmService.mWindowPlacerLocked.requestTraversal();
if (changed && task != null && !occludesParent) {
getRootTask().convertActivityToTranslucent(this);
@@ -10338,6 +10339,10 @@
mLetterboxUiController.shouldIgnoreOrientationRequestLoop());
proto.write(SHOULD_OVERRIDE_FORCE_RESIZE_APP,
mLetterboxUiController.shouldOverrideForceResizeApp());
+ proto.write(SHOULD_ENABLE_USER_ASPECT_RATIO_SETTINGS,
+ mLetterboxUiController.shouldEnableUserAspectRatioSettings());
+ proto.write(IS_USER_FULLSCREEN_OVERRIDE_ENABLED,
+ mLetterboxUiController.isUserFullscreenOverrideEnabled());
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index e7621ff..182e1c1 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -18,6 +18,7 @@
import static android.app.ActivityManager.INTENT_SENDER_ACTIVITY;
import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.app.PendingIntent.FLAG_ONE_SHOT;
@@ -144,22 +145,20 @@
}
private IntentSender createIntentSenderForOriginalIntent(int callingUid, int flags) {
- Bundle bOptions = deferCrossProfileAppsAnimationIfNecessary();
+ ActivityOptions activityOptions = deferCrossProfileAppsAnimationIfNecessary();
+ activityOptions.setPendingIntentCreatorBackgroundActivityStartMode(
+ MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
final TaskFragment taskFragment = getLaunchTaskFragment();
// If the original intent is going to be embedded, try to forward the embedding TaskFragment
// and its task id to embed back the original intent.
if (taskFragment != null) {
- ActivityOptions activityOptions = bOptions != null
- ? ActivityOptions.fromBundle(bOptions)
- : ActivityOptions.makeBasic();
activityOptions.setLaunchTaskFragmentToken(taskFragment.getFragmentToken());
- bOptions = activityOptions.toBundle();
}
final IIntentSender target = mService.getIntentSenderLocked(
INTENT_SENDER_ACTIVITY, mCallingPackage, mCallingFeatureId, callingUid, mUserId,
null /*token*/, null /*resultCode*/, 0 /*requestCode*/,
new Intent[] { mIntent }, new String[] { mResolvedType },
- flags, bOptions);
+ flags, activityOptions.toBundle());
return new IntentSender(target);
}
@@ -272,12 +271,12 @@
*
* @return the activity option used to start the original intent.
*/
- private Bundle deferCrossProfileAppsAnimationIfNecessary() {
+ private ActivityOptions deferCrossProfileAppsAnimationIfNecessary() {
if (hasCrossProfileAnimation()) {
mActivityOptions = null;
- return ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle();
+ return ActivityOptions.makeOpenCrossProfileAppsAnimation();
}
- return null;
+ return ActivityOptions.makeBasic();
}
private boolean interceptQuietProfileIfNeeded() {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 13f7152..f6d77ea 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -60,6 +60,7 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
+import static com.android.server.pm.PackageArchiver.isArchivingEnabled;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
@@ -104,7 +105,6 @@
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.AuxiliaryResolveInfo;
-import android.content.pm.Flags;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
@@ -1034,7 +1034,7 @@
}
if (err == ActivityManager.START_SUCCESS && aInfo == null) {
- if (Flags.archiving()) {
+ if (isArchivingEnabled()) {
PackageArchiver packageArchiver = mService
.getPackageManagerInternalLocked()
.getPackageArchiver();
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index b94206d..2bd49bf 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -1311,7 +1311,7 @@
Rect insets;
if (mainWindow != null) {
insets = mainWindow.getInsetsStateWithVisibilityOverride().calculateInsets(
- mBounds, WindowInsets.Type.systemBars(),
+ mBounds, WindowInsets.Type.tappableElement(),
false /* ignoreVisibility */).toRect();
InsetUtils.addInsets(insets, mainWindow.mActivityRecord.getLetterboxInsets());
} else {
@@ -1591,12 +1591,13 @@
private static void setLaunchBehind(@NonNull ActivityRecord activity) {
if (!activity.isVisibleRequested()) {
- activity.setVisibility(true);
// The transition could commit the visibility and in the finishing state, that could
// skip commitVisibility call in setVisibility cause the activity won't visible here.
// Call it again to make sure the activity could be visible while handling the pending
// animation.
- activity.commitVisibility(true, true);
+ // Do not performLayout during prepare animation, because it could cause focus window
+ // change. Let that happen after the BackNavigationInfo has returned to shell.
+ activity.commitVisibility(true, false /* performLayout */);
activity.mTransitionController.mSnapshotController
.mActivitySnapshotController.addOnBackPressedActivity(activity);
}
@@ -1667,10 +1668,14 @@
ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "onBackNavigationDone backType=%s, "
+ "triggerBack=%b", backType, triggerBack);
- mNavigationMonitor.stopMonitorForRemote();
- mBackAnimationInProgress = false;
- mShowWallpaper = false;
- mPendingAnimationBuilder = null;
+ synchronized (mWindowManagerService.mGlobalLock) {
+ mNavigationMonitor.stopMonitorForRemote();
+ mBackAnimationInProgress = false;
+ mShowWallpaper = false;
+ // All animation should be done, clear any un-send animation.
+ mPendingAnimation = null;
+ mPendingAnimationBuilder = null;
+ }
}
static TaskSnapshot getSnapshot(@NonNull WindowContainer w,
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index fc3a338..0f36d8e 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -18,6 +18,10 @@
import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+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.ComponentOptions.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;
@@ -31,6 +35,7 @@
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY;
import static com.android.server.wm.ActivityTaskSupervisor.getApplicationLabel;
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;
@@ -94,9 +99,9 @@
public static final ActivityOptions ACTIVITY_OPTIONS_SYSTEM_DEFINED =
ActivityOptions.makeBasic()
.setPendingIntentBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED)
+ MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED)
.setPendingIntentCreatorBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
+ MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
private final ActivityTaskManagerService mService;
@@ -120,38 +125,56 @@
static final int BAL_BLOCK = 0;
- static final int BAL_ALLOW_DEFAULT = 1;
+ static final int BAL_ALLOW_DEFAULT =
+ FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_ALLOW_DEFAULT;
// Following codes are in order of precedence
/** Important UIDs which should be always allowed to launch activities */
- static final int BAL_ALLOW_ALLOWLISTED_UID = 2;
+ static final int BAL_ALLOW_ALLOWLISTED_UID =
+ FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_ALLOW_ALLOWLISTED_UID;
/** Apps that fulfill a certain role that can can always launch new tasks */
- static final int BAL_ALLOW_ALLOWLISTED_COMPONENT = 3;
+ static final int BAL_ALLOW_ALLOWLISTED_COMPONENT =
+ FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_ALLOW_ALLOWLISTED_COMPONENT;
- /** Apps which currently have a visible window or are bound by a service with a visible
- * window */
- static final int BAL_ALLOW_VISIBLE_WINDOW = 4;
+ /**
+ * Apps which currently have a visible window or are bound by a service with a visible
+ * window
+ */
+ static final int BAL_ALLOW_VISIBLE_WINDOW =
+ FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_ALLOW_VISIBLE_WINDOW;
/** Allowed due to the PendingIntent sender */
- static final int BAL_ALLOW_PENDING_INTENT = 5;
+ static final int BAL_ALLOW_PENDING_INTENT =
+ FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_ALLOW_PENDING_INTENT;
- /** App has START_ACTIVITIES_FROM_BACKGROUND permission or BAL instrumentation privileges
- * granted to it */
- static final int BAL_ALLOW_PERMISSION = 6;
+ /**
+ * App has START_ACTIVITIES_FROM_BACKGROUND permission or BAL instrumentation privileges
+ * granted to it
+ */
+ static final int BAL_ALLOW_PERMISSION =
+ FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_ALLOW_BAL_PERMISSION;
/** Process has SYSTEM_ALERT_WINDOW permission granted to it */
- static final int BAL_ALLOW_SAW_PERMISSION = 7;
+ static final int BAL_ALLOW_SAW_PERMISSION =
+ FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_ALLOW_SAW_PERMISSION;
/** App is in grace period after an activity was started or finished */
- static final int BAL_ALLOW_GRACE_PERIOD = 8;
+ static final int BAL_ALLOW_GRACE_PERIOD =
+ FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_ALLOW_GRACE_PERIOD;
/** App is in a foreground task or bound to a foreground service (but not itself visible) */
- static final int BAL_ALLOW_FOREGROUND = 9;
+ static final int BAL_ALLOW_FOREGROUND =
+ FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_ALLOW_FOREGROUND;
/** Process belongs to a SDK sandbox */
- static final int BAL_ALLOW_SDK_SANDBOX = 10;
+ static final int BAL_ALLOW_SDK_SANDBOX =
+ FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_ALLOW_SDK_SANDBOX;
+
+ /** Process belongs to a SDK sandbox */
+ static final int BAL_ALLOW_NON_APP_VISIBLE_WINDOW =
+ FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_ALLOW_NON_APP_VISIBLE_WINDOW;
static String balCodeToString(@BalCode int balCode) {
return switch (balCode) {
@@ -160,6 +183,7 @@
case BAL_ALLOW_DEFAULT -> "BAL_ALLOW_DEFAULT";
case BAL_ALLOW_FOREGROUND -> "BAL_ALLOW_FOREGROUND";
case BAL_ALLOW_GRACE_PERIOD -> "BAL_ALLOW_GRACE_PERIOD";
+ case BAL_ALLOW_NON_APP_VISIBLE_WINDOW -> "BAL_ALLOW_NON_APP_VISIBLE_WINDOW";
case BAL_ALLOW_PENDING_INTENT -> "BAL_ALLOW_PENDING_INTENT";
case BAL_ALLOW_PERMISSION -> "BAL_ALLOW_PERMISSION";
case BAL_ALLOW_SAW_PERMISSION -> "BAL_ALLOW_SAW_PERMISSION";
@@ -221,6 +245,7 @@
private final WindowProcessController mRealCallerApp;
private final boolean mIsCallForResult;
private final ActivityOptions mCheckedOptions;
+ private final String mAutoOptInReason;
private BalVerdict mResultForCaller;
private BalVerdict mResultForRealCaller;
@@ -244,28 +269,38 @@
mRealCallingPackage = mService.getPackageNameIfUnique(realCallingUid, realCallingPid);
mIsCallForResult = resultRecord != null;
mCheckedOptions = checkedOptions;
- if (balRequireOptInByPendingIntentCreator() // auto-opt in introduced with this feature
- && (originatingPendingIntent == null // not a PendingIntent
- || mIsCallForResult) // sent for result
- ) {
+ @BackgroundActivityStartMode int callerBackgroundActivityStartMode =
+ checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode();
+ @BackgroundActivityStartMode int realCallerBackgroundActivityStartMode =
+ checkedOptions.getPendingIntentBackgroundActivityStartMode();
+
+ if (balRequireOptInByPendingIntentCreator() && originatingPendingIntent == null) {
+ mAutoOptInReason = "notPendingIntent";
+ } else if (balRequireOptInByPendingIntentCreator() && mIsCallForResult) {
+ mAutoOptInReason = "callForResult";
+ } else if (callingUid == realCallingUid && !balRequireOptInSameUid()) {
+ mAutoOptInReason = "sameUid";
+ } else {
+ mAutoOptInReason = null;
+ }
+
+ if (mAutoOptInReason != null) {
// grant BAL privileges unless explicitly opted out
mBalAllowedByPiCreatorWithHardening = mBalAllowedByPiCreator =
- checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
- == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED
+ callerBackgroundActivityStartMode == MODE_BACKGROUND_ACTIVITY_START_DENIED
? BackgroundStartPrivileges.NONE
: BackgroundStartPrivileges.ALLOW_BAL;
- mBalAllowedByPiSender =
- checkedOptions.getPendingIntentBackgroundActivityStartMode()
- == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED
- ? BackgroundStartPrivileges.NONE
- : BackgroundStartPrivileges.ALLOW_BAL;
+ mBalAllowedByPiSender = realCallerBackgroundActivityStartMode
+ == MODE_BACKGROUND_ACTIVITY_START_DENIED
+ ? BackgroundStartPrivileges.NONE
+ : BackgroundStartPrivileges.ALLOW_BAL;
} else {
// for PendingIntents we restrict BAL based on target_sdk
mBalAllowedByPiCreatorWithHardening = getBackgroundStartPrivilegesAllowedByCreator(
callingUid, callingPackage, checkedOptions);
final BackgroundStartPrivileges mBalAllowedByPiCreatorWithoutHardening =
- checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
- == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED
+ callerBackgroundActivityStartMode
+ == MODE_BACKGROUND_ACTIVITY_START_DENIED
? BackgroundStartPrivileges.NONE
: BackgroundStartPrivileges.ALLOW_BAL;
mBalAllowedByPiCreator = balRequireOptInByPendingIntentCreator()
@@ -307,11 +342,11 @@
private BackgroundStartPrivileges getBackgroundStartPrivilegesAllowedByCreator(
int callingUid, String callingPackage, ActivityOptions checkedOptions) {
switch (checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()) {
- case ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED:
+ case MODE_BACKGROUND_ACTIVITY_START_ALLOWED:
return BackgroundStartPrivileges.ALLOW_BAL;
- case ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED:
+ case MODE_BACKGROUND_ACTIVITY_START_DENIED:
return BackgroundStartPrivileges.NONE;
- case ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED:
+ case MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED:
// no explicit choice by the app - let us decide what to do
if (callingPackage != null) {
// determine based on the calling/creating package
@@ -412,6 +447,7 @@
sb.append("; hasRealCaller: ").append(hasRealCaller());
sb.append("; isCallForResult: ").append(mIsCallForResult);
sb.append("; isPendingIntent: ").append(isPendingIntent());
+ sb.append("; autoOptInReason: ").append(mAutoOptInReason);
if (hasRealCaller()) {
sb.append("; realCallingPackage: ")
.append(getDebugPackageName(mRealCallingPackage, mRealCallingUid));
@@ -437,6 +473,50 @@
sb.append("]");
return sb.toString();
}
+
+ public boolean isPendingIntentBalAllowedByPermission() {
+ return PendingIntentRecord.isPendingIntentBalAllowedByPermission(mCheckedOptions);
+ }
+
+ public boolean callerExplicitOptInOrAutoOptIn() {
+ if (mAutoOptInReason == null) {
+ return mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
+ == MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+ } else {
+ return mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
+ != MODE_BACKGROUND_ACTIVITY_START_DENIED;
+ }
+ }
+
+ public boolean realCallerExplicitOptInOrAutoOptIn() {
+ if (mAutoOptInReason == null) {
+ return mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
+ == MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+ } else {
+ return mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
+ != MODE_BACKGROUND_ACTIVITY_START_DENIED;
+ }
+ }
+
+ public boolean callerExplicitOptOut() {
+ return mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
+ == MODE_BACKGROUND_ACTIVITY_START_DENIED;
+ }
+
+ public boolean realCallerExplicitOptOut() {
+ return mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
+ == MODE_BACKGROUND_ACTIVITY_START_DENIED;
+ }
+
+ public boolean callerExplicitOptInOrOut() {
+ return mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
+ != MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+ }
+
+ public boolean realCallerExplicitOptInOrOut() {
+ return mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
+ != MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+ }
}
static class BalVerdict {
@@ -605,7 +685,7 @@
// PendingIntents is null).
BalVerdict resultForRealCaller = state.callerIsRealCaller() && resultForCaller.allows()
? resultForCaller
- : checkBackgroundActivityStartAllowedBySender(state, checkedOptions)
+ : checkBackgroundActivityStartAllowedBySender(state)
.setBasedOnRealCaller();
state.setResultForRealCaller(resultForRealCaller);
@@ -615,18 +695,14 @@
}
// Handle cases with explicit opt-in
- if (resultForCaller.allows()
- && checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
- == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) {
+ if (resultForCaller.allows() && state.callerExplicitOptInOrAutoOptIn()) {
if (DEBUG_ACTIVITY_STARTS) {
Slog.d(TAG, "Activity start explicitly allowed by caller. "
+ state.dump());
}
return allowBasedOnCaller(state);
}
- if (resultForRealCaller.allows()
- && checkedOptions.getPendingIntentBackgroundActivityStartMode()
- == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) {
+ if (resultForRealCaller.allows() && state.realCallerExplicitOptInOrAutoOptIn()) {
if (DEBUG_ACTIVITY_STARTS) {
Slog.d(TAG, "Activity start explicitly allowed by real caller. "
+ state.dump());
@@ -634,12 +710,9 @@
return allowBasedOnRealCaller(state);
}
// Handle PendingIntent cases with default behavior next
- boolean callerCanAllow = resultForCaller.allows()
- && checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
- == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+ boolean callerCanAllow = resultForCaller.allows() && !state.callerExplicitOptOut();
boolean realCallerCanAllow = resultForRealCaller.allows()
- && checkedOptions.getPendingIntentBackgroundActivityStartMode()
- == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+ && !state.realCallerExplicitOptOut();
if (callerCanAllow && realCallerCanAllow) {
// Both caller and real caller allow with system defined behavior
if (state.mBalAllowedByPiCreatorWithHardening.allowsBackgroundActivityStarts()) {
@@ -788,7 +861,7 @@
/*background*/ false, "callingUid has visible window");
}
if (mService.mActiveUids.hasNonAppVisibleWindow(callingUid)) {
- return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
+ return new BalVerdict(BAL_ALLOW_NON_APP_VISIBLE_WINDOW,
/*background*/ false, "callingUid has non-app visible window");
}
@@ -861,11 +934,9 @@
* @return A code denoting which BAL rule allows an activity to be started,
* or {@link #BAL_BLOCK} if the launch should be blocked
*/
- BalVerdict checkBackgroundActivityStartAllowedBySender(
- BalState state,
- ActivityOptions checkedOptions) {
+ BalVerdict checkBackgroundActivityStartAllowedBySender(BalState state) {
- if (PendingIntentRecord.isPendingIntentBalAllowedByPermission(checkedOptions)
+ if (state.isPendingIntentBalAllowedByPermission()
&& ActivityManager.checkComponentPermission(
android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
state.mRealCallingUid, NO_PROCESS_UID, true) == PackageManager.PERMISSION_GRANTED) {
@@ -884,7 +955,7 @@
/*background*/ false, "realCallingUid has visible window");
}
if (mService.mActiveUids.hasNonAppVisibleWindow(state.mRealCallingUid)) {
- return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
+ return new BalVerdict(BAL_ALLOW_NON_APP_VISIBLE_WINDOW,
/*background*/ false, "realCallingUid has non-app visible window");
}
} else {
@@ -989,7 +1060,8 @@
|| balCode == BAL_ALLOW_PERMISSION
|| balCode == BAL_ALLOW_PENDING_INTENT
|| balCode == BAL_ALLOW_SAW_PERMISSION
- || balCode == BAL_ALLOW_VISIBLE_WINDOW) {
+ || balCode == BAL_ALLOW_VISIBLE_WINDOW
+ || balCode == BAL_ALLOW_NON_APP_VISIBLE_WINDOW) {
return true;
}
}
@@ -1501,7 +1573,8 @@
Intent intent = state.mIntent;
if (code == BAL_ALLOW_PENDING_INTENT
- && (callingUid == Process.SYSTEM_UID || realCallingUid == Process.SYSTEM_UID)) {
+ && (callingUid < Process.FIRST_APPLICATION_UID
+ || realCallingUid < Process.FIRST_APPLICATION_UID)) {
String activityName = intent != null
? requireNonNull(intent.getComponent()).flattenToShortString() : "";
writeBalAllowedLog(activityName, BAL_ALLOW_PENDING_INTENT,
@@ -1524,13 +1597,11 @@
state.mRealCallingUid,
state.mResultForCaller == null ? BAL_BLOCK : state.mResultForCaller.getRawCode(),
state.mBalAllowedByPiCreator.allowsBackgroundActivityStarts(),
- state.mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
- != ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED,
+ state.callerExplicitOptInOrOut(),
state.mResultForRealCaller == null ? BAL_BLOCK
: state.mResultForRealCaller.getRawCode(),
state.mBalAllowedByPiSender.allowsBackgroundActivityStarts(),
- state.mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
- != ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED
+ state.realCallerExplicitOptInOrOut()
);
}
diff --git a/services/core/java/com/android/server/wm/LegacyTransitionTracer.java b/services/core/java/com/android/server/wm/LegacyTransitionTracer.java
new file mode 100644
index 0000000..fb2d5be
--- /dev/null
+++ b/services/core/java/com/android/server/wm/LegacyTransitionTracer.java
@@ -0,0 +1,331 @@
+/*
+ * 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.wm;
+
+import static android.os.Build.IS_USER;
+
+import static com.android.server.wm.shell.TransitionTraceProto.MAGIC_NUMBER;
+import static com.android.server.wm.shell.TransitionTraceProto.MAGIC_NUMBER_H;
+import static com.android.server.wm.shell.TransitionTraceProto.MAGIC_NUMBER_L;
+import static com.android.server.wm.shell.TransitionTraceProto.REAL_TO_ELAPSED_TIME_OFFSET_NANOS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.util.TraceBuffer;
+import com.android.server.wm.Transition.ChangeInfo;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Helper class to collect and dump transition traces.
+ */
+class LegacyTransitionTracer implements TransitionTracer {
+
+ private static final String LOG_TAG = "TransitionTracer";
+
+ private static final int ALWAYS_ON_TRACING_CAPACITY = 15 * 1024; // 15 KB
+ private static final int ACTIVE_TRACING_BUFFER_CAPACITY = 5000 * 1024; // 5 MB
+
+ // This will be the size the proto output streams are initialized to.
+ // Ideally this should fit most or all the proto objects we will create and be no bigger than
+ // that to ensure to don't use excessive amounts of memory.
+ private static final int CHUNK_SIZE = 64;
+
+ static final String WINSCOPE_EXT = ".winscope";
+ private static final String TRACE_FILE =
+ "/data/misc/wmtrace/wm_transition_trace" + WINSCOPE_EXT;
+ private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
+
+ private final TraceBuffer mTraceBuffer = new TraceBuffer(ALWAYS_ON_TRACING_CAPACITY);
+
+ private final Object mEnabledLock = new Object();
+ private volatile boolean mActiveTracingEnabled = false;
+
+ /**
+ * Records key information about a transition that has been sent to Shell to be played.
+ * More information will be appended to the same proto object once the transition is finished or
+ * aborted.
+ * Transition information won't be added to the trace buffer until
+ * {@link #logFinishedTransition} or {@link #logAbortedTransition} is called for this
+ * transition.
+ *
+ * @param transition The transition that has been sent to Shell.
+ * @param targets Information about the target windows of the transition.
+ */
+ @Override
+ public void logSentTransition(Transition transition, ArrayList<ChangeInfo> targets) {
+ try {
+ final ProtoOutputStream outputStream = new ProtoOutputStream(CHUNK_SIZE);
+ final long protoToken = outputStream
+ .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS);
+ outputStream.write(com.android.server.wm.shell.Transition.ID, transition.getSyncId());
+ outputStream.write(com.android.server.wm.shell.Transition.CREATE_TIME_NS,
+ transition.mLogger.mCreateTimeNs);
+ outputStream.write(com.android.server.wm.shell.Transition.SEND_TIME_NS,
+ transition.mLogger.mSendTimeNs);
+ outputStream.write(com.android.server.wm.shell.Transition.START_TRANSACTION_ID,
+ transition.getStartTransaction().getId());
+ outputStream.write(com.android.server.wm.shell.Transition.FINISH_TRANSACTION_ID,
+ transition.getFinishTransaction().getId());
+ dumpTransitionTargetsToProto(outputStream, transition, targets);
+ outputStream.end(protoToken);
+
+ mTraceBuffer.add(outputStream);
+ } catch (Exception e) {
+ // Don't let any errors in the tracing cause the transition to fail
+ Log.e(LOG_TAG, "Unexpected exception thrown while logging transitions", e);
+ }
+ }
+
+ /**
+ * Completes the information dumped in {@link #logSentTransition} for a transition
+ * that has finished or aborted, and add the proto object to the trace buffer.
+ *
+ * @param transition The transition that has finished.
+ */
+ @Override
+ public void logFinishedTransition(Transition transition) {
+ try {
+ final ProtoOutputStream outputStream = new ProtoOutputStream(CHUNK_SIZE);
+ final long protoToken = outputStream
+ .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS);
+ outputStream.write(com.android.server.wm.shell.Transition.ID, transition.getSyncId());
+ outputStream.write(com.android.server.wm.shell.Transition.FINISH_TIME_NS,
+ transition.mLogger.mFinishTimeNs);
+ outputStream.end(protoToken);
+
+ mTraceBuffer.add(outputStream);
+ } catch (Exception e) {
+ // Don't let any errors in the tracing cause the transition to fail
+ Log.e(LOG_TAG, "Unexpected exception thrown while logging transitions", e);
+ }
+ }
+
+ /**
+ * Same as {@link #logFinishedTransition} but don't add the transition to the trace buffer
+ * unless actively tracing.
+ *
+ * @param transition The transition that has been aborted
+ */
+ @Override
+ public void logAbortedTransition(Transition transition) {
+ try {
+ final ProtoOutputStream outputStream = new ProtoOutputStream(CHUNK_SIZE);
+ final long protoToken = outputStream
+ .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS);
+ outputStream.write(com.android.server.wm.shell.Transition.ID, transition.getSyncId());
+ outputStream.write(com.android.server.wm.shell.Transition.ABORT_TIME_NS,
+ transition.mLogger.mAbortTimeNs);
+ outputStream.end(protoToken);
+
+ mTraceBuffer.add(outputStream);
+ } catch (Exception e) {
+ // Don't let any errors in the tracing cause the transition to fail
+ Log.e(LOG_TAG, "Unexpected exception thrown while logging transitions", e);
+ }
+ }
+
+ @Override
+ public void logRemovingStartingWindow(@NonNull StartingData startingData) {
+ if (startingData.mTransitionId == 0) {
+ return;
+ }
+ try {
+ final ProtoOutputStream outputStream = new ProtoOutputStream(CHUNK_SIZE);
+ final long protoToken = outputStream
+ .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS);
+ outputStream.write(com.android.server.wm.shell.Transition.ID,
+ startingData.mTransitionId);
+ outputStream.write(
+ com.android.server.wm.shell.Transition.STARTING_WINDOW_REMOVE_TIME_NS,
+ SystemClock.elapsedRealtimeNanos());
+ outputStream.end(protoToken);
+
+ mTraceBuffer.add(outputStream);
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Unexpected exception thrown while logging transitions", e);
+ }
+ }
+
+ private void dumpTransitionTargetsToProto(ProtoOutputStream outputStream,
+ Transition transition, ArrayList<ChangeInfo> targets) {
+ Trace.beginSection("TransitionTracer#dumpTransitionTargetsToProto");
+ if (mActiveTracingEnabled) {
+ outputStream.write(com.android.server.wm.shell.Transition.ID,
+ transition.getSyncId());
+ }
+
+ outputStream.write(com.android.server.wm.shell.Transition.TYPE, transition.mType);
+ outputStream.write(com.android.server.wm.shell.Transition.FLAGS, transition.getFlags());
+
+ for (int i = 0; i < targets.size(); ++i) {
+ final long changeToken = outputStream
+ .start(com.android.server.wm.shell.Transition.TARGETS);
+
+ final Transition.ChangeInfo target = targets.get(i);
+
+ final int layerId;
+ if (target.mContainer.mSurfaceControl.isValid()) {
+ layerId = target.mContainer.mSurfaceControl.getLayerId();
+ } else {
+ layerId = -1;
+ }
+
+ outputStream.write(com.android.server.wm.shell.Target.MODE, target.mReadyMode);
+ outputStream.write(com.android.server.wm.shell.Target.FLAGS, target.mReadyFlags);
+ outputStream.write(com.android.server.wm.shell.Target.LAYER_ID, layerId);
+
+ if (mActiveTracingEnabled) {
+ // What we use in the WM trace
+ final int windowId = System.identityHashCode(target.mContainer);
+ outputStream.write(com.android.server.wm.shell.Target.WINDOW_ID, windowId);
+ }
+
+ outputStream.end(changeToken);
+ }
+
+ Trace.endSection();
+ }
+
+ /**
+ * Starts collecting transitions for the trace.
+ * If called while a trace is already running, this will reset the trace.
+ */
+ @Override
+ public void startTrace(@Nullable PrintWriter pw) {
+ if (IS_USER) {
+ LogAndPrintln.e(pw, "Tracing is not supported on user builds.");
+ return;
+ }
+ Trace.beginSection("TransitionTracer#startTrace");
+ LogAndPrintln.i(pw, "Starting shell transition trace.");
+ synchronized (mEnabledLock) {
+ mActiveTracingEnabled = true;
+ mTraceBuffer.resetBuffer();
+ mTraceBuffer.setCapacity(ACTIVE_TRACING_BUFFER_CAPACITY);
+ }
+ Trace.endSection();
+ }
+
+ /**
+ * Stops collecting the transition trace and dump to trace to file.
+ *
+ * Dumps the trace to @link{TRACE_FILE}.
+ */
+ @Override
+ public void stopTrace(@Nullable PrintWriter pw) {
+ stopTrace(pw, new File(TRACE_FILE));
+ }
+
+ /**
+ * Stops collecting the transition trace and dump to trace to file.
+ * @param outputFile The file to dump the transition trace to.
+ */
+ public void stopTrace(@Nullable PrintWriter pw, File outputFile) {
+ if (IS_USER) {
+ LogAndPrintln.e(pw, "Tracing is not supported on user builds.");
+ return;
+ }
+ Trace.beginSection("TransitionTracer#stopTrace");
+ LogAndPrintln.i(pw, "Stopping shell transition trace.");
+ synchronized (mEnabledLock) {
+ mActiveTracingEnabled = false;
+ writeTraceToFileLocked(pw, outputFile);
+ mTraceBuffer.resetBuffer();
+ mTraceBuffer.setCapacity(ALWAYS_ON_TRACING_CAPACITY);
+ }
+ Trace.endSection();
+ }
+
+ /**
+ * Being called while taking a bugreport so that tracing files can be included in the bugreport.
+ *
+ * @param pw Print writer
+ */
+ @Override
+ public void saveForBugreport(@Nullable PrintWriter pw) {
+ if (IS_USER) {
+ LogAndPrintln.e(pw, "Tracing is not supported on user builds.");
+ return;
+ }
+ Trace.beginSection("TransitionTracer#saveForBugreport");
+ synchronized (mEnabledLock) {
+ final File outputFile = new File(TRACE_FILE);
+ writeTraceToFileLocked(pw, outputFile);
+ }
+ Trace.endSection();
+ }
+
+ @Override
+ public boolean isTracing() {
+ return mActiveTracingEnabled;
+ }
+
+ private void writeTraceToFileLocked(@Nullable PrintWriter pw, File file) {
+ Trace.beginSection("TransitionTracer#writeTraceToFileLocked");
+ try {
+ ProtoOutputStream proto = new ProtoOutputStream(CHUNK_SIZE);
+ proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
+ long timeOffsetNs =
+ TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis())
+ - SystemClock.elapsedRealtimeNanos();
+ proto.write(REAL_TO_ELAPSED_TIME_OFFSET_NANOS, timeOffsetNs);
+ int pid = android.os.Process.myPid();
+ LogAndPrintln.i(pw, "Writing file to " + file.getAbsolutePath()
+ + " from process " + pid);
+ mTraceBuffer.writeTraceToFile(file, proto);
+ } catch (IOException e) {
+ LogAndPrintln.e(pw, "Unable to write buffer to file", e);
+ }
+ Trace.endSection();
+ }
+
+ private static class LogAndPrintln {
+ private static void i(@Nullable PrintWriter pw, String msg) {
+ Log.i(LOG_TAG, msg);
+ if (pw != null) {
+ pw.println(msg);
+ pw.flush();
+ }
+ }
+
+ private static void e(@Nullable PrintWriter pw, String msg) {
+ Log.e(LOG_TAG, msg);
+ if (pw != null) {
+ pw.println("ERROR: " + msg);
+ pw.flush();
+ }
+ }
+
+ private static void e(@Nullable PrintWriter pw, String msg, @NonNull Exception e) {
+ Log.e(LOG_TAG, msg, e);
+ if (pw != null) {
+ pw.println("ERROR: " + msg + " ::\n " + e);
+ pw.flush();
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 47972b3..fcc1e5b 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -1187,16 +1187,23 @@
&& mUserAspectRatio != USER_MIN_ASPECT_RATIO_FULLSCREEN;
}
- boolean shouldApplyUserFullscreenOverride() {
+ boolean isUserFullscreenOverrideEnabled() {
if (FALSE.equals(mBooleanPropertyAllowUserAspectRatioOverride)
|| FALSE.equals(mBooleanPropertyAllowUserAspectRatioFullscreenOverride)
|| !mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled()) {
return false;
}
+ return true;
+ }
- mUserAspectRatio = getUserMinAspectRatioOverrideCode();
+ boolean shouldApplyUserFullscreenOverride() {
+ if (isUserFullscreenOverrideEnabled()) {
+ mUserAspectRatio = getUserMinAspectRatioOverrideCode();
- return mUserAspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN;
+ return mUserAspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN;
+ }
+
+ return false;
}
boolean isSystemOverrideToFullscreenEnabled() {
diff --git a/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java b/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java
new file mode 100644
index 0000000..eae9951
--- /dev/null
+++ b/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.annotation.NonNull;
+import android.internal.perfetto.protos.PerfettoTrace;
+import android.os.SystemClock;
+import android.tracing.perfetto.DataSourceParams;
+import android.tracing.perfetto.InitArguments;
+import android.tracing.perfetto.Producer;
+import android.tracing.transition.TransitionDataSource;
+import android.util.proto.ProtoOutputStream;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
+
+class PerfettoTransitionTracer implements TransitionTracer {
+ private final AtomicInteger mActiveTraces = new AtomicInteger(0);
+ private final TransitionDataSource mDataSource =
+ new TransitionDataSource(this.mActiveTraces::incrementAndGet, () -> {},
+ this.mActiveTraces::decrementAndGet);
+
+ PerfettoTransitionTracer() {
+ Producer.init(InitArguments.DEFAULTS);
+ mDataSource.register(DataSourceParams.DEFAULTS);
+ }
+
+ /**
+ * Records key information about a transition that has been sent to Shell to be played.
+ * More information will be appended to the same proto object once the transition is finished or
+ * aborted.
+ * Transition information won't be added to the trace buffer until
+ * {@link #logFinishedTransition} or {@link #logAbortedTransition} is called for this
+ * transition.
+ *
+ * @param transition The transition that has been sent to Shell.
+ * @param targets Information about the target windows of the transition.
+ */
+ @Override
+ public void logSentTransition(Transition transition, ArrayList<Transition.ChangeInfo> targets) {
+ if (!isTracing()) {
+ return;
+ }
+
+ mDataSource.trace((ctx) -> {
+ final ProtoOutputStream os = ctx.newTracePacket();
+
+ final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
+
+ os.write(PerfettoTrace.ShellTransition.ID, transition.getSyncId());
+ os.write(PerfettoTrace.ShellTransition.CREATE_TIME_NS,
+ transition.mLogger.mCreateTimeNs);
+ os.write(PerfettoTrace.ShellTransition.SEND_TIME_NS, transition.mLogger.mSendTimeNs);
+ os.write(PerfettoTrace.ShellTransition.START_TRANSACTION_ID,
+ transition.getStartTransaction().getId());
+ os.write(PerfettoTrace.ShellTransition.FINISH_TRANSACTION_ID,
+ transition.getFinishTransaction().getId());
+ os.write(PerfettoTrace.ShellTransition.TYPE, transition.mType);
+ os.write(PerfettoTrace.ShellTransition.FLAGS, transition.getFlags());
+
+ addTransitionTargetsToProto(os, targets);
+
+ os.end(token);
+ });
+ }
+
+ /**
+ * Completes the information dumped in {@link #logSentTransition} for a transition
+ * that has finished or aborted, and add the proto object to the trace buffer.
+ *
+ * @param transition The transition that has finished.
+ */
+ @Override
+ public void logFinishedTransition(Transition transition) {
+ if (!isTracing()) {
+ return;
+ }
+
+ mDataSource.trace((ctx) -> {
+ final ProtoOutputStream os = ctx.newTracePacket();
+
+ final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
+ os.write(PerfettoTrace.ShellTransition.ID, transition.getSyncId());
+ os.write(PerfettoTrace.ShellTransition.FINISH_TIME_NS,
+ transition.mLogger.mFinishTimeNs);
+ os.end(token);
+ });
+ }
+
+ /**
+ * Same as {@link #logFinishedTransition} but don't add the transition to the trace buffer
+ * unless actively tracing.
+ *
+ * @param transition The transition that has been aborted
+ */
+ @Override
+ public void logAbortedTransition(Transition transition) {
+ if (!isTracing()) {
+ return;
+ }
+
+ mDataSource.trace((ctx) -> {
+ final ProtoOutputStream os = ctx.newTracePacket();
+
+ final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
+ os.write(PerfettoTrace.ShellTransition.ID, transition.getSyncId());
+ os.write(PerfettoTrace.ShellTransition.WM_ABORT_TIME_NS,
+ transition.mLogger.mAbortTimeNs);
+ os.end(token);
+ });
+ }
+
+ @Override
+ public void logRemovingStartingWindow(@NonNull StartingData startingData) {
+ if (!isTracing()) {
+ return;
+ }
+
+ mDataSource.trace((ctx) -> {
+ final ProtoOutputStream os = ctx.newTracePacket();
+
+ final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
+ os.write(PerfettoTrace.ShellTransition.ID, startingData.mTransitionId);
+ os.write(PerfettoTrace.ShellTransition.STARTING_WINDOW_REMOVE_TIME_NS,
+ SystemClock.elapsedRealtimeNanos());
+ os.end(token);
+ });
+ }
+
+ @Override
+ public void startTrace(PrintWriter pw) {
+ // No-op
+ }
+
+ @Override
+ public void stopTrace(PrintWriter pw) {
+ // No-op
+ }
+
+ @Override
+ public void saveForBugreport(PrintWriter pw) {
+ // Nothing to do here. Handled by Perfetto.
+ }
+
+ @Override
+ public boolean isTracing() {
+ return mActiveTraces.get() > 0;
+ }
+
+ private void addTransitionTargetsToProto(
+ ProtoOutputStream os,
+ ArrayList<Transition.ChangeInfo> targets
+ ) {
+ for (int i = 0; i < targets.size(); ++i) {
+ final Transition.ChangeInfo target = targets.get(i);
+
+ final int layerId;
+ if (target.mContainer.mSurfaceControl.isValid()) {
+ layerId = target.mContainer.mSurfaceControl.getLayerId();
+ } else {
+ layerId = -1;
+ }
+ final int windowId = System.identityHashCode(target.mContainer);
+
+ final long token = os.start(PerfettoTrace.ShellTransition.TARGETS);
+ os.write(PerfettoTrace.ShellTransition.Target.MODE, target.mReadyMode);
+ os.write(PerfettoTrace.ShellTransition.Target.FLAGS, target.mReadyFlags);
+ os.write(PerfettoTrace.ShellTransition.Target.LAYER_ID, layerId);
+ os.write(PerfettoTrace.ShellTransition.Target.WINDOW_ID, windowId);
+ os.end(token);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 6033220..02b3f15 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -808,7 +808,6 @@
mWmService.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
mWmService.mAtmService.mTaskFragmentOrganizerController.dispatchPendingEvents();
mWmService.mSyncEngine.onSurfacePlacement();
- mWmService.mAnimator.executeAfterPrepareSurfacesRunnables();
checkAppTransitionReady(surfacePlacer);
diff --git a/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
new file mode 100644
index 0000000..5f488b7
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
@@ -0,0 +1,284 @@
+/*
+ * 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.server.wm;
+
+import static android.content.Context.MEDIA_PROJECTION_SERVICE;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_ERROR;
+
+import android.media.projection.IMediaProjectionManager;
+import android.media.projection.IMediaProjectionWatcherCallback;
+import android.media.projection.MediaProjectionInfo;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.view.ContentRecordingSession;
+import android.window.IScreenRecordingCallback;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.protolog.common.ProtoLog;
+
+import java.io.PrintWriter;
+import java.util.Map;
+import java.util.Set;
+
+public class ScreenRecordingCallbackController {
+
+ private final class Callback implements IBinder.DeathRecipient {
+
+ IScreenRecordingCallback mCallback;
+ int mUid;
+
+ Callback(IScreenRecordingCallback callback, int uid) {
+ this.mCallback = callback;
+ this.mUid = uid;
+ }
+
+ public void binderDied() {
+ unregister(mCallback);
+ }
+ }
+
+ @GuardedBy("WindowManagerService.mGlobalLock")
+ private final Map<IBinder, Callback> mCallbacks = new ArrayMap<>();
+
+ @GuardedBy("WindowManagerService.mGlobalLock")
+ private final Map<Integer /*UID*/, Boolean> mLastInvokedStateByUid = new ArrayMap<>();
+
+ private final WindowManagerService mWms;
+
+ @GuardedBy("WindowManagerService.mGlobalLock")
+ private WindowContainer<WindowContainer> mRecordedWC;
+
+ private boolean mWatcherCallbackRegistered = false;
+
+ private final class MediaProjectionWatcherCallback extends
+ IMediaProjectionWatcherCallback.Stub {
+ @Override
+ public void onStart(MediaProjectionInfo mediaProjectionInfo) {
+ onScreenRecordingStart(mediaProjectionInfo);
+ }
+
+ @Override
+ public void onStop(MediaProjectionInfo mediaProjectionInfo) {
+ onScreenRecordingStop();
+ }
+
+ @Override
+ public void onRecordingSessionSet(MediaProjectionInfo mediaProjectionInfo,
+ ContentRecordingSession contentRecordingSession) {
+ }
+ }
+
+ ScreenRecordingCallbackController(WindowManagerService wms) {
+ mWms = wms;
+ }
+
+ @GuardedBy("WindowManagerService.mGlobalLock")
+ private void setRecordedWindowContainer(MediaProjectionInfo mediaProjectionInfo) {
+ if (mediaProjectionInfo.getLaunchCookie() == null) {
+ mRecordedWC = (WindowContainer) mWms.mRoot.getDefaultDisplay();
+ } else {
+ mRecordedWC = mWms.mRoot.getActivity(activity -> activity.mLaunchCookie
+ == mediaProjectionInfo.getLaunchCookie()).getTask();
+ }
+ }
+
+ @GuardedBy("WindowManagerService.mGlobalLock")
+ private void ensureMediaProjectionWatcherCallbackRegistered() {
+ if (mWatcherCallbackRegistered) {
+ return;
+ }
+
+ IBinder binder = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
+ IMediaProjectionManager mediaProjectionManager =
+ IMediaProjectionManager.Stub.asInterface(binder);
+
+ long identityToken = Binder.clearCallingIdentity();
+ MediaProjectionInfo mediaProjectionInfo = null;
+ try {
+ mediaProjectionInfo = mediaProjectionManager.addCallback(
+ new MediaProjectionWatcherCallback());
+ mWatcherCallbackRegistered = true;
+ } catch (RemoteException e) {
+ ProtoLog.e(WM_ERROR, "Failed to register MediaProjectionWatcherCallback");
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+
+ if (mediaProjectionInfo != null) {
+ setRecordedWindowContainer(mediaProjectionInfo);
+ }
+ }
+
+ boolean register(IScreenRecordingCallback callback) {
+ synchronized (mWms.mGlobalLock) {
+ ensureMediaProjectionWatcherCallbackRegistered();
+
+ IBinder binder = callback.asBinder();
+ int uid = Binder.getCallingUid();
+
+ if (mCallbacks.containsKey(binder)) {
+ return mLastInvokedStateByUid.get(uid);
+ }
+
+ Callback callbackInfo = new Callback(callback, uid);
+ try {
+ binder.linkToDeath(callbackInfo, 0);
+ } catch (RemoteException e) {
+ return false;
+ }
+
+ boolean uidInRecording = uidHasRecordedActivity(callbackInfo.mUid);
+ mLastInvokedStateByUid.put(callbackInfo.mUid, uidInRecording);
+ mCallbacks.put(binder, callbackInfo);
+ return uidInRecording;
+ }
+ }
+
+ void unregister(IScreenRecordingCallback callback) {
+ synchronized (mWms.mGlobalLock) {
+ IBinder binder = callback.asBinder();
+ Callback callbackInfo = mCallbacks.remove(binder);
+ binder.unlinkToDeath(callbackInfo, 0);
+
+ boolean uidHasCallback = false;
+ for (Callback cb : mCallbacks.values()) {
+ if (cb.mUid == callbackInfo.mUid) {
+ uidHasCallback = true;
+ break;
+ }
+ }
+ if (!uidHasCallback) {
+ mLastInvokedStateByUid.remove(callbackInfo.mUid);
+ }
+ }
+ }
+
+ private void onScreenRecordingStart(MediaProjectionInfo mediaProjectionInfo) {
+ synchronized (mWms.mGlobalLock) {
+ setRecordedWindowContainer(mediaProjectionInfo);
+ dispatchCallbacks(getRecordedUids(), true /* visibleInScreenRecording*/);
+ }
+ }
+
+ private void onScreenRecordingStop() {
+ synchronized (mWms.mGlobalLock) {
+ dispatchCallbacks(getRecordedUids(), false /*visibleInScreenRecording*/);
+ mRecordedWC = null;
+ }
+ }
+
+ @GuardedBy("WindowManagerService.mGlobalLock")
+ void onProcessActivityVisibilityChanged(int uid, boolean processVisible) {
+ // If recording isn't active or there's no registered callback for the uid, there's nothing
+ // to do on this visibility change.
+ if (mRecordedWC == null || !mLastInvokedStateByUid.containsKey(uid)) {
+ return;
+ }
+
+ // If the callbacks are already in the correct state, avoid making duplicate callbacks for
+ // the same state. This can happen when:
+ // * a process becomes visible but its UID already has a recorded activity from another
+ // process.
+ // * a process becomes invisible but its UID already doesn't have any recorded activities.
+ if (processVisible == mLastInvokedStateByUid.get(uid)) {
+ return;
+ }
+
+ // If the process visibility change doesn't change the visibility of the UID, avoid making
+ // duplicate callbacks for the same state. This can happen when:
+ // * a process becomes visible but the newly visible activity isn't in the recorded window
+ // container.
+ // * a process becomes invisible but there are still activities being recorded for the UID.
+ boolean uidInRecording = uidHasRecordedActivity(uid);
+ if ((processVisible && !uidInRecording) || (!processVisible && uidInRecording)) {
+ return;
+ }
+
+ dispatchCallbacks(Set.of(uid), processVisible);
+ }
+
+ @GuardedBy("WindowManagerService.mGlobalLock")
+ private boolean uidHasRecordedActivity(int uid) {
+ if (mRecordedWC == null) {
+ return false;
+ }
+ boolean[] hasRecordedActivity = {false};
+ mRecordedWC.forAllActivities(activityRecord -> {
+ if (activityRecord.getUid() == uid && activityRecord.isVisibleRequested()) {
+ hasRecordedActivity[0] = true;
+ return true;
+ }
+ return false;
+ }, true /*traverseTopToBottom*/);
+ return hasRecordedActivity[0];
+ }
+
+ @GuardedBy("WindowManagerService.mGlobalLock")
+ private Set<Integer> getRecordedUids() {
+ Set<Integer> result = new ArraySet<>();
+ if (mRecordedWC == null) {
+ return result;
+ }
+ mRecordedWC.forAllActivities(activityRecord -> {
+ if (activityRecord.isVisibleRequested() && mLastInvokedStateByUid.containsKey(
+ activityRecord.getUid())) {
+ result.add(activityRecord.getUid());
+ }
+ }, true /*traverseTopToBottom*/);
+ return result;
+ }
+
+ @GuardedBy("WindowManagerService.mGlobalLock")
+ private void dispatchCallbacks(Set<Integer> uids, boolean visibleInScreenRecording) {
+ if (uids.isEmpty()) {
+ return;
+ }
+
+ for (Integer uid : uids) {
+ mLastInvokedStateByUid.put(uid, visibleInScreenRecording);
+ }
+
+ for (Callback callback : mCallbacks.values()) {
+ if (!uids.contains(callback.mUid)) {
+ continue;
+ }
+ try {
+ callback.mCallback.onScreenRecordingStateChanged(visibleInScreenRecording);
+ } catch (RemoteException e) {
+ // Client has died. Cleanup is handled via DeathRecipient.
+ }
+ }
+ }
+
+ void dump(PrintWriter pw) {
+ pw.format("ScreenRecordingCallbackController:\n");
+ pw.format(" Registered callbacks:\n");
+ for (Map.Entry<IBinder, Callback> entry : mCallbacks.entrySet()) {
+ pw.format(" callback=%s uid=%s\n", entry.getKey(), entry.getValue().mUid);
+ }
+ pw.format(" Last invoked states:\n");
+ for (Map.Entry<Integer, Boolean> entry : mLastInvokedStateByUid.entrySet()) {
+ pw.format(" uid=%s isVisibleInScreenRecording=%s\n", entry.getKey(),
+ entry.getValue());
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/SensitiveContentPackages.java b/services/core/java/com/android/server/wm/SensitiveContentPackages.java
index 3862b82..a7d6903b 100644
--- a/services/core/java/com/android/server/wm/SensitiveContentPackages.java
+++ b/services/core/java/com/android/server/wm/SensitiveContentPackages.java
@@ -21,7 +21,6 @@
import java.io.PrintWriter;
import java.util.Objects;
-import java.util.Set;
/**
* Cache of distinct package/uid pairs that require being blocked from screen capture. This class is
@@ -41,10 +40,33 @@
return false;
}
- /** Replaces the set of package/uid pairs to set that should be blocked from screen capture */
- public void setShouldBlockScreenCaptureForApp(@NonNull Set<PackageInfo> packageInfos) {
- mProtectedPackages.clear();
+ /**
+ * Adds the set of package/uid pairs to set that should be blocked from screen capture
+ *
+ * @param packageInfos packages to be blocked
+ * @return {@code true} if packages set is modified, {@code false} otherwise.
+ */
+ public boolean addBlockScreenCaptureForApps(@NonNull ArraySet<PackageInfo> packageInfos) {
+ if (mProtectedPackages.equals(packageInfos)) {
+ // new set is equal to current set of packages, no need to update
+ return false;
+ }
mProtectedPackages.addAll(packageInfos);
+ return true;
+ }
+
+ /**
+ * Clears the set of package/uid pairs that should be blocked from screen capture
+ *
+ * @return {@code true} if packages set is modified, {@code false} otherwise.
+ */
+ public boolean clearBlockedApps() {
+ if (mProtectedPackages.isEmpty()) {
+ // set was already empty
+ return false;
+ }
+ mProtectedPackages.clear();
+ return true;
}
void dump(PrintWriter pw) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index a7a6bf2..8f9ed83 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -208,6 +208,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -1702,6 +1703,8 @@
final ActivityRecord r = findActivityInHistory(newR.mActivityComponent, newR.mUserId);
if (r == null) return null;
+ moveTaskFragmentsToBottomIfNeeded(r, finishCount);
+
final PooledPredicate f = PooledLambda.obtainPredicate(
(ActivityRecord ar, ActivityRecord boundaryActivity) ->
finishActivityAbove(ar, boundaryActivity, finishCount),
@@ -1722,6 +1725,50 @@
return r;
}
+ /**
+ * Moves {@link TaskFragment}s to the bottom if the flag
+ * {@link TaskFragment#isMoveToBottomIfClearWhenLaunch} is {@code true}.
+ */
+ @VisibleForTesting
+ void moveTaskFragmentsToBottomIfNeeded(@NonNull ActivityRecord r, @NonNull int[] finishCount) {
+ final int activityIndex = mChildren.indexOf(r);
+ if (activityIndex < 0) {
+ return;
+ }
+
+ List<TaskFragment> taskFragmentsToMove = null;
+
+ // Find the TaskFragments that need to be moved
+ for (int i = mChildren.size() - 1; i > activityIndex; i--) {
+ final TaskFragment taskFragment = mChildren.get(i).asTaskFragment();
+ if (taskFragment != null && taskFragment.isMoveToBottomIfClearWhenLaunch()) {
+ if (taskFragmentsToMove == null) {
+ taskFragmentsToMove = new ArrayList<>();
+ }
+ taskFragmentsToMove.add(taskFragment);
+ }
+ }
+ if (taskFragmentsToMove == null) {
+ return;
+ }
+
+ // Move the TaskFragments to the bottom of the Task. Their relative orders are preserved.
+ final int size = taskFragmentsToMove.size();
+ for (int i = 0; i < size; i++) {
+ final TaskFragment taskFragment = taskFragmentsToMove.get(i);
+
+ // The visibility of the TaskFragment may change. Collect it in the transition so that
+ // transition animation can be properly played.
+ mTransitionController.collect(taskFragment);
+
+ positionChildAt(POSITION_BOTTOM, taskFragment, false /* includeParents */);
+ }
+
+ // Treat it as if the TaskFragments are finished so that a transition animation can be
+ // played to send the TaskFragments back and bring the activity to front.
+ finishCount[0] += size;
+ }
+
private static boolean finishActivityAbove(ActivityRecord r, ActivityRecord boundaryActivity,
@NonNull int[] finishCount) {
// Stop operation once we reach the boundary activity.
@@ -3511,9 +3558,11 @@
&& top.mLetterboxUiController.isSystemOverrideToFullscreenEnabled();
appCompatTaskInfo.isFromLetterboxDoubleTap = top != null
&& top.mLetterboxUiController.isFromDoubleTap();
- if (appCompatTaskInfo.isLetterboxDoubleTapEnabled) {
+ if (top != null) {
appCompatTaskInfo.topActivityLetterboxWidth = top.getBounds().width();
appCompatTaskInfo.topActivityLetterboxHeight = top.getBounds().height();
+ }
+ if (appCompatTaskInfo.isLetterboxDoubleTapEnabled) {
if (appCompatTaskInfo.isTopActivityPillarboxed()) {
// Pillarboxed
appCompatTaskInfo.topActivityLetterboxHorizontalPosition =
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index f56759f..7d418ea 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -363,6 +363,12 @@
*/
private boolean mIsolatedNav;
+ /**
+ * Whether the TaskFragment should move to bottom of task when any activity below it is
+ * launched in clear top mode.
+ */
+ private boolean mMoveToBottomIfClearWhenLaunch;
+
/** When set, will force the task to report as invisible. */
static final int FLAG_FORCE_HIDDEN_FOR_PINNED_TASK = 1;
static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1;
@@ -3045,6 +3051,14 @@
mEmbeddedDimArea = embeddedDimArea;
}
+ void setMoveToBottomIfClearWhenLaunch(boolean moveToBottomIfClearWhenLaunch) {
+ mMoveToBottomIfClearWhenLaunch = moveToBottomIfClearWhenLaunch;
+ }
+
+ boolean isMoveToBottomIfClearWhenLaunch() {
+ return mMoveToBottomIfClearWhenLaunch;
+ }
+
@VisibleForTesting
boolean isDimmingOnParentTask() {
return mEmbeddedDimArea == EMBEDDED_DIM_AREA_PARENT_TASK;
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index f620a97..2accf9a 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -2841,6 +2841,19 @@
}
}
+ /** Returns {@code true} if the display should use high performance hint for this transition. */
+ boolean shouldUsePerfHint(@NonNull DisplayContent dc) {
+ if (mOverrideOptions != null
+ && mOverrideOptions.getType() == ActivityOptions.ANIM_SCENE_TRANSITION
+ && mType == TRANSIT_TO_BACK && mParticipants.size() == 1) {
+ // This should be from convertFromTranslucent that makes the occluded activity invisible
+ // without animation. So do not use perf hint (especially early-wakeup) that may disturb
+ // SurfaceFlinger scheduling around the last frame.
+ return false;
+ }
+ return mTargetDisplays.contains(dc);
+ }
+
/**
* Returns {@code true} if the transition and the corresponding transaction should be applied
* on display thread. Currently, this only checks for display rotation change because the order
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 708d63e..59e3350 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -1237,8 +1237,15 @@
// enableHighPerfTransition(true) is also called in Transition#recordDisplay.
for (int i = mAtm.mRootWindowContainer.getChildCount() - 1; i >= 0; i--) {
final DisplayContent dc = mAtm.mRootWindowContainer.getChildAt(i);
- if (isTransitionOnDisplay(dc)) {
+ if (mCollectingTransition != null && mCollectingTransition.shouldUsePerfHint(dc)) {
dc.enableHighPerfTransition(true);
+ continue;
+ }
+ for (int j = mPlayingTransitions.size() - 1; j >= 0; j--) {
+ if (mPlayingTransitions.get(j).shouldUsePerfHint(dc)) {
+ dc.enableHighPerfTransition(true);
+ break;
+ }
}
}
// Usually transitions put quite a load onto the system already (with all the things
diff --git a/services/core/java/com/android/server/wm/TransitionTracer.java b/services/core/java/com/android/server/wm/TransitionTracer.java
index c59d2d3..0f3fe22 100644
--- a/services/core/java/com/android/server/wm/TransitionTracer.java
+++ b/services/core/java/com/android/server/wm/TransitionTracer.java
@@ -1,323 +1,19 @@
-/*
- * 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.wm;
-import static android.os.Build.IS_USER;
-
-import static com.android.server.wm.shell.TransitionTraceProto.MAGIC_NUMBER;
-import static com.android.server.wm.shell.TransitionTraceProto.MAGIC_NUMBER_H;
-import static com.android.server.wm.shell.TransitionTraceProto.MAGIC_NUMBER_L;
-import static com.android.server.wm.shell.TransitionTraceProto.REAL_TO_ELAPSED_TIME_OFFSET_NANOS;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.os.SystemClock;
-import android.os.Trace;
-import android.util.Log;
-import android.util.proto.ProtoOutputStream;
-import com.android.internal.util.TraceBuffer;
-import com.android.server.wm.Transition.ChangeInfo;
-
-import java.io.File;
-import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.concurrent.TimeUnit;
-/**
- * Helper class to collect and dump transition traces.
- */
-public class TransitionTracer {
+interface TransitionTracer {
+ void logSentTransition(Transition transition, ArrayList<Transition.ChangeInfo> targets);
+ void logFinishedTransition(Transition transition);
+ void logAbortedTransition(Transition transition);
+ void logRemovingStartingWindow(@NonNull StartingData startingData);
- private static final String LOG_TAG = "TransitionTracer";
-
- private static final int ALWAYS_ON_TRACING_CAPACITY = 15 * 1024; // 15 KB
- private static final int ACTIVE_TRACING_BUFFER_CAPACITY = 5000 * 1024; // 5 MB
-
- // This will be the size the proto output streams are initialized to.
- // Ideally this should fit most or all the proto objects we will create and be no bigger than
- // that to ensure to don't use excessive amounts of memory.
- private static final int CHUNK_SIZE = 64;
-
- static final String WINSCOPE_EXT = ".winscope";
- private static final String TRACE_FILE =
- "/data/misc/wmtrace/wm_transition_trace" + WINSCOPE_EXT;
- private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
-
- private final TraceBuffer mTraceBuffer = new TraceBuffer(ALWAYS_ON_TRACING_CAPACITY);
-
- private final Object mEnabledLock = new Object();
- private volatile boolean mActiveTracingEnabled = false;
-
- /**
- * Records key information about a transition that has been sent to Shell to be played.
- * More information will be appended to the same proto object once the transition is finished or
- * aborted.
- * Transition information won't be added to the trace buffer until
- * {@link #logFinishedTransition} or {@link #logAbortedTransition} is called for this
- * transition.
- *
- * @param transition The transition that has been sent to Shell.
- * @param targets Information about the target windows of the transition.
- */
- public void logSentTransition(Transition transition, ArrayList<ChangeInfo> targets) {
- try {
- final ProtoOutputStream outputStream = new ProtoOutputStream(CHUNK_SIZE);
- final long protoToken = outputStream
- .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS);
- outputStream.write(com.android.server.wm.shell.Transition.ID, transition.getSyncId());
- outputStream.write(com.android.server.wm.shell.Transition.CREATE_TIME_NS,
- transition.mLogger.mCreateTimeNs);
- outputStream.write(com.android.server.wm.shell.Transition.SEND_TIME_NS,
- transition.mLogger.mSendTimeNs);
- outputStream.write(com.android.server.wm.shell.Transition.START_TRANSACTION_ID,
- transition.getStartTransaction().getId());
- outputStream.write(com.android.server.wm.shell.Transition.FINISH_TRANSACTION_ID,
- transition.getFinishTransaction().getId());
- dumpTransitionTargetsToProto(outputStream, transition, targets);
- outputStream.end(protoToken);
-
- mTraceBuffer.add(outputStream);
- } catch (Exception e) {
- // Don't let any errors in the tracing cause the transition to fail
- Log.e(LOG_TAG, "Unexpected exception thrown while logging transitions", e);
- }
- }
-
- /**
- * Completes the information dumped in {@link #logSentTransition} for a transition
- * that has finished or aborted, and add the proto object to the trace buffer.
- *
- * @param transition The transition that has finished.
- */
- public void logFinishedTransition(Transition transition) {
- try {
- final ProtoOutputStream outputStream = new ProtoOutputStream(CHUNK_SIZE);
- final long protoToken = outputStream
- .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS);
- outputStream.write(com.android.server.wm.shell.Transition.ID, transition.getSyncId());
- outputStream.write(com.android.server.wm.shell.Transition.FINISH_TIME_NS,
- transition.mLogger.mFinishTimeNs);
- outputStream.end(protoToken);
-
- mTraceBuffer.add(outputStream);
- } catch (Exception e) {
- // Don't let any errors in the tracing cause the transition to fail
- Log.e(LOG_TAG, "Unexpected exception thrown while logging transitions", e);
- }
- }
-
- /**
- * Same as {@link #logFinishedTransition} but don't add the transition to the trace buffer
- * unless actively tracing.
- *
- * @param transition The transition that has been aborted
- */
- public void logAbortedTransition(Transition transition) {
- try {
- final ProtoOutputStream outputStream = new ProtoOutputStream(CHUNK_SIZE);
- final long protoToken = outputStream
- .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS);
- outputStream.write(com.android.server.wm.shell.Transition.ID, transition.getSyncId());
- outputStream.write(com.android.server.wm.shell.Transition.ABORT_TIME_NS,
- transition.mLogger.mAbortTimeNs);
- outputStream.end(protoToken);
-
- mTraceBuffer.add(outputStream);
- } catch (Exception e) {
- // Don't let any errors in the tracing cause the transition to fail
- Log.e(LOG_TAG, "Unexpected exception thrown while logging transitions", e);
- }
- }
-
- void logRemovingStartingWindow(@NonNull StartingData startingData) {
- if (startingData.mTransitionId == 0) {
- return;
- }
- try {
- final ProtoOutputStream outputStream = new ProtoOutputStream(CHUNK_SIZE);
- final long protoToken = outputStream
- .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS);
- outputStream.write(com.android.server.wm.shell.Transition.ID,
- startingData.mTransitionId);
- outputStream.write(
- com.android.server.wm.shell.Transition.STARTING_WINDOW_REMOVE_TIME_NS,
- SystemClock.elapsedRealtimeNanos());
- outputStream.end(protoToken);
-
- mTraceBuffer.add(outputStream);
- } catch (Exception e) {
- Log.e(LOG_TAG, "Unexpected exception thrown while logging transitions", e);
- }
- }
-
- private void dumpTransitionTargetsToProto(ProtoOutputStream outputStream,
- Transition transition, ArrayList<ChangeInfo> targets) {
- Trace.beginSection("TransitionTracer#dumpTransitionTargetsToProto");
- if (mActiveTracingEnabled) {
- outputStream.write(com.android.server.wm.shell.Transition.ID,
- transition.getSyncId());
- }
-
- outputStream.write(com.android.server.wm.shell.Transition.TYPE, transition.mType);
- outputStream.write(com.android.server.wm.shell.Transition.FLAGS, transition.getFlags());
-
- for (int i = 0; i < targets.size(); ++i) {
- final long changeToken = outputStream
- .start(com.android.server.wm.shell.Transition.TARGETS);
-
- final Transition.ChangeInfo target = targets.get(i);
-
- final int layerId;
- if (target.mContainer.mSurfaceControl.isValid()) {
- layerId = target.mContainer.mSurfaceControl.getLayerId();
- } else {
- layerId = -1;
- }
-
- outputStream.write(com.android.server.wm.shell.Target.MODE, target.mReadyMode);
- outputStream.write(com.android.server.wm.shell.Target.FLAGS, target.mReadyFlags);
- outputStream.write(com.android.server.wm.shell.Target.LAYER_ID, layerId);
-
- if (mActiveTracingEnabled) {
- // What we use in the WM trace
- final int windowId = System.identityHashCode(target.mContainer);
- outputStream.write(com.android.server.wm.shell.Target.WINDOW_ID, windowId);
- }
-
- outputStream.end(changeToken);
- }
-
- Trace.endSection();
- }
-
- /**
- * Starts collecting transitions for the trace.
- * If called while a trace is already running, this will reset the trace.
- */
- public void startTrace(@Nullable PrintWriter pw) {
- if (IS_USER) {
- LogAndPrintln.e(pw, "Tracing is not supported on user builds.");
- return;
- }
- Trace.beginSection("TransitionTracer#startTrace");
- LogAndPrintln.i(pw, "Starting shell transition trace.");
- synchronized (mEnabledLock) {
- mActiveTracingEnabled = true;
- mTraceBuffer.resetBuffer();
- mTraceBuffer.setCapacity(ACTIVE_TRACING_BUFFER_CAPACITY);
- }
- Trace.endSection();
- }
-
- /**
- * Stops collecting the transition trace and dump to trace to file.
- *
- * Dumps the trace to @link{TRACE_FILE}.
- */
- public void stopTrace(@Nullable PrintWriter pw) {
- stopTrace(pw, new File(TRACE_FILE));
- }
-
- /**
- * Stops collecting the transition trace and dump to trace to file.
- * @param outputFile The file to dump the transition trace to.
- */
- public void stopTrace(@Nullable PrintWriter pw, File outputFile) {
- if (IS_USER) {
- LogAndPrintln.e(pw, "Tracing is not supported on user builds.");
- return;
- }
- Trace.beginSection("TransitionTracer#stopTrace");
- LogAndPrintln.i(pw, "Stopping shell transition trace.");
- synchronized (mEnabledLock) {
- mActiveTracingEnabled = false;
- writeTraceToFileLocked(pw, outputFile);
- mTraceBuffer.resetBuffer();
- mTraceBuffer.setCapacity(ALWAYS_ON_TRACING_CAPACITY);
- }
- Trace.endSection();
- }
-
- /**
- * Being called while taking a bugreport so that tracing files can be included in the bugreport.
- *
- * @param pw Print writer
- */
- public void saveForBugreport(@Nullable PrintWriter pw) {
- if (IS_USER) {
- LogAndPrintln.e(pw, "Tracing is not supported on user builds.");
- return;
- }
- Trace.beginSection("TransitionTracer#saveForBugreport");
- synchronized (mEnabledLock) {
- final File outputFile = new File(TRACE_FILE);
- writeTraceToFileLocked(pw, outputFile);
- }
- Trace.endSection();
- }
-
- boolean isActiveTracingEnabled() {
- return mActiveTracingEnabled;
- }
-
- private void writeTraceToFileLocked(@Nullable PrintWriter pw, File file) {
- Trace.beginSection("TransitionTracer#writeTraceToFileLocked");
- try {
- ProtoOutputStream proto = new ProtoOutputStream(CHUNK_SIZE);
- proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
- long timeOffsetNs =
- TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis())
- - SystemClock.elapsedRealtimeNanos();
- proto.write(REAL_TO_ELAPSED_TIME_OFFSET_NANOS, timeOffsetNs);
- int pid = android.os.Process.myPid();
- LogAndPrintln.i(pw, "Writing file to " + file.getAbsolutePath()
- + " from process " + pid);
- mTraceBuffer.writeTraceToFile(file, proto);
- } catch (IOException e) {
- LogAndPrintln.e(pw, "Unable to write buffer to file", e);
- }
- Trace.endSection();
- }
-
- private static class LogAndPrintln {
- private static void i(@Nullable PrintWriter pw, String msg) {
- Log.i(LOG_TAG, msg);
- if (pw != null) {
- pw.println(msg);
- pw.flush();
- }
- }
-
- private static void e(@Nullable PrintWriter pw, String msg) {
- Log.e(LOG_TAG, msg);
- if (pw != null) {
- pw.println("ERROR: " + msg);
- pw.flush();
- }
- }
-
- private static void e(@Nullable PrintWriter pw, String msg, @NonNull Exception e) {
- Log.e(LOG_TAG, msg, e);
- if (pw != null) {
- pw.println("ERROR: " + msg + " ::\n " + e);
- pw.flush();
- }
- }
- }
+ void startTrace(@Nullable PrintWriter pw);
+ void stopTrace(@Nullable PrintWriter pw);
+ boolean isTracing();
+ void saveForBugreport(@Nullable PrintWriter pw);
}
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index a9f0554..d68f932 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -34,6 +34,7 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.H.WALLPAPER_DRAW_PENDING_TIMEOUT;
+import static com.android.window.flags.Flags.multiCrop;
import android.annotation.Nullable;
import android.content.res.Resources;
@@ -48,6 +49,7 @@
import android.util.ArraySet;
import android.util.MathUtils;
import android.util.Slog;
+import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.SurfaceControl;
@@ -60,6 +62,7 @@
import com.android.internal.protolog.ProtoLogImpl;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ToBooleanFunction;
+import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -73,6 +76,7 @@
class WallpaperController {
private static final String TAG = TAG_WITH_CLASS_NAME ? "WallpaperController" : TAG_WM;
private WindowManagerService mService;
+ private WallpaperCropUtils mWallpaperCropUtils = null;
private DisplayContent mDisplayContent;
private final ArrayList<WallpaperWindowToken> mWallpaperTokens = new ArrayList<>();
@@ -240,9 +244,8 @@
mMinWallpaperScale =
resources.getFloat(com.android.internal.R.dimen.config_wallpaperMinScale);
mMaxWallpaperScale = resources.getFloat(R.dimen.config_wallpaperMaxScale);
- mShouldOffsetWallpaperCenter =
- resources.getBoolean(
- com.android.internal.R.bool.config_offsetWallpaperToCenterOfLargestDisplay);
+ mShouldOffsetWallpaperCenter = resources.getBoolean(
+ com.android.internal.R.bool.config_offsetWallpaperToCenterOfLargestDisplay);
}
void resetLargestDisplay(Display display) {
@@ -266,7 +269,7 @@
}
@Nullable private Point findLargestDisplaySize() {
- if (!mShouldOffsetWallpaperCenter) {
+ if (!mShouldOffsetWallpaperCenter || multiCrop()) {
return null;
}
Point largestDisplaySize = new Point();
@@ -284,6 +287,10 @@
return largestDisplaySize;
}
+ void setWallpaperCropUtils(WallpaperCropUtils wallpaperCropUtils) {
+ mWallpaperCropUtils = wallpaperCropUtils;
+ }
+
WindowState getWallpaperTarget() {
return mWallpaperTarget;
}
@@ -357,26 +364,92 @@
boolean updateWallpaperOffset(WindowState wallpaperWin, boolean sync) {
// Size of the display the wallpaper is rendered on.
final Rect lastWallpaperBounds = wallpaperWin.getParentFrame();
- // Full size of the wallpaper (usually larger than bounds above to parallax scroll when
- // swiping through Launcher pages).
- final Rect wallpaperFrame = wallpaperWin.getFrame();
+ int screenWidth = lastWallpaperBounds.width();
+ int screenHeight = lastWallpaperBounds.height();
+ float screenRatio = ((float) screenWidth) / screenHeight;
+ Point screenSize = new Point(screenWidth, screenHeight);
+
WallpaperWindowToken token = wallpaperWin.mToken.asWallpaperToken();
- final int diffWidth = wallpaperFrame.width() - lastWallpaperBounds.width();
- final int diffHeight = wallpaperFrame.height() - lastWallpaperBounds.height();
- if ((wallpaperWin.mAttrs.flags & WindowManager.LayoutParams.FLAG_SCALED) != 0
- && Math.abs(diffWidth) > 1 && Math.abs(diffHeight) > 1) {
- Slog.d(TAG, "Skip wallpaper offset with inconsistent orientation, bounds="
- + lastWallpaperBounds + " frame=" + wallpaperFrame);
- // With FLAG_SCALED, the requested size should at least make the frame match one of
- // side. If both sides contain differences, the client side may not have updated the
- // latest size according to the current orientation. So skip calculating the offset to
- // avoid the wallpaper not filling the screen.
- return false;
+ /*
+ * TODO(b/270726737) adapt comments once flag gets removed and multiCrop is always true
+ * Size of the wallpaper. May have more width/height ratio than the screen for parallax.
+ *
+ * If multiCrop is true, we use a map, cropHints, defining which sub-area of the wallpaper
+ * to show for a given screen orientation. In this case, wallpaperFrame represents the
+ * sub-area of WallpaperWin to show for the current screen size.
+ *
+ * If multiCrop is false, don't show a custom sub-area of the wallpaper. Just show the
+ * whole wallpaperWin if possible, and center and zoom if necessary.
+ */
+ final Rect wallpaperFrame;
+
+ /*
+ * The values cropZoom, cropOffsetX and cropOffsetY are only used if multiCrop is true.
+ * Zoom and offsets to be applied in order to show wallpaperFrame on screen.
+ */
+ final float cropZoom;
+ final int cropOffsetX;
+ final int cropOffsetY;
+
+ /*
+ * Difference of width/height between the wallpaper and the screen.
+ * This is the additional room that we have to apply offsets (i.e. parallax).
+ */
+ final int diffWidth;
+ final int diffHeight;
+
+ /*
+ * zoom, offsetX and offsetY are not related to cropping the wallpaper:
+ * - zoom is used to apply an additional zoom (e.g. for launcher animations).
+ * - offsetX, offsetY are used to apply an offset to the wallpaper (e.g. parallax effect).
+ */
+ final float zoom;
+ int offsetX;
+ int offsetY;
+
+ if (multiCrop()) {
+ if (mWallpaperCropUtils == null) {
+ Slog.e(TAG, "Update wallpaper offsets before the system is ready. Aborting");
+ return false;
+ }
+ Point bitmapSize = new Point(
+ wallpaperWin.mRequestedWidth, wallpaperWin.mRequestedHeight);
+ SparseArray<Rect> cropHints = token.getCropHints();
+ wallpaperFrame = mWallpaperCropUtils.getCrop(
+ screenSize, bitmapSize, cropHints, wallpaperWin.isRtl());
+
+ cropZoom = wallpaperFrame.isEmpty() ? 1f
+ : ((float) screenHeight) / wallpaperFrame.height() / wallpaperWin.mVScale;
+
+ // A positive x / y offset shifts the wallpaper to the right / bottom respectively.
+ cropOffsetX = -wallpaperFrame.left
+ + (int) ((cropZoom - 1f) * wallpaperFrame.height() * screenRatio / 2f);
+ cropOffsetY = -wallpaperFrame.top
+ + (int) ((cropZoom - 1f) * wallpaperFrame.height() / 2f);
+
+ diffWidth = (int) (wallpaperFrame.width() * wallpaperWin.mHScale) - screenWidth;
+ diffHeight = (int) (wallpaperFrame.height() * wallpaperWin.mVScale) - screenHeight;
+ } else {
+ wallpaperFrame = wallpaperWin.getFrame();
+ cropZoom = 1f;
+ cropOffsetX = 0;
+ cropOffsetY = 0;
+ diffWidth = wallpaperFrame.width() - screenWidth;
+ diffHeight = wallpaperFrame.height() - screenHeight;
+
+ if ((wallpaperWin.mAttrs.flags & WindowManager.LayoutParams.FLAG_SCALED) != 0
+ && Math.abs(diffWidth) > 1 && Math.abs(diffHeight) > 1) {
+ Slog.d(TAG, "Skip wallpaper offset with inconsistent orientation, bounds="
+ + lastWallpaperBounds + " frame=" + wallpaperFrame);
+ // With FLAG_SCALED, the requested size should at least make the frame match one of
+ // side. If both sides contain differences, the client side may not have updated the
+ // latest size according to the current orientation. So skip calculating the offset
+ // to avoid the wallpaper not filling the screen.
+ return false;
+ }
}
- int newXOffset = 0;
- int newYOffset = 0;
boolean rawChanged = false;
// Set the default wallpaper x-offset to either edge of the screen (depending on RTL), to
// match the behavior of most Launchers
@@ -396,17 +469,17 @@
int displayOffset = getDisplayWidthOffset(availw, lastWallpaperBounds,
wallpaperWin.isRtl());
availw -= displayOffset;
- int offset = availw > 0 ? -(int)(availw * wpx + .5f) : 0;
+ offsetX = availw > 0 ? -(int) (availw * wpx + .5f) : 0;
if (token.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) {
// if device is LTR, then offset wallpaper to the left (the wallpaper is drawn
// always starting from the left of the screen).
- offset += token.mWallpaperDisplayOffsetX;
+ offsetX += token.mWallpaperDisplayOffsetX;
} else if (!wallpaperWin.isRtl()) {
// In RTL the offset is calculated so that the wallpaper ends up right aligned (see
// offset above).
- offset -= displayOffset;
+ offsetX -= displayOffset;
}
- newXOffset = offset;
+ offsetX += cropOffsetX * wallpaperWin.mHScale;
if (wallpaperWin.mWallpaperX != wpx || wallpaperWin.mWallpaperXStep != wpxs) {
wallpaperWin.mWallpaperX = wpx;
@@ -416,11 +489,11 @@
float wpy = token.mWallpaperY >= 0 ? token.mWallpaperY : 0.5f;
float wpys = token.mWallpaperYStep >= 0 ? token.mWallpaperYStep : -1.0f;
- offset = diffHeight > 0 ? -(int) (diffHeight * wpy + .5f) : 0;
+ offsetY = diffHeight > 0 ? -(int) (diffHeight * wpy + .5f) : 0;
if (token.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
- offset += token.mWallpaperDisplayOffsetY;
+ offsetY += token.mWallpaperDisplayOffsetY;
}
- newYOffset = offset;
+ offsetY += cropOffsetY * wallpaperWin.mVScale;
if (wallpaperWin.mWallpaperY != wpy || wallpaperWin.mWallpaperYStep != wpys) {
wallpaperWin.mWallpaperY = wpy;
@@ -432,10 +505,10 @@
wallpaperWin.mWallpaperZoomOut = mLastWallpaperZoomOut;
rawChanged = true;
}
-
- boolean changed = wallpaperWin.setWallpaperOffset(newXOffset, newYOffset,
- wallpaperWin.mShouldScaleWallpaper
- ? zoomOutToScale(wallpaperWin.mWallpaperZoomOut) : 1);
+ zoom = wallpaperWin.mShouldScaleWallpaper
+ ? zoomOutToScale(wallpaperWin.mWallpaperZoomOut) : 1f;
+ final float totalZoom = zoom * cropZoom;
+ boolean changed = wallpaperWin.setWallpaperOffset(offsetX, offsetY, totalZoom);
if (rawChanged && (wallpaperWin.mAttrs.privateFlags &
WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS) != 0) {
@@ -496,7 +569,7 @@
* display).
*/
private int getDisplayWidthOffset(int availWidth, Rect displayFrame, boolean isRtl) {
- if (!mShouldOffsetWallpaperCenter) {
+ if (!mShouldOffsetWallpaperCenter || multiCrop()) {
return 0;
}
if (mLargestDisplaySize == null) {
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 15bd607..1bcd882 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -25,9 +25,11 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.Nullable;
+import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
+import android.util.SparseArray;
import android.view.animation.Animation;
import com.android.internal.protolog.common.ProtoLog;
@@ -49,6 +51,12 @@
int mWallpaperDisplayOffsetX = Integer.MIN_VALUE;
int mWallpaperDisplayOffsetY = Integer.MIN_VALUE;
+ /**
+ * Map from {@link android.app.WallpaperManager.ScreenOrientation} to crop rectangles.
+ * Crop rectangles represent the part of the wallpaper displayed for each screen orientation.
+ */
+ private SparseArray<Rect> mCropHints = new SparseArray<>();
+
WallpaperWindowToken(WindowManagerService service, IBinder token, boolean explicit,
DisplayContent dc, boolean ownerCanManageAppTokens) {
this(service, token, explicit, dc, ownerCanManageAppTokens, null /* options */);
@@ -98,6 +106,14 @@
return mShowWhenLocked;
}
+ void setCropHints(SparseArray<Rect> cropHints) {
+ mCropHints = cropHints.clone();
+ }
+
+ SparseArray<Rect> getCropHints() {
+ return mCropHints;
+ }
+
void sendWindowWallpaperCommand(
String action, int x, int y, int z, Bundle extras, boolean sync) {
for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 22b690e..d0b9a6e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -32,7 +32,9 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.Message;
+import android.util.ArraySet;
import android.util.Pair;
+import android.util.SparseArray;
import android.view.ContentRecordingSession;
import android.view.Display;
import android.view.IInputFilter;
@@ -51,6 +53,7 @@
import com.android.internal.policy.KeyInterceptionInfo;
import com.android.server.input.InputManagerService;
import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils;
import com.android.server.wm.SensitiveContentPackages.PackageInfo;
import java.lang.annotation.Retention;
@@ -698,6 +701,21 @@
public abstract void setWallpaperShowWhenLocked(IBinder windowToken, boolean showWhenLocked);
/**
+ * Sets the crop hints of a {@link WallpaperWindowToken}. Only effective for image wallpapers.
+ *
+ * @param windowToken wallpaper token previously added via {@link #addWindowToken}
+ * @param cropHints a map that represents which part of the wallpaper should be shown, for
+ * each type of {@link android.app.WallpaperManager.ScreenOrientation}.
+ */
+ public abstract void setWallpaperCropHints(IBinder windowToken, SparseArray<Rect> cropHints);
+
+ /**
+ * Transmits the {@link WallpaperCropUtils} instance to {@link WallpaperController}.
+ * {@link WallpaperCropUtils} contains the helpers to properly position the wallpaper.
+ */
+ public abstract void setWallpaperCropUtils(WallpaperCropUtils wallpaperCropUtils);
+
+ /**
* Returns {@code true} if a Window owned by {@code uid} has focus.
*/
public abstract boolean isUidFocused(int uid);
@@ -1020,5 +1038,13 @@
*
* @param packageInfos set of {@link PackageInfo} whose windows should be blocked from capture
*/
- public abstract void setShouldBlockScreenCaptureForApp(@NonNull Set<PackageInfo> packageInfos);
+ public abstract void addBlockScreenCaptureForApps(@NonNull ArraySet<PackageInfo> packageInfos);
+
+ /**
+ * Clears apps added to collection of apps in which screen capture should be disabled.
+ *
+ * <p> This clears and resets any existing set or added applications from
+ * * {@link #addBlockScreenCaptureForApps(ArraySet)}
+ */
+ public abstract void clearBlockedApps();
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9544835..f8ac8da 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -153,6 +153,7 @@
import static com.android.server.wm.WindowManagerServiceDumpProto.POLICY;
import static com.android.server.wm.WindowManagerServiceDumpProto.ROOT_WINDOW_CONTAINER;
import static com.android.server.wm.WindowManagerServiceDumpProto.WINDOW_FRAMES_VALID;
+import static com.android.window.flags.Flags.multiCrop;
import android.Manifest;
import android.Manifest.permission;
@@ -238,6 +239,7 @@
import android.util.MergedConfiguration;
import android.util.Pair;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.TimeUtils;
@@ -303,6 +305,7 @@
import android.view.inputmethod.ImeTracker;
import android.window.AddToSurfaceSyncGroupResult;
import android.window.ClientWindowFrames;
+import android.window.IScreenRecordingCallback;
import android.window.ISurfaceSyncGroupCompletedListener;
import android.window.ITaskFpsCallback;
import android.window.ITrustedPresentationListener;
@@ -341,6 +344,7 @@
import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
import com.android.server.power.ShutdownThread;
import com.android.server.utils.PriorityDump;
+import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils;
import dalvik.annotation.optimization.NeverCompile;
@@ -369,7 +373,6 @@
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
-import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
@@ -1104,6 +1107,8 @@
void onAppFreezeTimeout();
}
+ private final ScreenRecordingCallbackController mScreenRecordingCallbackController;
+
public static WindowManagerService main(final Context context, final InputManagerService im,
final boolean showBootMsgs, WindowManagerPolicy policy,
ActivityTaskManagerService atm) {
@@ -1213,7 +1218,12 @@
mWindowTracing = WindowTracing.createDefaultAndStartLooper(this,
Choreographer.getInstance());
- mTransitionTracer = new TransitionTracer();
+
+ if (android.tracing.Flags.perfettoTransitionTracing()) {
+ mTransitionTracer = new PerfettoTransitionTracer();
+ } else {
+ mTransitionTracer = new LegacyTransitionTracer();
+ }
LocalServices.addService(WindowManagerPolicy.class, mPolicy);
@@ -1340,6 +1350,7 @@
mBlurController = new BlurController(mContext, mPowerManager);
mTaskFpsCallbackController = new TaskFpsCallbackController(mContext);
mAccessibilityController = new AccessibilityController(this);
+ mScreenRecordingCallbackController = new ScreenRecordingCallbackController(this);
mSystemPerformanceHinter = new SystemPerformanceHinter(mContext, displayId -> {
synchronized (mGlobalLock) {
DisplayContent dc = mRoot.getDisplayContent(displayId);
@@ -6087,7 +6098,7 @@
@Override
public boolean isTransitionTraceEnabled() {
- return mTransitionTracer.isActiveTracingEnabled();
+ return mTransitionTracer.isTracing();
}
@Override
@@ -7183,6 +7194,7 @@
mSystemPerformanceHinter.dump(pw, "");
mTrustedPresentationListenerController.dump(pw);
mSensitiveContentPackages.dump(pw);
+ mScreenRecordingCallbackController.dump(pw);
}
}
@@ -8119,6 +8131,25 @@
}
@Override
+ public void setWallpaperCropHints(IBinder binder, SparseArray<Rect> cropHints) {
+ synchronized (mGlobalLock) {
+ final WindowToken token = mRoot.getWindowToken(binder);
+ if (token == null || token.asWallpaperToken() == null) {
+ ProtoLog.w(WM_ERROR,
+ "setWallpaperCropHints: non-existent wallpaper token: %s", binder);
+ return;
+ }
+ token.asWallpaperToken().setCropHints(cropHints);
+ }
+ }
+
+ @Override
+ public void setWallpaperCropUtils(WallpaperCropUtils wallpaperCropUtils) {
+ mRoot.getDisplayContent(DEFAULT_DISPLAY).mWallpaperController
+ .setWallpaperCropUtils(wallpaperCropUtils);
+ }
+
+ @Override
public boolean isUidFocused(int uid) {
synchronized (mGlobalLock) {
for (int i = mRoot.getChildCount() - 1; i >= 0; i--) {
@@ -8566,10 +8597,23 @@
}
@Override
- public void setShouldBlockScreenCaptureForApp(Set<PackageInfo> packageInfos) {
+ public void addBlockScreenCaptureForApps(ArraySet<PackageInfo> packageInfos) {
synchronized (mGlobalLock) {
- mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(packageInfos);
- WindowManagerService.this.refreshScreenCaptureDisabled();
+ boolean modified =
+ mSensitiveContentPackages.addBlockScreenCaptureForApps(packageInfos);
+ if (modified) {
+ WindowManagerService.this.refreshScreenCaptureDisabled();
+ }
+ }
+ }
+
+ @Override
+ public void clearBlockedApps() {
+ synchronized (mGlobalLock) {
+ boolean modified = mSensitiveContentPackages.clearBlockedApps();
+ if (modified) {
+ WindowManagerService.this.refreshScreenCaptureDisabled();
+ }
}
}
}
@@ -9344,7 +9388,8 @@
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
- if (!mAtmService.isCallerRecents(callingUid)) {
+ if (!mAtmService.isCallerRecents(callingUid)
+ && (!multiCrop() || callingUid != SYSTEM_UID)) {
Slog.e(TAG, "Unable to verify uid for getPossibleDisplayInfo"
+ " on uid " + callingUid);
return new ArrayList<>();
@@ -9884,4 +9929,18 @@
int id) {
mTrustedPresentationListenerController.unregisterListener(listener, id);
}
+
+ @Override
+ public boolean registerScreenRecordingCallback(IScreenRecordingCallback callback) {
+ return mScreenRecordingCallbackController.register(callback);
+ }
+
+ @Override
+ public void unregisterScreenRecordingCallback(IScreenRecordingCallback callback) {
+ mScreenRecordingCallbackController.unregister(callback);
+ }
+
+ void onProcessActivityVisibilityChanged(int uid, boolean visible) {
+ mScreenRecordingCallbackController.onProcessActivityVisibilityChanged(uid, visible);
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 59d0210..205ed97 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -36,6 +36,7 @@
import static android.window.TaskFragmentOperation.OP_TYPE_SET_COMPANION_TASK_FRAGMENT;
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;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_RELATIVE_BOUNDS;
import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_UNKNOWN;
@@ -1034,8 +1035,14 @@
launchOpts.remove(WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID);
final SafeActivityOptions safeOptions =
SafeActivityOptions.fromBundle(launchOpts, caller.mPid, caller.mUid);
+ if (transition != null) {
+ transition.deferTransitionReady();
+ }
waitAsyncStart(() -> mService.mTaskSupervisor.startActivityFromRecents(
caller.mPid, caller.mUid, taskId, safeOptions));
+ if (transition != null) {
+ transition.continueTransitionReady();
+ }
break;
}
case HIERARCHY_OP_TYPE_REORDER:
@@ -1113,11 +1120,17 @@
activityOptions.setCallerDisplayId(DEFAULT_DISPLAY);
}
final Bundle options = activityOptions != null ? activityOptions.toBundle() : null;
+ if (transition != null) {
+ transition.deferTransitionReady();
+ }
int res = waitAsyncStart(() -> mService.mAmInternal.sendIntentSender(
hop.getPendingIntent().getTarget(),
hop.getPendingIntent().getWhitelistToken(), 0 /* code */,
hop.getActivityIntent(), resolvedType, null /* finishReceiver */,
null /* requiredPermission */, options));
+ if (transition != null) {
+ transition.continueTransitionReady();
+ }
if (ActivityManager.isStartResultSuccessful(res)) {
effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
@@ -1502,6 +1515,11 @@
: EMBEDDED_DIM_AREA_TASK_FRAGMENT);
break;
}
+ case OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH: {
+ taskFragment.setMoveToBottomIfClearWhenLaunch(
+ operation.isMoveToBottomIfClearWhenLaunch());
+ break;
+ }
}
return effects;
}
@@ -1554,6 +1572,17 @@
return false;
}
+ if ((opType == OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH)
+ && !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
+ final Throwable exception = new SecurityException(
+ "Only a system organizer can perform "
+ + "OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH."
+ );
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment,
+ opType, exception);
+ return false;
+ }
+
final IBinder secondaryFragmentToken = operation.getSecondaryFragmentToken();
return secondaryFragmentToken == null
|| validateTaskFragment(mLaunchTaskFragments.get(secondaryFragmentToken), opType,
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index b8fa5e5..6d2e8cc 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -1271,8 +1271,10 @@
& (ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE)) != 0;
if (!wasAnyVisible && anyVisible) {
mAtm.mVisibleActivityProcessTracker.onAnyActivityVisible(this);
+ mAtm.mWindowManager.onProcessActivityVisibilityChanged(mUid, true /*visible*/);
} else if (wasAnyVisible && !anyVisible) {
mAtm.mVisibleActivityProcessTracker.onAllActivitiesInvisible(this);
+ mAtm.mWindowManager.onProcessActivityVisibilityChanged(mUid, false /*visible*/);
} else if (wasAnyVisible && !wasResumed && hasResumedActivity()) {
mAtm.mVisibleActivityProcessTracker.onActivityResumedWhileVisible(this);
}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 775570c..dfa9dce 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -37,7 +37,6 @@
"com_android_server_adb_AdbDebuggingManager.cpp",
"com_android_server_am_BatteryStatsService.cpp",
"com_android_server_biometrics_SurfaceToNativeHandleConverter.cpp",
- "com_android_server_BootReceiver.cpp",
"com_android_server_ConsumerIrService.cpp",
"com_android_server_companion_virtual_InputController.cpp",
"com_android_server_devicepolicy_CryptoTestHelper.cpp",
@@ -95,16 +94,6 @@
header_libs: [
"bionic_libc_platform_headers",
],
-
- static_libs: [
- "libunwindstack",
- ],
-
- whole_static_libs: [
- "libdebuggerd_tombstone_proto_to_text",
- ],
-
- runtime_libs: ["libdexfile"],
}
cc_defaults {
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index 15eb7c6..cc08488 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -33,7 +33,3 @@
# Bug component : 158088 = per-file *AnrTimer*
per-file *AnrTimer* = file:/PERFORMANCE_OWNERS
-
-# Bug component : 158088 = per-file com_android_server_utils_AnrTimer*.java
-per-file com_android_server_utils_AnrTimer*.java = file:/PERFORMANCE_OWNERS
-per-file com_android_server_BootReceiver.cpp = file:/STABILITY_OWNERS
diff --git a/services/core/jni/com_android_server_BootReceiver.cpp b/services/core/jni/com_android_server_BootReceiver.cpp
deleted file mode 100644
index 3892d28..0000000
--- a/services/core/jni/com_android_server_BootReceiver.cpp
+++ /dev/null
@@ -1,57 +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.
- */
-
-#include <libdebuggerd/tombstone.h>
-#include <nativehelper/JNIHelp.h>
-
-#include <sstream>
-
-#include "jni.h"
-#include "tombstone.pb.h"
-
-namespace android {
-
-static void writeToString(std::stringstream& ss, const std::string& line, bool should_log) {
- ss << line << std::endl;
-}
-
-static jstring com_android_server_BootReceiver_getTombstoneText(JNIEnv* env, jobject,
- jbyteArray tombstoneBytes) {
- Tombstone tombstone;
- tombstone.ParseFromArray(env->GetByteArrayElements(tombstoneBytes, 0),
- env->GetArrayLength(tombstoneBytes));
-
- std::stringstream tombstoneString;
-
- tombstone_proto_to_text(tombstone,
- std::bind(&writeToString, std::ref(tombstoneString),
- std::placeholders::_1, std::placeholders::_2));
-
- return env->NewStringUTF(tombstoneString.str().c_str());
-}
-
-static const JNINativeMethod sMethods[] = {
- /* name, signature, funcPtr */
- {"getTombstoneText", "([B)Ljava/lang/String;",
- (jstring*)com_android_server_BootReceiver_getTombstoneText},
-};
-
-int register_com_android_server_BootReceiver(JNIEnv* env) {
- return jniRegisterNativeMethods(env, "com/android/server/BootReceiver", sMethods,
- NELEM(sMethods));
-}
-
-} // namespace android
diff --git a/services/core/jni/com_android_server_companion_virtual_InputController.cpp b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
index 4cd018b..50d48b7 100644
--- a/services/core/jni/com_android_server_companion_virtual_InputController.cpp
+++ b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
@@ -44,6 +44,7 @@
MOUSE,
TOUCHSCREEN,
DPAD,
+ STYLUS,
};
static unique_fd invalidFd() {
@@ -98,6 +99,24 @@
ioctl(fd, UI_SET_ABSBIT, ABS_MT_TOUCH_MAJOR);
ioctl(fd, UI_SET_ABSBIT, ABS_MT_PRESSURE);
ioctl(fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT);
+ break;
+ case DeviceType::STYLUS:
+ ioctl(fd, UI_SET_EVBIT, EV_ABS);
+ ioctl(fd, UI_SET_KEYBIT, BTN_TOUCH);
+ ioctl(fd, UI_SET_KEYBIT, BTN_STYLUS);
+ ioctl(fd, UI_SET_KEYBIT, BTN_STYLUS2);
+ ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_PEN);
+ ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_RUBBER);
+ ioctl(fd, UI_SET_ABSBIT, ABS_X);
+ ioctl(fd, UI_SET_ABSBIT, ABS_Y);
+ ioctl(fd, UI_SET_ABSBIT, ABS_TILT_X);
+ ioctl(fd, UI_SET_ABSBIT, ABS_TILT_Y);
+ ioctl(fd, UI_SET_ABSBIT, ABS_PRESSURE);
+ ioctl(fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT);
+ break;
+ default:
+ ALOGE("Invalid input device type %d", static_cast<int32_t>(deviceType));
+ return invalidFd();
}
int version;
@@ -158,6 +177,47 @@
ALOGE("Error creating touchscreen uinput tracking ids: %s", strerror(errno));
return invalidFd();
}
+ } else if (deviceType == DeviceType::STYLUS) {
+ uinput_abs_setup xAbsSetup;
+ xAbsSetup.code = ABS_X;
+ xAbsSetup.absinfo.maximum = screenWidth - 1;
+ xAbsSetup.absinfo.minimum = 0;
+ if (ioctl(fd, UI_ABS_SETUP, &xAbsSetup) != 0) {
+ ALOGE("Error creating stylus uinput x axis: %s", strerror(errno));
+ return invalidFd();
+ }
+ uinput_abs_setup yAbsSetup;
+ yAbsSetup.code = ABS_Y;
+ yAbsSetup.absinfo.maximum = screenHeight - 1;
+ yAbsSetup.absinfo.minimum = 0;
+ if (ioctl(fd, UI_ABS_SETUP, &yAbsSetup) != 0) {
+ ALOGE("Error creating stylus uinput y axis: %s", strerror(errno));
+ return invalidFd();
+ }
+ uinput_abs_setup tiltXAbsSetup;
+ tiltXAbsSetup.code = ABS_TILT_X;
+ tiltXAbsSetup.absinfo.maximum = 90;
+ tiltXAbsSetup.absinfo.minimum = -90;
+ if (ioctl(fd, UI_ABS_SETUP, &tiltXAbsSetup) != 0) {
+ ALOGE("Error creating stylus uinput tilt x axis: %s", strerror(errno));
+ return invalidFd();
+ }
+ uinput_abs_setup tiltYAbsSetup;
+ tiltYAbsSetup.code = ABS_TILT_Y;
+ tiltYAbsSetup.absinfo.maximum = 90;
+ tiltYAbsSetup.absinfo.minimum = -90;
+ if (ioctl(fd, UI_ABS_SETUP, &tiltYAbsSetup) != 0) {
+ ALOGE("Error creating stylus uinput tilt y axis: %s", strerror(errno));
+ return invalidFd();
+ }
+ uinput_abs_setup pressureAbsSetup;
+ pressureAbsSetup.code = ABS_PRESSURE;
+ pressureAbsSetup.absinfo.maximum = 255;
+ pressureAbsSetup.absinfo.minimum = 0;
+ if (ioctl(fd, UI_ABS_SETUP, &pressureAbsSetup) != 0) {
+ ALOGE("Error creating touchscreen uinput pressure axis: %s", strerror(errno));
+ return invalidFd();
+ }
}
if (ioctl(fd, UI_DEV_SETUP, &setup) != 0) {
ALOGE("Error creating uinput device: %s", strerror(errno));
@@ -182,6 +242,17 @@
fallback.absmax[ABS_MT_TOUCH_MAJOR] = screenWidth - 1;
fallback.absmin[ABS_MT_PRESSURE] = 0;
fallback.absmax[ABS_MT_PRESSURE] = 255;
+ } else if (deviceType == DeviceType::STYLUS) {
+ fallback.absmin[ABS_X] = 0;
+ fallback.absmax[ABS_X] = screenWidth - 1;
+ fallback.absmin[ABS_Y] = 0;
+ fallback.absmax[ABS_Y] = screenHeight - 1;
+ fallback.absmin[ABS_TILT_X] = -90;
+ fallback.absmax[ABS_TILT_X] = 90;
+ fallback.absmin[ABS_TILT_Y] = -90;
+ fallback.absmax[ABS_TILT_Y] = 90;
+ fallback.absmin[ABS_PRESSURE] = 0;
+ fallback.absmax[ABS_PRESSURE] = 255;
}
if (TEMP_FAILURE_RETRY(write(fd, &fallback, sizeof(fallback))) != sizeof(fallback)) {
ALOGE("Error creating uinput device: %s", strerror(errno));
@@ -234,6 +305,13 @@
return fd.ok() ? reinterpret_cast<jlong>(new VirtualTouchscreen(std::move(fd))) : INVALID_PTR;
}
+static jlong nativeOpenUinputStylus(JNIEnv* env, jobject thiz, jstring name, jint vendorId,
+ jint productId, jstring phys, jint height, jint width) {
+ auto fd =
+ openUinputJni(env, name, vendorId, productId, phys, DeviceType::STYLUS, height, width);
+ return fd.ok() ? reinterpret_cast<jlong>(new VirtualStylus(std::move(fd))) : INVALID_PTR;
+}
+
static void nativeCloseUinput(JNIEnv* env, jobject thiz, jlong ptr) {
VirtualInputDevice* virtualInputDevice = reinterpret_cast<VirtualInputDevice*>(ptr);
delete virtualInputDevice;
@@ -287,6 +365,22 @@
std::chrono::nanoseconds(eventTimeNanos));
}
+// Native methods for VirtualStylus
+static bool nativeWriteStylusMotionEvent(JNIEnv* env, jobject thiz, jlong ptr, jint toolType,
+ jint action, jint locationX, jint locationY, jint pressure,
+ jint tiltX, jint tiltY, jlong eventTimeNanos) {
+ VirtualStylus* virtualStylus = reinterpret_cast<VirtualStylus*>(ptr);
+ return virtualStylus->writeMotionEvent(toolType, action, locationX, locationY, pressure, tiltX,
+ tiltY, std::chrono::nanoseconds(eventTimeNanos));
+}
+
+static bool nativeWriteStylusButtonEvent(JNIEnv* env, jobject thiz, jlong ptr, jint buttonCode,
+ jint action, jlong eventTimeNanos) {
+ VirtualStylus* virtualStylus = reinterpret_cast<VirtualStylus*>(ptr);
+ return virtualStylus->writeButtonEvent(buttonCode, action,
+ std::chrono::nanoseconds(eventTimeNanos));
+}
+
static JNINativeMethod methods[] = {
{"nativeOpenUinputDpad", "(Ljava/lang/String;IILjava/lang/String;)J",
(void*)nativeOpenUinputDpad},
@@ -296,6 +390,8 @@
(void*)nativeOpenUinputMouse},
{"nativeOpenUinputTouchscreen", "(Ljava/lang/String;IILjava/lang/String;II)J",
(void*)nativeOpenUinputTouchscreen},
+ {"nativeOpenUinputStylus", "(Ljava/lang/String;IILjava/lang/String;II)J",
+ (void*)nativeOpenUinputStylus},
{"nativeCloseUinput", "(J)V", (void*)nativeCloseUinput},
{"nativeWriteDpadKeyEvent", "(JIIJ)Z", (void*)nativeWriteDpadKeyEvent},
{"nativeWriteKeyEvent", "(JIIJ)Z", (void*)nativeWriteKeyEvent},
@@ -303,6 +399,8 @@
{"nativeWriteTouchEvent", "(JIIIFFFFJ)Z", (void*)nativeWriteTouchEvent},
{"nativeWriteRelativeEvent", "(JFFJ)Z", (void*)nativeWriteRelativeEvent},
{"nativeWriteScrollEvent", "(JFFJ)Z", (void*)nativeWriteScrollEvent},
+ {"nativeWriteStylusMotionEvent", "(JIIIIIIIJ)Z", (void*)nativeWriteStylusMotionEvent},
+ {"nativeWriteStylusButtonEvent", "(JIIJ)Z", (void*)nativeWriteStylusButtonEvent},
};
int register_android_server_companion_virtual_InputController(JNIEnv* env) {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 414339d..2b9bb7a 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -685,6 +685,8 @@
{ // acquire lock
std::scoped_lock _l(mLock);
+ outConfig->mousePointerSpeed = mLocked.pointerSpeed;
+ outConfig->mousePointerAccelerationEnabled = mLocked.mousePointerAccelerationEnabled;
outConfig->pointerVelocityControlParameters.scale = exp2f(mLocked.pointerSpeed
* POINTER_SPEED_EXPONENT);
outConfig->pointerVelocityControlParameters.acceleration =
diff --git a/services/core/jni/com_android_server_utils_AnrTimer.cpp b/services/core/jni/com_android_server_utils_AnrTimer.cpp
index 1e48ace..8ca5333 100644
--- a/services/core/jni/com_android_server_utils_AnrTimer.cpp
+++ b/services/core/jni/com_android_server_utils_AnrTimer.cpp
@@ -113,8 +113,10 @@
static const timer_id_t NOTIMER = 0;
// A notifier is called with a timer ID, the timer's tag, and the client's cookie. The pid
- // and uid that were originally assigned to the timer are passed as well.
- using notifier_t = bool (*)(timer_id_t, int pid, int uid, void* cookie, jweak object);
+ // and uid that were originally assigned to the timer are passed as well. The elapsed time
+ // is the time since the timer was scheduled.
+ using notifier_t = bool (*)(timer_id_t, int pid, int uid, nsecs_t elapsed,
+ void* cookie, jweak object);
enum Status {
Invalid,
@@ -278,6 +280,9 @@
// The state of this timer.
Status status;
+ // The time at which the timer was started.
+ nsecs_t started;
+
// The scheduled timeout. This is an absolute time. It may be extended.
nsecs_t scheduled;
@@ -297,6 +302,7 @@
timeout(0),
extend(false),
status(Invalid),
+ started(0),
scheduled(0),
extended(false) {
}
@@ -310,6 +316,7 @@
timeout(0),
extend(false),
status(Invalid),
+ started(0),
scheduled(0),
extended(false) {
}
@@ -322,7 +329,8 @@
timeout(timeout),
extend(extend),
status(Running),
- scheduled(now() + timeout),
+ started(now()),
+ scheduled(started + timeout),
extended(false) {
if (extend && pid != 0) {
initial.fill(pid);
@@ -714,6 +722,7 @@
// Save the timer attributes for the notification
int pid = 0;
int uid = 0;
+ nsecs_t elapsed = 0;
bool expired = false;
{
AutoMutex _l(lock_);
@@ -727,11 +736,14 @@
// accept or discard).
insert(t);
}
+ pid = t.pid;
+ uid = t.uid;
+ elapsed = now() - t.started;
}
// Deliver the notification outside of the lock.
if (expired) {
- if (!notifier_(timerId, pid, uid, notifierCookie_, notifierObject_)) {
+ if (!notifier_(timerId, pid, uid, elapsed, notifierCookie_, notifierObject_)) {
AutoMutex _l(lock_);
// Notification failed, which means the listener will never call accept() or
// discard(). Do not reinsert the timer.
@@ -804,7 +816,7 @@
static AnrArgs gAnrArgs;
// The cookie is the address of the AnrArgs object to which the notification should be sent.
-static bool anrNotify(AnrTimerService::timer_id_t timerId, int pid, int uid,
+static bool anrNotify(AnrTimerService::timer_id_t timerId, int pid, int uid, nsecs_t elapsed,
void* cookie, jweak jtimer) {
AutoMutex _l(gAnrLock);
AnrArgs* target = reinterpret_cast<AnrArgs* >(cookie);
@@ -816,7 +828,8 @@
jboolean r = false;
jobject timer = env->NewGlobalRef(jtimer);
if (timer != nullptr) {
- r = env->CallBooleanMethod(timer, target->func, timerId, pid, uid);
+ // Convert the elsapsed time from ns (native) to ms (Java)
+ r = env->CallBooleanMethod(timer, target->func, timerId, pid, uid, ns2ms(elapsed));
env->DeleteGlobalRef(timer);
}
target->vm->DetachCurrentThread();
@@ -909,7 +922,7 @@
jclass service = FindClassOrDie(env, className);
gAnrArgs.clazz = MakeGlobalRefOrDie(env, service);
- gAnrArgs.func = env->GetMethodID(gAnrArgs.clazz, "expire", "(III)Z");
+ gAnrArgs.func = env->GetMethodID(gAnrArgs.clazz, "expire", "(IIIJ)Z");
env->GetJavaVM(&gAnrArgs.vm);
nativeSupportEnabled = NATIVE_SUPPORT;
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index a4b1f84..5d1eb49 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -66,7 +66,6 @@
int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env);
int register_android_server_companion_virtual_InputController(JNIEnv* env);
int register_android_server_app_GameManagerService(JNIEnv* env);
-int register_com_android_server_BootReceiver(JNIEnv* env);
int register_com_android_server_wm_TaskFpsCallbackController(JNIEnv* env);
int register_com_android_server_display_DisplayControl(JNIEnv* env);
int register_com_android_server_SystemClockTime(JNIEnv* env);
@@ -129,7 +128,6 @@
register_android_server_sensor_SensorService(vm, env);
register_android_server_companion_virtual_InputController(env);
register_android_server_app_GameManagerService(env);
- register_com_android_server_BootReceiver(env);
register_com_android_server_wm_TaskFpsCallbackController(env);
register_com_android_server_display_DisplayControl(env);
register_com_android_server_SystemClockTime(env);
diff --git a/services/core/lint-baseline.xml b/services/core/lint-baseline.xml
index 070bd4b..2ccd1e4 100644
--- a/services/core/lint-baseline.xml
+++ b/services/core/lint-baseline.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 7.2.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
<issue
id="NonUserGetterCalled"
@@ -145,4 +145,4 @@
line="7158"/>
</issue>
-</issues>
+</issues>
\ No newline at end of file
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 3cbceec..a469165 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -625,6 +625,9 @@
If no mode is specified, the mapping will be used for the default mode.
If no setting is specified, the mapping will be used for the normal brightness setting.
+
+ If no mapping is defined for one of the settings, the mapping for the normal setting will be
+ used as a fallback.
-->
<xs:complexType name="luxToBrightnessMapping">
<xs:element name="map" type="nonNegativeFloatToFloatMap">
diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
index 69a5e5c..db985fd 100644
--- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
@@ -50,7 +50,8 @@
long startedTimestamp) {
super(context, sessionCallback, lock, userId, callingUid, request, callback,
RequestInfo.TYPE_UNDEFINED,
- callingAppInfo, enabledProviders, cancellationSignal, startedTimestamp);
+ callingAppInfo, enabledProviders, cancellationSignal, startedTimestamp,
+ /*shouldBindClientToDeath=*/ true);
}
/**
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index 31409ab..b24accb 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -63,7 +63,8 @@
long startedTimestamp) {
super(context, sessionCallback, lock, userId, callingUid, request, callback,
RequestInfo.TYPE_CREATE,
- callingAppInfo, enabledProviders, cancellationSignal, startedTimestamp);
+ callingAppInfo, enabledProviders, cancellationSignal, startedTimestamp,
+ /*shouldBindClientToDeath=*/ true);
mRequestSessionMetric.collectCreateFlowInitialMetricInfo(
/*origin=*/request.getOrigin() != null, request);
mPrimaryProviders = primaryProviders;
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index fb0729f..667e086 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -63,7 +63,6 @@
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
-import android.view.autofill.IAutoFillManagerClient;
import com.android.internal.annotations.GuardedBy;
import com.android.server.credentials.metrics.ApiName;
@@ -484,7 +483,7 @@
public ICancellationSignal getCandidateCredentials(
GetCredentialRequest request,
IGetCandidateCredentialsCallback callback,
- IAutoFillManagerClient clientCallback,
+ IBinder clientBinder,
final String callingPackage) {
Slog.i(TAG, "starting getCandidateCredentials with callingPackage: "
+ callingPackage);
@@ -506,7 +505,7 @@
constructCallingAppInfo(callingPackage, userId, request.getOrigin()),
getEnabledProvidersForUser(userId),
CancellationSignal.fromTransport(cancelTransport),
- clientCallback
+ clientBinder
);
addSessionLocked(userId, session);
diff --git a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
index 0f914c3..25281ba 100644
--- a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
@@ -30,11 +30,11 @@
import android.credentials.ui.ProviderData;
import android.credentials.ui.RequestInfo;
import android.os.CancellationSignal;
+import android.os.IBinder;
import android.os.RemoteException;
import android.service.credentials.CallingAppInfo;
import android.service.credentials.PermissionUtils;
import android.util.Slog;
-import android.view.autofill.IAutoFillManagerClient;
import java.util.ArrayList;
import java.util.List;
@@ -52,7 +52,7 @@
private static final String SESSION_ID_KEY = "autofill_session_id";
private static final String REQUEST_ID_KEY = "autofill_request_id";
- private final IAutoFillManagerClient mAutoFillCallback;
+ private final IBinder mClientBinder;
private final int mAutofillSessionId;
private final int mAutofillRequestId;
@@ -62,13 +62,16 @@
IGetCandidateCredentialsCallback callback, GetCredentialRequest request,
CallingAppInfo callingAppInfo, Set<ComponentName> enabledProviders,
CancellationSignal cancellationSignal,
- IAutoFillManagerClient autoFillCallback) {
+ IBinder clientBinder) {
super(context, sessionCallback, lock, userId, callingUid, request, callback,
RequestInfo.TYPE_GET, callingAppInfo, enabledProviders,
- cancellationSignal, 0L);
- mAutoFillCallback = autoFillCallback;
+ cancellationSignal, 0L, /*shouldBindClientToDeath=*/ false);
+ mClientBinder = clientBinder;
mAutofillSessionId = request.getData().getInt(SESSION_ID_KEY, -1);
mAutofillRequestId = request.getData().getInt(REQUEST_ID_KEY, -1);
+ if (mClientBinder != null) {
+ setUpClientCallbackListener(mClientBinder);
+ }
}
/**
@@ -144,17 +147,27 @@
@Override
public void onFinalErrorReceived(ComponentName componentName, String errorType,
String message) {
- // Not applicable for session without UI
+ respondToClientWithErrorAndFinish(errorType, message);
}
@Override
public void onUiCancellation(boolean isUserCancellation) {
- // Not applicable for session without UI
+ String exception = GetCandidateCredentialsException.TYPE_USER_CANCELED;
+ String message = "User cancelled the selector";
+ if (!isUserCancellation) {
+ exception = GetCandidateCredentialsException.TYPE_INTERRUPTED;
+ message = "The UI was interrupted - please try again.";
+ }
+ mRequestSessionMetric.collectFrameworkException(exception);
+ respondToClientWithErrorAndFinish(exception, message);
}
@Override
public void onUiSelectorInvocationFailure() {
- // Not applicable for session without UI
+ String exception = GetCandidateCredentialsException.TYPE_NO_CREDENTIAL;
+ mRequestSessionMetric.collectFrameworkException(exception);
+ respondToClientWithErrorAndFinish(exception,
+ "No credentials available.");
}
@Override
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index 3f57c80..49ea19a 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -57,7 +57,7 @@
long startedTimestamp) {
super(context, sessionCallback, lock, userId, callingUid, request, callback,
getRequestInfoFromRequest(request), callingAppInfo, enabledProviders,
- cancellationSignal, startedTimestamp);
+ cancellationSignal, startedTimestamp, /*shouldBindClientToDeath=*/ true);
mRequestSessionMetric.collectGetFlowInitialMetricInfo(request);
}
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index da44aac..67c52e6 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -122,7 +122,8 @@
@NonNull String requestType,
CallingAppInfo callingAppInfo,
Set<ComponentName> enabledProviders,
- CancellationSignal cancellationSignal, long timestampStarted) {
+ CancellationSignal cancellationSignal, long timestampStarted,
+ boolean shouldBindClientToDeath) {
mContext = context;
mLock = lock;
mSessionCallback = sessionCallback;
@@ -146,16 +147,18 @@
mRequestSessionMetric.collectInitialPhaseMetricInfo(timestampStarted,
mCallingUid, ApiName.getMetricCodeFromRequestInfo(mRequestType));
setCancellationListener();
- if (Flags.clearSessionEnabled()) {
- setUpClientCallbackListener();
+ if (shouldBindClientToDeath && Flags.clearSessionEnabled()) {
+ if (mClientCallback != null && mClientCallback instanceof IInterface) {
+ setUpClientCallbackListener(((IInterface) mClientCallback).asBinder());
+ }
}
}
- private void setUpClientCallbackListener() {
+ protected void setUpClientCallbackListener(IBinder clientBinder) {
if (mClientCallback != null && mClientCallback instanceof IInterface) {
IInterface callback = (IInterface) mClientCallback;
try {
- callback.asBinder().linkToDeath(mDeathRecipient, 0);
+ clientBinder.linkToDeath(mDeathRecipient, 0);
} catch (RemoteException e) {
Slog.e(TAG, e.getMessage());
}
diff --git a/services/foldables/devicestateprovider/proguard.flags b/services/foldables/devicestateprovider/proguard.flags
index 069cbc6..b810cad 100644
--- a/services/foldables/devicestateprovider/proguard.flags
+++ b/services/foldables/devicestateprovider/proguard.flags
@@ -1 +1 @@
--keep,allowoptimization,allowaccessmodification class com.android.server.policy.TentModeDeviceStatePolicy { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.policy.BookStyleDeviceStatePolicy { *; }
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java
new file mode 100644
index 0000000..d5a3cff
--- /dev/null
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java
@@ -0,0 +1,432 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.policy;
+
+import static android.hardware.SensorManager.SENSOR_DELAY_NORMAL;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.server.policy.BookStylePreferredScreenCalculator.PreferredScreen.OUTER;
+import static com.android.server.policy.BookStylePreferredScreenCalculator.HingeAngle.ANGLE_0;
+import static com.android.server.policy.BookStylePreferredScreenCalculator.HingeAngle.ANGLE_0_TO_45;
+import static com.android.server.policy.BookStylePreferredScreenCalculator.HingeAngle.ANGLE_45_TO_90;
+import static com.android.server.policy.BookStylePreferredScreenCalculator.HingeAngle.ANGLE_90_TO_180;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.util.ArraySet;
+import android.view.Display;
+import android.view.Surface;
+
+import com.android.server.policy.BookStylePreferredScreenCalculator.PreferredScreen;
+import com.android.server.policy.BookStylePreferredScreenCalculator.HingeAngle;
+import com.android.server.policy.BookStylePreferredScreenCalculator.StateTransition;
+import com.android.server.policy.BookStyleClosedStatePredicate.ConditionSensorListener.SensorSubscription;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+/**
+ * 'Closed' state predicate that takes into account the posture of the device
+ * It accepts list of state transitions that control how the device moves between
+ * device states.
+ * See {@link BookStyleStateTransitions} for detailed description of the default behavior.
+ */
+public class BookStyleClosedStatePredicate implements Predicate<FoldableDeviceStateProvider>,
+ DisplayManager.DisplayListener {
+
+ private final BookStylePreferredScreenCalculator mClosedStateCalculator;
+ private final Handler mHandler = new Handler();
+ private final PostureEstimator mPostureEstimator;
+ private final DisplayManager mDisplayManager;
+
+ /**
+ * Creates {@link BookStyleClosedStatePredicate}. It is expected that the device has a pair
+ * of accelerometer sensors (one for each movable part of the device), see parameter
+ * descriptions for the behaviour when these sensors are not available.
+ * @param context context that could be used to get system services
+ * @param updatesListener callback that will be executed whenever the predicate should be
+ * checked again
+ * @param leftAccelerometerSensor accelerometer sensor that is located in the half of the
+ * device that has the outer screen, in case if this sensor is
+ * not provided, tent/wedge mode will be detected only using
+ * orientation sensor and screen rotation, so this mode won't
+ * be accessible by putting the device on a flat surface
+ * @param rightAccelerometerSensor accelerometer sensor that is located on the opposite side
+ * across the hinge from the previous accelerometer sensor,
+ * in case if this sensor is not provided, reverse wedge mode
+ * won't be detected, so the device will use closed state using
+ * constant angle when folding
+ * @param stateTransitions definition of all possible state transitions, see
+ * {@link BookStyleStateTransitions} for sample and more details
+ */
+
+ public BookStyleClosedStatePredicate(@NonNull Context context,
+ @NonNull ClosedStateUpdatesListener updatesListener,
+ @Nullable Sensor leftAccelerometerSensor, @Nullable Sensor rightAccelerometerSensor,
+ @NonNull List<StateTransition> stateTransitions) {
+ mDisplayManager = context.getSystemService(DisplayManager.class);
+ mDisplayManager.registerDisplayListener(this, mHandler);
+
+ mClosedStateCalculator = new BookStylePreferredScreenCalculator(stateTransitions);
+
+ final SensorManager sensorManager = context.getSystemService(SensorManager.class);
+ final Sensor orientationSensor = sensorManager.getDefaultSensor(
+ Sensor.TYPE_DEVICE_ORIENTATION);
+
+ mPostureEstimator = new PostureEstimator(mHandler, sensorManager,
+ leftAccelerometerSensor, rightAccelerometerSensor, orientationSensor,
+ updatesListener::onClosedStateUpdated);
+ }
+
+ /**
+ * Based on the current sensor readings and current state, returns true if the device should use
+ * 'CLOSED' device state and false if it should not use 'CLOSED' state (e.g. could use half-open
+ * or open states).
+ */
+ @Override
+ public boolean test(FoldableDeviceStateProvider foldableDeviceStateProvider) {
+ final HingeAngle hingeAngle = hingeAngleFromFloat(
+ foldableDeviceStateProvider.getHingeAngle());
+
+ mPostureEstimator.onDeviceClosedStatusChanged(hingeAngle == ANGLE_0);
+
+ final PreferredScreen preferredScreen = mClosedStateCalculator.
+ calculatePreferredScreen(hingeAngle, mPostureEstimator.isLikelyTentOrWedgeMode(),
+ mPostureEstimator.isLikelyReverseWedgeMode(hingeAngle));
+
+ return preferredScreen == OUTER;
+ }
+
+ private HingeAngle hingeAngleFromFloat(float hingeAngle) {
+ if (hingeAngle == 0f) {
+ return ANGLE_0;
+ } else if (hingeAngle < 45f) {
+ return ANGLE_0_TO_45;
+ } else if (hingeAngle < 90f) {
+ return ANGLE_45_TO_90;
+ } else {
+ return ANGLE_90_TO_180;
+ }
+ }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ if (displayId == DEFAULT_DISPLAY) {
+ final Display display = mDisplayManager.getDisplay(displayId);
+ int displayState = display.getState();
+ boolean isDisplayOn = displayState == Display.STATE_ON;
+ mPostureEstimator.onDisplayPowerStatusChanged(isDisplayOn);
+ mPostureEstimator.onDisplayRotationChanged(display.getRotation());
+ }
+ }
+
+ @Override
+ public void onDisplayAdded(int displayId) {
+
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+
+ }
+
+ public interface ClosedStateUpdatesListener {
+ void onClosedStateUpdated();
+ }
+
+ /**
+ * Estimates if the device is going to enter wedge/tent mode based on the sensor data
+ */
+ private static class PostureEstimator implements SensorEventListener {
+
+
+ private static final int FLAT_INCLINATION_THRESHOLD_DEGREES = 8;
+
+ /**
+ * Alpha parameter of the accelerometer low pass filter: the lower the value, the less high
+ * frequency noise it filter but reduces the latency.
+ */
+ private static final float GRAVITY_VECTOR_LOW_PASS_ALPHA_VALUE = 0.8f;
+
+
+ @Nullable
+ private final Sensor mLeftAccelerometerSensor;
+ @Nullable
+ private final Sensor mRightAccelerometerSensor;
+ private final Sensor mOrientationSensor;
+ private final Runnable mOnSensorUpdatedListener;
+
+ private final ConditionSensorListener mConditionedSensorListener;
+
+ @Nullable
+ private float[] mRightGravityVector;
+
+ @Nullable
+ private float[] mLeftGravityVector;
+
+ @Nullable
+ private Integer mLastScreenRotation;
+
+ @Nullable
+ private SensorEvent mLastDeviceOrientationSensorEvent = null;
+
+ private boolean mScreenTurnedOn = false;
+ private boolean mDeviceClosed = false;
+
+ public PostureEstimator(Handler handler, SensorManager sensorManager,
+ @Nullable Sensor leftAccelerometerSensor, @Nullable Sensor rightAccelerometerSensor,
+ Sensor orientationSensor, Runnable onSensorUpdated) {
+ mLeftAccelerometerSensor = leftAccelerometerSensor;
+ mRightAccelerometerSensor = rightAccelerometerSensor;
+ mOrientationSensor = orientationSensor;
+
+ mOnSensorUpdatedListener = onSensorUpdated;
+
+ final List<SensorSubscription> sensorSubscriptions = new ArrayList<>();
+ if (mLeftAccelerometerSensor != null) {
+ sensorSubscriptions.add(new SensorSubscription(
+ mLeftAccelerometerSensor,
+ /* allowedToListen= */ () -> mScreenTurnedOn && !mDeviceClosed,
+ /* cleanup= */ () -> mLeftGravityVector = null));
+ }
+
+ if (mRightAccelerometerSensor != null) {
+ sensorSubscriptions.add(new SensorSubscription(
+ mRightAccelerometerSensor,
+ /* allowedToListen= */ () -> mScreenTurnedOn,
+ /* cleanup= */ () -> mRightGravityVector = null));
+ }
+
+ sensorSubscriptions.add(new SensorSubscription(mOrientationSensor,
+ /* allowedToListen= */ () -> mScreenTurnedOn,
+ /* cleanup= */ () -> mLastDeviceOrientationSensorEvent = null));
+
+ mConditionedSensorListener = new ConditionSensorListener(sensorManager, this, handler,
+ sensorSubscriptions);
+ }
+
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ if (event.sensor == mRightAccelerometerSensor) {
+ if (mRightGravityVector == null) {
+ mRightGravityVector = new float[3];
+ }
+ setNewValueWithHighPassFilter(mRightGravityVector, event.values);
+
+ final boolean isRightMostlyFlat = Objects.equals(
+ isGravityVectorMostlyFlat(mRightGravityVector), Boolean.TRUE);
+
+ if (isRightMostlyFlat) {
+ // Reset orientation sensor when the device becomes flat
+ mLastDeviceOrientationSensorEvent = null;
+ }
+ } else if (event.sensor == mLeftAccelerometerSensor) {
+ if (mLeftGravityVector == null) {
+ mLeftGravityVector = new float[3];
+ }
+ setNewValueWithHighPassFilter(mLeftGravityVector, event.values);
+ } else if (event.sensor == mOrientationSensor) {
+ mLastDeviceOrientationSensorEvent = event;
+ }
+
+ mOnSensorUpdatedListener.run();
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+
+ }
+
+ private void setNewValueWithHighPassFilter(float[] output, float[] newValues) {
+ final float alpha = GRAVITY_VECTOR_LOW_PASS_ALPHA_VALUE;
+ output[0] = alpha * output[0] + (1 - alpha) * newValues[0];
+ output[1] = alpha * output[1] + (1 - alpha) * newValues[1];
+ output[2] = alpha * output[2] + (1 - alpha) * newValues[2];
+ }
+
+ /**
+ * Returns true if the phone likely in reverse wedge mode (when a foldable phone is lying
+ * on the outer screen mostly flat to the ground)
+ */
+ public boolean isLikelyReverseWedgeMode(HingeAngle hingeAngle) {
+ return hingeAngle != ANGLE_0 && Objects.equals(
+ isGravityVectorMostlyFlat(mLeftGravityVector), Boolean.TRUE);
+ }
+
+ /**
+ * Returns true if the phone is likely in tent or wedge mode when unfolding. Tent mode
+ * is detected by checking if the phone is in seascape position, screen is rotated to
+ * landscape or seascape, or if the right side of the device is mostly flat.
+ */
+ public boolean isLikelyTentOrWedgeMode() {
+ boolean isScreenLandscapeOrSeascape = Objects.equals(mLastScreenRotation,
+ Surface.ROTATION_270) || Objects.equals(mLastScreenRotation,
+ Surface.ROTATION_90);
+ if (isScreenLandscapeOrSeascape) {
+ return true;
+ }
+
+ boolean isRightMostlyFlat = Objects.equals(
+ isGravityVectorMostlyFlat(mRightGravityVector), Boolean.TRUE);
+ if (isRightMostlyFlat) {
+ return true;
+ }
+
+ boolean isSensorSeaScape = Objects.equals(getOrientationSensorRotation(),
+ Surface.ROTATION_270);
+ if (isSensorSeaScape) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns true if the passed gravity vector implies that the phone is mostly flat (the
+ * vector is close to be perpendicular to the ground and has a positive Z component).
+ * Returns null if there is no data from the sensor.
+ */
+ private Boolean isGravityVectorMostlyFlat(@Nullable float[] vector) {
+ if (vector == null) return null;
+ if (vector[0] == 0.0f && vector[1] == 0.0f && vector[2] == 0.0f) {
+ // Likely we haven't received the actual data yet, treat it as no data
+ return null;
+ }
+
+ double vectorMagnitude = Math.sqrt(
+ vector[0] * vector[0] + vector[1] * vector[1] + vector[2] * vector[2]);
+ float normalizedGravityZ = (float) (vector[2] / vectorMagnitude);
+
+ final int inclination = (int) Math.round(Math.toDegrees(Math.acos(normalizedGravityZ)));
+ return inclination < FLAT_INCLINATION_THRESHOLD_DEGREES;
+ }
+
+ private Integer getOrientationSensorRotation() {
+ if (mLastDeviceOrientationSensorEvent == null) return null;
+ return (int) mLastDeviceOrientationSensorEvent.values[0];
+ }
+
+ /**
+ * Called whenever display status changes, we use this signal to start/stop listening
+ * to sensors when the display is off to save battery. Using display state instead of
+ * general power state to reduce the time when sensors are on, we don't need to listen
+ * to the extra sensors when the screen is off.
+ */
+ public void onDisplayPowerStatusChanged(boolean screenTurnedOn) {
+ mScreenTurnedOn = screenTurnedOn;
+ mConditionedSensorListener.updateListeningState();
+ }
+
+ /**
+ * Called whenever we display rotation might have been updated
+ * @param rotation new rotation
+ */
+ public void onDisplayRotationChanged(int rotation) {
+ mLastScreenRotation = rotation;
+ }
+
+ /**
+ * Called whenever foldable device becomes fully closed or opened
+ */
+ public void onDeviceClosedStatusChanged(boolean deviceClosed) {
+ mDeviceClosed = deviceClosed;
+ mConditionedSensorListener.updateListeningState();
+ }
+ }
+
+ /**
+ * Helper class that subscribes or unsubscribes from a sensor based on a condition specified
+ * in {@link SensorSubscription}
+ */
+ static class ConditionSensorListener {
+ private final List<SensorSubscription> mSensorSubscriptions;
+ private final ArraySet<Sensor> mIsListening = new ArraySet<>();
+
+ private final SensorManager mSensorManager;
+ private final SensorEventListener mSensorEventListener;
+
+ private final Handler mHandler;
+
+ public ConditionSensorListener(SensorManager sensorManager,
+ SensorEventListener sensorEventListener, Handler handler,
+ List<SensorSubscription> sensorSubscriptions) {
+ mSensorManager = sensorManager;
+ mSensorEventListener = sensorEventListener;
+ mSensorSubscriptions = sensorSubscriptions;
+ mHandler = handler;
+ }
+
+ /**
+ * Updates current listening state of the sensor based on the provided conditions
+ */
+ public void updateListeningState() {
+ for (int i = 0; i < mSensorSubscriptions.size(); i++) {
+ final SensorSubscription subscription = mSensorSubscriptions.get(i);
+ final Sensor sensor = subscription.mSensor;
+
+ final boolean shouldBeListening = subscription.mAllowedToListenSupplier.get();
+ final boolean isListening = mIsListening.contains(sensor);
+ final boolean shouldUpdateListening = isListening != shouldBeListening;
+
+ if (shouldUpdateListening) {
+ if (shouldBeListening) {
+ mIsListening.add(sensor);
+ mSensorManager.registerListener(mSensorEventListener, sensor,
+ SENSOR_DELAY_NORMAL, mHandler);
+ } else {
+ mIsListening.remove(sensor);
+ mSensorManager.unregisterListener(mSensorEventListener, sensor);
+ subscription.mOnUnsubscribe.run();
+ }
+ }
+ }
+ }
+
+ /**
+ * Represents a configuration of a single sensor subscription
+ */
+ public static class SensorSubscription {
+ private final Sensor mSensor;
+ private final Supplier<Boolean> mAllowedToListenSupplier;
+ private final Runnable mOnUnsubscribe;
+
+ /**
+ * @param sensor sensor to listen to
+ * @param allowedToListen return true when it is allowed to listen to the sensor
+ * @param cleanup a runnable that will be closed just before unsubscribing from the
+ * sensor
+ */
+
+ public SensorSubscription(Sensor sensor, Supplier<Boolean> allowedToListen,
+ Runnable cleanup) {
+ mSensor = sensor;
+ mAllowedToListenSupplier = allowedToListen;
+ mOnUnsubscribe = cleanup;
+ }
+ }
+ }
+}
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/TentModeDeviceStatePolicy.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
similarity index 73%
rename from services/foldables/devicestateprovider/src/com/android/server/policy/TentModeDeviceStatePolicy.java
rename to services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
index 5968b63..ad938af 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/TentModeDeviceStatePolicy.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
@@ -21,10 +21,12 @@
import static com.android.server.devicestate.DeviceState.FLAG_EMULATED_ONLY;
import static com.android.server.devicestate.DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE;
import static com.android.server.devicestate.DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL;
+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 android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorManager;
@@ -39,12 +41,15 @@
import java.util.function.Predicate;
/**
- * Device state policy for a foldable device that supports tent mode: a mode when the device
- * keeps the outer display on until reaching a certain hinge angle threshold.
+ * Device state policy for a foldable device with two screens in a book style, where the hinge is
+ * located on the left side of the device when in folded posture.
+ * The policy supports tent/wedge mode: a mode when the device keeps the outer display on
+ * until reaching certain conditions like hinge angle threshold.
*
* Contains configuration for {@link FoldableDeviceStateProvider}.
*/
-public class TentModeDeviceStatePolicy extends DeviceStatePolicy {
+public class BookStyleDeviceStatePolicy extends DeviceStatePolicy implements
+ BookStyleClosedStatePredicate.ClosedStateUpdatesListener {
private static final int DEVICE_STATE_CLOSED = 0;
private static final int DEVICE_STATE_HALF_OPENED = 1;
@@ -57,9 +62,10 @@
private static final int MIN_CLOSED_ANGLE_DEGREES = 0;
private static final int MAX_CLOSED_ANGLE_DEGREES = 5;
- private final DeviceStateProvider mProvider;
+ private final FoldableDeviceStateProvider mProvider;
private final boolean mIsDualDisplayBlockingEnabled;
+ private final boolean mEnablePostureBasedClosedState;
private static final Predicate<FoldableDeviceStateProvider> ALLOWED = p -> true;
private static final Predicate<FoldableDeviceStateProvider> NOT_ALLOWED = p -> false;
@@ -73,30 +79,30 @@
* between folded and unfolded modes, otherwise when folding the
* display switch will happen at 0 degrees
*/
- public TentModeDeviceStatePolicy(@NonNull Context context,
- @NonNull Sensor hingeAngleSensor, @NonNull Sensor hallSensor, int closeAngleDegrees) {
- this(new FeatureFlagsImpl(), context, hingeAngleSensor, hallSensor, closeAngleDegrees);
- }
-
- public TentModeDeviceStatePolicy(@NonNull FeatureFlags featureFlags, @NonNull Context context,
- @NonNull Sensor hingeAngleSensor, @NonNull Sensor hallSensor,
- int closeAngleDegrees) {
+ public BookStyleDeviceStatePolicy(@NonNull FeatureFlags featureFlags, @NonNull Context context,
+ @NonNull Sensor hingeAngleSensor, @NonNull Sensor hallSensor,
+ @Nullable Sensor leftAccelerometerSensor, @Nullable Sensor rightAccelerometerSensor,
+ Integer closeAngleDegrees) {
super(context);
final SensorManager sensorManager = mContext.getSystemService(SensorManager.class);
final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
- final DeviceStateConfiguration[] configuration = createConfiguration(closeAngleDegrees);
-
+ mEnablePostureBasedClosedState = featureFlags.enableFoldablesPostureBasedClosedState();
mIsDualDisplayBlockingEnabled = featureFlags.enableDualDisplayBlocking();
+ final DeviceStateConfiguration[] configuration = createConfiguration(
+ leftAccelerometerSensor, rightAccelerometerSensor, closeAngleDegrees);
+
mProvider = new FoldableDeviceStateProvider(mContext, sensorManager,
hingeAngleSensor, hallSensor, displayManager, configuration);
}
- private DeviceStateConfiguration[] createConfiguration(int closeAngleDegrees) {
+ private DeviceStateConfiguration[] createConfiguration(@Nullable Sensor leftAccelerometerSensor,
+ @Nullable Sensor rightAccelerometerSensor, Integer closeAngleDegrees) {
return new DeviceStateConfiguration[]{
- createClosedConfiguration(closeAngleDegrees),
+ createClosedConfiguration(leftAccelerometerSensor, rightAccelerometerSensor,
+ closeAngleDegrees),
createConfig(DEVICE_STATE_HALF_OPENED,
/* name= */ "HALF_OPENED",
/* activeStatePredicate= */ (provider) -> {
@@ -123,8 +129,10 @@
};
}
- private DeviceStateConfiguration createClosedConfiguration(int closeAngleDegrees) {
- if (closeAngleDegrees > 0) {
+ private DeviceStateConfiguration 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,
@@ -137,6 +145,19 @@
);
}
+ 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)
+ );
+ }
+
// 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,
@@ -148,6 +169,11 @@
}
@Override
+ public void onClosedStateUpdated() {
+ mProvider.notifyDeviceStateChangedIfNeeded();
+ }
+
+ @Override
public DeviceStateProvider getDeviceStateProvider() {
return mProvider;
}
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStylePreferredScreenCalculator.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStylePreferredScreenCalculator.java
new file mode 100644
index 0000000..8977422
--- /dev/null
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStylePreferredScreenCalculator.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.policy;
+
+import android.annotation.Nullable;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Calculates if we should use outer or inner display on foldable devices based on a several
+ * inputs like device orientation, hinge angle signals.
+ *
+ * This is a stateful class and acts like a state machine with fixed number of states
+ * and transitions. It allows to list all possible state transitions instead of performing
+ * imperative logic to make sure that we cover all scenarios and improve debuggability.
+ *
+ * See {@link BookStyleStateTransitions} for detailed description of the default behavior.
+ */
+public class BookStylePreferredScreenCalculator {
+
+ /**
+ * When calculating the new state we will re-calculate it until it settles down. We re-calculate
+ * it because the new state might trigger another state transition and this might happen
+ * several times. We don't want to have infinite loops in state calculation, so this value
+ * limits the number of such state transitions.
+ * For example, in the default configuration {@link BookStyleStateTransitions}, after each
+ * transition with 'set sticky flag' output it will perform a transition to a state without
+ * 'set sticky flag' output.
+ * We also have a unit test covering all possible states which checks that we don't have such
+ * states that could end up in an infinite transition. See sample test for the default
+ * transitions in {@link BookStyleClosedStateCalculatorTest}.
+ */
+ private static final int MAX_STATE_CHANGES = 16;
+
+ private State mState = new State(
+ /* stickyKeepOuterUntil90Degrees= */ false,
+ /* stickyKeepInnerUntil45Degrees= */ false,
+ PreferredScreen.INVALID);
+
+ private final List<StateTransition> mStateTransitions;
+
+ /**
+ * Creates BookStyleClosedStateCalculator
+ * @param stateTransitions list of all state transitions
+ */
+ public BookStylePreferredScreenCalculator(List<StateTransition> stateTransitions) {
+ mStateTransitions = stateTransitions;
+ }
+
+ /**
+ * Calculates updated {@link PreferredScreen} based on the current inputs and the current state.
+ * The calculation is done based on defined {@link StateTransition}s, it might perform
+ * multiple transitions until we settle down on a single state. Multiple transitions could be
+ * performed in case if {@link StateTransition} causes another update of the state.
+ * There is a limit of maximum {@link MAX_STATE_CHANGES} state transitions, after which
+ * this method will throw an {@link IllegalStateException}.
+ *
+ * @param angle current hinge angle
+ * @param likelyTentOrWedge true if the device is likely in tent or wedge mode
+ * @param likelyReverseWedge true if the device is likely in reverse wedge mode
+ * @return updated {@link PreferredScreen}
+ */
+ public PreferredScreen calculatePreferredScreen(HingeAngle angle, boolean likelyTentOrWedge,
+ boolean likelyReverseWedge) {
+ int attempts = 0;
+ State newState = calculateNewState(mState, angle, likelyTentOrWedge, likelyReverseWedge);
+ while (attempts < MAX_STATE_CHANGES && !Objects.equals(mState, newState)) {
+ mState = newState;
+ newState = calculateNewState(mState, angle, likelyTentOrWedge, likelyReverseWedge);
+ attempts++;
+ }
+
+ if (attempts >= MAX_STATE_CHANGES) {
+ throw new IllegalStateException(
+ "Can't settle state " + mState + ", inputs: hingeAngle = " + angle
+ + ", likelyTentOrWedge = " + likelyTentOrWedge
+ + ", likelyReverseWedge = " + likelyReverseWedge);
+ }
+
+ final State oldState = mState;
+ mState = newState;
+
+ if (mState.mPreferredScreen == PreferredScreen.INVALID) {
+ throw new IllegalStateException(
+ "Reached invalid state " + mState + ", inputs: hingeAngle = " + angle
+ + ", likelyTentOrWedge = " + likelyTentOrWedge
+ + ", likelyReverseWedge = " + likelyReverseWedge + ", old state: "
+ + oldState);
+ }
+
+ return mState.mPreferredScreen;
+ }
+
+ /**
+ * Returns the current state of the calculator
+ */
+ public State getState() {
+ return mState;
+ }
+
+ private State calculateNewState(State current, HingeAngle hingeAngle, boolean likelyTentOrWedge,
+ boolean likelyReverseWedge) {
+ for (int i = 0; i < mStateTransitions.size(); i++) {
+ final State newState = mStateTransitions.get(i).tryTransition(hingeAngle,
+ likelyTentOrWedge, likelyReverseWedge, current);
+ if (newState != null) {
+ return newState;
+ }
+ }
+
+ throw new IllegalArgumentException(
+ "Entry not found for state: " + current + ", hingeAngle = " + hingeAngle
+ + ", likelyTentOrWedge = " + likelyTentOrWedge + ", likelyReverseWedge = "
+ + likelyReverseWedge);
+ }
+
+ /**
+ * The angle between two halves of the foldable device in degrees. The angle is '0' when
+ * the device is fully closed and '180' when the device is fully open and flat.
+ */
+ public enum HingeAngle {
+ ANGLE_0,
+ ANGLE_0_TO_45,
+ ANGLE_45_TO_90,
+ ANGLE_90_TO_180
+ }
+
+ /**
+ * Resulting closed state of the device, where OPEN state indicates that the device should use
+ * the inner display and CLOSED means that it should use the outer (cover) screen.
+ */
+ public enum PreferredScreen {
+ INNER,
+ OUTER,
+ INVALID
+ }
+
+ /**
+ * Describes a state transition for the posture based active screen calculator
+ */
+ public static class StateTransition {
+ private final Input mInput;
+ private final State mOutput;
+
+ public StateTransition(HingeAngle hingeAngle, boolean likelyTentOrWedge,
+ boolean likelyReverseWedge,
+ boolean stickyKeepOuterUntil90Degrees, boolean stickyKeepInnerUntil45Degrees,
+ PreferredScreen preferredScreen, Boolean setStickyKeepOuterUntil90Degrees,
+ Boolean setStickyKeepInnerUntil45Degrees) {
+ mInput = new Input(hingeAngle, likelyTentOrWedge, likelyReverseWedge,
+ stickyKeepOuterUntil90Degrees, stickyKeepInnerUntil45Degrees);
+ mOutput = new State(setStickyKeepOuterUntil90Degrees,
+ setStickyKeepInnerUntil45Degrees, preferredScreen);
+ }
+
+ /**
+ * Returns true if the state transition is applicable for the given inputs
+ */
+ private boolean isApplicable(HingeAngle hingeAngle, boolean likelyTentOrWedge,
+ boolean likelyReverseWedge, State currentState) {
+ return mInput.hingeAngle == hingeAngle
+ && mInput.likelyTentOrWedge == likelyTentOrWedge
+ && mInput.likelyReverseWedge == likelyReverseWedge
+ && Objects.equals(mInput.stickyKeepOuterUntil90Degrees,
+ currentState.stickyKeepOuterUntil90Degrees)
+ && Objects.equals(mInput.stickyKeepInnerUntil45Degrees,
+ currentState.stickyKeepInnerUntil45Degrees);
+ }
+
+ /**
+ * Try to perform transition for the inputs, returns new state if this
+ * transition is applicable for the given state and inputs
+ */
+ @Nullable
+ State tryTransition(HingeAngle hingeAngle, boolean likelyTentOrWedge,
+ boolean likelyReverseWedge, State currentState) {
+ if (!isApplicable(hingeAngle, likelyTentOrWedge, likelyReverseWedge, currentState)) {
+ return null;
+ }
+
+ boolean stickyKeepOuterUntil90Degrees = currentState.stickyKeepOuterUntil90Degrees;
+ boolean stickyKeepInnerUntil45Degrees = currentState.stickyKeepInnerUntil45Degrees;
+
+ if (mOutput.stickyKeepOuterUntil90Degrees != null) {
+ stickyKeepOuterUntil90Degrees =
+ mOutput.stickyKeepOuterUntil90Degrees;
+ }
+
+ if (mOutput.stickyKeepInnerUntil45Degrees != null) {
+ stickyKeepInnerUntil45Degrees =
+ mOutput.stickyKeepInnerUntil45Degrees;
+ }
+
+ return new State(stickyKeepOuterUntil90Degrees, stickyKeepInnerUntil45Degrees,
+ mOutput.mPreferredScreen);
+ }
+ }
+
+ /**
+ * The input part of the {@link StateTransition}, these are the values that are used
+ * to decide which {@link State} output to choose.
+ */
+ private static class Input {
+ final HingeAngle hingeAngle;
+ final boolean likelyTentOrWedge;
+ final boolean likelyReverseWedge;
+ final boolean stickyKeepOuterUntil90Degrees;
+ final boolean stickyKeepInnerUntil45Degrees;
+
+ public Input(HingeAngle hingeAngle, boolean likelyTentOrWedge,
+ boolean likelyReverseWedge,
+ boolean stickyKeepOuterUntil90Degrees, boolean stickyKeepInnerUntil45Degrees) {
+ this.hingeAngle = hingeAngle;
+ this.likelyTentOrWedge = likelyTentOrWedge;
+ this.likelyReverseWedge = likelyReverseWedge;
+ this.stickyKeepOuterUntil90Degrees = stickyKeepOuterUntil90Degrees;
+ this.stickyKeepInnerUntil45Degrees = stickyKeepInnerUntil45Degrees;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Input)) return false;
+ Input that = (Input) o;
+ return likelyTentOrWedge == that.likelyTentOrWedge
+ && likelyReverseWedge == that.likelyReverseWedge
+ && stickyKeepOuterUntil90Degrees == that.stickyKeepOuterUntil90Degrees
+ && stickyKeepInnerUntil45Degrees == that.stickyKeepInnerUntil45Degrees
+ && hingeAngle == that.hingeAngle;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(hingeAngle, likelyTentOrWedge, likelyReverseWedge,
+ stickyKeepOuterUntil90Degrees, stickyKeepInnerUntil45Degrees);
+ }
+
+ @Override
+ public String toString() {
+ return "InputState{" +
+ "hingeAngle=" + hingeAngle +
+ ", likelyTentOrWedge=" + likelyTentOrWedge +
+ ", likelyReverseWedge=" + likelyReverseWedge +
+ ", stickyKeepOuterUntil90Degrees=" + stickyKeepOuterUntil90Degrees +
+ ", stickyKeepInnerUntil45Degrees=" + stickyKeepInnerUntil45Degrees +
+ '}';
+ }
+ }
+
+ /**
+ * Class that holds a state of the calculator, it could be used to store the current
+ * state or to define the target (output) state based on some input in {@link StateTransition}.
+ */
+ public static class State {
+ public Boolean stickyKeepOuterUntil90Degrees;
+ public Boolean stickyKeepInnerUntil45Degrees;
+
+ PreferredScreen mPreferredScreen;
+
+ public State(Boolean stickyKeepOuterUntil90Degrees,
+ Boolean stickyKeepInnerUntil45Degrees,
+ PreferredScreen preferredScreen) {
+ this.stickyKeepOuterUntil90Degrees = stickyKeepOuterUntil90Degrees;
+ this.stickyKeepInnerUntil45Degrees = stickyKeepInnerUntil45Degrees;
+ this.mPreferredScreen = preferredScreen;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof State)) return false;
+ State that = (State) o;
+ return Objects.equals(stickyKeepOuterUntil90Degrees,
+ that.stickyKeepOuterUntil90Degrees) && Objects.equals(
+ stickyKeepInnerUntil45Degrees, that.stickyKeepInnerUntil45Degrees)
+ && mPreferredScreen == that.mPreferredScreen;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(stickyKeepOuterUntil90Degrees, stickyKeepInnerUntil45Degrees,
+ mPreferredScreen);
+ }
+
+ @Override
+ public String toString() {
+ return "State{" +
+ "stickyKeepOuterUntil90Degrees=" + stickyKeepOuterUntil90Degrees +
+ ", stickyKeepInnerUntil90Degrees=" + stickyKeepInnerUntil45Degrees +
+ ", closedState=" + mPreferredScreen +
+ '}';
+ }
+ }
+}
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleStateTransitions.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleStateTransitions.java
new file mode 100644
index 0000000..16daacb
--- /dev/null
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleStateTransitions.java
@@ -0,0 +1,722 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.policy;
+
+import com.android.server.policy.BookStylePreferredScreenCalculator.PreferredScreen;
+import com.android.server.policy.BookStylePreferredScreenCalculator.HingeAngle;
+import com.android.server.policy.BookStylePreferredScreenCalculator.StateTransition;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Describes all possible state transitions for {@link BookStylePreferredScreenCalculator}.
+ * It contains a default configuration for a foldable device that has two screens: smaller outer
+ * screen which has portrait natural orientation and a larger inner screen and allows to use the
+ * device in tent mode or wedge mode.
+ *
+ * As the output state could affect calculating of the new state, it could potentially cause
+ * infinite loop and make the state never settle down. This could be avoided using automated test
+ * that checks all possible inputs and asserts that the final state is valid.
+ * See sample test for the default transitions in {@link BookStyleClosedStateCalculatorTest}.
+ *
+ * - Tent mode is defined as a posture when the device is partially opened and placed on the ground
+ * on the edges that are parallel to the hinge.
+ * - Wedge mode is when the device is partially opened and placed flat on the ground with the part
+ * of the device that doesn't have the display
+ * - Reverse wedge mode is when the device is partially opened and placed flat on the ground with
+ * the outer screen down, so the outer screen is not accessible
+ *
+ * Behavior description:
+ * - When unfolding with screens off we assume that no sensor data available except hinge angle
+ * (based on hall sensor), so we switch to the inner screen immediately
+ *
+ * - When unfolding when screen is 'on' we can check if we are likely in tent or wedge mode
+ * - If not likely tent/wedge mode or sensors data not available, then we unfold immediately
+ * After unfolding, the state of the inner screen 'on' is sticky between 0 and 45 degrees, so
+ * it won't jump back to the outer screen even if you move the phone into tent/wedge mode. The
+ * stickiness is reset after fully closing the device or unfolding past 45 degrees.
+ * - If likely tent or wedge mode, switch only at 90 degrees
+ * Tent/wedge mode is 'sticky' between 0 and 90 degrees, so it won't reset until you either
+ * fully close the device or unfold past 90 degrees.
+ *
+ * - When folding we can check if we are likely in reverse wedge mode
+ * - If not likely in reverse wedge mode or sensor data is not available we switch to the outer
+ * screen at 45 degrees and enable sticky tent/wedge mode as before, this allows to enter
+ * tent/wedge mode even if you are not on an even surface or holding phone in landscape
+ * - If likely in reverse wedge mode, switch to the outer screen only at 0 degrees to allow
+ * some use cases like using camera in this posture, the check happens after passing 45 degrees
+ * and inner screen becomes sticky turned 'on' until fully closing or unfolding past 45 degrees
+ */
+public class BookStyleStateTransitions {
+
+ public static final List<StateTransition> DEFAULT_STATE_TRANSITIONS = new ArrayList<>();
+
+ static {
+ // region Angle 0
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_0,
+ /* likelyTentOrWedge */ false,
+ /* likelyReverseWedge */ false,
+ /* stickyKeepOuterUntil90Degrees */ false,
+ /* stickyKeepInnerUntil45Degrees */ false,
+ PreferredScreen.OUTER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ true
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_0,
+ /* likelyTentOrWedge */ false,
+ /* likelyReverseWedge */ false,
+ /* stickyKeepOuterUntil90Degrees */ false,
+ /* stickyKeepInnerUntil45Degrees */ true,
+ PreferredScreen.OUTER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_0,
+ /* likelyTentOrWedge */ false,
+ /* likelyReverseWedge */ false,
+ /* stickyKeepOuterUntil90Degrees */ true,
+ /* stickyKeepInnerUntil45Degrees */ false,
+ PreferredScreen.OUTER,
+ /* setStickyKeepOuterUntil90Degrees */ false,
+ /* setStickyKeepInnerUntil45Degrees */ true
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_0,
+ /* likelyTentOrWedge */ false,
+ /* likelyReverseWedge */ false,
+ /* stickyKeepOuterUntil90Degrees */ true,
+ /* stickyKeepInnerUntil45Degrees */ true,
+ PreferredScreen.INVALID,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_0,
+ /* likelyTentOrWedge */ false,
+ /* likelyReverseWedge */ true,
+ /* stickyKeepOuterUntil90Degrees */ false,
+ /* stickyKeepInnerUntil45Degrees */ false,
+ PreferredScreen.OUTER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ true
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_0,
+ /* likelyTentOrWedge */ false,
+ /* likelyReverseWedge */ true,
+ /* stickyKeepOuterUntil90Degrees */ false,
+ /* stickyKeepInnerUntil45Degrees */ true,
+ PreferredScreen.OUTER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_0,
+ /* likelyTentOrWedge */ false,
+ /* likelyReverseWedge */ true,
+ /* stickyKeepOuterUntil90Degrees */ true,
+ /* stickyKeepInnerUntil45Degrees */ false,
+ PreferredScreen.OUTER,
+ /* setStickyKeepOuterUntil90Degrees */ false,
+ /* setStickyKeepInnerUntil45Degrees */ true
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_0,
+ /* likelyTentOrWedge */ false,
+ /* likelyReverseWedge */ true,
+ /* stickyKeepOuterUntil90Degrees */ true,
+ /* stickyKeepInnerUntil45Degrees */ true,
+ PreferredScreen.INVALID,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_0,
+ /* likelyTentOrWedge */ true,
+ /* likelyReverseWedge */ false,
+ /* stickyKeepOuterUntil90Degrees */ false,
+ /* stickyKeepInnerUntil45Degrees */ false,
+ PreferredScreen.OUTER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_0,
+ /* likelyTentOrWedge */ true,
+ /* likelyReverseWedge */ false,
+ /* stickyKeepOuterUntil90Degrees */ false,
+ /* stickyKeepInnerUntil45Degrees */ true,
+ PreferredScreen.OUTER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ false
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_0,
+ /* likelyTentOrWedge */ true,
+ /* likelyReverseWedge */ false,
+ /* stickyKeepOuterUntil90Degrees */ true,
+ /* stickyKeepInnerUntil45Degrees */ false,
+ PreferredScreen.OUTER,
+ /* setStickyKeepOuterUntil90Degrees */ false,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_0,
+ /* likelyTentOrWedge */ true,
+ /* likelyReverseWedge */ false,
+ /* stickyKeepOuterUntil90Degrees */ true,
+ /* stickyKeepInnerUntil45Degrees */ true,
+ PreferredScreen.INVALID,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_0,
+ /* likelyTentOrWedge */ true,
+ /* likelyReverseWedge */ true,
+ /* stickyKeepOuterUntil90Degrees */ false,
+ /* stickyKeepInnerUntil45Degrees */ false,
+ PreferredScreen.OUTER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_0,
+ /* likelyTentOrWedge */ true,
+ /* likelyReverseWedge */ true,
+ /* stickyKeepOuterUntil90Degrees */ false,
+ /* stickyKeepInnerUntil45Degrees */ true,
+ PreferredScreen.OUTER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ false
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_0,
+ /* likelyTentOrWedge */ true,
+ /* likelyReverseWedge */ true,
+ /* stickyKeepOuterUntil90Degrees */ true,
+ /* stickyKeepInnerUntil45Degrees */ false,
+ PreferredScreen.OUTER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_0,
+ /* likelyTentOrWedge */ true,
+ /* likelyReverseWedge */ true,
+ /* stickyKeepOuterUntil90Degrees */ true,
+ /* stickyKeepInnerUntil45Degrees */ true,
+ PreferredScreen.INVALID,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ // endregion
+
+ // region Angle 0-45
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_0_TO_45,
+ /* likelyTentOrWedge */ false,
+ /* likelyReverseWedge */ false,
+ /* stickyKeepOuterUntil90Degrees */ false,
+ /* stickyKeepInnerUntil45Degrees */ false,
+ PreferredScreen.OUTER,
+ /* setStickyKeepOuterUntil90Degrees */ true,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_0_TO_45,
+ /* likelyTentOrWedge */ false,
+ /* likelyReverseWedge */ false,
+ /* stickyKeepOuterUntil90Degrees */ false,
+ /* stickyKeepInnerUntil45Degrees */ true,
+ PreferredScreen.INNER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_0_TO_45,
+ /* likelyTentOrWedge */ false,
+ /* likelyReverseWedge */ false,
+ /* stickyKeepOuterUntil90Degrees */ true,
+ /* stickyKeepInnerUntil45Degrees */ false,
+ PreferredScreen.OUTER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_0_TO_45,
+ /* likelyTentOrWedge */ false,
+ /* likelyReverseWedge */ false,
+ /* stickyKeepOuterUntil90Degrees */ true,
+ /* stickyKeepInnerUntil45Degrees */ true,
+ PreferredScreen.INVALID,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_0_TO_45,
+ /* likelyTentOrWedge */ false,
+ /* likelyReverseWedge */ true,
+ /* stickyKeepOuterUntil90Degrees */ false,
+ /* stickyKeepInnerUntil45Degrees */ false,
+ PreferredScreen.INNER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ true
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_0_TO_45,
+ /* likelyTentOrWedge */ false,
+ /* likelyReverseWedge */ true,
+ /* stickyKeepOuterUntil90Degrees */ false,
+ /* stickyKeepInnerUntil45Degrees */ true,
+ PreferredScreen.INNER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_0_TO_45,
+ /* likelyTentOrWedge */ false,
+ /* likelyReverseWedge */ true,
+ /* stickyKeepOuterUntil90Degrees */ true,
+ /* stickyKeepInnerUntil45Degrees */ false,
+ PreferredScreen.OUTER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_0_TO_45,
+ /* likelyTentOrWedge */ false,
+ /* likelyReverseWedge */ true,
+ /* stickyKeepOuterUntil90Degrees */ true,
+ /* stickyKeepInnerUntil45Degrees */ true,
+ PreferredScreen.INVALID,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_0_TO_45,
+ /* likelyTentOrWedge */ true,
+ /* likelyReverseWedge */ false,
+ /* stickyKeepOuterUntil90Degrees */ false,
+ /* stickyKeepInnerUntil45Degrees */ false,
+ PreferredScreen.OUTER,
+ /* setStickyKeepOuterUntil90Degrees */ true,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_0_TO_45,
+ /* likelyTentOrWedge */ true,
+ /* likelyReverseWedge */ false,
+ /* stickyKeepOuterUntil90Degrees */ false,
+ /* stickyKeepInnerUntil45Degrees */ true,
+ PreferredScreen.INNER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_0_TO_45,
+ /* likelyTentOrWedge */ true,
+ /* likelyReverseWedge */ false,
+ /* stickyKeepOuterUntil90Degrees */ true,
+ /* stickyKeepInnerUntil45Degrees */ false,
+ PreferredScreen.OUTER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_0_TO_45,
+ /* likelyTentOrWedge */ true,
+ /* likelyReverseWedge */ false,
+ /* stickyKeepOuterUntil90Degrees */ true,
+ /* stickyKeepInnerUntil45Degrees */ true,
+ PreferredScreen.INVALID,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_0_TO_45,
+ /* likelyTentOrWedge */ true,
+ /* likelyReverseWedge */ true,
+ /* stickyKeepOuterUntil90Degrees */ false,
+ /* stickyKeepInnerUntil45Degrees */ false,
+ PreferredScreen.INNER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ true
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_0_TO_45,
+ /* likelyTentOrWedge */ true,
+ /* likelyReverseWedge */ true,
+ /* stickyKeepOuterUntil90Degrees */ false,
+ /* stickyKeepInnerUntil45Degrees */ true,
+ PreferredScreen.INNER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_0_TO_45,
+ /* likelyTentOrWedge */ true,
+ /* likelyReverseWedge */ true,
+ /* stickyKeepOuterUntil90Degrees */ true,
+ /* stickyKeepInnerUntil45Degrees */ false,
+ PreferredScreen.OUTER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_0_TO_45,
+ /* likelyTentOrWedge */ true,
+ /* likelyReverseWedge */ true,
+ /* stickyKeepOuterUntil90Degrees */ true,
+ /* stickyKeepInnerUntil45Degrees */ true,
+ PreferredScreen.INVALID,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ // endregion
+
+ // region Angle 45-90
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_45_TO_90,
+ /* likelyTentOrWedge */ false,
+ /* likelyReverseWedge */ false,
+ /* stickyKeepOuterUntil90Degrees */ false,
+ /* stickyKeepInnerUntil45Degrees */ false,
+ PreferredScreen.INNER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_45_TO_90,
+ /* likelyTentOrWedge */ false,
+ /* likelyReverseWedge */ false,
+ /* stickyKeepOuterUntil90Degrees */ false,
+ /* stickyKeepInnerUntil45Degrees */ true,
+ PreferredScreen.INNER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ false
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_45_TO_90,
+ /* likelyTentOrWedge */ false,
+ /* likelyReverseWedge */ false,
+ /* stickyKeepOuterUntil90Degrees */ true,
+ /* stickyKeepInnerUntil45Degrees */ false,
+ PreferredScreen.OUTER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_45_TO_90,
+ /* likelyTentOrWedge */ false,
+ /* likelyReverseWedge */ false,
+ /* stickyKeepOuterUntil90Degrees */ true,
+ /* stickyKeepInnerUntil45Degrees */ true,
+ PreferredScreen.INVALID,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_45_TO_90,
+ /* likelyTentOrWedge */ false,
+ /* likelyReverseWedge */ true,
+ /* stickyKeepOuterUntil90Degrees */ false,
+ /* stickyKeepInnerUntil45Degrees */ false,
+ PreferredScreen.INNER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ true
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_45_TO_90,
+ /* likelyTentOrWedge */ false,
+ /* likelyReverseWedge */ true,
+ /* stickyKeepOuterUntil90Degrees */ false,
+ /* stickyKeepInnerUntil45Degrees */ true,
+ PreferredScreen.INNER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_45_TO_90,
+ /* likelyTentOrWedge */ false,
+ /* likelyReverseWedge */ true,
+ /* stickyKeepOuterUntil90Degrees */ true,
+ /* stickyKeepInnerUntil45Degrees */ false,
+ PreferredScreen.OUTER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_45_TO_90,
+ /* likelyTentOrWedge */ false,
+ /* likelyReverseWedge */ true,
+ /* stickyKeepOuterUntil90Degrees */ true,
+ /* stickyKeepInnerUntil45Degrees */ true,
+ PreferredScreen.INVALID,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_45_TO_90,
+ /* likelyTentOrWedge */ true,
+ /* likelyReverseWedge */ false,
+ /* stickyKeepOuterUntil90Degrees */ false,
+ /* stickyKeepInnerUntil45Degrees */ false,
+ PreferredScreen.INNER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_45_TO_90,
+ /* likelyTentOrWedge */ true,
+ /* likelyReverseWedge */ false,
+ /* stickyKeepOuterUntil90Degrees */ false,
+ /* stickyKeepInnerUntil45Degrees */ true,
+ PreferredScreen.INNER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ false
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_45_TO_90,
+ /* likelyTentOrWedge */ true,
+ /* likelyReverseWedge */ false,
+ /* stickyKeepOuterUntil90Degrees */ true,
+ /* stickyKeepInnerUntil45Degrees */ false,
+ PreferredScreen.OUTER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_45_TO_90,
+ /* likelyTentOrWedge */ true,
+ /* likelyReverseWedge */ false,
+ /* stickyKeepOuterUntil90Degrees */ true,
+ /* stickyKeepInnerUntil45Degrees */ true,
+ PreferredScreen.INVALID,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_45_TO_90,
+ /* likelyTentOrWedge */ true,
+ /* likelyReverseWedge */ true,
+ /* stickyKeepOuterUntil90Degrees */ false,
+ /* stickyKeepInnerUntil45Degrees */ false,
+ PreferredScreen.INNER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ true
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_45_TO_90,
+ /* likelyTentOrWedge */ true,
+ /* likelyReverseWedge */ true,
+ /* stickyKeepOuterUntil90Degrees */ false,
+ /* stickyKeepInnerUntil45Degrees */ true,
+ PreferredScreen.INNER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_45_TO_90,
+ /* likelyTentOrWedge */ true,
+ /* likelyReverseWedge */ true,
+ /* stickyKeepOuterUntil90Degrees */ true,
+ /* stickyKeepInnerUntil45Degrees */ false,
+ PreferredScreen.OUTER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_45_TO_90,
+ /* likelyTentOrWedge */ true,
+ /* likelyReverseWedge */ true,
+ /* stickyKeepOuterUntil90Degrees */ true,
+ /* stickyKeepInnerUntil45Degrees */ true,
+ PreferredScreen.INVALID,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ // endregion
+
+ // region Angle 90-180
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_90_TO_180,
+ /* likelyTentOrWedge */ false,
+ /* likelyReverseWedge */ false,
+ /* stickyKeepOuterUntil90Degrees */ false,
+ /* stickyKeepInnerUntil45Degrees */ false,
+ PreferredScreen.INNER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_90_TO_180,
+ /* likelyTentOrWedge */ false,
+ /* likelyReverseWedge */ false,
+ /* stickyKeepOuterUntil90Degrees */ false,
+ /* stickyKeepInnerUntil45Degrees */ true,
+ PreferredScreen.INNER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ false
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_90_TO_180,
+ /* likelyTentOrWedge */ false,
+ /* likelyReverseWedge */ false,
+ /* stickyKeepOuterUntil90Degrees */ true,
+ /* stickyKeepInnerUntil45Degrees */ false,
+ PreferredScreen.INNER,
+ /* setStickyKeepOuterUntil90Degrees */ false,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_90_TO_180,
+ /* likelyTentOrWedge */ false,
+ /* likelyReverseWedge */ false,
+ /* stickyKeepOuterUntil90Degrees */ true,
+ /* stickyKeepInnerUntil45Degrees */ true,
+ PreferredScreen.INVALID,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_90_TO_180,
+ /* likelyTentOrWedge */ false,
+ /* likelyReverseWedge */ true,
+ /* stickyKeepOuterUntil90Degrees */ false,
+ /* stickyKeepInnerUntil45Degrees */ false,
+ PreferredScreen.INNER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_90_TO_180,
+ /* likelyTentOrWedge */ false,
+ /* likelyReverseWedge */ true,
+ /* stickyKeepOuterUntil90Degrees */ false,
+ /* stickyKeepInnerUntil45Degrees */ true,
+ PreferredScreen.INNER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ false
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_90_TO_180,
+ /* likelyTentOrWedge */ false,
+ /* likelyReverseWedge */ true,
+ /* stickyKeepOuterUntil90Degrees */ true,
+ /* stickyKeepInnerUntil45Degrees */ false,
+ PreferredScreen.INNER,
+ /* setStickyKeepOuterUntil90Degrees */ false,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_90_TO_180,
+ /* likelyTentOrWedge */ false,
+ /* likelyReverseWedge */ true,
+ /* stickyKeepOuterUntil90Degrees */ true,
+ /* stickyKeepInnerUntil45Degrees */ true,
+ PreferredScreen.INVALID,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_90_TO_180,
+ /* likelyTentOrWedge */ true,
+ /* likelyReverseWedge */ false,
+ /* stickyKeepOuterUntil90Degrees */ false,
+ /* stickyKeepInnerUntil45Degrees */ false,
+ PreferredScreen.INNER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_90_TO_180,
+ /* likelyTentOrWedge */ true,
+ /* likelyReverseWedge */ false,
+ /* stickyKeepOuterUntil90Degrees */ false,
+ /* stickyKeepInnerUntil45Degrees */ true,
+ PreferredScreen.INNER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ false
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_90_TO_180,
+ /* likelyTentOrWedge */ true,
+ /* likelyReverseWedge */ false,
+ /* stickyKeepOuterUntil90Degrees */ true,
+ /* stickyKeepInnerUntil45Degrees */ false,
+ PreferredScreen.INNER,
+ /* setStickyKeepOuterUntil90Degrees */ false,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_90_TO_180,
+ /* likelyTentOrWedge */ true,
+ /* likelyReverseWedge */ false,
+ /* stickyKeepOuterUntil90Degrees */ true,
+ /* stickyKeepInnerUntil45Degrees */ true,
+ PreferredScreen.INVALID,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_90_TO_180,
+ /* likelyTentOrWedge */ true,
+ /* likelyReverseWedge */ true,
+ /* stickyKeepOuterUntil90Degrees */ false,
+ /* stickyKeepInnerUntil45Degrees */ false,
+ PreferredScreen.INNER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_90_TO_180,
+ /* likelyTentOrWedge */ true,
+ /* likelyReverseWedge */ true,
+ /* stickyKeepOuterUntil90Degrees */ false,
+ /* stickyKeepInnerUntil45Degrees */ true,
+ PreferredScreen.INNER,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ false
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_90_TO_180,
+ /* likelyTentOrWedge */ true,
+ /* likelyReverseWedge */ true,
+ /* stickyKeepOuterUntil90Degrees */ true,
+ /* stickyKeepInnerUntil45Degrees */ false,
+ PreferredScreen.INNER,
+ /* setStickyKeepOuterUntil90Degrees */ false,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+ HingeAngle.ANGLE_90_TO_180,
+ /* likelyTentOrWedge */ true,
+ /* likelyReverseWedge */ true,
+ /* stickyKeepOuterUntil90Degrees */ true,
+ /* stickyKeepInnerUntil45Degrees */ true,
+ PreferredScreen.INVALID,
+ /* setStickyKeepOuterUntil90Degrees */ null,
+ /* setStickyKeepInnerUntil45Degrees */ null
+ ));
+ // endregion
+ }
+}
diff --git a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java
new file mode 100644
index 0000000..8d01b7a
--- /dev/null
+++ b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java
@@ -0,0 +1,703 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.policy;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.STATE_OFF;
+import static android.view.Display.STATE_ON;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Instrumentation;
+import android.content.res.Configuration;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManager;
+import android.hardware.input.InputSensorInfo;
+import android.os.Handler;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.view.Display;
+import android.view.Surface;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.devicestate.DeviceStateProvider;
+import com.android.server.devicestate.DeviceStateProvider.Listener;
+import com.android.server.policy.feature.flags.FakeFeatureFlagsImpl;
+import com.android.server.policy.feature.flags.Flags;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.internal.util.reflection.FieldSetter;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Unit tests for {@link BookStyleDeviceStatePolicy.Provider}.
+ * <p/>
+ * Run with <code>atest BookStyleDeviceStatePolicyTest</code>.
+ */
+@RunWith(AndroidTestingRunner.class)
+public final class BookStyleDeviceStatePolicyTest {
+
+ 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;
+
+ @Captor
+ private ArgumentCaptor<Integer> mDeviceStateCaptor;
+ @Captor
+ private ArgumentCaptor<DisplayManager.DisplayListener> mDisplayListenerCaptor;
+ @Mock
+ private SensorManager mSensorManager;
+ @Mock
+ private InputSensorInfo mInputSensorInfo;
+ @Mock
+ private Listener mListener;
+ @Mock
+ DisplayManager mDisplayManager;
+ @Mock
+ private Display mDisplay;
+
+ private final FakeFeatureFlagsImpl mFakeFeatureFlags = new FakeFeatureFlagsImpl();
+
+ private final Configuration mConfiguration = new Configuration();
+
+ private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
+
+ @Rule
+ public final TestableContext mContext = new TestableContext(
+ mInstrumentation.getTargetContext());
+
+ private Sensor mHallSensor;
+ private Sensor mOrientationSensor;
+ private Sensor mHingeAngleSensor;
+ private Sensor mLeftAccelerometer;
+ private Sensor mRightAccelerometer;
+
+ private Map<Sensor, List<SensorEventListener>> mSensorEventListeners = new HashMap<>();
+ private DeviceStateProvider mProvider;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ mFakeFeatureFlags.setFlag(Flags.FLAG_ENABLE_FOLDABLES_POSTURE_BASED_CLOSED_STATE, true);
+ mFakeFeatureFlags.setFlag(Flags.FLAG_ENABLE_DUAL_DISPLAY_BLOCKING, true);
+
+ when(mInputSensorInfo.getName()).thenReturn("hall-effect");
+ mHallSensor = new Sensor(mInputSensorInfo);
+ when(mInputSensorInfo.getName()).thenReturn("hinge-angle");
+ mHingeAngleSensor = new Sensor(mInputSensorInfo);
+ when(mInputSensorInfo.getName()).thenReturn("left-accelerometer");
+ mLeftAccelerometer = new Sensor(mInputSensorInfo);
+ when(mInputSensorInfo.getName()).thenReturn("right-accelerometer");
+ mRightAccelerometer = new Sensor(mInputSensorInfo);
+ when(mInputSensorInfo.getName()).thenReturn("orientation");
+ mOrientationSensor = new Sensor(mInputSensorInfo);
+
+ mContext.addMockSystemService(SensorManager.class, mSensorManager);
+
+ when(mSensorManager.getDefaultSensor(eq(Sensor.TYPE_HINGE_ANGLE), eq(true)))
+ .thenReturn(mHingeAngleSensor);
+ when(mSensorManager.getDefaultSensor(eq(Sensor.TYPE_DEVICE_ORIENTATION)))
+ .thenReturn(mOrientationSensor);
+
+ when(mDisplayManager.getDisplay(eq(DEFAULT_DISPLAY))).thenReturn(mDisplay);
+ mContext.addMockSystemService(DisplayManager.class, mDisplayManager);
+
+ mContext.ensureTestableResources();
+ when(mContext.getResources().getConfiguration()).thenReturn(mConfiguration);
+
+ final List<Sensor> sensors = new ArrayList<>();
+ sensors.add(mHallSensor);
+ sensors.add(mHingeAngleSensor);
+ sensors.add(mOrientationSensor);
+ sensors.add(mLeftAccelerometer);
+ sensors.add(mRightAccelerometer);
+
+ when(mSensorManager.registerListener(any(), any(), anyInt(), any())).thenAnswer(
+ invocation -> {
+ final SensorEventListener listener = invocation.getArgument(0);
+ final Sensor sensor = invocation.getArgument(1);
+ addSensorListener(sensor, listener);
+ return true;
+ });
+ when(mSensorManager.registerListener(any(), any(), anyInt())).thenAnswer(
+ invocation -> {
+ final SensorEventListener listener = invocation.getArgument(0);
+ final Sensor sensor = invocation.getArgument(1);
+ addSensorListener(sensor, listener);
+ return true;
+ });
+
+ doAnswer(invocation -> {
+ final SensorEventListener listener = invocation.getArgument(0);
+ final boolean[] removed = {false};
+ mSensorEventListeners.forEach((sensor, sensorEventListeners) ->
+ removed[0] |= sensorEventListeners.remove(listener));
+
+ if (!removed[0]) {
+ throw new IllegalArgumentException(
+ "Trying to unregister listener " + listener + " that was not registered");
+ }
+
+ return null;
+ }).when(mSensorManager).unregisterListener(any(SensorEventListener.class));
+
+ doAnswer(invocation -> {
+ final SensorEventListener listener = invocation.getArgument(0);
+ final Sensor sensor = invocation.getArgument(1);
+
+ boolean removed = mSensorEventListeners.get(sensor).remove(listener);
+ if (!removed) {
+ throw new IllegalArgumentException(
+ "Trying to unregister listener " + listener
+ + " that was not registered for sensor " + sensor);
+ }
+
+ return null;
+ }).when(mSensorManager).unregisterListener(any(SensorEventListener.class),
+ any(Sensor.class));
+
+ try {
+ FieldSetter.setField(mHallSensor, mHallSensor.getClass()
+ .getDeclaredField("mStringType"), "com.google.sensor.hall_effect");
+ } catch (NoSuchFieldException e) {
+ throw new RuntimeException(e);
+ }
+
+ when(mSensorManager.getSensorList(eq(Sensor.TYPE_ALL)))
+ .thenReturn(sensors);
+
+ mInstrumentation.runOnMainSync(() -> mProvider = createProvider());
+
+ verify(mDisplayManager, atLeastOnce()).registerDisplayListener(
+ mDisplayListenerCaptor.capture(), nullable(Handler.class));
+ setScreenOn(true);
+ }
+
+ @Test
+ public void test_noSensorEventsYet_reportOpenedState() {
+ mProvider.setListener(mListener);
+ verify(mListener).onStateChanged(mDeviceStateCaptor.capture());
+ assertEquals(DEVICE_STATE_OPENED, mDeviceStateCaptor.getValue().intValue());
+ }
+
+ @Test
+ public void test_deviceClosedSensorEventsBecameAvailable_reportsClosedState() {
+ mProvider.setListener(mListener);
+ clearInvocations(mListener);
+
+ sendHingeAngle(0f);
+
+ verify(mListener).onStateChanged(mDeviceStateCaptor.capture());
+ assertEquals(DEVICE_STATE_CLOSED, mDeviceStateCaptor.getValue().intValue());
+ }
+
+ @Test
+ public void test_hingeAngleClosed_reportsClosedState() {
+ sendHingeAngle(0f);
+
+ mProvider.setListener(mListener);
+ verify(mListener).onStateChanged(mDeviceStateCaptor.capture());
+ assertEquals(DEVICE_STATE_CLOSED, mDeviceStateCaptor.getValue().intValue());
+ }
+
+ @Test
+ public void test_hingeAngleFullyOpened_reportsOpenedState() {
+ sendHingeAngle(180f);
+
+ mProvider.setListener(mListener);
+ verify(mListener).onStateChanged(mDeviceStateCaptor.capture());
+ assertEquals(DEVICE_STATE_OPENED, mDeviceStateCaptor.getValue().intValue());
+ }
+
+ @Test
+ public void test_unfoldingFromClosedToFullyOpened_reportsOpenedEvent() {
+ sendHingeAngle(0f);
+ mProvider.setListener(mListener);
+ clearInvocations(mListener);
+
+ sendHingeAngle(180f);
+
+ verify(mListener).onStateChanged(mDeviceStateCaptor.capture());
+ assertEquals(DEVICE_STATE_OPENED, mDeviceStateCaptor.getValue().intValue());
+ }
+
+ @Test
+ public void test_foldingFromFullyOpenToFullyClosed_movesToClosedState() {
+ sendHingeAngle(180f);
+
+ sendHingeAngle(0f);
+
+ mProvider.setListener(mListener);
+ verify(mListener).onStateChanged(mDeviceStateCaptor.capture());
+ assertEquals(DEVICE_STATE_CLOSED, mDeviceStateCaptor.getValue().intValue());
+ }
+
+ @Test
+ public void test_slowUnfolding_reportsEventsInOrder() {
+ sendHingeAngle(0f);
+ mProvider.setListener(mListener);
+
+ sendHingeAngle(5f);
+ sendHingeAngle(10f);
+ sendHingeAngle(60f);
+ sendHingeAngle(100f);
+ sendHingeAngle(180f);
+
+ verify(mListener, atLeastOnce()).onStateChanged(mDeviceStateCaptor.capture());
+ assertThat(mDeviceStateCaptor.getAllValues()).containsExactly(
+ DEVICE_STATE_CLOSED,
+ DEVICE_STATE_HALF_OPENED,
+ DEVICE_STATE_OPENED
+ );
+ }
+
+ @Test
+ public void test_slowFolding_reportsEventsInOrder() {
+ sendHingeAngle(180f);
+ mProvider.setListener(mListener);
+
+ sendHingeAngle(180f);
+ sendHingeAngle(100f);
+ sendHingeAngle(60f);
+ sendHingeAngle(10f);
+ sendHingeAngle(5f);
+
+ verify(mListener, atLeastOnce()).onStateChanged(mDeviceStateCaptor.capture());
+ assertThat(mDeviceStateCaptor.getAllValues()).containsExactly(
+ DEVICE_STATE_OPENED,
+ DEVICE_STATE_HALF_OPENED,
+ DEVICE_STATE_CLOSED
+ );
+ }
+
+ @Test
+ public void test_hingeAngleOpen_screenOff_reportsHalfFolded() {
+ sendHingeAngle(0f);
+ setScreenOn(false);
+ mProvider.setListener(mListener);
+
+ sendHingeAngle(10f);
+
+ verify(mListener, atLeastOnce()).onStateChanged(mDeviceStateCaptor.capture());
+ assertThat(mDeviceStateCaptor.getAllValues()).containsExactly(
+ DEVICE_STATE_CLOSED,
+ DEVICE_STATE_HALF_OPENED
+ );
+ }
+
+ @Test
+ public void test_slowUnfoldingWithScreenOff_reportsEventsInOrder() {
+ sendHingeAngle(0f);
+ setScreenOn(false);
+ mProvider.setListener(mListener);
+
+ sendHingeAngle(5f);
+ assertLatestReportedState(DEVICE_STATE_HALF_OPENED);
+ sendHingeAngle(10f);
+ assertLatestReportedState(DEVICE_STATE_HALF_OPENED);
+ sendHingeAngle(60f);
+ assertLatestReportedState(DEVICE_STATE_HALF_OPENED);
+ sendHingeAngle(100f);
+ sendHingeAngle(180f);
+ assertLatestReportedState(DEVICE_STATE_OPENED);
+
+ verify(mListener, atLeastOnce()).onStateChanged(mDeviceStateCaptor.capture());
+ assertThat(mDeviceStateCaptor.getAllValues()).containsExactly(
+ DEVICE_STATE_CLOSED,
+ DEVICE_STATE_HALF_OPENED,
+ DEVICE_STATE_OPENED
+ );
+ }
+
+ @Test
+ public void test_unfoldWithScreenOff_reportsHalfOpened() {
+ sendHingeAngle(0f);
+ setScreenOn(false);
+ mProvider.setListener(mListener);
+
+ sendHingeAngle(5f);
+ sendHingeAngle(10f);
+
+ verify(mListener, atLeastOnce()).onStateChanged(mDeviceStateCaptor.capture());
+ assertThat(mDeviceStateCaptor.getAllValues()).containsExactly(
+ DEVICE_STATE_CLOSED,
+ DEVICE_STATE_HALF_OPENED
+ );
+ }
+
+ @Test
+ public void test_slowUnfoldingAndFolding_reportsEventsInOrder() {
+ sendHingeAngle(0f);
+ mProvider.setListener(mListener);
+ assertLatestReportedState(DEVICE_STATE_CLOSED);
+
+ // Started unfolding
+ sendHingeAngle(5f);
+ sendHingeAngle(30f);
+ assertLatestReportedState(DEVICE_STATE_HALF_OPENED);
+ sendHingeAngle(60f);
+ assertLatestReportedState(DEVICE_STATE_HALF_OPENED);
+ sendHingeAngle(100f);
+ assertLatestReportedState(DEVICE_STATE_HALF_OPENED);
+ sendHingeAngle(180f);
+ assertLatestReportedState(DEVICE_STATE_OPENED);
+
+ // Started folding
+ sendHingeAngle(100f);
+ assertLatestReportedState(DEVICE_STATE_HALF_OPENED);
+ sendHingeAngle(60f);
+ assertLatestReportedState(DEVICE_STATE_HALF_OPENED);
+ sendHingeAngle(30f);
+ assertLatestReportedState(DEVICE_STATE_CLOSED);
+ sendHingeAngle(5f);
+ assertLatestReportedState(DEVICE_STATE_CLOSED);
+
+ verify(mListener, atLeastOnce()).onStateChanged(mDeviceStateCaptor.capture());
+ assertThat(mDeviceStateCaptor.getAllValues()).containsExactly(
+ DEVICE_STATE_CLOSED,
+ DEVICE_STATE_HALF_OPENED,
+ DEVICE_STATE_OPENED,
+ DEVICE_STATE_HALF_OPENED,
+ DEVICE_STATE_CLOSED
+ );
+ }
+
+ @Test
+ public void test_unfoldTo30Degrees_screenOnRightSideMostlyFlat_keepsClosedState() {
+ sendHingeAngle(0f);
+ sendRightSideFlatSensorEvent(true);
+ mProvider.setListener(mListener);
+ assertLatestReportedState(DEVICE_STATE_CLOSED);
+ clearInvocations(mListener);
+
+ sendHingeAngle(30f);
+
+ verify(mListener, never()).onStateChanged(mDeviceStateCaptor.capture());
+ }
+
+ @Test
+ public void test_unfoldTo30Degrees_seascapeDeviceOrientation_keepsClosedState() {
+ sendHingeAngle(0f);
+ sendRightSideFlatSensorEvent(false);
+ sendDeviceOrientation(Surface.ROTATION_270);
+ mProvider.setListener(mListener);
+ assertLatestReportedState(DEVICE_STATE_CLOSED);
+ clearInvocations(mListener);
+
+ sendHingeAngle(30f);
+
+ verify(mListener, never()).onStateChanged(mDeviceStateCaptor.capture());
+ }
+
+ @Test
+ public void test_unfoldTo30Degrees_landscapeScreenRotation_keepsClosedState() {
+ sendHingeAngle(0f);
+ sendRightSideFlatSensorEvent(false);
+ sendScreenRotation(Surface.ROTATION_90);
+ mProvider.setListener(mListener);
+ assertLatestReportedState(DEVICE_STATE_CLOSED);
+ clearInvocations(mListener);
+
+ sendHingeAngle(30f);
+
+ verify(mListener, never()).onStateChanged(mDeviceStateCaptor.capture());
+ }
+
+ @Test
+ public void test_unfoldTo30Degrees_seascapeScreenRotation_keepsClosedState() {
+ sendHingeAngle(0f);
+ sendRightSideFlatSensorEvent(false);
+ sendScreenRotation(Surface.ROTATION_270);
+ mProvider.setListener(mListener);
+ assertLatestReportedState(DEVICE_STATE_CLOSED);
+ clearInvocations(mListener);
+
+ sendHingeAngle(30f);
+
+ verify(mListener, never()).onStateChanged(mDeviceStateCaptor.capture());
+ }
+
+ @Test
+ public void test_unfoldTo30Degrees_screenOnRightSideNotFlat_switchesToHalfOpenState() {
+ sendHingeAngle(0f);
+ sendRightSideFlatSensorEvent(false);
+ mProvider.setListener(mListener);
+ assertLatestReportedState(DEVICE_STATE_CLOSED);
+ clearInvocations(mListener);
+
+ sendHingeAngle(30f);
+
+ verify(mListener).onStateChanged(DEVICE_STATE_HALF_OPENED);
+ }
+
+ @Test
+ public void test_unfoldTo30Degrees_screenOffRightSideFlat_switchesToHalfOpenState() {
+ sendHingeAngle(0f);
+ setScreenOn(false);
+ // This sensor event should be ignored as screen is off
+ sendRightSideFlatSensorEvent(true);
+ mProvider.setListener(mListener);
+ assertLatestReportedState(DEVICE_STATE_CLOSED);
+ clearInvocations(mListener);
+
+ sendHingeAngle(30f);
+
+ verify(mListener).onStateChanged(DEVICE_STATE_HALF_OPENED);
+ }
+
+ @Test
+ public void test_unfoldTo60Degrees_andFoldTo10_switchesToClosedState() {
+ sendHingeAngle(0f);
+ sendRightSideFlatSensorEvent(false);
+ mProvider.setListener(mListener);
+ assertLatestReportedState(DEVICE_STATE_CLOSED);
+ sendHingeAngle(60f);
+ assertLatestReportedState(DEVICE_STATE_HALF_OPENED);
+ clearInvocations(mListener);
+
+ sendHingeAngle(10f);
+
+ verify(mListener).onStateChanged(DEVICE_STATE_CLOSED);
+ }
+
+ @Test
+ public void test_foldTo10AndUnfoldTo85Degrees_keepsClosedState() {
+ sendHingeAngle(0f);
+ sendRightSideFlatSensorEvent(false);
+ mProvider.setListener(mListener);
+ assertLatestReportedState(DEVICE_STATE_CLOSED);
+ sendHingeAngle(180f);
+ assertLatestReportedState(DEVICE_STATE_OPENED);
+ sendHingeAngle(10f);
+ assertLatestReportedState(DEVICE_STATE_CLOSED);
+
+ sendHingeAngle(85f);
+
+ // Keeps 'tent'/'wedge' mode even when right side is not flat
+ // as user manually folded the device not all the way
+ assertLatestReportedState(DEVICE_STATE_CLOSED);
+ }
+
+ @Test
+ public void test_foldTo0AndUnfoldTo85Degrees_doesNotKeepClosedState() {
+ sendHingeAngle(0f);
+ sendRightSideFlatSensorEvent(false);
+ mProvider.setListener(mListener);
+ assertLatestReportedState(DEVICE_STATE_CLOSED);
+ sendHingeAngle(180f);
+ assertLatestReportedState(DEVICE_STATE_OPENED);
+ sendHingeAngle(0f);
+ assertLatestReportedState(DEVICE_STATE_CLOSED);
+
+ sendHingeAngle(85f);
+
+ // Do not enter 'tent'/'wedge' mode when right side is not flat
+ // as user fully folded the device before that
+ assertLatestReportedState(DEVICE_STATE_HALF_OPENED);
+ }
+
+ @Test
+ public void test_foldTo10_leftSideIsFlat_keepsInnerScreenForReverseWedge() {
+ sendHingeAngle(180f);
+ sendLeftSideFlatSensorEvent(true);
+ mProvider.setListener(mListener);
+ assertLatestReportedState(DEVICE_STATE_OPENED);
+
+ sendHingeAngle(10f);
+
+ // Keep the inner screen for reverse wedge mode (e.g. for astrophotography use case)
+ assertLatestReportedState(DEVICE_STATE_HALF_OPENED);
+ }
+
+ @Test
+ public void test_foldTo10_leftSideIsNotFlat_switchesToOuterScreen() {
+ sendHingeAngle(180f);
+ sendLeftSideFlatSensorEvent(false);
+ mProvider.setListener(mListener);
+ assertLatestReportedState(DEVICE_STATE_OPENED);
+
+ sendHingeAngle(10f);
+
+ // Do not keep the inner screen as it is not reverse wedge mode
+ assertLatestReportedState(DEVICE_STATE_CLOSED);
+ }
+
+ @Test
+ public void test_foldTo10_noAccelerometerEvents_switchesToOuterScreen() {
+ sendHingeAngle(180f);
+ mProvider.setListener(mListener);
+ assertLatestReportedState(DEVICE_STATE_OPENED);
+
+ sendHingeAngle(10f);
+
+ // Do not keep the inner screen as it is not reverse wedge mode
+ assertLatestReportedState(DEVICE_STATE_CLOSED);
+ }
+
+ @Test
+ public void test_deviceClosed_screenIsOff_noSensorListeners() {
+ mProvider.setListener(mListener);
+
+ sendHingeAngle(0f);
+ setScreenOn(false);
+
+ assertNoListenersForSensor(mLeftAccelerometer);
+ assertNoListenersForSensor(mRightAccelerometer);
+ assertNoListenersForSensor(mOrientationSensor);
+ }
+
+ @Test
+ public void test_deviceClosed_screenIsOn_doesNotListenForOneAccelerometer() {
+ mProvider.setListener(mListener);
+
+ sendHingeAngle(0f);
+ setScreenOn(true);
+
+ assertNoListenersForSensor(mLeftAccelerometer);
+ assertListensForSensor(mRightAccelerometer);
+ assertListensForSensor(mOrientationSensor);
+ }
+
+ @Test
+ public void test_deviceOpened_screenIsOn_listensToSensors() {
+ mProvider.setListener(mListener);
+
+ sendHingeAngle(180f);
+ setScreenOn(true);
+
+ assertListensForSensor(mLeftAccelerometer);
+ assertListensForSensor(mRightAccelerometer);
+ assertListensForSensor(mOrientationSensor);
+ }
+
+ private void assertLatestReportedState(int state) {
+ final ArgumentCaptor<Integer> integerCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mListener, atLeastOnce()).onStateChanged(integerCaptor.capture());
+ assertEquals(state, integerCaptor.getValue().intValue());
+ }
+
+ private void sendHingeAngle(float angle) {
+ sendSensorEvent(mHingeAngleSensor, new float[]{angle});
+ }
+
+ private void sendDeviceOrientation(int orientation) {
+ sendSensorEvent(mOrientationSensor, new float[]{orientation});
+ }
+
+ private void sendScreenRotation(int rotation) {
+ when(mDisplay.getRotation()).thenReturn(rotation);
+ mDisplayListenerCaptor.getAllValues().forEach((l) -> l.onDisplayChanged(DEFAULT_DISPLAY));
+ }
+
+ private void sendRightSideFlatSensorEvent(boolean flat) {
+ sendAccelerometerFlatEvents(mRightAccelerometer, flat);
+ }
+
+ private void sendLeftSideFlatSensorEvent(boolean flat) {
+ sendAccelerometerFlatEvents(mLeftAccelerometer, flat);
+ }
+
+ private static final int ACCELEROMETER_EVENTS = 10;
+
+ private void sendAccelerometerFlatEvents(Sensor sensor, boolean flat) {
+ final float[] values = flat ? new float[]{0.00021f, -0.00013f, 9.7899f} :
+ new float[]{6.124f, 4.411f, -1.7899f};
+ // Send the same values multiple times to bypass noise filter
+ for (int i = 0; i < ACCELEROMETER_EVENTS; i++) {
+ sendSensorEvent(sensor, values);
+ }
+ }
+
+ private void setScreenOn(boolean isOn) {
+ int state = isOn ? STATE_ON : STATE_OFF;
+ when(mDisplay.getState()).thenReturn(state);
+ mDisplayListenerCaptor.getAllValues().forEach((l) -> l.onDisplayChanged(DEFAULT_DISPLAY));
+ }
+
+ private void sendSensorEvent(Sensor sensor, float[] values) {
+ SensorEvent event = mock(SensorEvent.class);
+ event.sensor = sensor;
+ try {
+ FieldSetter.setField(event, event.getClass().getField("values"),
+ values);
+ } catch (NoSuchFieldException e) {
+ throw new RuntimeException(e);
+ }
+
+ List<SensorEventListener> listeners = mSensorEventListeners.get(sensor);
+ if (listeners != null) {
+ listeners.forEach(sensorEventListener -> sensorEventListener.onSensorChanged(event));
+ }
+ }
+
+ private void assertNoListenersForSensor(Sensor sensor) {
+ final List<SensorEventListener> listeners = mSensorEventListeners.getOrDefault(sensor,
+ new ArrayList<>());
+ assertWithMessage("Expected no listeners for sensor " + sensor + " but found some").that(
+ listeners).isEmpty();
+ }
+
+ private void assertListensForSensor(Sensor sensor) {
+ final List<SensorEventListener> listeners = mSensorEventListeners.getOrDefault(sensor,
+ new ArrayList<>());
+ assertWithMessage(
+ "Expected at least one listener for sensor " + sensor).that(
+ listeners).isNotEmpty();
+ }
+
+ private void addSensorListener(Sensor sensor, SensorEventListener listener) {
+ List<SensorEventListener> listeners = mSensorEventListeners.computeIfAbsent(
+ sensor, k -> new ArrayList<>());
+ listeners.add(listener);
+ }
+
+ private DeviceStateProvider createProvider() {
+ return new BookStyleDeviceStatePolicy(mFakeFeatureFlags, mContext, mHingeAngleSensor,
+ mHallSensor, mLeftAccelerometer, mRightAccelerometer,
+ /* closeAngleDegrees= */ null).getDeviceStateProvider();
+ }
+}
diff --git a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStylePreferredScreenCalculatorTest.java b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStylePreferredScreenCalculatorTest.java
new file mode 100644
index 0000000..ae05b3f
--- /dev/null
+++ b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStylePreferredScreenCalculatorTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.policy;
+
+
+import static com.android.server.policy.BookStyleStateTransitions.DEFAULT_STATE_TRANSITIONS;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.testing.AndroidTestingRunner;
+
+import com.android.server.policy.BookStylePreferredScreenCalculator.PreferredScreen;
+import com.android.server.policy.BookStylePreferredScreenCalculator.HingeAngle;
+
+import com.google.common.collect.Lists;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link BookStylePreferredScreenCalculator}.
+ * <p/>
+ * Run with <code>atest BookStyleClosedStateCalculatorTest</code>.
+ */
+@RunWith(AndroidTestingRunner.class)
+public final class BookStylePreferredScreenCalculatorTest {
+
+ private final BookStylePreferredScreenCalculator mCalculator =
+ new BookStylePreferredScreenCalculator(DEFAULT_STATE_TRANSITIONS);
+
+ private final List<HingeAngle> mHingeAngleValues = Arrays.asList(HingeAngle.values());
+ private final List<Boolean> mLikelyTentModeValues = Arrays.asList(true, false);
+ private final List<Boolean> mLikelyReverseWedgeModeValues = Arrays.asList(true, false);
+
+ @Test
+ public void transitionAllStates_noCrashes() {
+ final List<List<Object>> arguments = Lists.cartesianProduct(Arrays.asList(
+ mHingeAngleValues,
+ mLikelyTentModeValues,
+ mLikelyReverseWedgeModeValues
+ ));
+
+ arguments.forEach(objects -> {
+ final HingeAngle hingeAngle = (HingeAngle) objects.get(0);
+ final boolean likelyTent = (boolean) objects.get(1);
+ final boolean likelyReverseWedge = (boolean) objects.get(2);
+
+ final String description =
+ "Input: hinge angle = " + hingeAngle + ", likelyTent = " + likelyTent
+ + ", likelyReverseWedge = " + likelyReverseWedge;
+
+ // Verify that there are no crashes because of infinite state transitions and
+ // that it returns a valid active state
+ try {
+ PreferredScreen preferredScreen = mCalculator.calculatePreferredScreen(hingeAngle, likelyTent,
+ likelyReverseWedge);
+
+ assertWithMessage(description).that(preferredScreen).isNotEqualTo(PreferredScreen.INVALID);
+ } catch (Throwable exception) {
+ throw new AssertionError(description, exception);
+ }
+ });
+ }
+}
diff --git a/services/lint-baseline.xml b/services/lint-baseline.xml
index 8489c17..a311d07 100644
--- a/services/lint-baseline.xml
+++ b/services/lint-baseline.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="8.1.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
<issue
id="SimpleManualPermissionEnforcement"
@@ -56,4 +56,4 @@
column="13"/>
</issue>
-</issues>
+</issues>
\ No newline at end of file
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt
index 94caf28..c8a6545 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt
@@ -17,6 +17,7 @@
package com.android.server.permission.access.appop
import android.app.AppOpsManager
+import android.util.Slog
import com.android.server.permission.access.GetStateScope
import com.android.server.permission.access.MutableAccessState
import com.android.server.permission.access.MutateStateScope
@@ -84,6 +85,10 @@
appOpName: String,
mode: Int
): Boolean {
+ if (userId !in newState.userStates) {
+ Slog.e(LOG_TAG, "Unable to set app op mode for missing user $userId")
+ return false
+ }
val defaultMode = AppOpsManager.opToDefaultMode(appOpName)
val oldMode =
newState.userStates[userId]!!
@@ -152,4 +157,8 @@
*/
abstract fun onStateMutated()
}
+
+ companion object {
+ private val LOG_TAG = AppIdAppOpPolicy::class.java.simpleName
+ }
}
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
index 0d9470e..2f15dc7 100644
--- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
@@ -17,6 +17,7 @@
package com.android.server.permission.access.appop
import android.app.AppOpsManager
+import android.util.Slog
import com.android.server.permission.access.GetStateScope
import com.android.server.permission.access.MutableAccessState
import com.android.server.permission.access.MutateStateScope
@@ -87,6 +88,10 @@
appOpName: String,
mode: Int
): Boolean {
+ if (userId !in newState.userStates) {
+ Slog.e(LOG_TAG, "Unable to set app op mode for missing user $userId")
+ return false
+ }
val defaultMode = AppOpsManager.opToDefaultMode(appOpName)
val oldMode =
newState.userStates[userId]!!
@@ -155,4 +160,8 @@
*/
abstract fun onStateMutated()
}
+
+ companion object {
+ private val LOG_TAG = PackageAppOpPolicy::class.java.simpleName
+ }
}
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 022268d..62d2d7e 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
@@ -134,7 +134,9 @@
) {
val changedPermissionNames = MutableIndexedSet<String>()
packageNames.forEachIndexed { _, packageName ->
- val packageState = newState.externalState.packageStates[packageName]!!
+ // The package may still be removed even if it was once notified as installed.
+ val packageState = newState.externalState.packageStates[packageName]
+ ?: return@forEachIndexed
adoptPermissions(packageState, changedPermissionNames)
addPermissionGroups(packageState)
addPermissions(packageState, changedPermissionNames)
@@ -147,12 +149,14 @@
}
packageNames.forEachIndexed { _, packageName ->
- val packageState = newState.externalState.packageStates[packageName]!!
+ val packageState = newState.externalState.packageStates[packageName]
+ ?: return@forEachIndexed
val installedPackageState = if (isSystemUpdated) packageState else null
evaluateAllPermissionStatesForPackage(packageState, installedPackageState)
}
packageNames.forEachIndexed { _, packageName ->
- val packageState = newState.externalState.packageStates[packageName]!!
+ val packageState = newState.externalState.packageStates[packageName]
+ ?: return@forEachIndexed
newState.externalState.userIds.forEachIndexed { _, userId ->
inheritImplicitPermissionStates(packageState.appId, userId)
}
@@ -1607,6 +1611,13 @@
flagMask: Int,
flagValues: Int
): Boolean {
+ if (userId !in newState.userStates) {
+ // Despite that we check UserManagerInternal.exists() in PermissionService, we may still
+ // sometimes get race conditions between that check and the actual mutateState() call.
+ // This should rarely happen but at least we should not crash.
+ Slog.e(LOG_TAG, "Unable to update permission flags for missing user $userId")
+ return false
+ }
val oldFlags =
newState.userStates[userId]!!
.appIdPermissionFlags[appId]
diff --git a/services/print/lint-baseline.xml b/services/print/lint-baseline.xml
index 1bf031a..11c0cc8 100644
--- a/services/print/lint-baseline.xml
+++ b/services/print/lint-baseline.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="8.1.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
<issue
id="SimpleManualPermissionEnforcement"
@@ -12,4 +12,4 @@
column="13"/>
</issue>
-</issues>
+</issues>
\ No newline at end of file
diff --git a/services/profcollect/Android.bp b/services/profcollect/Android.bp
index 2040bb6..fe431f5 100644
--- a/services/profcollect/Android.bp
+++ b/services/profcollect/Android.bp
@@ -22,24 +22,25 @@
}
filegroup {
- name: "services.profcollect-javasources",
- srcs: ["src/**/*.java"],
- path: "src",
- visibility: ["//frameworks/base/services"],
+ name: "services.profcollect-javasources",
+ srcs: ["src/**/*.java"],
+ path: "src",
+ visibility: ["//frameworks/base/services"],
}
filegroup {
- name: "services.profcollect-sources",
- srcs: [
- ":services.profcollect-javasources",
- ":profcollectd_aidl",
- ],
- visibility: ["//frameworks/base/services:__subpackages__"],
+ name: "services.profcollect-sources",
+ srcs: [
+ ":services.profcollect-javasources",
+ ":profcollectd_aidl",
+ ],
+ visibility: ["//frameworks/base/services:__subpackages__"],
}
java_library_static {
- name: "services.profcollect",
- defaults: ["platform_service_defaults"],
- srcs: [":services.profcollect-sources"],
- libs: ["services.core"],
+ name: "services.profcollect",
+ defaults: ["platform_service_defaults"],
+ srcs: [":services.profcollect-sources"],
+ static_libs: ["services.core"],
+ libs: ["service-art.stubs.system_server"],
}
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index 4007672..582b712 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -41,12 +41,15 @@
import com.android.internal.R;
import com.android.internal.os.BackgroundThread;
import com.android.server.IoThread;
+import com.android.server.LocalManagerRegistry;
import com.android.server.LocalServices;
import com.android.server.SystemService;
+import com.android.server.art.ArtManagerLocal;
import com.android.server.wm.ActivityMetricsLaunchObserver;
import com.android.server.wm.ActivityMetricsLaunchObserverRegistry;
import com.android.server.wm.ActivityTaskManagerInternal;
+import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
@@ -261,6 +264,7 @@
BackgroundThread.get().getThreadHandler().post(
() -> {
registerAppLaunchObserver();
+ registerDex2oatObserver();
registerOTAObserver();
});
}
@@ -304,6 +308,44 @@
}
}
+ private void registerDex2oatObserver() {
+ ArtManagerLocal aml = LocalManagerRegistry.getManager(ArtManagerLocal.class);
+ if (aml == null) {
+ Log.w(LOG_TAG, "Couldn't get ArtManagerLocal");
+ return;
+ }
+ aml.setBatchDexoptStartCallback(ForkJoinPool.commonPool(),
+ (snapshot, reason, defaultPackages, builder, passedSignal) -> {
+ traceOnDex2oatStart();
+ });
+ }
+
+ private void traceOnDex2oatStart() {
+ if (mIProfcollect == null) {
+ return;
+ }
+ // Sample for a fraction of dex2oat runs.
+ final int traceFrequency =
+ DeviceConfig.getInt(DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT,
+ "dex2oat_trace_freq", 10);
+ int randomNum = ThreadLocalRandom.current().nextInt(100);
+ if (randomNum < traceFrequency) {
+ if (DEBUG) {
+ Log.d(LOG_TAG, "Tracing on dex2oat event");
+ }
+ BackgroundThread.get().getThreadHandler().post(() -> {
+ try {
+ // Dex2oat could take a while before it starts. Add a short delay before start
+ // tracing.
+ Thread.sleep(1000);
+ mIProfcollect.trace_once("dex2oat");
+ } catch (RemoteException | InterruptedException e) {
+ Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
+ }
+ });
+ }
+ }
+
private void registerOTAObserver() {
UpdateEngine updateEngine = new UpdateEngine();
updateEngine.bind(new UpdateEngineCallback() {
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java
index 3c8f5c9..30afa72 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java
@@ -15,9 +15,16 @@
*/
package com.android.server.inputmethod;
+import static com.android.server.inputmethod.ClientController.ClientControllerCallback;
+import static com.android.server.inputmethod.ClientController.ClientState;
+
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.pm.PackageManagerInternal;
@@ -38,6 +45,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
// This test is designed to run on both device and host (Ravenwood) side.
public final class ClientControllerTest {
@@ -58,9 +67,6 @@
@Mock
private IRemoteInputConnection mConnection;
- @Mock
- private IBinder.DeathRecipient mDeathRecipient;
-
private Handler mHandler;
private ClientController mController;
@@ -68,9 +74,10 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ when(mClient.asBinder()).thenReturn((IBinder) mClient);
+
mHandler = new Handler(Looper.getMainLooper());
mController = new ClientController(mMockPackageManagerInternal);
- when(mClient.asBinder()).thenReturn((IBinder) mClient);
}
@Test
@@ -80,18 +87,77 @@
var invoker = IInputMethodClientInvoker.create(mClient, mHandler);
synchronized (ImfLock.class) {
- mController.addClient(invoker, mConnection, ANY_DISPLAY_ID, mDeathRecipient,
- ANY_CALLER_UID, ANY_CALLER_PID);
+ mController.addClient(invoker, mConnection, ANY_DISPLAY_ID, ANY_CALLER_UID,
+ ANY_CALLER_PID);
SecurityException thrown = assertThrows(SecurityException.class,
() -> {
synchronized (ImfLock.class) {
mController.addClient(invoker, mConnection, ANY_DISPLAY_ID,
- mDeathRecipient, ANY_CALLER_UID, ANY_CALLER_PID);
+ ANY_CALLER_UID, ANY_CALLER_PID);
}
});
assertThat(thrown.getMessage()).isEqualTo(
"uid=1/pid=1/displayId=0 is already registered");
}
}
+
+ @Test
+ // TODO(b/314150112): Enable host side mode for this test once b/315544364 is fixed.
+ @IgnoreUnderRavenwood(blockedBy = {InputBinding.class, IInputMethodClientInvoker.class})
+ public void testAddClient() throws Exception {
+ synchronized (ImfLock.class) {
+ var invoker = IInputMethodClientInvoker.create(mClient, mHandler);
+ var added = mController.addClient(invoker, mConnection, ANY_DISPLAY_ID, ANY_CALLER_UID,
+ ANY_CALLER_PID);
+
+ verify(invoker.asBinder()).linkToDeath(any(IBinder.DeathRecipient.class), eq(0));
+ assertThat(mController.mClients).containsEntry(invoker.asBinder(), added);
+ }
+ }
+
+ @Test
+ // TODO(b/314150112): Enable host side mode for this test once b/315544364 is fixed.
+ @IgnoreUnderRavenwood(blockedBy = {InputBinding.class, IInputMethodClientInvoker.class})
+ public void testRemoveClient() {
+ var callback = new TestClientControllerCallback();
+ ClientState added;
+ synchronized (ImfLock.class) {
+ mController.addClientControllerCallback(callback);
+
+ var invoker = IInputMethodClientInvoker.create(mClient, mHandler);
+ added = mController.addClient(invoker, mConnection, ANY_DISPLAY_ID, ANY_CALLER_UID,
+ ANY_CALLER_PID);
+ assertThat(mController.mClients).containsEntry(invoker.asBinder(), added);
+ assertThat(mController.removeClient(mClient)).isTrue();
+ }
+
+ // Test callback
+ var removed = callback.waitForRemovedClient(5, TimeUnit.SECONDS);
+ assertThat(removed).isSameInstanceAs(added);
+ }
+
+ private static class TestClientControllerCallback implements ClientControllerCallback {
+
+ private final CountDownLatch mLatch = new CountDownLatch(1);
+
+ private ClientState mRemoved;
+
+ @Override
+ public void onClientRemoved(ClientState removed) {
+ mRemoved = removed;
+ mLatch.countDown();
+ }
+
+ ClientState waitForRemovedClient(long timeout, TimeUnit unit) {
+ try {
+ assertWithMessage("ClientController callback wasn't called on user removed").that(
+ mLatch.await(timeout, unit)).isTrue();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new IllegalStateException("Unexpected thread interruption", e);
+ }
+ return mRemoved;
+ }
+ }
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index c67e7c5..b29fc88 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -824,6 +824,16 @@
mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
AUTO_BRIGHTNESS_MODE_DOZE,
Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_BRIGHT), SMALL_DELTA);
+
+ // Should fall back to the normal preset
+ assertArrayEquals(new float[]{0.0f, 95},
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
+ AUTO_BRIGHTNESS_MODE_DOZE,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_DIM), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.35f, 0.45f},
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
+ AUTO_BRIGHTNESS_MODE_DOZE,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_DIM), SMALL_DELTA);
}
@Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
deleted file mode 100644
index 4cc68cf..0000000
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
+++ /dev/null
@@ -1,1971 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.display;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-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.assertNotNull;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isA;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.res.Resources;
-import android.hardware.Sensor;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.hardware.display.BrightnessInfo;
-import android.hardware.display.DisplayManagerInternal;
-import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
-import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.PowerManager;
-import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.os.test.TestLooper;
-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.TestableContext;
-import android.util.FloatProperty;
-import android.util.SparseArray;
-import android.view.Display;
-import android.view.DisplayInfo;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.modules.utils.testing.ExtendedMockitoRule;
-import com.android.server.LocalServices;
-import com.android.server.am.BatteryStatsService;
-import com.android.server.display.RampAnimator.DualRampAnimator;
-import com.android.server.display.brightness.BrightnessEvent;
-import com.android.server.display.brightness.clamper.BrightnessClamperController;
-import com.android.server.display.brightness.clamper.HdrClamper;
-import com.android.server.display.color.ColorDisplayService;
-import com.android.server.display.config.SensorData;
-import com.android.server.display.feature.DisplayManagerFlags;
-import com.android.server.display.feature.flags.Flags;
-import com.android.server.display.layout.Layout;
-import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
-import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.testutils.OffsettableClock;
-
-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.Captor;
-import org.mockito.Mock;
-import org.mockito.quality.Strictness;
-import org.mockito.stubbing.Answer;
-
-import java.util.List;
-
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public final class DisplayPowerController2Test {
- private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
- private static final String UNIQUE_ID = "unique_id_test123";
- private static final int FOLLOWER_DISPLAY_ID = DISPLAY_ID + 1;
- private static final String FOLLOWER_UNIQUE_ID = "unique_id_456";
- 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 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;
- private static final float BRIGHTNESS_RAMP_RATE_SLOW_DECREASE = 0.1f;
- private static final float BRIGHTNESS_RAMP_RATE_SLOW_INCREASE = 0.2f;
- private static final float BRIGHTNESS_RAMP_RATE_SLOW_INCREASE_IDLE = 0.5f;
- private static final float BRIGHTNESS_RAMP_RATE_SLOW_DECREASE_IDLE = 0.6f;
-
- private static final long BRIGHTNESS_RAMP_INCREASE_MAX = 1000;
- private static final long BRIGHTNESS_RAMP_DECREASE_MAX = 2000;
- private static final long BRIGHTNESS_RAMP_INCREASE_MAX_IDLE = 3000;
- private static final long BRIGHTNESS_RAMP_DECREASE_MAX_IDLE = 4000;
-
- private OffsettableClock mClock;
- private TestLooper mTestLooper;
- private Handler mHandler;
- private DisplayPowerControllerHolder mHolder;
- private Sensor mProxSensor;
-
- @Mock
- private DisplayPowerCallbacks mDisplayPowerCallbacksMock;
- @Mock
- private SensorManager mSensorManagerMock;
- @Mock
- private DisplayBlanker mDisplayBlankerMock;
- @Mock
- private BrightnessTracker mBrightnessTrackerMock;
- @Mock
- private WindowManagerPolicy mWindowManagerPolicyMock;
- @Mock
- private PowerManager mPowerManagerMock;
- @Mock
- private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock;
- @Mock
- private DisplayWhiteBalanceController mDisplayWhiteBalanceControllerMock;
- @Mock
- private DisplayManagerFlags mDisplayManagerFlagsMock;
- @Mock
- private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession;
- @Captor
- private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
-
- @Rule
- public final TestableContext mContext = new TestableContext(
- InstrumentationRegistry.getInstrumentation().getContext());
-
- @Rule
- public final ExtendedMockitoRule mExtendedMockitoRule =
- new ExtendedMockitoRule.Builder(this)
- .setStrictness(Strictness.LENIENT)
- .spyStatic(SystemProperties.class)
- .spyStatic(BatteryStatsService.class)
- .spyStatic(ActivityManager.class)
- .build();
-
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
- @Before
- public void setUp() throws Exception {
- mClock = new OffsettableClock.Stopped();
- mTestLooper = new TestLooper(mClock::now);
- mHandler = new Handler(mTestLooper.getLooper());
-
- // Set some settings to minimize unexpected events and have a consistent starting state
- Settings.System.putInt(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_MODE,
- Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
- Settings.System.putFloatForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0, UserHandle.USER_CURRENT);
-
- addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
- addLocalServiceMock(ColorDisplayService.ColorDisplayServiceInternal.class,
- mCdsiMock);
-
- mContext.addMockSystemService(PowerManager.class, mPowerManagerMock);
-
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.bool.config_displayColorFadeDisabled, false);
-
- doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
- SystemProperties.set(anyString(), any()));
- doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService);
- doAnswer((Answer<Boolean>) invocationOnMock -> false)
- .when(ActivityManager::isLowRamDeviceStatic);
-
- setUpSensors();
- mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
- }
-
- @After
- public void tearDown() {
- LocalServices.removeServiceForTest(WindowManagerPolicy.class);
- LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
- }
-
- @Test
- public void testReleaseProxSuspendBlockersOnExit() throws Exception {
- when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
- // Send a display power request
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
- dpr.useProximitySensor = true;
- mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
-
- // Run updatePowerState to start listener for the prox sensor
- advanceTime(1);
-
- SensorEventListener listener = getSensorEventListener(mProxSensor);
- assertNotNull(listener);
-
- listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 5));
- advanceTime(1);
-
- // two times, one for unfinished business and one for proximity
- verify(mHolder.wakelockController, times(2)).acquireWakelock(
- WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
- verify(mHolder.wakelockController).acquireWakelock(
- WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
-
- mHolder.dpc.stop();
- advanceTime(1);
- // two times, one for unfinished business and one for proximity
- verify(mHolder.wakelockController, times(2)).acquireWakelock(
- WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
- verify(mHolder.wakelockController).acquireWakelock(
- WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
- }
-
- @Test
- public void testScreenOffBecauseOfProximity() throws Exception {
- when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
- // Send a display power request
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
- dpr.useProximitySensor = true;
- mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
-
- // Run updatePowerState to start listener for the prox sensor
- advanceTime(1);
-
- SensorEventListener listener = getSensorEventListener(mProxSensor);
- assertNotNull(listener);
-
- // Send a positive proximity event
- listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 1));
- advanceTime(1);
-
- // The display should have been turned off
- verify(mHolder.displayPowerState).setScreenState(Display.STATE_OFF);
-
- clearInvocations(mHolder.displayPowerState);
- when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
- // Send a negative proximity event
- listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor,
- (int) PROX_SENSOR_MAX_RANGE + 1));
- // Advance time by less than PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY
- advanceTime(1);
-
- // The prox sensor is debounced so the display should not have been turned back on yet
- verify(mHolder.displayPowerState, never()).setScreenState(Display.STATE_ON);
-
- // Advance time by more than PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY
- advanceTime(1000);
-
- // The display should have been turned back on
- verify(mHolder.displayPowerState).setScreenState(Display.STATE_ON);
- }
-
- @Test
- public void testScreenOffBecauseOfProximity_ProxSensorGone() throws Exception {
- when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
- // Send a display power request
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
- dpr.useProximitySensor = true;
- mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
-
- // Run updatePowerState to start listener for the prox sensor
- advanceTime(1);
-
- SensorEventListener listener = getSensorEventListener(mProxSensor);
- assertNotNull(listener);
-
- // Send a positive proximity event
- listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 1));
- advanceTime(1);
-
- // The display should have been turned off
- verify(mHolder.displayPowerState).setScreenState(Display.STATE_OFF);
-
- when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
- // The display device changes and we no longer have a prox sensor
- reset(mSensorManagerMock);
- setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
- mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
- mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
-
- advanceTime(1); // Run updatePowerState
-
- // The display should have been turned back on and the listener should have been
- // unregistered
- verify(mHolder.displayPowerState).setScreenState(Display.STATE_ON);
- verify(mSensorManagerMock).unregisterListener(listener);
- }
-
- @Test
- public void testProximitySensorListenerNotRegisteredForNonDefaultDisplay() {
- when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
- // send a display power request
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
- dpr.useProximitySensor = true;
- final DisplayPowerControllerHolder followerDpc =
- createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
- followerDpc.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
-
- // Run updatePowerState
- advanceTime(1);
-
- verify(mSensorManagerMock, never()).registerListener(any(SensorEventListener.class),
- eq(mProxSensor), anyInt(), any(Handler.class));
- }
-
- @Test
- public void testDisplayBrightnessFollowers_BothDpcsSupportNits() {
- DisplayPowerControllerHolder followerDpc =
- createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
- when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
- when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
- ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
- verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
- BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
- mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
-
- // Test different float scale values
- float leadBrightness = 0.3f;
- float followerBrightness = 0.4f;
- float nits = 300;
- when(mHolder.automaticBrightnessController.convertToNits(leadBrightness)).thenReturn(nits);
- when(followerDpc.automaticBrightnessController.getBrightnessFromNits(nits))
- .thenReturn(followerBrightness);
- when(mHolder.brightnessSetting.getBrightness()).thenReturn(leadBrightness);
- listener.onBrightnessChanged(leadBrightness);
- advanceTime(1); // Send messages, run updatePowerState
- verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(), anyFloat(), eq(false));
- verify(followerDpc.animator).animateTo(eq(followerBrightness), anyFloat(),
- anyFloat(), eq(false));
-
- clearInvocations(mHolder.animator, followerDpc.animator);
-
- // Test the same float scale value
- float brightness = 0.6f;
- nits = 600;
- when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
- when(followerDpc.automaticBrightnessController.getBrightnessFromNits(nits))
- .thenReturn(brightness);
- when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
- listener.onBrightnessChanged(brightness);
- advanceTime(1); // Send messages, run updatePowerState
- verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
- verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
- }
-
- @Test
- public void testDisplayBrightnessFollowers_FollowerDoesNotSupportNits() {
- DisplayPowerControllerHolder followerDpc =
- createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
- when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
- when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
- ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
- verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
- BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
- mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
-
- float brightness = 0.3f;
- when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(300f);
- when(followerDpc.automaticBrightnessController.getBrightnessFromNits(anyFloat()))
- .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
- when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
- listener.onBrightnessChanged(brightness);
- advanceTime(1); // Send messages, run updatePowerState
- verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
- verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
- }
-
- @Test
- public void testDisplayBrightnessFollowers_LeadDpcDoesNotSupportNits() {
- DisplayPowerControllerHolder followerDpc =
- createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
- when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
- when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
- ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
- verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
- BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
- mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
-
- float brightness = 0.3f;
- when(mHolder.automaticBrightnessController.convertToNits(anyFloat())).thenReturn(-1f);
- when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
- listener.onBrightnessChanged(brightness);
- advanceTime(1); // Send messages, run updatePowerState
- verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
- verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
- }
-
- @Test
- public void testDisplayBrightnessFollowers_NeitherDpcSupportsNits() {
- DisplayPowerControllerHolder followerDpc =
- createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
- when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
- when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
- ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
- verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
- BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
- mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
-
- float brightness = 0.3f;
- when(mHolder.automaticBrightnessController.convertToNits(anyFloat())).thenReturn(-1f);
- when(followerDpc.automaticBrightnessController.getBrightnessFromNits(anyFloat()))
- .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
- when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
- listener.onBrightnessChanged(brightness);
- advanceTime(1); // Send messages, run updatePowerState
- verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
- verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
- }
-
- @Test
- public void testDisplayBrightnessFollowers_AutomaticBrightness() {
- Settings.System.putInt(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_MODE,
- Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
- DisplayPowerControllerHolder followerDpc =
- createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
- when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
- when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
- when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
- float leadBrightness = 0.1f;
- float rawLeadBrightness = 0.3f;
- float followerBrightness = 0.4f;
- float nits = 300;
- float ambientLux = 3000;
- when(mHolder.automaticBrightnessController.getRawAutomaticScreenBrightness())
- .thenReturn(rawLeadBrightness);
- when(mHolder.automaticBrightnessController
- .getAutomaticScreenBrightness(any(BrightnessEvent.class)))
- .thenReturn(leadBrightness);
- when(mHolder.automaticBrightnessController.convertToNits(rawLeadBrightness))
- .thenReturn(nits);
- when(mHolder.automaticBrightnessController.getAmbientLux()).thenReturn(ambientLux);
- when(followerDpc.automaticBrightnessController.getBrightnessFromNits(nits))
- .thenReturn(followerBrightness);
-
- mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
-
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
- // One triggered by handleBrightnessModeChange, another triggered by setBrightnessToFollow
- verify(followerDpc.hbmController, times(2)).onAmbientLuxChange(ambientLux);
- verify(followerDpc.animator, times(2)).animateTo(eq(followerBrightness), anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-
- when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(leadBrightness);
- when(followerDpc.displayPowerState.getScreenBrightness()).thenReturn(followerBrightness);
- clearInvocations(mHolder.animator, followerDpc.animator);
-
- leadBrightness = 0.05f;
- rawLeadBrightness = 0.2f;
- followerBrightness = 0.3f;
- nits = 200;
- ambientLux = 2000;
- when(mHolder.automaticBrightnessController.getRawAutomaticScreenBrightness())
- .thenReturn(rawLeadBrightness);
- when(mHolder.automaticBrightnessController
- .getAutomaticScreenBrightness(any(BrightnessEvent.class)))
- .thenReturn(leadBrightness);
- when(mHolder.automaticBrightnessController.convertToNits(rawLeadBrightness))
- .thenReturn(nits);
- when(mHolder.automaticBrightnessController.getAmbientLux()).thenReturn(ambientLux);
- when(followerDpc.automaticBrightnessController.getBrightnessFromNits(nits))
- .thenReturn(followerBrightness);
-
- mHolder.dpc.updateBrightness();
- advanceTime(1); // Run updatePowerState
-
- // The second time, the animation rate should be slow
- verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE), eq(false));
- verify(followerDpc.hbmController).onAmbientLuxChange(ambientLux);
- verify(followerDpc.animator).animateTo(eq(followerBrightness), anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE), eq(false));
- }
-
- @Test
- public void testDisplayBrightnessFollowersRemoval_RemoveSingleFollower() {
- DisplayPowerControllerHolder followerDpc = createDisplayPowerController(FOLLOWER_DISPLAY_ID,
- FOLLOWER_UNIQUE_ID);
- DisplayPowerControllerHolder secondFollowerDpc = createDisplayPowerController(
- SECOND_FOLLOWER_DISPLAY_ID, SECOND_FOLLOWER_UNIQUE_DISPLAY_ID);
- when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
- when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
- when(secondFollowerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- secondFollowerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
- ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
- verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
- BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
- // Set the initial brightness on the DPC we're going to remove so we have a fixed value for
- // it to return to.
- listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
- verify(followerDpc.brightnessSetting).registerListener(listenerCaptor.capture());
- BrightnessSetting.BrightnessSettingListener followerListener = listenerCaptor.getValue();
- final float initialFollowerBrightness = 0.3f;
- when(followerDpc.brightnessSetting.getBrightness()).thenReturn(initialFollowerBrightness);
- followerListener.onBrightnessChanged(initialFollowerBrightness);
- advanceTime(1);
- verify(followerDpc.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-
- when(followerDpc.displayPowerState.getScreenBrightness())
- .thenReturn(initialFollowerBrightness);
-
- mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
- mHolder.dpc.addDisplayBrightnessFollower(secondFollowerDpc.dpc);
- clearInvocations(followerDpc.animator);
-
- // Validate both followers are correctly registered and receiving brightness updates
- float brightness = 0.6f;
- float nits = 600;
- when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
- when(followerDpc.automaticBrightnessController.getBrightnessFromNits(nits))
- .thenReturn(brightness);
- when(secondFollowerDpc.automaticBrightnessController.getBrightnessFromNits(nits))
- .thenReturn(brightness);
- when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
- listener.onBrightnessChanged(brightness);
- advanceTime(1); // Send messages, run updatePowerState
- verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
- verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
- verify(secondFollowerDpc.animator).animateTo(eq(brightness), anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-
- when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness);
- when(followerDpc.displayPowerState.getScreenBrightness()).thenReturn(brightness);
- when(secondFollowerDpc.displayPowerState.getScreenBrightness()).thenReturn(brightness);
- clearInvocations(mHolder.animator, followerDpc.animator, secondFollowerDpc.animator);
-
- // Remove the first follower and validate it goes back to its original brightness.
- mHolder.dpc.removeDisplayBrightnessFollower(followerDpc.dpc);
- advanceTime(1);
- verify(followerDpc.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE), eq(false));
-
- when(followerDpc.displayPowerState.getScreenBrightness())
- .thenReturn(initialFollowerBrightness);
- clearInvocations(followerDpc.animator);
-
- // Change the brightness of the lead display and validate only the second follower responds
- brightness = 0.7f;
- nits = 700;
- when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
- when(followerDpc.automaticBrightnessController.getBrightnessFromNits(nits))
- .thenReturn(brightness);
- when(secondFollowerDpc.automaticBrightnessController.getBrightnessFromNits(nits))
- .thenReturn(brightness);
- when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
- listener.onBrightnessChanged(brightness);
- advanceTime(1); // Send messages, run updatePowerState
- verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
- verify(followerDpc.animator, never()).animateTo(anyFloat(), anyFloat(), anyFloat(),
- anyBoolean());
- verify(secondFollowerDpc.animator).animateTo(eq(brightness), anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
- }
-
- @Test
- public void testDisplayBrightnessFollowersRemoval_RemoveAllFollowers() {
- DisplayPowerControllerHolder followerHolder =
- createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
- DisplayPowerControllerHolder secondFollowerHolder =
- createDisplayPowerController(SECOND_FOLLOWER_DISPLAY_ID,
- SECOND_FOLLOWER_UNIQUE_DISPLAY_ID);
- when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
- when(followerHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
- when(secondFollowerHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- followerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- secondFollowerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
- ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
- verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
- BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
- // Set the initial brightness on the DPCs we're going to remove so we have a fixed value for
- // it to return to.
- listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
- verify(followerHolder.brightnessSetting).registerListener(listenerCaptor.capture());
- BrightnessSetting.BrightnessSettingListener followerListener = listenerCaptor.getValue();
- listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
- verify(secondFollowerHolder.brightnessSetting).registerListener(listenerCaptor.capture());
- BrightnessSetting.BrightnessSettingListener secondFollowerListener =
- listenerCaptor.getValue();
- final float initialFollowerBrightness = 0.3f;
- when(followerHolder.brightnessSetting.getBrightness()).thenReturn(
- initialFollowerBrightness);
- when(secondFollowerHolder.brightnessSetting.getBrightness()).thenReturn(
- initialFollowerBrightness);
- followerListener.onBrightnessChanged(initialFollowerBrightness);
- secondFollowerListener.onBrightnessChanged(initialFollowerBrightness);
- advanceTime(1);
- verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
- verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-
- when(followerHolder.displayPowerState.getScreenBrightness())
- .thenReturn(initialFollowerBrightness);
- when(secondFollowerHolder.displayPowerState.getScreenBrightness())
- .thenReturn(initialFollowerBrightness);
-
- mHolder.dpc.addDisplayBrightnessFollower(followerHolder.dpc);
- mHolder.dpc.addDisplayBrightnessFollower(secondFollowerHolder.dpc);
- clearInvocations(followerHolder.animator, secondFollowerHolder.animator);
-
- // Validate both followers are correctly registered and receiving brightness updates
- float brightness = 0.6f;
- float nits = 600;
- when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
- when(followerHolder.automaticBrightnessController.getBrightnessFromNits(nits))
- .thenReturn(brightness);
- when(secondFollowerHolder.automaticBrightnessController.getBrightnessFromNits(nits))
- .thenReturn(brightness);
- when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
- listener.onBrightnessChanged(brightness);
- advanceTime(1); // Send messages, run updatePowerState
- verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
- verify(followerHolder.animator).animateTo(eq(brightness), anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
- verify(secondFollowerHolder.animator).animateTo(eq(brightness), anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-
- when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness);
- when(followerHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness);
- when(secondFollowerHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness);
- clearInvocations(mHolder.animator, followerHolder.animator, secondFollowerHolder.animator);
-
- // Stop the lead DPC and validate that the followers go back to their original brightness.
- mHolder.dpc.stop();
- advanceTime(1);
- verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE), eq(false));
- verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE), eq(false));
- clearInvocations(followerHolder.animator, secondFollowerHolder.animator);
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_FAST_HDR_TRANSITIONS)
- public void testDisplayBrightnessHdr_SkipAnimationOnHdrAppearance() {
- Settings.System.putInt(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_MODE,
- Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
- final float sdrBrightness = 0.1f;
- final float hdrBrightness = 0.3f;
- when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
- when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
- when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
- any(BrightnessEvent.class))).thenReturn(sdrBrightness);
- when(mHolder.hdrClamper.getMaxBrightness()).thenReturn(1.0f);
-
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- verify(mHolder.animator).animateTo(eq(sdrBrightness), eq(sdrBrightness),
- eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-
- when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(sdrBrightness);
- when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(sdrBrightness);
-
- when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
- BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR);
- when(mHolder.hbmController.getHdrBrightnessValue()).thenReturn(hdrBrightness);
- clearInvocations(mHolder.animator);
-
- mHolder.dpc.updateBrightness();
- advanceTime(1); // Run updatePowerState
-
- verify(mHolder.animator).animateTo(eq(hdrBrightness), eq(sdrBrightness),
- eq(BRIGHTNESS_RAMP_RATE_MINIMUM), eq(false));
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_FAST_HDR_TRANSITIONS)
- public void testDisplayBrightnessHdr_SkipAnimationOnHdrRemoval() {
- Settings.System.putInt(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_MODE,
- Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
- final float sdrBrightness = 0.1f;
- final float hdrBrightness = 0.3f;
- when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
- when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
- when(mHolder.automaticBrightnessController.isInIdleMode()).thenReturn(true);
- when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
- any(BrightnessEvent.class))).thenReturn(sdrBrightness);
- when(mHolder.hdrClamper.getMaxBrightness()).thenReturn(1.0f);
-
- when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
- BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR);
- when(mHolder.hbmController.getHdrBrightnessValue()).thenReturn(hdrBrightness);
-
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- verify(mHolder.animator).animateTo(eq(hdrBrightness), eq(sdrBrightness),
- eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-
- when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(hdrBrightness);
- when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(sdrBrightness);
- when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
- BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
-
- clearInvocations(mHolder.animator);
-
- mHolder.dpc.updateBrightness();
- advanceTime(1); // Run updatePowerState
-
- verify(mHolder.animator).animateTo(eq(sdrBrightness), eq(sdrBrightness),
- eq(BRIGHTNESS_RAMP_RATE_MINIMUM), eq(false));
- }
-
- @Test
- public void testDoesNotSetScreenStateForNonDefaultDisplayUntilBootCompleted() {
- // We should still set screen state for the default display
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
- verify(mHolder.displayPowerState, times(2)).setScreenState(anyInt());
-
- mHolder = createDisplayPowerController(42, UNIQUE_ID);
-
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
- verify(mHolder.displayPowerState, never()).setScreenState(anyInt());
-
- mHolder.dpc.onBootCompleted();
- advanceTime(1); // Run updatePowerState
- verify(mHolder.displayPowerState).setScreenState(anyInt());
- }
-
- @Test
- public void testSetScreenOffBrightnessSensorEnabled_DisplayIsOff() {
- Settings.System.putInt(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_MODE,
- Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-
- when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
-
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- dpr.policy = DisplayPowerRequest.POLICY_OFF;
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
- .setLightSensorEnabled(true);
-
- // The display turns on and we use the brightness value recommended by
- // ScreenOffBrightnessSensorController
- clearInvocations(mHolder.screenOffBrightnessSensorController);
- float brightness = 0.14f;
- when(mHolder.screenOffBrightnessSensorController.getAutomaticScreenBrightness())
- .thenReturn(brightness);
- dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
- when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
- when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
- any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
- .getAutomaticScreenBrightness();
- verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat(), eq(false));
- }
-
- @Test
- public void testSetScreenOffBrightnessSensorEnabled_DisplayIsInDoze() {
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, false);
- mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
-
- Settings.System.putInt(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_MODE,
- Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-
- 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
-
- verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
- .setLightSensorEnabled(true);
-
- // The display turns on and we use the brightness value recommended by
- // ScreenOffBrightnessSensorController
- clearInvocations(mHolder.screenOffBrightnessSensorController);
- float brightness = 0.14f;
- when(mHolder.screenOffBrightnessSensorController.getAutomaticScreenBrightness())
- .thenReturn(brightness);
- dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
- when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
- when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
- any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
- .getAutomaticScreenBrightness();
- verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat(), eq(false));
- }
-
- @Test
- public void testSetScreenOffBrightnessSensorDisabled_AutoBrightnessIsDisabled() {
- // Tests are set up with manual brightness by default, so no need to set it here.
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- dpr.policy = DisplayPowerRequest.POLICY_OFF;
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
- .setLightSensorEnabled(false);
- }
-
- @Test
- public void testSetScreenOffBrightnessSensorDisabled_DisplayIsDisabled() {
- Settings.System.putInt(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_MODE,
- Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
- mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ false);
-
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
- .setLightSensorEnabled(false);
- }
-
- @Test
- public void testSetScreenOffBrightnessSensorDisabled_DisplayIsOn() {
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
- .setLightSensorEnabled(false);
- }
-
- @Test
- public void testSetScreenOffBrightnessSensorDisabled_DisplayIsAFollower() {
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- dpr.policy = DisplayPowerRequest.POLICY_OFF;
-
- mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, /* leadDisplayId= */ 42);
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
- .setLightSensorEnabled(false);
- }
-
- @Test
- public void testStopScreenOffBrightnessSensorControllerWhenDisplayDeviceChanges() {
- // New display device
- setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
- mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
-
- mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- verify(mHolder.screenOffBrightnessSensorController).stop();
- }
-
- @Test
- public void testAutoBrightnessEnabled_DisplayIsOn() {
- Settings.System.putInt(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_MODE,
- Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
- when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- verify(mHolder.automaticBrightnessController).configure(
- AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
- /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
- /* userChangedBrightness= */ false, /* adjustment= */ 0,
- /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_BRIGHT,
- /* shouldResetShortTermModel= */ false
- );
- verify(mHolder.hbmController)
- .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED);
- }
-
- @Test
- public void testAutoBrightnessEnabled_DisplayIsInDoze() {
- Settings.System.putInt(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_MODE,
- Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true);
- mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
-
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- dpr.policy = DisplayPowerRequest.POLICY_DOZE;
- when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- verify(mHolder.automaticBrightnessController).configure(
- AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
- /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
- /* userChangedBrightness= */ false, /* adjustment= */ 0,
- /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_DOZE,
- /* shouldResetShortTermModel= */ false
- );
- verify(mHolder.hbmController)
- .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED);
- }
-
- @Test
- public void testAutoBrightnessDisabled_ManualBrightnessMode() {
- Settings.System.putInt(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_MODE,
- Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
-
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
- when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- // One triggered by the test, the other by handleBrightnessModeChange
- verify(mHolder.automaticBrightnessController, times(2)).configure(
- AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
- /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
- /* userChangedBrightness= */ false, /* adjustment= */ 0,
- /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_BRIGHT,
- /* shouldResetShortTermModel= */ false
- );
- verify(mHolder.hbmController, times(2))
- .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
- }
-
- @Test
- public void testAutoBrightnessDisabled_DisplayIsOff() {
- Settings.System.putInt(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_MODE,
- Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- dpr.policy = DisplayPowerRequest.POLICY_OFF;
- when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- verify(mHolder.automaticBrightnessController).configure(
- AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
- /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
- /* userChangedBrightness= */ false, /* adjustment= */ 0,
- /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_OFF,
- /* shouldResetShortTermModel= */ false
- );
- verify(mHolder.hbmController).setAutoBrightnessEnabled(
- AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE);
- }
-
- @Test
- public void testAutoBrightnessDisabled_DisplayIsInDoze() {
- Settings.System.putInt(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_MODE,
- Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, false);
- mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
-
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- dpr.policy = DisplayPowerRequest.POLICY_DOZE;
- when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- verify(mHolder.automaticBrightnessController).configure(
- AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
- /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
- /* userChangedBrightness= */ false, /* adjustment= */ 0,
- /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_DOZE,
- /* shouldResetShortTermModel= */ false
- );
- verify(mHolder.hbmController).setAutoBrightnessEnabled(
- AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE);
- }
-
- @Test
- public void testAutoBrightnessDisabled_FollowerDisplay() {
- Settings.System.putInt(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_MODE,
- Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
- mHolder.dpc.setBrightnessToFollow(0.3f, -1, 0, /* slowChange= */ false);
-
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
- when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- // One triggered by the test, the other by handleBrightnessModeChange
- verify(mHolder.automaticBrightnessController, times(2)).configure(
- AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
- /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
- /* userChangedBrightness= */ false, /* adjustment= */ 0,
- /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_BRIGHT,
- /* shouldResetShortTermModel= */ false
- );
-
- // HBM should be allowed for the follower display
- verify(mHolder.hbmController)
- .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED);
- }
-
- @Test
- public void testBrightnessNitsPersistWhenDisplayDeviceChanges() {
- float brightness = 0.3f;
- float nits = 500;
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay,
- true);
- mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
- when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-
- mHolder.dpc.setBrightness(brightness);
- verify(mHolder.brightnessSetting).setBrightnessNitsForDefaultDisplay(nits);
-
- float newBrightness = 0.4f;
- when(mHolder.brightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits);
- when(mHolder.automaticBrightnessController.getBrightnessFromNits(nits))
- .thenReturn(newBrightness);
- // New display device
- setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
- mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
- mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
- // One triggered by handleBrightnessModeChange, another triggered by onDisplayChanged
- verify(mHolder.animator, times(2)).animateTo(eq(newBrightness), anyFloat(), anyFloat(),
- eq(false));
- }
-
- @Test
- public void testShortTermModelPersistsWhenDisplayDeviceChanges() {
- float lux = 2000;
- float nits = 500;
- when(mHolder.automaticBrightnessController.getUserLux()).thenReturn(lux);
- when(mHolder.automaticBrightnessController.getUserNits()).thenReturn(nits);
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1);
- clearInvocations(mHolder.injector);
-
- // New display device
- setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
- mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
- mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
- advanceTime(1);
-
- verify(mHolder.injector).getAutomaticBrightnessController(
- any(AutomaticBrightnessController.Callbacks.class),
- any(Looper.class),
- eq(mSensorManagerMock),
- /* lightSensor= */ any(),
- /* brightnessMappingStrategyMap= */ any(SparseArray.class),
- /* lightSensorWarmUpTime= */ anyInt(),
- /* brightnessMin= */ anyFloat(),
- /* brightnessMax= */ anyFloat(),
- /* dozeScaleFactor */ anyFloat(),
- /* lightSensorRate= */ anyInt(),
- /* initialLightSensorRate= */ anyInt(),
- /* brighteningLightDebounceConfig */ anyLong(),
- /* darkeningLightDebounceConfig */ anyLong(),
- /* brighteningLightDebounceConfigIdle= */ anyLong(),
- /* darkeningLightDebounceConfigIdle= */ anyLong(),
- /* resetAmbientLuxAfterWarmUpConfig= */ anyBoolean(),
- any(HysteresisLevels.class),
- any(HysteresisLevels.class),
- any(HysteresisLevels.class),
- any(HysteresisLevels.class),
- eq(mContext),
- any(BrightnessRangeController.class),
- any(BrightnessThrottler.class),
- /* ambientLightHorizonShort= */ anyInt(),
- /* ambientLightHorizonLong= */ anyInt(),
- eq(lux),
- eq(nits)
- );
- }
-
- @Test
- public void testUpdateBrightnessThrottlingDataId() {
- mHolder.display.getDisplayInfoLocked().thermalBrightnessThrottlingDataId =
- "throttling-data-id";
- clearInvocations(mHolder.display.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig());
-
- mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- verify(mHolder.display.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig())
- .getThermalBrightnessThrottlingDataMapByThrottlingId();
- }
-
- @Test
- public void testSetBrightness_BrightnessShouldBeClamped() {
- float clampedBrightness = 0.6f;
- when(mHolder.hbmController.getCurrentBrightnessMax()).thenReturn(clampedBrightness);
-
- mHolder.dpc.setBrightness(PowerManager.BRIGHTNESS_MAX);
-
- verify(mHolder.brightnessSetting).setBrightness(clampedBrightness);
- }
-
- @Test
- public void testDwbcCallsHappenOnHandler() {
- mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
-
- mHolder.dpc.setAutomaticScreenBrightnessMode(AUTO_BRIGHTNESS_MODE_IDLE);
- verify(mDisplayWhiteBalanceControllerMock, never()).setStrongModeEnabled(true);
-
- // dispatch handler looper
- advanceTime(1);
- verify(mDisplayWhiteBalanceControllerMock, times(1)).setStrongModeEnabled(true);
- }
-
- @Test
- public void testRampRatesIdle() {
- Settings.System.putInt(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_MODE,
- Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
- float brightness = 0.6f;
- when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
- when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
- when(mHolder.automaticBrightnessController.isInIdleMode()).thenReturn(true);
- when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
- any(BrightnessEvent.class))).thenReturn(brightness);
-
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-
- when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness);
- brightness = 0.05f;
- when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
- any(BrightnessEvent.class))).thenReturn(brightness);
-
- mHolder.dpc.updateBrightness();
- advanceTime(1); // Run updatePowerState
-
- // The second time, the animation rate should be slow
- verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE_IDLE), eq(false));
-
- brightness = 0.9f;
- when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
- any(BrightnessEvent.class))).thenReturn(brightness);
-
- mHolder.dpc.updateBrightness();
- advanceTime(1); // Run updatePowerState
- // The third time, the animation rate should be slow
- verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_SLOW_INCREASE_IDLE), eq(false));
- }
-
- @Test
- public void testRampRateForHdrContent_HdrClamperOff() {
- float hdrBrightness = 0.8f;
- float clampedBrightness = 0.6f;
- float transitionRate = 1.5f;
-
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
- when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
- when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
- when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
- BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR);
- when(mHolder.hbmController.getHdrBrightnessValue()).thenReturn(hdrBrightness);
- when(mHolder.hdrClamper.getMaxBrightness()).thenReturn(clampedBrightness);
- when(mHolder.hdrClamper.getTransitionRate()).thenReturn(transitionRate);
-
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- verify(mHolder.animator, atLeastOnce()).animateTo(eq(hdrBrightness), anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
- }
-
- @Test
- public void testRampRateForHdrContent_HdrClamperOn() {
- float clampedBrightness = 0.6f;
- float transitionRate = 1.5f;
- when(mDisplayManagerFlagsMock.isHdrClamperEnabled()).thenReturn(true);
- mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true);
-
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
- when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
- when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
- when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
- BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR);
- when(mHolder.hbmController.getHdrBrightnessValue()).thenReturn(PowerManager.BRIGHTNESS_MAX);
- when(mHolder.hdrClamper.getMaxBrightness()).thenReturn(clampedBrightness);
- when(mHolder.hdrClamper.getTransitionRate()).thenReturn(transitionRate);
-
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- verify(mHolder.animator, atLeastOnce()).animateTo(eq(clampedBrightness), anyFloat(),
- eq(transitionRate), eq(true));
- }
-
- @Test
- public void testRampRateForClampersControllerApplied() {
- float transitionRate = 1.5f;
- mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
- when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
- when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
- when(mHolder.clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer(
- invocation -> DisplayBrightnessState.builder()
- .setIsSlowChange(invocation.getArgument(2))
- .setBrightness(invocation.getArgument(1))
- .setMaxBrightness(PowerManager.BRIGHTNESS_MAX)
- .setCustomAnimationRate(transitionRate).build());
-
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- verify(mHolder.animator, atLeastOnce()).animateTo(anyFloat(), anyFloat(),
- eq(transitionRate), anyBoolean());
- }
-
- @Test
- public void testRampRateForClampersControllerNotApplied_ifDoze() {
- float transitionRate = 1.5f;
- mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- dpr.policy = DisplayPowerRequest.POLICY_DOZE;
- dpr.dozeScreenState = Display.STATE_UNKNOWN;
- when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
- when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
- when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
- when(mHolder.clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer(
- invocation -> DisplayBrightnessState.builder()
- .setIsSlowChange(invocation.getArgument(2))
- .setBrightness(invocation.getArgument(1))
- .setMaxBrightness(PowerManager.BRIGHTNESS_MAX)
- .setCustomAnimationRate(transitionRate).build());
-
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- verify(mHolder.animator, atLeastOnce()).animateTo(anyFloat(), anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE), anyBoolean());
- verify(mHolder.animator, never()).animateTo(anyFloat(), anyFloat(),
- eq(transitionRate), anyBoolean());
- }
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1)
- public void testRampMaxTimeInteractiveThenIdle() {
- // Send a display power request
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
- dpr.useProximitySensor = true;
- mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
-
- // Run updatePowerState
- advanceTime(1);
-
- setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
- mHolder.config, /* isEnabled= */ true);
- verify(mHolder.animator).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX,
- BRIGHTNESS_RAMP_DECREASE_MAX);
-
- // switch to idle mode
- mHolder.dpc.setAutomaticScreenBrightnessMode(AUTO_BRIGHTNESS_MODE_IDLE);
- advanceTime(1);
-
- // A second time, when switching to idle mode.
- verify(mHolder.animator, times(2)).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX,
- BRIGHTNESS_RAMP_DECREASE_MAX);
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1)
- public void testRampMaxTimeInteractiveThenIdle_DifferentValues() {
- when(mDisplayManagerFlagsMock.isAdaptiveTone1Enabled()).thenReturn(true);
- mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true);
-
- // Send a display power request
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
- dpr.useProximitySensor = true;
- mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
-
- // Run updatePowerState
- advanceTime(1);
-
- setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
- mHolder.config, /* isEnabled= */ true);
- verify(mHolder.animator).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX,
- BRIGHTNESS_RAMP_DECREASE_MAX);
-
- // switch to idle mode
- mHolder.dpc.setAutomaticScreenBrightnessMode(AUTO_BRIGHTNESS_MODE_IDLE);
- advanceTime(1);
-
- // A second time, when switching to idle mode.
- verify(mHolder.animator).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX_IDLE,
- BRIGHTNESS_RAMP_DECREASE_MAX_IDLE);
- }
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1)
- public void testRampMaxTimeIdle() {
- // Send a display power request
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
- dpr.useProximitySensor = true;
- mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
- // Run updatePowerState
- advanceTime(1);
- // Once on setup
- verify(mHolder.animator).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX,
- BRIGHTNESS_RAMP_DECREASE_MAX);
-
- setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
- mHolder.config, /* isEnabled= */ true);
-
- // switch to idle mode
- mHolder.dpc.setAutomaticScreenBrightnessMode(AUTO_BRIGHTNESS_MODE_IDLE);
-
- // A second time when switching to idle mode.
- verify(mHolder.animator, times(2)).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX,
- BRIGHTNESS_RAMP_DECREASE_MAX);
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1)
- public void testRampMaxTimeIdle_DifferentValues() {
- when(mDisplayManagerFlagsMock.isAdaptiveTone1Enabled()).thenReturn(true);
- mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true);
-
- // Send a display power request
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
- dpr.useProximitySensor = true;
- mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
-
- // Run updatePowerState
- advanceTime(1);
-
- setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
- mHolder.config, /* isEnabled= */ true);
-
- // switch to idle mode
- mHolder.dpc.setAutomaticScreenBrightnessMode(AUTO_BRIGHTNESS_MODE_IDLE);
-
- verify(mHolder.animator).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX_IDLE,
- BRIGHTNESS_RAMP_DECREASE_MAX_IDLE);
- }
-
- @Test
- public void testDozeScreenStateOverride_toSupportedOffloadStateFromDoze_DisplayStateChanges() {
- // set up.
- int initState = Display.STATE_DOZE;
- int supportedTargetState = Display.STATE_DOZE_SUSPEND;
- mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
- when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
- doAnswer(invocation -> {
- when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0));
- return null;
- }).when(mHolder.displayPowerState).setScreenState(anyInt());
- mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
-
- // start with DOZE.
- when(mHolder.displayPowerState.getScreenState()).thenReturn(initState);
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- dpr.policy = DisplayPowerRequest.POLICY_DOZE;
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- mHolder.dpc.overrideDozeScreenState(supportedTargetState);
- advanceTime(1); // Run updatePowerState
-
- verify(mHolder.displayPowerState).setScreenState(supportedTargetState);
- }
-
- @Test
- public void testDozeScreenStateOverride_toUnSupportedOffloadStateFromDoze_stateRemains() {
- // set up.
- int initState = Display.STATE_DOZE;
- int unSupportedTargetState = Display.STATE_ON;
- mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
- when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
- doAnswer(invocation -> {
- when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0));
- return null;
- }).when(mHolder.displayPowerState).setScreenState(anyInt());
- mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
-
- // start with DOZE.
- when(mHolder.displayPowerState.getScreenState()).thenReturn(initState);
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- dpr.policy = DisplayPowerRequest.POLICY_DOZE;
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- mHolder.dpc.overrideDozeScreenState(unSupportedTargetState);
- advanceTime(1); // Run updatePowerState
-
- verify(mHolder.displayPowerState, never()).setScreenState(anyInt());
- }
-
- @Test
- public void testDozeScreenStateOverride_toSupportedOffloadStateFromOFF_stateRemains() {
- // set up.
- int initState = Display.STATE_OFF;
- int supportedTargetState = Display.STATE_DOZE_SUSPEND;
- mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
- doAnswer(invocation -> {
- when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0));
- return null;
- }).when(mHolder.displayPowerState).setScreenState(anyInt());
- mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
-
- // start with OFF.
- when(mHolder.displayPowerState.getScreenState()).thenReturn(initState);
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- dpr.policy = DisplayPowerRequest.POLICY_OFF;
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- mHolder.dpc.overrideDozeScreenState(supportedTargetState);
- advanceTime(1); // Run updatePowerState
-
- verify(mHolder.displayPowerState, never()).setScreenState(anyInt());
- }
-
- @Test
- public void testBrightnessFromOffload() {
- when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
- mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
- Settings.System.putInt(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_MODE,
- Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
- float brightness = 0.34f;
- when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
- when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
- when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
- any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
- mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
-
- mHolder.dpc.setBrightnessFromOffload(brightness);
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- // One triggered by handleBrightnessModeChange, another triggered by
- // setBrightnessFromOffload
- verify(mHolder.animator, times(2)).animateTo(eq(brightness), anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
- }
-
- @Test
- public void testSwitchToDozeAutoBrightnessMode() {
- when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(true);
- 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
-
- // One triggered by handleBrightnessModeChange, another triggered by requestPowerState
- verify(mHolder.automaticBrightnessController, times(2))
- .switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
-
- // Back to default mode
- when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
- dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- verify(mHolder.automaticBrightnessController).switchMode(AUTO_BRIGHTNESS_MODE_DEFAULT);
- }
-
- @Test
- public void testDoesNotSwitchFromIdleToDozeAutoBrightnessMode() {
- when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(true);
- when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
- when(mHolder.automaticBrightnessController.isInIdleMode()).thenReturn(true);
-
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- verify(mHolder.automaticBrightnessController, never())
- .switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
- }
-
- @Test
- public void testDoesNotSwitchDozeAutoBrightnessModeIfFeatureFlagOff() {
- when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(false);
- when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
-
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- verify(mHolder.automaticBrightnessController, never())
- .switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
- }
-
- /**
- * Creates a mock and registers it to {@link LocalServices}.
- */
- private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
- LocalServices.removeServiceForTest(clazz);
- LocalServices.addService(clazz, mock);
- }
-
- private void advanceTime(long timeMs) {
- mClock.fastForward(timeMs);
- mTestLooper.dispatchAll();
- }
-
- private void setUpSensors() throws Exception {
- mProxSensor = TestUtils.createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY,
- PROX_SENSOR_MAX_RANGE);
- Sensor screenOffBrightnessSensor = TestUtils.createSensor(
- Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
- when(mSensorManagerMock.getSensorList(eq(Sensor.TYPE_ALL)))
- .thenReturn(List.of(mProxSensor, screenOffBrightnessSensor));
- }
-
- private SensorEventListener getSensorEventListener(Sensor sensor) {
- verify(mSensorManagerMock).registerListener(mSensorEventListenerCaptor.capture(),
- eq(sensor), eq(SensorManager.SENSOR_DELAY_NORMAL), isA(Handler.class));
- return mSensorEventListenerCaptor.getValue();
- }
-
- private void setUpDisplay(int displayId, String uniqueId, LogicalDisplay logicalDisplayMock,
- DisplayDevice displayDeviceMock, DisplayDeviceConfig displayDeviceConfigMock,
- boolean isEnabled) {
- DisplayInfo info = new DisplayInfo();
- DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo();
- deviceInfo.uniqueId = uniqueId;
-
- when(logicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId);
- when(logicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(displayDeviceMock);
- when(logicalDisplayMock.getDisplayInfoLocked()).thenReturn(info);
- when(logicalDisplayMock.isEnabledLocked()).thenReturn(isEnabled);
- when(logicalDisplayMock.isInTransitionLocked()).thenReturn(false);
- when(displayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
- when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId);
- when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock);
- when(displayDeviceConfigMock.getProximitySensor()).thenReturn(
- new SensorData(Sensor.STRING_TYPE_PROXIMITY, null));
- when(displayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
- when(displayDeviceConfigMock.isAutoBrightnessAvailable()).thenReturn(true);
- when(displayDeviceConfigMock.getAmbientLightSensor()).thenReturn(
- new SensorData());
- when(displayDeviceConfigMock.getScreenOffBrightnessSensor()).thenReturn(
- new SensorData(Sensor.STRING_TYPE_LIGHT, null));
- when(displayDeviceConfigMock.getScreenOffBrightnessSensorValueToLux())
- .thenReturn(new int[0]);
-
- when(displayDeviceConfigMock.getBrightnessRampFastDecrease())
- .thenReturn(BRIGHTNESS_RAMP_RATE_FAST_DECREASE);
- when(displayDeviceConfigMock.getBrightnessRampFastIncrease())
- .thenReturn(BRIGHTNESS_RAMP_RATE_FAST_INCREASE);
- when(displayDeviceConfigMock.getBrightnessRampSlowDecrease())
- .thenReturn(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE);
- when(displayDeviceConfigMock.getBrightnessRampSlowIncrease())
- .thenReturn(BRIGHTNESS_RAMP_RATE_SLOW_INCREASE);
- when(displayDeviceConfigMock.getBrightnessRampSlowIncreaseIdle())
- .thenReturn(BRIGHTNESS_RAMP_RATE_SLOW_INCREASE_IDLE);
- when(displayDeviceConfigMock.getBrightnessRampSlowDecreaseIdle())
- .thenReturn(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE_IDLE);
-
- when(displayDeviceConfigMock.getBrightnessRampIncreaseMaxMillis())
- .thenReturn(BRIGHTNESS_RAMP_INCREASE_MAX);
- when(displayDeviceConfigMock.getBrightnessRampDecreaseMaxMillis())
- .thenReturn(BRIGHTNESS_RAMP_DECREASE_MAX);
- when(displayDeviceConfigMock.getBrightnessRampIncreaseMaxIdleMillis())
- .thenReturn(BRIGHTNESS_RAMP_INCREASE_MAX_IDLE);
- when(displayDeviceConfigMock.getBrightnessRampDecreaseMaxIdleMillis())
- .thenReturn(BRIGHTNESS_RAMP_DECREASE_MAX_IDLE);
- }
-
- private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
- String uniqueId) {
- return createDisplayPowerController(displayId, uniqueId, /* isEnabled= */ true);
- }
-
- private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
- String uniqueId, boolean isEnabled) {
- final DisplayPowerState displayPowerState = mock(DisplayPowerState.class);
- final DualRampAnimator<DisplayPowerState> animator = mock(DualRampAnimator.class);
- final AutomaticBrightnessController automaticBrightnessController =
- mock(AutomaticBrightnessController.class);
- final WakelockController wakelockController = mock(WakelockController.class);
- final BrightnessMappingStrategy brightnessMappingStrategy =
- mock(BrightnessMappingStrategy.class);
- final HysteresisLevels hysteresisLevels = mock(HysteresisLevels.class);
- final ScreenOffBrightnessSensorController screenOffBrightnessSensorController =
- mock(ScreenOffBrightnessSensorController.class);
- final HighBrightnessModeController hbmController = mock(HighBrightnessModeController.class);
- final HdrClamper hdrClamper = mock(HdrClamper.class);
- BrightnessClamperController clamperController = mock(BrightnessClamperController.class);
-
- when(hbmController.getCurrentBrightnessMax()).thenReturn(PowerManager.BRIGHTNESS_MAX);
- when(clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer(
- invocation -> DisplayBrightnessState.builder()
- .setIsSlowChange(invocation.getArgument(2))
- .setBrightness(invocation.getArgument(1))
- .setMaxBrightness(PowerManager.BRIGHTNESS_MAX)
- .setCustomAnimationRate(-1).build());
-
- TestInjector injector = spy(new TestInjector(displayPowerState, animator,
- automaticBrightnessController, wakelockController, brightnessMappingStrategy,
- hysteresisLevels, screenOffBrightnessSensorController, hbmController, hdrClamper,
- clamperController, mDisplayManagerFlagsMock));
-
- final LogicalDisplay display = mock(LogicalDisplay.class);
- final DisplayDevice device = mock(DisplayDevice.class);
- final HighBrightnessModeMetadata hbmMetadata = mock(HighBrightnessModeMetadata.class);
- final BrightnessSetting brightnessSetting = mock(BrightnessSetting.class);
- final DisplayDeviceConfig config = mock(DisplayDeviceConfig.class);
-
- setUpDisplay(displayId, uniqueId, display, device, config, isEnabled);
-
- final DisplayPowerController2 dpc = new DisplayPowerController2(
- mContext, injector, mDisplayPowerCallbacksMock, mHandler,
- mSensorManagerMock, mDisplayBlankerMock, display,
- mBrightnessTrackerMock, brightnessSetting, () -> {
- },
- hbmMetadata, /* bootCompleted= */ false, mDisplayManagerFlagsMock);
-
- return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting,
- animator, automaticBrightnessController, wakelockController,
- screenOffBrightnessSensorController, hbmController, hdrClamper, clamperController,
- hbmMetadata, brightnessMappingStrategy, injector, config);
- }
-
- /**
- * A class for holding a DisplayPowerController under test and all the mocks specifically
- * related to it.
- */
- private static class DisplayPowerControllerHolder {
- public final DisplayPowerController2 dpc;
- public final LogicalDisplay display;
- public final DisplayPowerState displayPowerState;
- public final BrightnessSetting brightnessSetting;
- public final DualRampAnimator<DisplayPowerState> animator;
- public final AutomaticBrightnessController automaticBrightnessController;
- public final WakelockController wakelockController;
- public final ScreenOffBrightnessSensorController screenOffBrightnessSensorController;
- public final HighBrightnessModeController hbmController;
-
- public final HdrClamper hdrClamper;
- public final BrightnessClamperController clamperController;
- public final HighBrightnessModeMetadata hbmMetadata;
- public final BrightnessMappingStrategy brightnessMappingStrategy;
- public final DisplayPowerController2.Injector injector;
- public final DisplayDeviceConfig config;
-
- DisplayPowerControllerHolder(DisplayPowerController2 dpc, LogicalDisplay display,
- DisplayPowerState displayPowerState, BrightnessSetting brightnessSetting,
- DualRampAnimator<DisplayPowerState> animator,
- AutomaticBrightnessController automaticBrightnessController,
- WakelockController wakelockController,
- ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
- HighBrightnessModeController hbmController,
- HdrClamper hdrClamper,
- BrightnessClamperController clamperController,
- HighBrightnessModeMetadata hbmMetadata,
- BrightnessMappingStrategy brightnessMappingStrategy,
- DisplayPowerController2.Injector injector,
- DisplayDeviceConfig config) {
- this.dpc = dpc;
- this.display = display;
- this.displayPowerState = displayPowerState;
- this.brightnessSetting = brightnessSetting;
- this.animator = animator;
- this.automaticBrightnessController = automaticBrightnessController;
- this.wakelockController = wakelockController;
- this.screenOffBrightnessSensorController = screenOffBrightnessSensorController;
- this.hbmController = hbmController;
- this.hdrClamper = hdrClamper;
- this.clamperController = clamperController;
- this.hbmMetadata = hbmMetadata;
- this.brightnessMappingStrategy = brightnessMappingStrategy;
- this.injector = injector;
- this.config = config;
- }
- }
-
- private class TestInjector extends DisplayPowerController2.Injector {
- private final DisplayPowerState mDisplayPowerState;
- private final DualRampAnimator<DisplayPowerState> mAnimator;
- private final AutomaticBrightnessController mAutomaticBrightnessController;
- private final WakelockController mWakelockController;
- private final BrightnessMappingStrategy mBrightnessMappingStrategy;
- private final HysteresisLevels mHysteresisLevels;
- private final ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController;
- private final HighBrightnessModeController mHighBrightnessModeController;
-
- private final HdrClamper mHdrClamper;
-
- private final BrightnessClamperController mClamperController;
-
- private final DisplayManagerFlags mFlags;
-
- TestInjector(DisplayPowerState dps, DualRampAnimator<DisplayPowerState> animator,
- AutomaticBrightnessController automaticBrightnessController,
- WakelockController wakelockController,
- BrightnessMappingStrategy brightnessMappingStrategy,
- HysteresisLevels hysteresisLevels,
- ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
- HighBrightnessModeController highBrightnessModeController,
- HdrClamper hdrClamper,
- BrightnessClamperController clamperController,
- DisplayManagerFlags flags) {
- mDisplayPowerState = dps;
- mAnimator = animator;
- mAutomaticBrightnessController = automaticBrightnessController;
- mWakelockController = wakelockController;
- mBrightnessMappingStrategy = brightnessMappingStrategy;
- mHysteresisLevels = hysteresisLevels;
- mScreenOffBrightnessSensorController = screenOffBrightnessSensorController;
- mHighBrightnessModeController = highBrightnessModeController;
- mHdrClamper = hdrClamper;
- mClamperController = clamperController;
- mFlags = flags;
- }
-
- @Override
- DisplayPowerController2.Clock getClock() {
- return mClock::now;
- }
-
- @Override
- DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade,
- int displayId, int displayState) {
- return mDisplayPowerState;
- }
-
- @Override
- DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps,
- FloatProperty<DisplayPowerState> firstProperty,
- FloatProperty<DisplayPowerState> secondProperty) {
- return mAnimator;
- }
-
- @Override
- WakelockController getWakelockController(int displayId,
- DisplayPowerCallbacks displayPowerCallbacks) {
- return mWakelockController;
- }
-
- @Override
- DisplayPowerProximityStateController getDisplayPowerProximityStateController(
- WakelockController wakelockController, DisplayDeviceConfig displayDeviceConfig,
- Looper looper, Runnable nudgeUpdatePowerState, int displayId,
- SensorManager sensorManager) {
- return new DisplayPowerProximityStateController(wakelockController,
- displayDeviceConfig, looper, nudgeUpdatePowerState, displayId,
- sensorManager,
- new DisplayPowerProximityStateController.Injector() {
- @Override
- DisplayPowerProximityStateController.Clock createClock() {
- return mClock::now;
- }
- });
- }
-
- @Override
- AutomaticBrightnessController getAutomaticBrightnessController(
- AutomaticBrightnessController.Callbacks callbacks, Looper looper,
- SensorManager sensorManager, Sensor lightSensor,
- SparseArray<BrightnessMappingStrategy> brightnessMappingStrategyMap,
- int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
- float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
- long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
- long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle,
- boolean resetAmbientLuxAfterWarmUpConfig,
- HysteresisLevels ambientBrightnessThresholds,
- HysteresisLevels screenBrightnessThresholds,
- HysteresisLevels ambientBrightnessThresholdsIdle,
- HysteresisLevels screenBrightnessThresholdsIdle, Context context,
- BrightnessRangeController brightnessRangeController,
- BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort,
- int ambientLightHorizonLong, float userLux, float userNits) {
- return mAutomaticBrightnessController;
- }
-
- @Override
- BrightnessMappingStrategy getDefaultModeBrightnessMapper(Context context,
- DisplayDeviceConfig displayDeviceConfig,
- DisplayWhiteBalanceController displayWhiteBalanceController) {
- return mBrightnessMappingStrategy;
- }
-
- @Override
- HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
- float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
- float[] darkeningThresholdLevels, float minDarkeningThreshold,
- float minBrighteningThreshold) {
- return mHysteresisLevels;
- }
-
- @Override
- HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
- float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
- float[] darkeningThresholdLevels, float minDarkeningThreshold,
- float minBrighteningThreshold, boolean potentialOldBrightnessRange) {
- return mHysteresisLevels;
- }
-
- @Override
- ScreenOffBrightnessSensorController getScreenOffBrightnessSensorController(
- SensorManager sensorManager, Sensor lightSensor, Handler handler,
- ScreenOffBrightnessSensorController.Clock clock, int[] sensorValueToLux,
- BrightnessMappingStrategy brightnessMapper) {
- return mScreenOffBrightnessSensorController;
- }
-
- @Override
- HighBrightnessModeController getHighBrightnessModeController(Handler handler, int width,
- int height, IBinder displayToken, String displayUniqueId, float brightnessMin,
- float brightnessMax, DisplayDeviceConfig.HighBrightnessModeData hbmData,
- HighBrightnessModeController.HdrBrightnessDeviceConfig hdrBrightnessCfg,
- Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata,
- Context context) {
- return mHighBrightnessModeController;
- }
-
- @Override
- BrightnessRangeController getBrightnessRangeController(
- HighBrightnessModeController hbmController, Runnable modeChangeCallback,
- DisplayDeviceConfig displayDeviceConfig, Handler handler,
- DisplayManagerFlags flags, IBinder displayToken, DisplayDeviceInfo info) {
- return new BrightnessRangeController(hbmController, modeChangeCallback,
- displayDeviceConfig, mHdrClamper, mFlags, displayToken, info);
- }
-
- @Override
- BrightnessClamperController getBrightnessClamperController(Handler handler,
- BrightnessClamperController.ClamperChangeListener clamperChangeListener,
- BrightnessClamperController.DisplayDeviceData data, Context context,
- DisplayManagerFlags flags) {
- return mClamperController;
- }
-
- @Override
- DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
- SensorManager sensorManager, Resources resources) {
- return mDisplayWhiteBalanceControllerMock;
- }
- }
-}
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 943862f..88a9758 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -18,6 +18,8 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+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.assertNotNull;
@@ -67,15 +69,16 @@
import android.view.DisplayInfo;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.internal.util.test.LocalServiceKeeperRule;
import com.android.modules.utils.testing.ExtendedMockitoRule;
+import com.android.server.LocalServices;
import com.android.server.am.BatteryStatsService;
import com.android.server.display.RampAnimator.DualRampAnimator;
import com.android.server.display.brightness.BrightnessEvent;
+import com.android.server.display.brightness.clamper.BrightnessClamperController;
+import com.android.server.display.brightness.clamper.HdrClamper;
import com.android.server.display.color.ColorDisplayService;
import com.android.server.display.config.SensorData;
import com.android.server.display.feature.DisplayManagerFlags;
@@ -85,6 +88,7 @@
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.testutils.OffsettableClock;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -147,7 +151,6 @@
private DisplayManagerFlags mDisplayManagerFlagsMock;
@Mock
private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession;
-
@Captor
private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
@@ -165,9 +168,6 @@
.build();
@Rule
- public LocalServiceKeeperRule mLocalServiceKeeperRule = new LocalServiceKeeperRule();
-
- @Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@Before
@@ -183,10 +183,9 @@
Settings.System.putFloatForUser(mContext.getContentResolver(),
Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0, UserHandle.USER_CURRENT);
- mLocalServiceKeeperRule.overrideLocalService(
- WindowManagerPolicy.class, mWindowManagerPolicyMock);
- mLocalServiceKeeperRule.overrideLocalService(
- ColorDisplayService.ColorDisplayServiceInternal.class, mCdsiMock);
+ addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
+ addLocalServiceMock(ColorDisplayService.ColorDisplayServiceInternal.class,
+ mCdsiMock);
mContext.addMockSystemService(PowerManager.class, mPowerManagerMock);
@@ -203,6 +202,12 @@
mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
}
+ @After
+ public void tearDown() {
+ LocalServices.removeServiceForTest(WindowManagerPolicy.class);
+ LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
+ }
+
@Test
public void testReleaseProxSuspendBlockersOnExit() throws Exception {
when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
@@ -222,19 +227,18 @@
advanceTime(1);
// two times, one for unfinished business and one for proximity
- verify(mDisplayPowerCallbacksMock).acquireSuspendBlocker(
- mHolder.dpc.getSuspendBlockerUnfinishedBusinessId(DISPLAY_ID));
- verify(mDisplayPowerCallbacksMock).acquireSuspendBlocker(
- mHolder.dpc.getSuspendBlockerProxDebounceId(DISPLAY_ID));
+ verify(mHolder.wakelockController, times(2)).acquireWakelock(
+ WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
+ verify(mHolder.wakelockController).acquireWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
mHolder.dpc.stop();
advanceTime(1);
-
// two times, one for unfinished business and one for proximity
- verify(mDisplayPowerCallbacksMock).releaseSuspendBlocker(
- mHolder.dpc.getSuspendBlockerUnfinishedBusinessId(DISPLAY_ID));
- verify(mDisplayPowerCallbacksMock).releaseSuspendBlocker(
- mHolder.dpc.getSuspendBlockerProxDebounceId(DISPLAY_ID));
+ verify(mHolder.wakelockController, times(2)).acquireWakelock(
+ WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
+ verify(mHolder.wakelockController).acquireWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
}
@Test
@@ -316,14 +320,13 @@
@Test
public void testProximitySensorListenerNotRegisteredForNonDefaultDisplay() {
- DisplayPowerControllerHolder followerDpc =
- createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-
when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
// send a display power request
DisplayPowerRequest dpr = new DisplayPowerRequest();
dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
dpr.useProximitySensor = true;
+ final DisplayPowerControllerHolder followerDpc =
+ createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
followerDpc.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
// Run updatePowerState
@@ -334,7 +337,6 @@
}
@Test
- @FlakyTest(bugId = 294107062)
public void testDisplayBrightnessFollowers_BothDpcsSupportNits() {
DisplayPowerControllerHolder followerDpc =
createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
@@ -363,13 +365,10 @@
when(mHolder.brightnessSetting.getBrightness()).thenReturn(leadBrightness);
listener.onBrightnessChanged(leadBrightness);
advanceTime(1); // Send messages, run updatePowerState
- verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+ verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(), anyFloat(), eq(false));
verify(followerDpc.animator).animateTo(eq(followerBrightness), anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+ anyFloat(), eq(false));
- when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(leadBrightness);
- when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(followerBrightness);
clearInvocations(mHolder.animator, followerDpc.animator);
// Test the same float scale value
@@ -388,7 +387,6 @@
}
@Test
- @FlakyTest(bugId = 294107062)
public void testDisplayBrightnessFollowers_FollowerDoesNotSupportNits() {
DisplayPowerControllerHolder followerDpc =
createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
@@ -421,7 +419,6 @@
}
@Test
- @FlakyTest(bugId = 294107062)
public void testDisplayBrightnessFollowers_LeadDpcDoesNotSupportNits() {
DisplayPowerControllerHolder followerDpc =
createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
@@ -452,7 +449,6 @@
}
@Test
- @FlakyTest(bugId = 294107062)
public void testDisplayBrightnessFollowers_NeitherDpcSupportsNits() {
DisplayPowerControllerHolder followerDpc =
createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
@@ -485,7 +481,6 @@
}
@Test
- @FlakyTest(bugId = 294107062)
public void testDisplayBrightnessFollowers_AutomaticBrightness() {
Settings.System.putInt(mContext.getContentResolver(),
Settings.System.SCREEN_BRIGHTNESS_MODE,
@@ -557,7 +552,6 @@
}
@Test
- @FlakyTest(bugId = 294107062)
public void testDisplayBrightnessFollowersRemoval_RemoveSingleFollower() {
DisplayPowerControllerHolder followerDpc = createDisplayPowerController(FOLLOWER_DISPLAY_ID,
FOLLOWER_UNIQUE_ID);
@@ -650,7 +644,6 @@
}
@Test
- @FlakyTest(bugId = 294107062)
public void testDisplayBrightnessFollowersRemoval_RemoveAllFollowers() {
DisplayPowerControllerHolder followerHolder =
createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
@@ -737,6 +730,82 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FAST_HDR_TRANSITIONS)
+ public void testDisplayBrightnessHdr_SkipAnimationOnHdrAppearance() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ final float sdrBrightness = 0.1f;
+ final float hdrBrightness = 0.3f;
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+ any(BrightnessEvent.class))).thenReturn(sdrBrightness);
+ when(mHolder.hdrClamper.getMaxBrightness()).thenReturn(1.0f);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator).animateTo(eq(sdrBrightness), eq(sdrBrightness),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+
+ when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(sdrBrightness);
+ when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(sdrBrightness);
+
+ when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR);
+ when(mHolder.hbmController.getHdrBrightnessValue()).thenReturn(hdrBrightness);
+ clearInvocations(mHolder.animator);
+
+ mHolder.dpc.updateBrightness();
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator).animateTo(eq(hdrBrightness), eq(sdrBrightness),
+ eq(BRIGHTNESS_RAMP_RATE_MINIMUM), eq(false));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_FAST_HDR_TRANSITIONS)
+ public void testDisplayBrightnessHdr_SkipAnimationOnHdrRemoval() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ final float sdrBrightness = 0.1f;
+ final float hdrBrightness = 0.3f;
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(mHolder.automaticBrightnessController.isInIdleMode()).thenReturn(true);
+ when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+ any(BrightnessEvent.class))).thenReturn(sdrBrightness);
+ when(mHolder.hdrClamper.getMaxBrightness()).thenReturn(1.0f);
+
+ when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR);
+ when(mHolder.hbmController.getHdrBrightnessValue()).thenReturn(hdrBrightness);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator).animateTo(eq(hdrBrightness), eq(sdrBrightness),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+
+ when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(hdrBrightness);
+ when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(sdrBrightness);
+ when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
+
+ clearInvocations(mHolder.animator);
+
+ mHolder.dpc.updateBrightness();
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator).animateTo(eq(sdrBrightness), eq(sdrBrightness),
+ eq(BRIGHTNESS_RAMP_RATE_MINIMUM), eq(false));
+ }
+
+ @Test
public void testDoesNotSetScreenStateForNonDefaultDisplayUntilBootCompleted() {
// We should still set screen state for the default display
DisplayPowerRequest dpr = new DisplayPowerRequest();
@@ -761,6 +830,8 @@
Settings.System.SCREEN_BRIGHTNESS_MODE,
Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
+
DisplayPowerRequest dpr = new DisplayPowerRequest();
dpr.policy = DisplayPowerRequest.POLICY_OFF;
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
@@ -798,6 +869,7 @@
Settings.System.SCREEN_BRIGHTNESS_MODE,
Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
DisplayPowerRequest dpr = new DisplayPowerRequest();
dpr.policy = DisplayPowerRequest.POLICY_DOZE;
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
@@ -842,7 +914,6 @@
Settings.System.putInt(mContext.getContentResolver(),
Settings.System.SCREEN_BRIGHTNESS_MODE,
Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-
mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ false);
DisplayPowerRequest dpr = new DisplayPowerRequest();
@@ -1048,7 +1119,6 @@
mContext.getOrCreateTestableResources().addOverride(
com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay,
true);
-
mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
@@ -1199,76 +1269,98 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FAST_HDR_TRANSITIONS)
- public void testDisplayBrightnessHdr_SkipAnimationOnHdrAppearance() {
- Settings.System.putInt(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_MODE,
- Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
- final float sdrBrightness = 0.1f;
- final float hdrBrightness = 0.3f;
- when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
- when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
- when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
- any(BrightnessEvent.class))).thenReturn(sdrBrightness);
+ public void testRampRateForHdrContent_HdrClamperOff() {
+ float hdrBrightness = 0.8f;
+ float clampedBrightness = 0.6f;
+ float transitionRate = 1.5f;
DisplayPowerRequest dpr = new DisplayPowerRequest();
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- verify(mHolder.animator).animateTo(eq(sdrBrightness), eq(sdrBrightness),
- eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-
- when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(sdrBrightness);
- when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(sdrBrightness);
-
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
+ when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR);
when(mHolder.hbmController.getHdrBrightnessValue()).thenReturn(hdrBrightness);
- clearInvocations(mHolder.animator);
+ when(mHolder.hdrClamper.getMaxBrightness()).thenReturn(clampedBrightness);
+ when(mHolder.hdrClamper.getTransitionRate()).thenReturn(transitionRate);
- mHolder.dpc.updateBrightness();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- verify(mHolder.animator).animateTo(eq(hdrBrightness), eq(sdrBrightness),
- eq(BRIGHTNESS_RAMP_RATE_MINIMUM), eq(false));
+ verify(mHolder.animator, atLeastOnce()).animateTo(eq(hdrBrightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FAST_HDR_TRANSITIONS)
- public void testDisplayBrightnessHdr_SkipAnimationOnHdrRemoval() {
- Settings.System.putInt(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_MODE,
- Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
- final float sdrBrightness = 0.1f;
- final float hdrBrightness = 0.3f;
- when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
- when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
- when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
- any(BrightnessEvent.class))).thenReturn(sdrBrightness);
-
- when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
- BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR);
- when(mHolder.hbmController.getHdrBrightnessValue()).thenReturn(hdrBrightness);
+ public void testRampRateForHdrContent_HdrClamperOn() {
+ float clampedBrightness = 0.6f;
+ float transitionRate = 1.5f;
+ when(mDisplayManagerFlagsMock.isHdrClamperEnabled()).thenReturn(true);
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true);
DisplayPowerRequest dpr = new DisplayPowerRequest();
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
+ when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
+ when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR);
+ when(mHolder.hbmController.getHdrBrightnessValue()).thenReturn(PowerManager.BRIGHTNESS_MAX);
+ when(mHolder.hdrClamper.getMaxBrightness()).thenReturn(clampedBrightness);
+ when(mHolder.hdrClamper.getTransitionRate()).thenReturn(transitionRate);
+
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- verify(mHolder.animator).animateTo(eq(hdrBrightness), eq(sdrBrightness),
- eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+ verify(mHolder.animator, atLeastOnce()).animateTo(eq(clampedBrightness), anyFloat(),
+ eq(transitionRate), eq(true));
+ }
- when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(hdrBrightness);
- when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(sdrBrightness);
- when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
- BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
+ @Test
+ public void testRampRateForClampersControllerApplied() {
+ float transitionRate = 1.5f;
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
+ when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
+ when(mHolder.clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer(
+ invocation -> DisplayBrightnessState.builder()
+ .setIsSlowChange(invocation.getArgument(2))
+ .setBrightness(invocation.getArgument(1))
+ .setMaxBrightness(PowerManager.BRIGHTNESS_MAX)
+ .setCustomAnimationRate(transitionRate).build());
- clearInvocations(mHolder.animator);
-
- mHolder.dpc.updateBrightness();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- verify(mHolder.animator).animateTo(eq(sdrBrightness), eq(sdrBrightness),
- eq(BRIGHTNESS_RAMP_RATE_MINIMUM), eq(false));
+ verify(mHolder.animator, atLeastOnce()).animateTo(anyFloat(), anyFloat(),
+ eq(transitionRate), anyBoolean());
+ }
+
+ @Test
+ public void testRampRateForClampersControllerNotApplied_ifDoze() {
+ float transitionRate = 1.5f;
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+ dpr.dozeScreenState = Display.STATE_UNKNOWN;
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
+ when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
+ when(mHolder.clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer(
+ invocation -> DisplayBrightnessState.builder()
+ .setIsSlowChange(invocation.getArgument(2))
+ .setBrightness(invocation.getArgument(1))
+ .setMaxBrightness(PowerManager.BRIGHTNESS_MAX)
+ .setCustomAnimationRate(transitionRate).build());
+
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator, atLeastOnce()).animateTo(anyFloat(), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE), anyBoolean());
+ verify(mHolder.animator, never()).animateTo(anyFloat(), anyFloat(),
+ eq(transitionRate), anyBoolean());
}
@Test
@@ -1285,14 +1377,14 @@
setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
mHolder.config, /* isEnabled= */ true);
-
verify(mHolder.animator).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX,
BRIGHTNESS_RAMP_DECREASE_MAX);
- // switch to idle
+ // switch to idle mode
mHolder.dpc.setAutomaticScreenBrightnessMode(AUTO_BRIGHTNESS_MODE_IDLE);
advanceTime(1);
+ // A second time, when switching to idle mode.
verify(mHolder.animator, times(2)).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX,
BRIGHTNESS_RAMP_DECREASE_MAX);
}
@@ -1301,6 +1393,8 @@
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1)
public void testRampMaxTimeInteractiveThenIdle_DifferentValues() {
when(mDisplayManagerFlagsMock.isAdaptiveTone1Enabled()).thenReturn(true);
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true);
+
// Send a display power request
DisplayPowerRequest dpr = new DisplayPowerRequest();
dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
@@ -1312,14 +1406,14 @@
setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
mHolder.config, /* isEnabled= */ true);
-
verify(mHolder.animator).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX,
BRIGHTNESS_RAMP_DECREASE_MAX);
- // switch to idle
+ // switch to idle mode
mHolder.dpc.setAutomaticScreenBrightnessMode(AUTO_BRIGHTNESS_MODE_IDLE);
advanceTime(1);
+ // A second time, when switching to idle mode.
verify(mHolder.animator).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX_IDLE,
BRIGHTNESS_RAMP_DECREASE_MAX_IDLE);
}
@@ -1332,11 +1426,9 @@
dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
dpr.useProximitySensor = true;
mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
-
// Run updatePowerState
advanceTime(1);
-
- // once on setup
+ // Once on setup
verify(mHolder.animator).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX,
BRIGHTNESS_RAMP_DECREASE_MAX);
@@ -1346,7 +1438,7 @@
// switch to idle mode
mHolder.dpc.setAutomaticScreenBrightnessMode(AUTO_BRIGHTNESS_MODE_IDLE);
- // second time when switching to idle screen brightness mode
+ // A second time when switching to idle mode.
verify(mHolder.animator, times(2)).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX,
BRIGHTNESS_RAMP_DECREASE_MAX);
}
@@ -1355,6 +1447,7 @@
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1)
public void testRampMaxTimeIdle_DifferentValues() {
when(mDisplayManagerFlagsMock.isAdaptiveTone1Enabled()).thenReturn(true);
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true);
// Send a display power request
DisplayPowerRequest dpr = new DisplayPowerRequest();
@@ -1374,6 +1467,7 @@
verify(mHolder.animator).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX_IDLE,
BRIGHTNESS_RAMP_DECREASE_MAX_IDLE);
}
+
@Test
public void testDozeScreenStateOverride_toSupportedOffloadStateFromDoze_DisplayStateChanges() {
// set up.
@@ -1451,6 +1545,89 @@
verify(mHolder.displayPowerState, never()).setScreenState(anyInt());
}
+ @Test
+ public void testBrightnessFromOffload() {
+ when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ float brightness = 0.34f;
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+ any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
+
+ mHolder.dpc.setBrightnessFromOffload(brightness);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ // One triggered by handleBrightnessModeChange, another triggered by
+ // setBrightnessFromOffload
+ verify(mHolder.animator, times(2)).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+ }
+
+ @Test
+ public void testSwitchToDozeAutoBrightnessMode() {
+ when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(true);
+ 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
+
+ // One triggered by handleBrightnessModeChange, another triggered by requestPowerState
+ verify(mHolder.automaticBrightnessController, times(2))
+ .switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
+
+ // Back to default mode
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.automaticBrightnessController).switchMode(AUTO_BRIGHTNESS_MODE_DEFAULT);
+ }
+
+ @Test
+ public void testDoesNotSwitchFromIdleToDozeAutoBrightnessMode() {
+ when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(true);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+ when(mHolder.automaticBrightnessController.isInIdleMode()).thenReturn(true);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.automaticBrightnessController, never())
+ .switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
+ }
+
+ @Test
+ public void testDoesNotSwitchDozeAutoBrightnessModeIfFeatureFlagOff() {
+ when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(false);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.automaticBrightnessController, never())
+ .switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
+ }
+
+ /**
+ * Creates a mock and registers it to {@link LocalServices}.
+ */
+ private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
+ LocalServices.removeServiceForTest(clazz);
+ LocalServices.addService(clazz, mock);
+ }
+
private void advanceTime(long timeMs) {
mClock.fastForward(timeMs);
mTestLooper.dispatchAll();
@@ -1505,10 +1682,10 @@
.thenReturn(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE);
when(displayDeviceConfigMock.getBrightnessRampSlowIncrease())
.thenReturn(BRIGHTNESS_RAMP_RATE_SLOW_INCREASE);
- when(displayDeviceConfigMock.getBrightnessRampSlowDecreaseIdle())
- .thenReturn(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE_IDLE);
when(displayDeviceConfigMock.getBrightnessRampSlowIncreaseIdle())
.thenReturn(BRIGHTNESS_RAMP_RATE_SLOW_INCREASE_IDLE);
+ when(displayDeviceConfigMock.getBrightnessRampSlowDecreaseIdle())
+ .thenReturn(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE_IDLE);
when(displayDeviceConfigMock.getBrightnessRampIncreaseMaxMillis())
.thenReturn(BRIGHTNESS_RAMP_INCREASE_MAX);
@@ -1531,18 +1708,28 @@
final DualRampAnimator<DisplayPowerState> animator = mock(DualRampAnimator.class);
final AutomaticBrightnessController automaticBrightnessController =
mock(AutomaticBrightnessController.class);
+ final WakelockController wakelockController = mock(WakelockController.class);
final BrightnessMappingStrategy brightnessMappingStrategy =
mock(BrightnessMappingStrategy.class);
final HysteresisLevels hysteresisLevels = mock(HysteresisLevels.class);
final ScreenOffBrightnessSensorController screenOffBrightnessSensorController =
mock(ScreenOffBrightnessSensorController.class);
final HighBrightnessModeController hbmController = mock(HighBrightnessModeController.class);
+ final HdrClamper hdrClamper = mock(HdrClamper.class);
+ BrightnessClamperController clamperController = mock(BrightnessClamperController.class);
when(hbmController.getCurrentBrightnessMax()).thenReturn(PowerManager.BRIGHTNESS_MAX);
+ when(clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer(
+ invocation -> DisplayBrightnessState.builder()
+ .setIsSlowChange(invocation.getArgument(2))
+ .setBrightness(invocation.getArgument(1))
+ .setMaxBrightness(PowerManager.BRIGHTNESS_MAX)
+ .setCustomAnimationRate(-1).build());
- DisplayPowerController.Injector injector = spy(new TestInjector(displayPowerState, animator,
- automaticBrightnessController, brightnessMappingStrategy, hysteresisLevels,
- screenOffBrightnessSensorController, hbmController));
+ TestInjector injector = spy(new TestInjector(displayPowerState, animator,
+ automaticBrightnessController, wakelockController, brightnessMappingStrategy,
+ hysteresisLevels, screenOffBrightnessSensorController, hbmController, hdrClamper,
+ clamperController, mDisplayManagerFlagsMock));
final LogicalDisplay display = mock(LogicalDisplay.class);
final DisplayDevice device = mock(DisplayDevice.class);
@@ -1555,12 +1742,14 @@
final DisplayPowerController dpc = new DisplayPowerController(
mContext, injector, mDisplayPowerCallbacksMock, mHandler,
mSensorManagerMock, mDisplayBlankerMock, display,
- mBrightnessTrackerMock, brightnessSetting, () -> {},
+ mBrightnessTrackerMock, brightnessSetting, () -> {
+ },
hbmMetadata, /* bootCompleted= */ false, mDisplayManagerFlagsMock);
return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting,
- animator, automaticBrightnessController, screenOffBrightnessSensorController,
- hbmController, hbmMetadata, brightnessMappingStrategy, injector, config);
+ animator, automaticBrightnessController, wakelockController,
+ screenOffBrightnessSensorController, hbmController, hdrClamper, clamperController,
+ hbmMetadata, brightnessMappingStrategy, injector, config);
}
/**
@@ -1574,8 +1763,12 @@
public final BrightnessSetting brightnessSetting;
public final DualRampAnimator<DisplayPowerState> animator;
public final AutomaticBrightnessController automaticBrightnessController;
+ public final WakelockController wakelockController;
public final ScreenOffBrightnessSensorController screenOffBrightnessSensorController;
public final HighBrightnessModeController hbmController;
+
+ public final HdrClamper hdrClamper;
+ public final BrightnessClamperController clamperController;
public final HighBrightnessModeMetadata hbmMetadata;
public final BrightnessMappingStrategy brightnessMappingStrategy;
public final DisplayPowerController.Injector injector;
@@ -1585,8 +1778,11 @@
DisplayPowerState displayPowerState, BrightnessSetting brightnessSetting,
DualRampAnimator<DisplayPowerState> animator,
AutomaticBrightnessController automaticBrightnessController,
+ WakelockController wakelockController,
ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
HighBrightnessModeController hbmController,
+ HdrClamper hdrClamper,
+ BrightnessClamperController clamperController,
HighBrightnessModeMetadata hbmMetadata,
BrightnessMappingStrategy brightnessMappingStrategy,
DisplayPowerController.Injector injector,
@@ -1597,8 +1793,11 @@
this.brightnessSetting = brightnessSetting;
this.animator = animator;
this.automaticBrightnessController = automaticBrightnessController;
+ this.wakelockController = wakelockController;
this.screenOffBrightnessSensorController = screenOffBrightnessSensorController;
this.hbmController = hbmController;
+ this.hdrClamper = hdrClamper;
+ this.clamperController = clamperController;
this.hbmMetadata = hbmMetadata;
this.brightnessMappingStrategy = brightnessMappingStrategy;
this.injector = injector;
@@ -1610,24 +1809,39 @@
private final DisplayPowerState mDisplayPowerState;
private final DualRampAnimator<DisplayPowerState> mAnimator;
private final AutomaticBrightnessController mAutomaticBrightnessController;
+ private final WakelockController mWakelockController;
private final BrightnessMappingStrategy mBrightnessMappingStrategy;
private final HysteresisLevels mHysteresisLevels;
private final ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController;
private final HighBrightnessModeController mHighBrightnessModeController;
+ private final HdrClamper mHdrClamper;
+
+ private final BrightnessClamperController mClamperController;
+
+ private final DisplayManagerFlags mFlags;
+
TestInjector(DisplayPowerState dps, DualRampAnimator<DisplayPowerState> animator,
AutomaticBrightnessController automaticBrightnessController,
+ WakelockController wakelockController,
BrightnessMappingStrategy brightnessMappingStrategy,
HysteresisLevels hysteresisLevels,
ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
- HighBrightnessModeController highBrightnessModeController) {
+ HighBrightnessModeController highBrightnessModeController,
+ HdrClamper hdrClamper,
+ BrightnessClamperController clamperController,
+ DisplayManagerFlags flags) {
mDisplayPowerState = dps;
mAnimator = animator;
mAutomaticBrightnessController = automaticBrightnessController;
+ mWakelockController = wakelockController;
mBrightnessMappingStrategy = brightnessMappingStrategy;
mHysteresisLevels = hysteresisLevels;
mScreenOffBrightnessSensorController = screenOffBrightnessSensorController;
mHighBrightnessModeController = highBrightnessModeController;
+ mHdrClamper = hdrClamper;
+ mClamperController = clamperController;
+ mFlags = flags;
}
@Override
@@ -1649,6 +1863,28 @@
}
@Override
+ WakelockController getWakelockController(int displayId,
+ DisplayPowerCallbacks displayPowerCallbacks) {
+ return mWakelockController;
+ }
+
+ @Override
+ DisplayPowerProximityStateController getDisplayPowerProximityStateController(
+ WakelockController wakelockController, DisplayDeviceConfig displayDeviceConfig,
+ Looper looper, Runnable nudgeUpdatePowerState, int displayId,
+ SensorManager sensorManager) {
+ return new DisplayPowerProximityStateController(wakelockController,
+ displayDeviceConfig, looper, nudgeUpdatePowerState, displayId,
+ sensorManager,
+ new DisplayPowerProximityStateController.Injector() {
+ @Override
+ DisplayPowerProximityStateController.Clock createClock() {
+ return mClock::now;
+ }
+ });
+ }
+
+ @Override
AutomaticBrightnessController getAutomaticBrightnessController(
AutomaticBrightnessController.Callbacks callbacks, Looper looper,
SensorManager sensorManager, Sensor lightSensor,
@@ -1710,6 +1946,23 @@
}
@Override
+ BrightnessRangeController getBrightnessRangeController(
+ HighBrightnessModeController hbmController, Runnable modeChangeCallback,
+ DisplayDeviceConfig displayDeviceConfig, Handler handler,
+ DisplayManagerFlags flags, IBinder displayToken, DisplayDeviceInfo info) {
+ return new BrightnessRangeController(hbmController, modeChangeCallback,
+ displayDeviceConfig, mHdrClamper, mFlags, displayToken, info);
+ }
+
+ @Override
+ BrightnessClamperController getBrightnessClamperController(Handler handler,
+ BrightnessClamperController.ClamperChangeListener clamperChangeListener,
+ BrightnessClamperController.DisplayDeviceData data, Context context,
+ DisplayManagerFlags flags) {
+ return mClamperController;
+ }
+
+ @Override
DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
SensorManager sensorManager, Resources resources) {
return mDisplayWhiteBalanceControllerMock;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java b/services/tests/displayservicetests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java
index 5c50acb..a8af98f 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java
@@ -72,14 +72,18 @@
@Test
public void testFindHighestRefreshRateForDefaultDisplay() {
+ when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(mDisplayMock);
+ assertEquals(120,
+ RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext),
+ /* delta= */ 0);
+ }
+
+ @Test
+ public void testFindHighestRefreshRate_DisplayIsNull() {
when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(null);
assertEquals(DEFAULT_REFRESH_RATE,
RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext),
/* delta= */ 0);
- when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(mDisplayMock);
- assertEquals(120,
- RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext),
- /* delta= */ 0);
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt
new file mode 100644
index 0000000..638924e
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.mode
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.hardware.display.BrightnessInfo
+import android.view.Display
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.SmallTest
+import com.android.server.display.DisplayDeviceConfig
+import com.android.server.display.feature.DisplayManagerFlags
+import com.android.server.testutils.TestHandler
+import com.google.common.truth.Truth.assertThat
+import com.google.testing.junit.testparameterinjector.TestParameter
+import com.google.testing.junit.testparameterinjector.TestParameterInjector
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito
+import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(TestParameterInjector::class)
+class BrightnessObserverTest {
+
+ @get:Rule
+ val mockitoRule = MockitoJUnit.rule()
+
+ private lateinit var spyContext: Context
+ private val mockInjector = mock<DisplayModeDirector.Injector>()
+ private val mockFlags = mock<DisplayManagerFlags>()
+ private val mockDeviceConfig = mock<DisplayDeviceConfig>()
+
+ private val testHandler = TestHandler(null)
+
+ @Before
+ fun setUp() {
+ spyContext = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+ }
+
+ @Test
+ fun testLowLightBlockingZoneVotes(@TestParameter testCase: LowLightTestCase) {
+ setUpLowBrightnessZone()
+ whenever(mockFlags.isVsyncLowLightVoteEnabled).thenReturn(testCase.vsyncLowLightVoteEnabled)
+ val displayModeDirector = DisplayModeDirector(
+ spyContext, testHandler, mockInjector, mockFlags)
+ val brightnessObserver = displayModeDirector.BrightnessObserver(
+ spyContext, testHandler, mockInjector, testCase.vrrSupported, mockFlags)
+
+ brightnessObserver.onRefreshRateSettingChangedLocked(0.0f, 120.0f)
+ brightnessObserver.updateBlockingZoneThresholds(mockDeviceConfig, false)
+ brightnessObserver.onDeviceConfigRefreshRateInLowZoneChanged(60)
+
+ brightnessObserver.onDisplayChanged(Display.DEFAULT_DISPLAY)
+
+ assertThat(displayModeDirector.getVote(VotesStorage.GLOBAL_ID,
+ Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH)).isEqualTo(testCase.expectedVote)
+ }
+
+ private fun setUpLowBrightnessZone() {
+ whenever(mockInjector.getBrightnessInfo(Display.DEFAULT_DISPLAY)).thenReturn(
+ BrightnessInfo(/* brightness = */ 0.05f, /* adjustedBrightness = */ 0.05f,
+ /* brightnessMinimum = */ 0.0f, /* brightnessMaximum = */ 1.0f,
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+ /* highBrightnessTransitionPoint = */ 1.0f,
+ BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE))
+ whenever(mockDeviceConfig.highDisplayBrightnessThresholds).thenReturn(floatArrayOf())
+ whenever(mockDeviceConfig.highAmbientBrightnessThresholds).thenReturn(floatArrayOf())
+ whenever(mockDeviceConfig.lowDisplayBrightnessThresholds).thenReturn(floatArrayOf(0.1f))
+ whenever(mockDeviceConfig.lowAmbientBrightnessThresholds).thenReturn(floatArrayOf(10f))
+ }
+
+ enum class LowLightTestCase(
+ val vrrSupported: Boolean,
+ val vsyncLowLightVoteEnabled: Boolean,
+ internal val expectedVote: Vote
+ ) {
+ ALL_ENABLED(true, true, CombinedVote(
+ listOf(DisableRefreshRateSwitchingVote(true),
+ SupportedModesVote(
+ listOf(SupportedModesVote.SupportedMode(60f, 60f),
+ SupportedModesVote.SupportedMode(120f, 120f)))))),
+ VRR_NOT_SUPPORTED(false, true, DisableRefreshRateSwitchingVote(true)),
+ VSYNC_VOTE_DISABLED(true, false, DisableRefreshRateSwitchingVote(true))
+ }
+}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index a0e5fd8..dd8c6a2 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
@@ -27,8 +27,6 @@
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertArrayEquals;
@@ -290,12 +288,14 @@
};
private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
+ private static final int DISPLAY_ID_2 = Display.DEFAULT_DISPLAY + 1;
private static final int MODE_ID = 1;
private static final float TRANSITION_POINT = 0.763f;
private static final float HBM_TRANSITION_POINT_INVALID = Float.POSITIVE_INFINITY;
private Context mContext;
+ private Resources mResources;
private FakesInjector mInjector;
private Handler mHandler;
@Rule
@@ -319,6 +319,8 @@
@Before
public void setUp() throws Exception {
mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+ mResources = mockResources();
+ when(mContext.getResources()).thenReturn(mResources);
final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext);
when(mContext.getContentResolver()).thenReturn(resolver);
mInjector = spy(new FakesInjector(mDisplayManagerInternalMock, mStatusBarMock,
@@ -326,6 +328,60 @@
mHandler = new Handler(Looper.getMainLooper());
}
+ private Resources mockResources() {
+ var resources = mock(Resources.class);
+ when(resources.getBoolean(R.bool.config_ignoreUdfpsVote))
+ .thenReturn(false);
+ when(resources.getBoolean(R.bool.config_refreshRateSynchronizationEnabled))
+ .thenReturn(false);
+ when(resources.getBoolean(R.bool.config_supportsDvrr))
+ .thenReturn(false);
+ when(resources.getInteger(R.integer.config_displayWhiteBalanceBrightnessFilterHorizon))
+ .thenReturn(10000);
+ when(resources.getInteger(R.integer.config_defaultPeakRefreshRate))
+ .thenReturn(0);
+ when(resources.getInteger(R.integer.config_externalDisplayPeakRefreshRate))
+ .thenReturn(0);
+ when(resources.getInteger(R.integer.config_externalDisplayPeakWidth))
+ .thenReturn(0);
+ when(resources.getInteger(R.integer.config_externalDisplayPeakHeight))
+ .thenReturn(0);
+ when(resources.getInteger(R.integer.config_fixedRefreshRateInHighZone))
+ .thenReturn(0);
+ when(resources.getInteger(R.integer.config_defaultRefreshRateInZone))
+ .thenReturn(0);
+ when(resources.getInteger(R.integer.config_defaultRefreshRate))
+ .thenReturn(60);
+ when(resources.getInteger(R.integer.config_defaultRefreshRateInHbmHdr))
+ .thenReturn(0);
+ when(resources.getInteger(R.integer.config_defaultRefreshRateInHbmSunlight))
+ .thenReturn(0);
+
+ when(resources.getString(R.string.config_displayLightSensorType))
+ .thenReturn(null);
+
+ when(resources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate))
+ .thenReturn(new int[]{});
+ when(resources.getIntArray(
+ R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate))
+ .thenReturn(new int[]{});
+ when(resources.getIntArray(
+ R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
+ .thenReturn(new int[]{});
+ when(resources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate))
+ .thenReturn(new int[]{});
+
+ doAnswer(invocation -> {
+ TypedValue value = invocation.getArgument(1);
+ value.type = TypedValue.TYPE_FLOAT;
+ value.data = Float.floatToIntBits(10f);
+ return null; // void method, so return null
+ }).when(resources).getValue(eq(R.dimen.config_displayWhiteBalanceBrightnessFilterIntercept),
+ any(), eq(true));
+
+ return resources;
+ }
+
private DisplayModeDirector createDirectorFromRefreshRateArray(
float[] refreshRates, int baseModeId) {
return createDirectorFromRefreshRateArray(refreshRates, baseModeId, refreshRates[0]);
@@ -1550,23 +1606,39 @@
public void testPeakRefreshRate_FlagEnabled() {
when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
.thenReturn(true);
- float highestRefreshRate = 130;
- doReturn(highestRefreshRate).when(() ->
- RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext));
DisplayModeDirector director =
- createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
+ 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),
+ new Display.Mode(/* modeId= */ 2, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 140),
+ };
+ 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);
setPeakRefreshRate(Float.POSITIVE_INFINITY);
- Vote vote = director.getVote(Display.DEFAULT_DISPLAY,
+ Vote vote1 = director.getVote(DISPLAY_ID,
Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
- assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
- highestRefreshRate);
+ Vote 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);
}
@Test
@@ -1584,32 +1656,85 @@
setPeakRefreshRate(peakRefreshRate);
- Vote vote = director.getVote(Display.DEFAULT_DISPLAY,
+ Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+ assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0,
+ /* frameRateHigh= */ peakRefreshRate);
+ }
+
+ @Test
+ public void testPeakRefreshRate_DisplayChanged() {
+ when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
+ .thenReturn(true);
+ DisplayModeDirector director =
+ new DisplayModeDirector(mContext, mHandler, mInjector, mDisplayManagerFlags);
+ director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+ mInjector.mDisplayInfo.supportedModes = 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),
+ };
+
+ Sensor lightSensor = createLightSensor();
+ SensorManager sensorManager = createMockSensorManager(lightSensor);
+ director.start(sensorManager);
+
+ setPeakRefreshRate(Float.POSITIVE_INFINITY);
+
+ Vote vote = director.getVote(DISPLAY_ID,
Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
- assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
- peakRefreshRate);
+ assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */ 130);
+
+ // The highest refresh rate of the display changes
+ mInjector.mDisplayInfo.supportedModes = new Display.Mode[] {
+ new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 60),
+ new Display.Mode(/* modeId= */ 2, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 140),
+ };
+ director.getDisplayObserver().onDisplayChanged(DISPLAY_ID);
+
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+ assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */ 140);
}
@Test
public void testMinRefreshRate_FlagEnabled() {
when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
.thenReturn(true);
- float highestRefreshRate = 130;
- doReturn(highestRefreshRate).when(() ->
- RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext));
DisplayModeDirector director =
- createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
+ 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),
+ new Display.Mode(/* modeId= */ 2, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 140),
+ };
+ 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);
setMinRefreshRate(Float.POSITIVE_INFINITY);
- Vote vote = director.getVote(Display.DEFAULT_DISPLAY,
+ Vote vote1 = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+ Vote vote2 = director.getVote(DISPLAY_ID_2,
Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
- assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ highestRefreshRate,
+ assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 130,
+ /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+ assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 140,
/* frameRateHigh= */ Float.POSITIVE_INFINITY);
}
@@ -1628,13 +1753,50 @@
setMinRefreshRate(minRefreshRate);
- Vote vote = director.getVote(Display.DEFAULT_DISPLAY,
- Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+ Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ minRefreshRate,
/* frameRateHigh= */ Float.POSITIVE_INFINITY);
}
@Test
+ public void testMinRefreshRate_DisplayChanged() {
+ when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
+ .thenReturn(true);
+ DisplayModeDirector director =
+ new DisplayModeDirector(mContext, mHandler, mInjector, mDisplayManagerFlags);
+ director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+ mInjector.mDisplayInfo.supportedModes = 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),
+ };
+
+ Sensor lightSensor = createLightSensor();
+ SensorManager sensorManager = createMockSensorManager(lightSensor);
+ director.start(sensorManager);
+
+ setMinRefreshRate(Float.POSITIVE_INFINITY);
+
+ Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+ assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 130,
+ /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+
+ // The highest refresh rate of the display changes
+ mInjector.mDisplayInfo.supportedModes = new Display.Mode[] {
+ new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 60),
+ new Display.Mode(/* modeId= */ 2, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 140),
+ };
+ director.getDisplayObserver().onDisplayChanged(DISPLAY_ID);
+
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+ assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 140,
+ /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+ }
+
+ @Test
public void testSensorRegistration() {
// First, configure brightness zones or DMD won't register for sensor data.
final FakeDeviceConfig config = mInjector.getDeviceConfig();
@@ -2806,31 +2968,31 @@
@Test
public void testNotifyDefaultDisplayDeviceUpdated() {
- Resources resources = mock(Resources.class);
- when(mContext.getResources()).thenReturn(resources);
- when(resources.getInteger(com.android.internal.R.integer.config_defaultPeakRefreshRate))
+ when(mResources.getInteger(com.android.internal.R.integer.config_defaultPeakRefreshRate))
.thenReturn(75);
- when(resources.getInteger(R.integer.config_defaultRefreshRate))
+ when(mResources.getInteger(R.integer.config_defaultRefreshRate))
.thenReturn(45);
- when(resources.getInteger(R.integer.config_fixedRefreshRateInHighZone))
+ when(mResources.getInteger(R.integer.config_fixedRefreshRateInHighZone))
.thenReturn(65);
- when(resources.getInteger(R.integer.config_defaultRefreshRateInZone))
+ when(mResources.getInteger(R.integer.config_defaultRefreshRateInZone))
.thenReturn(85);
- when(resources.getInteger(R.integer.config_defaultRefreshRateInHbmHdr))
+ when(mResources.getInteger(R.integer.config_defaultRefreshRateInHbmHdr))
.thenReturn(95);
- when(resources.getInteger(R.integer.config_defaultRefreshRateInHbmSunlight))
+ when(mResources.getInteger(R.integer.config_defaultRefreshRateInHbmSunlight))
.thenReturn(100);
- when(resources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate))
+ when(mResources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate))
.thenReturn(new int[]{5});
- when(resources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate))
+ when(mResources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate))
.thenReturn(new int[]{10});
when(
- resources.getIntArray(R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate))
+ mResources.getIntArray(
+ R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate))
.thenReturn(new int[]{250});
when(
- resources.getIntArray(R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
+ mResources.getIntArray(
+ R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
.thenReturn(new int[]{7000});
- when(resources.getInteger(
+ when(mResources.getInteger(
com.android.internal.R.integer.config_displayWhiteBalanceBrightnessFilterHorizon))
.thenReturn(3);
ArgumentCaptor<TypedValue> valueArgumentCaptor = ArgumentCaptor.forClass(TypedValue.class);
@@ -2838,7 +3000,7 @@
valueArgumentCaptor.getValue().type = 4;
valueArgumentCaptor.getValue().data = 13;
return null;
- }).when(resources).getValue(eq(com.android.internal.R.dimen
+ }).when(mResources).getValue(eq(com.android.internal.R.dimen
.config_displayWhiteBalanceBrightnessFilterIntercept),
valueArgumentCaptor.capture(), eq(true));
DisplayModeDirector director =
@@ -3329,7 +3491,7 @@
public static class FakesInjector implements DisplayModeDirector.Injector {
private final FakeDeviceConfig mDeviceConfig;
private final DisplayInfo mDisplayInfo;
- private final Display mDisplay;
+ private final Map<Integer, Display> mDisplays;
private boolean mDisplayInfoValid = true;
private final DisplayManagerInternal mDisplayManagerInternal;
private final StatusBarManagerInternal mStatusBarManagerInternal;
@@ -3350,7 +3512,8 @@
mDisplayInfo.defaultModeId = MODE_ID;
mDisplayInfo.supportedModes = new Display.Mode[] {new Display.Mode(MODE_ID,
800, 600, /* refreshRate= */ 60)};
- mDisplay = createDisplay(DISPLAY_ID);
+ mDisplays = Map.of(DISPLAY_ID, createDisplay(DISPLAY_ID),
+ DISPLAY_ID_2, createDisplay(DISPLAY_ID_2));
mDisplayManagerInternal = displayManagerInternal;
mStatusBarManagerInternal = statusBarManagerInternal;
mSensorManagerInternal = sensorManagerInternal;
@@ -3381,12 +3544,12 @@
@Override
public Display getDisplay(int displayId) {
- return mDisplay;
+ return mDisplays.get(displayId);
}
@Override
public Display[] getDisplays() {
- return new Display[] { mDisplay };
+ return mDisplays.values().toArray(new Display[0]);
}
@Override
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
index ff91d34..92016df 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
@@ -20,11 +20,10 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.Mode.INVALID_MODE_ID;
-
import static com.android.server.display.mode.DisplayModeDirector.SYNCHRONIZED_REFRESH_RATE_TOLERANCE;
import static com.android.server.display.mode.Vote.PRIORITY_LIMIT_MODE;
-import static com.android.server.display.mode.Vote.PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE;
import static com.android.server.display.mode.Vote.PRIORITY_SYNCHRONIZED_REFRESH_RATE;
+import static com.android.server.display.mode.Vote.PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE;
import static com.android.server.display.mode.VotesStorage.GLOBAL_ID;
import static com.google.common.truth.Truth.assertThat;
@@ -43,6 +42,7 @@
import android.os.Handler;
import android.os.Looper;
import android.provider.DeviceConfigInterface;
+import android.test.mock.MockContentResolver;
import android.view.Display;
import android.view.DisplayInfo;
@@ -51,21 +51,26 @@
import androidx.test.filters.SmallTest;
import com.android.internal.R;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.sensors.SensorManagerInternal;
+import junitparams.JUnitParamsRunner;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import junitparams.JUnitParamsRunner;
-
-
@SmallTest
@RunWith(JUnitParamsRunner.class)
public class DisplayObserverTest {
+ @Rule
+ public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+
private static final int EXTERNAL_DISPLAY = 1;
private static final int MAX_WIDTH = 1920;
private static final int MAX_HEIGHT = 1080;
@@ -120,6 +125,8 @@
mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
mResources = mock(Resources.class);
when(mContext.getResources()).thenReturn(mResources);
+ MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext);
+ when(mContext.getContentResolver()).thenReturn(resolver);
when(mResources.getInteger(R.integer.config_externalDisplayPeakRefreshRate))
.thenReturn(0);
when(mResources.getInteger(R.integer.config_externalDisplayPeakWidth))
diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java
index b363fd4..d781433 100644
--- a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java
@@ -20,11 +20,13 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.media.projection.MediaProjectionInfo;
@@ -35,6 +37,7 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableLooper.RunWithLooper;
+import android.util.ArraySet;
import androidx.test.filters.SmallTest;
@@ -52,7 +55,6 @@
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
-import java.util.Collections;
import java.util.Set;
@SmallTest
@@ -68,6 +70,8 @@
private static final int NOTIFICATION_UID_1 = 5;
private static final int NOTIFICATION_UID_2 = 6;
+ private static final ArraySet<PackageInfo> EMPTY_SET = new ArraySet<>();
+
@Rule
public final TestableContext mContext =
new TestableContext(getInstrumentation().getTargetContext(), null);
@@ -107,6 +111,9 @@
mSensitiveContentProtectionManagerService.mNotificationListener =
spy(mSensitiveContentProtectionManagerService.mNotificationListener);
+ doCallRealMethod()
+ .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .onListenerConnected();
// Setup RankingMap and two possilbe rankings
when(mSensitiveRanking.hasSensitiveContent()).thenReturn(true);
@@ -128,7 +135,7 @@
mSensitiveContentProtectionManagerService.onDestroy();
}
- private Set<PackageInfo> setupSensitiveNotification() {
+ private ArraySet<PackageInfo> setupSensitiveNotification() {
// Setup Notification Values
when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1);
when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
@@ -149,10 +156,11 @@
when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_2)))
.thenReturn(mNonSensitiveRanking);
- return Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1));
+ return new ArraySet<>(
+ Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1)));
}
- private Set<PackageInfo> setupMultipleSensitiveNotificationsFromSamePackageAndUid() {
+ private ArraySet<PackageInfo> setupMultipleSensitiveNotificationsFromSamePackageAndUid() {
// Setup Notification Values
when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1);
when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
@@ -173,10 +181,11 @@
when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_2)))
.thenReturn(mSensitiveRanking);
- return Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1));
+ return new ArraySet<>(
+ Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1)));
}
- private Set<PackageInfo> setupMultipleSensitiveNotificationsFromDifferentPackage() {
+ private ArraySet<PackageInfo> setupMultipleSensitiveNotificationsFromDifferentPackage() {
// Setup Notification Values
when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1);
when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
@@ -197,11 +206,12 @@
when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_2)))
.thenReturn(mSensitiveRanking);
- return Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1),
- new PackageInfo(NOTIFICATION_PKG_2, NOTIFICATION_UID_1));
+ return new ArraySet<>(
+ Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1),
+ new PackageInfo(NOTIFICATION_PKG_2, NOTIFICATION_UID_1)));
}
- private Set<PackageInfo> setupMultipleSensitiveNotificationsFromDifferentUid() {
+ private ArraySet<PackageInfo> setupMultipleSensitiveNotificationsFromDifferentUid() {
// Setup Notification Values
when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1);
when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
@@ -222,8 +232,9 @@
when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_2)))
.thenReturn(mSensitiveRanking);
- return Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1),
- new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_2));
+ return new ArraySet<>(
+ Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1),
+ new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_2)));
}
private void setupNoSensitiveNotifications() {
@@ -251,11 +262,11 @@
@Test
public void mediaProjectionOnStart_onProjectionStart_setWmBlockedPackages() {
- Set<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
+ ArraySet<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
- verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+ verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
}
@Test
@@ -264,7 +275,7 @@
mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
- verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
}
@Test
@@ -273,37 +284,37 @@
mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
- verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
}
@Test
public void mediaProjectionOnStart_multipleNotifications_setWmBlockedPackages() {
- Set<PackageInfo> expectedBlockedPackages =
+ ArraySet<PackageInfo> expectedBlockedPackages =
setupMultipleSensitiveNotificationsFromSamePackageAndUid();
mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
- verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+ verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
}
@Test
public void mediaProjectionOnStart_multiplePackages_setWmBlockedPackages() {
- Set<PackageInfo> expectedBlockedPackages =
+ ArraySet<PackageInfo> expectedBlockedPackages =
setupMultipleSensitiveNotificationsFromDifferentPackage();
mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
- verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+ verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
}
@Test
public void mediaProjectionOnStart_multipleUid_setWmBlockedPackages() {
- Set<PackageInfo> expectedBlockedPackages =
+ ArraySet<PackageInfo> expectedBlockedPackages =
setupMultipleSensitiveNotificationsFromDifferentUid();
mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
- verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+ verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
}
@Test
@@ -316,12 +327,12 @@
mMediaProjectionCallbackCaptor.getValue().onStop(mediaProjectionInfo);
- verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ verify(mWindowManager).clearBlockedApps();
}
@Test
public void mediaProjectionOnStart_afterOnStop_onProjectionStart_setWmBlockedPackages() {
- Set<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
+ ArraySet<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
MediaProjectionInfo mediaProjectionInfo = mock(MediaProjectionInfo.class);
mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo);
@@ -330,7 +341,7 @@
mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo);
- verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+ verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
}
@Test
@@ -341,7 +352,7 @@
mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
- verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
}
@Test
@@ -352,7 +363,7 @@
mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
- verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
}
@Test
@@ -363,7 +374,7 @@
mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
- verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
}
@Test
@@ -376,6 +387,314 @@
mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
- verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ }
+
+ @Test
+ public void nlsOnListenerConnected_projectionNotStarted_noop() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ setupSensitiveNotification();
+
+ mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
+
+ verifyZeroInteractions(mWindowManager);
+ }
+
+ @Test
+ public void nlsOnListenerConnected_projectionStopped_noop() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ setupSensitiveNotification();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStop(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+
+ mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
+
+ verifyZeroInteractions(mWindowManager);
+ }
+
+ @Test
+ public void nlsOnListenerConnected_projectionStarted_setWmBlockedPackages() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ ArraySet<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+
+ mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
+
+ verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
+ }
+
+ @Test
+ public void nlsOnListenerConnected_noSensitiveNotifications_noBlockedPackages() {
+ setupNoSensitiveNotifications();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+
+ mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
+
+ verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ }
+
+ @Test
+ public void nlsOnListenerConnected_noNotifications_noBlockedPackages() {
+ setupNoNotifications();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+
+ mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
+
+ verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ }
+
+ @Test
+ public void nlsOnListenerConnected_nullRankingMap_noBlockedPackages() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ setupSensitiveNotification();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+ doReturn(null)
+ .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .getCurrentRanking();
+
+ mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
+
+ verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ }
+
+ @Test
+ public void nlsOnListenerConnected_missingRanking_noBlockedPackages() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ setupSensitiveNotification();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+ when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1))).thenReturn(null);
+ doReturn(mRankingMap)
+ .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .getCurrentRanking();
+
+ mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
+
+ verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ }
+
+ @Test
+ public void nlsOnNotificationRankingUpdate_projectionNotStarted_noop() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ setupSensitiveNotification();
+
+ mSensitiveContentProtectionManagerService.mNotificationListener
+ .onNotificationRankingUpdate(mRankingMap);
+
+ verifyZeroInteractions(mWindowManager);
+ }
+
+ @Test
+ public void nlsOnNotificationRankingUpdate_projectionStopped_noop() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ setupSensitiveNotification();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStop(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+
+ mSensitiveContentProtectionManagerService.mNotificationListener
+ .onNotificationRankingUpdate(mRankingMap);
+
+ verifyZeroInteractions(mWindowManager);
+ }
+
+ @Test
+ public void nlsOnNotificationRankingUpdate_projectionStarted_setWmBlockedPackages() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ ArraySet<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+
+ mSensitiveContentProtectionManagerService.mNotificationListener
+ .onNotificationRankingUpdate(mRankingMap);
+
+ verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
+ }
+
+ @Test
+ public void nlsOnNotificationRankingUpdate_noSensitiveNotifications_noBlockedPackages() {
+ setupNoSensitiveNotifications();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+
+ mSensitiveContentProtectionManagerService.mNotificationListener
+ .onNotificationRankingUpdate(mRankingMap);
+
+ verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ }
+
+ @Test
+ public void nlsOnNotificationRankingUpdate_noNotifications_noBlockedPackages() {
+ setupNoNotifications();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+
+ mSensitiveContentProtectionManagerService.mNotificationListener
+ .onNotificationRankingUpdate(mRankingMap);
+
+ verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ }
+
+ @Test
+ public void nlsOnNotificationRankingUpdate_nullRankingMap_noBlockedPackages() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ setupSensitiveNotification();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+
+ mSensitiveContentProtectionManagerService.mNotificationListener
+ .onNotificationRankingUpdate(null);
+
+ verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ }
+
+ @Test
+ public void nlsOnNotificationRankingUpdate_missingRanking_noBlockedPackages() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ setupSensitiveNotification();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+ when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1))).thenReturn(null);
+ doReturn(mRankingMap)
+ .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .getCurrentRanking();
+
+ mSensitiveContentProtectionManagerService.mNotificationListener
+ .onNotificationRankingUpdate(mRankingMap);
+
+ verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ }
+
+ @Test
+ public void nlsOnNotificationRankingUpdate_getActiveNotificationsThrows_noBlockedPackages() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ setupSensitiveNotification();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+
+ doThrow(SecurityException.class)
+ .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .getActiveNotifications();
+
+ mSensitiveContentProtectionManagerService.mNotificationListener
+ .onNotificationRankingUpdate(mRankingMap);
+
+ verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ }
+
+ @Test
+ public void nlsOnNotificationPosted_projectionNotStarted_noop() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ setupSensitiveNotification();
+
+ mSensitiveContentProtectionManagerService.mNotificationListener
+ .onNotificationPosted(mNotification1, mRankingMap);
+
+ verifyZeroInteractions(mWindowManager);
+ }
+
+ @Test
+ public void nlsOnNotificationPosted_projectionStopped_noop() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ setupSensitiveNotification();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStop(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+
+ mSensitiveContentProtectionManagerService.mNotificationListener
+ .onNotificationPosted(mNotification1, mRankingMap);
+
+ verifyZeroInteractions(mWindowManager);
+ }
+
+ @Test
+ public void nlsOnNotificationPosted_projectionStarted_setWmBlockedPackages() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ setupSensitiveNotification();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+
+ mSensitiveContentProtectionManagerService.mNotificationListener
+ .onNotificationPosted(mNotification1, mRankingMap);
+
+ ArraySet<PackageInfo> expectedBlockedPackages = new ArraySet<>(
+ Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1)));
+ verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
+ }
+
+ @Test
+ public void nlsOnNotificationPosted_noSensitiveNotifications_noBlockedPackages() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ setupSensitiveNotification();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+
+ mSensitiveContentProtectionManagerService.mNotificationListener
+ .onNotificationPosted(mNotification2, mRankingMap);
+
+ verifyZeroInteractions(mWindowManager);
+ }
+
+ @Test
+ public void nlsOnNotificationPosted_noNotifications_noBlockedPackages() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ setupSensitiveNotification();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+
+ mSensitiveContentProtectionManagerService.mNotificationListener
+ .onNotificationPosted(null, mRankingMap);
+
+ verifyZeroInteractions(mWindowManager);
+ }
+
+ @Test
+ public void nlsOnNotificationPosted_nullRankingMap_noBlockedPackages() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ setupSensitiveNotification();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+
+ mSensitiveContentProtectionManagerService.mNotificationListener
+ .onNotificationPosted(mNotification1, null);
+
+ verifyZeroInteractions(mWindowManager);
+ }
+
+ @Test
+ public void nlsOnNotificationPosted_missingRanking_noBlockedPackages() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ setupSensitiveNotification();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+ when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1))).thenReturn(null);
+
+ mSensitiveContentProtectionManagerService.mNotificationListener
+ .onNotificationPosted(mNotification1, mRankingMap);
+
+ verifyZeroInteractions(mWindowManager);
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActiveServicesTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActiveServicesTest.java
index e2c338a..7e1dc08 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActiveServicesTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActiveServicesTest.java
@@ -202,7 +202,7 @@
final ServiceInfo regularService = new ServiceInfo();
regularService.processName = "com.foo";
String processName = ActiveServices.getProcessNameForService(regularService, null, null,
- null, false, false);
+ null, false, false, false);
assertEquals("com.foo", processName);
// Isolated service
@@ -211,29 +211,90 @@
isolatedService.flags = ServiceInfo.FLAG_ISOLATED_PROCESS;
final ComponentName component = new ComponentName("com.foo", "barService");
processName = ActiveServices.getProcessNameForService(isolatedService, component,
- null, null, false, false);
+ null, null, false, false, false);
assertEquals("com.foo:barService", processName);
+ // Isolated Service in package private process.
+ final ServiceInfo isolatedService1 = new ServiceInfo();
+ isolatedService1.processName = "com.foo:trusted_isolated";
+ isolatedService1.flags = ServiceInfo.FLAG_ISOLATED_PROCESS;
+ final ComponentName componentName = new ComponentName("com.foo", "barService");
+ processName = ActiveServices.getProcessNameForService(isolatedService1, componentName,
+ null, null, false, false, false);
+ assertEquals("com.foo:trusted_isolated:barService", processName);
+
+ // Isolated service in package-private shared process (main process)
+ final ServiceInfo isolatedPackageSharedService = new ServiceInfo();
+ final ComponentName componentName1 = new ComponentName("com.foo", "barService");
+ isolatedPackageSharedService.processName = "com.foo";
+ isolatedPackageSharedService.applicationInfo = new ApplicationInfo();
+ isolatedPackageSharedService.applicationInfo.processName = "com.foo";
+ isolatedPackageSharedService.flags = ServiceInfo.FLAG_ISOLATED_PROCESS;
+ String packageSharedIsolatedProcessName = ActiveServices.getProcessNameForService(
+ isolatedPackageSharedService, componentName1, null, null, false, false, true);
+ assertEquals("com.foo:barService", packageSharedIsolatedProcessName);
+
+ // Isolated service in package-private shared process
+ final ServiceInfo isolatedPackageSharedService1 = new ServiceInfo(
+ isolatedPackageSharedService);
+ isolatedPackageSharedService1.processName = "com.foo:trusted_isolated";
+ isolatedPackageSharedService1.applicationInfo = new ApplicationInfo();
+ isolatedPackageSharedService1.applicationInfo.processName = "com.foo";
+ isolatedPackageSharedService1.flags = ServiceInfo.FLAG_ISOLATED_PROCESS;
+ packageSharedIsolatedProcessName = ActiveServices.getProcessNameForService(
+ isolatedPackageSharedService1, componentName1, null, null, false, false, true);
+ assertEquals("com.foo:trusted_isolated", packageSharedIsolatedProcessName);
+
+
+ // Bind another one in the same isolated process
+ final ServiceInfo isolatedPackageSharedService2 = new ServiceInfo(
+ isolatedPackageSharedService1);
+ packageSharedIsolatedProcessName = ActiveServices.getProcessNameForService(
+ isolatedPackageSharedService2, componentName1, null, null, false, false, true);
+ assertEquals("com.foo:trusted_isolated", packageSharedIsolatedProcessName);
+
+ // Simulate another app trying to do the bind.
+ final ServiceInfo isolatedPackageSharedService3 = new ServiceInfo(
+ isolatedPackageSharedService1);
+ final String auxCallingPackage = "com.bar";
+ packageSharedIsolatedProcessName = ActiveServices.getProcessNameForService(
+ isolatedPackageSharedService3, componentName1, auxCallingPackage, null,
+ false, false, true);
+ assertEquals("com.foo:trusted_isolated", packageSharedIsolatedProcessName);
+
+ // Simulate another app owning the service
+ final ServiceInfo isolatedOtherPackageSharedService = new ServiceInfo(
+ isolatedPackageSharedService1);
+ final ComponentName componentName2 = new ComponentName("com.bar", "barService");
+ isolatedOtherPackageSharedService.processName = "com.bar:isolated";
+ isolatedPackageSharedService.applicationInfo.processName = "com.bar";
+ final String mainCallingPackage = "com.foo";
+ packageSharedIsolatedProcessName = ActiveServices.getProcessNameForService(
+ isolatedOtherPackageSharedService, componentName2, mainCallingPackage,
+ null, false, false, true);
+ assertEquals("com.bar:isolated", packageSharedIsolatedProcessName);
+
// Isolated service in shared isolated process
final ServiceInfo isolatedServiceShared1 = new ServiceInfo();
isolatedServiceShared1.flags = ServiceInfo.FLAG_ISOLATED_PROCESS;
final String instanceName = "pool";
final String callingPackage = "com.foo";
final String sharedIsolatedProcessName1 = ActiveServices.getProcessNameForService(
- isolatedServiceShared1, null, callingPackage, instanceName, false, true);
+ isolatedServiceShared1, null, callingPackage, instanceName, false, true, false);
assertEquals("com.foo:ishared:pool", sharedIsolatedProcessName1);
// Bind another one in the same isolated process
final ServiceInfo isolatedServiceShared2 = new ServiceInfo(isolatedServiceShared1);
final String sharedIsolatedProcessName2 = ActiveServices.getProcessNameForService(
- isolatedServiceShared2, null, callingPackage, instanceName, false, true);
+ isolatedServiceShared2, null, callingPackage, instanceName, false, true, false);
assertEquals(sharedIsolatedProcessName1, sharedIsolatedProcessName2);
// Simulate another app trying to do the bind
final ServiceInfo isolatedServiceShared3 = new ServiceInfo(isolatedServiceShared1);
final String otherCallingPackage = "com.bar";
final String sharedIsolatedProcessName3 = ActiveServices.getProcessNameForService(
- isolatedServiceShared3, null, otherCallingPackage, instanceName, false, true);
+ isolatedServiceShared3, null, otherCallingPackage, instanceName, false, true,
+ false);
Assert.assertNotEquals(sharedIsolatedProcessName2, sharedIsolatedProcessName3);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
new file mode 100644
index 0000000..7d3a110
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
@@ -0,0 +1,587 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.am;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.server.am.ActivityManagerService.Injector;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import android.app.ApplicationStartInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManagerInternal;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.platform.test.annotations.Presubmit;
+import android.text.TextUtils;
+
+import com.android.server.LocalServices;
+import com.android.server.ServiceThread;
+import com.android.server.appop.AppOpsService;
+import com.android.server.wm.ActivityTaskManagerService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+
+/**
+ * Test class for {@link android.app.ApplicationStartInfo}.
+ *
+ * Build/Install/Run:
+ * atest ApplicationStartInfoTest
+ */
+@Presubmit
+public class ApplicationStartInfoTest {
+
+ private static final String TAG = ApplicationStartInfoTest.class.getSimpleName();
+ private static final ComponentName COMPONENT = new ComponentName("com.android.test", ".Foo");
+
+ @Rule public ServiceThreadRule mServiceThreadRule = new ServiceThreadRule();
+ @Mock private AppOpsService mAppOpsService;
+ @Mock private PackageManagerInternal mPackageManagerInt;
+
+ private Context mContext = getInstrumentation().getTargetContext();
+ private TestInjector mInjector;
+ private ActivityManagerService mAms;
+ private ProcessList mProcessList;
+ private AppStartInfoTracker mAppStartInfoTracker;
+ private Handler mHandler;
+ private HandlerThread mHandlerThread;
+
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ mProcessList = spy(new ProcessList());
+ mAppStartInfoTracker = spy(new AppStartInfoTracker());
+ mAppStartInfoTracker.mEnabled = true;
+ setFieldValue(ProcessList.class, mProcessList, "mAppStartInfoTracker",
+ mAppStartInfoTracker);
+ mInjector = new TestInjector(mContext);
+ mAms = new ActivityManagerService(mInjector, mServiceThreadRule.getThread());
+ mAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
+ mAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
+ mAms.mAtmInternal = spy(mAms.mActivityTaskManager.getAtmInternal());
+ mAms.mPackageManagerInt = mPackageManagerInt;
+ mAppStartInfoTracker.mService = mAms;
+ doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
+ doReturn("com.android.test").when(mPackageManagerInt).getNameForUid(anyInt());
+ // Remove stale instance of PackageManagerInternal if there is any
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
+ }
+
+ @After
+ public void tearDown() {
+ mHandlerThread.quit();
+ }
+
+ @Test
+ public void testApplicationStartInfo() throws Exception {
+ mAppStartInfoTracker.clearProcessStartInfo(true);
+ mAppStartInfoTracker.mAppStartInfoLoaded.set(true);
+ mAppStartInfoTracker.mAppStartInfoHistoryListSize =
+ mAppStartInfoTracker.APP_START_INFO_HISTORY_LIST_SIZE;
+ mAppStartInfoTracker.mProcStartStoreDir = new File(mContext.getFilesDir(),
+ AppStartInfoTracker.APP_START_STORE_DIR);
+ assertTrue(FileUtils.createDir(mAppStartInfoTracker.mProcStartStoreDir));
+ mAppStartInfoTracker.mProcStartInfoFile = new File(mAppStartInfoTracker.mProcStartStoreDir,
+ AppStartInfoTracker.APP_START_INFO_FILE);
+
+ doNothing().when(mAppStartInfoTracker).schedulePersistProcessStartInfo(anyBoolean());
+
+ final int app1Uid = 10123;
+ final int app1Pid1 = 12345;
+ final int app1Pid2 = 12346;
+ final int app1DefiningUid = 23456;
+ final int app1UidUser2 = 1010123;
+ final int app1PidUser2 = 12347;
+ final String app1ProcessName = "com.android.test.stub1:process";
+ final String app1PackageName = "com.android.test.stub1";
+ final long appStartTimestampIntentStarted = 1000000;
+ final long appStartTimestampActivityLaunchFinished = 2000000;
+ final long appStartTimestampReportFullyDrawn = 3000000;
+ final long appStartTimestampService = 4000000;
+ final long appStartTimestampBroadcast = 5000000;
+ final long appStartTimestampRContentProvider = 6000000;
+
+ ProcessRecord app = makeProcessRecord(
+ app1Pid1, // pid
+ app1Uid, // uid
+ app1Uid, // packageUid
+ null, // definingUid
+ app1ProcessName, // processName
+ app1PackageName); // packageName
+
+ ArrayList<ApplicationStartInfo> list = new ArrayList<ApplicationStartInfo>();
+
+ // Case 1: Activity start intent failed
+ mAppStartInfoTracker.onIntentStarted(buildIntent(COMPONENT),
+ appStartTimestampIntentStarted);
+ mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+ verifyInProgressRecordsSize(1);
+ assertEquals(list.size(), 0);
+
+ verifyInProgApplicationStartInfo(
+ 0, // index
+ 0, // pid
+ 0, // uid
+ 0, // packageUid
+ null, // definingUid
+ null, // processName
+ ApplicationStartInfo.START_REASON_START_ACTIVITY, // reason
+ ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state
+ ApplicationStartInfo.START_TYPE_UNSET, // state type
+ ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
+
+ mAppStartInfoTracker.onIntentFailed(appStartTimestampIntentStarted);
+ list.clear();
+ mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+ verifyInProgressRecordsSize(0);
+ assertEquals(list.size(), 0);
+
+ mAppStartInfoTracker.clearProcessStartInfo(true);
+
+ // Case 2: Activity start launch cancelled
+ mAppStartInfoTracker.onIntentStarted(buildIntent(COMPONENT),
+ appStartTimestampIntentStarted);
+ list.clear();
+ mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+ verifyInProgressRecordsSize(1);
+ assertEquals(list.size(), 0);
+
+ mAppStartInfoTracker.onActivityLaunched(appStartTimestampIntentStarted, COMPONENT,
+ ApplicationStartInfo.START_TYPE_COLD, app);
+ list.clear();
+ mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+ verifyInProgressRecordsSize(1);
+ assertEquals(list.size(), 1);
+
+ verifyInProgApplicationStartInfo(
+ 0, // index
+ app1Pid1, // pid
+ app1Uid, // uid
+ app1Uid, // packageUid
+ null, // definingUid
+ app1ProcessName, // processName
+ ApplicationStartInfo.START_REASON_START_ACTIVITY, // reason
+ ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state
+ ApplicationStartInfo.START_TYPE_COLD, // state type
+ ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
+
+ mAppStartInfoTracker.onActivityLaunchCancelled(appStartTimestampIntentStarted);
+ list.clear();
+ mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+ verifyInProgressRecordsSize(0);
+ assertEquals(list.size(), 1);
+
+ verifyApplicationStartInfo(
+ list.get(0), // info
+ app1Pid1, // pid
+ app1Uid, // uid
+ app1Uid, // packageUid
+ null, // definingUid
+ app1ProcessName, // processName
+ ApplicationStartInfo.START_REASON_START_ACTIVITY, // reason
+ ApplicationStartInfo.STARTUP_STATE_ERROR, // startup state
+ ApplicationStartInfo.START_TYPE_COLD, // state type
+ ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
+
+ mAppStartInfoTracker.clearProcessStartInfo(true);
+
+ // Case 3: Activity start success
+ mAppStartInfoTracker.onIntentStarted(buildIntent(COMPONENT),
+ appStartTimestampIntentStarted);
+ list.clear();
+ mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+ verifyInProgressRecordsSize(1);
+ assertEquals(list.size(), 0);
+
+ mAppStartInfoTracker.onActivityLaunched(appStartTimestampIntentStarted, COMPONENT,
+ ApplicationStartInfo.START_TYPE_COLD, app);
+ list.clear();
+ mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+ verifyInProgressRecordsSize(1);
+ assertEquals(list.size(), 1);
+
+ verifyInProgApplicationStartInfo(
+ 0, // index
+ app1Pid1, // pid
+ app1Uid, // uid
+ app1Uid, // packageUid
+ null, // definingUid
+ app1ProcessName, // processName
+ ApplicationStartInfo.START_REASON_START_ACTIVITY, // reason
+ ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state
+ ApplicationStartInfo.START_TYPE_COLD, // state type
+ ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
+
+ verifyApplicationStartInfo(
+ list.get(0), // info
+ app1Pid1, // pid
+ app1Uid, // uid
+ app1Uid, // packageUid
+ null, // definingUid
+ app1ProcessName, // processName
+ ApplicationStartInfo.START_REASON_START_ACTIVITY, // reason
+ ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state
+ ApplicationStartInfo.START_TYPE_COLD, // state type
+ ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
+
+ mAppStartInfoTracker.onActivityLaunchFinished(appStartTimestampIntentStarted, COMPONENT,
+ appStartTimestampActivityLaunchFinished, ApplicationStartInfo.LAUNCH_MODE_STANDARD);
+ list.clear();
+ mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+ verifyInProgressRecordsSize(1);
+ assertEquals(list.size(), 1);
+
+ verifyInProgApplicationStartInfo(
+ 0, // index
+ app1Pid1, // pid
+ app1Uid, // uid
+ app1Uid, // packageUid
+ null, // definingUid
+ app1ProcessName, // processName
+ ApplicationStartInfo.START_REASON_START_ACTIVITY, // reason
+ ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN, // startup state
+ ApplicationStartInfo.START_TYPE_COLD, // state type
+ ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
+
+ mAppStartInfoTracker.onReportFullyDrawn(appStartTimestampIntentStarted,
+ appStartTimestampReportFullyDrawn);
+ list.clear();
+ mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+ verifyInProgressRecordsSize(0);
+ assertEquals(list.size(), 1);
+
+ verifyApplicationStartInfo(
+ list.get(0), // info
+ app1Pid1, // pid
+ app1Uid, // uid
+ app1Uid, // packageUid
+ null, // definingUid
+ app1ProcessName, // processName
+ ApplicationStartInfo.START_REASON_START_ACTIVITY, // reason
+ ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN, // startup state
+ ApplicationStartInfo.START_TYPE_COLD, // state type
+ ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
+
+ // Don't clear records for use in subsequent cases.
+
+ // Case 4: Create an other app1 record with different pid started for a service
+ sleep(1);
+ app = makeProcessRecord(
+ app1Pid2, // pid
+ app1Uid, // uid
+ app1Uid, // packageUid
+ app1DefiningUid, // definingUid
+ app1ProcessName, // processName
+ app1PackageName); // packageName
+ ServiceRecord service = ServiceRecord.newEmptyInstanceForTest(mAms);
+
+ mAppStartInfoTracker.handleProcessServiceStart(appStartTimestampService, app, service,
+ false);
+ list.clear();
+ mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, 0, 0, list);
+ assertEquals(list.size(), 2);
+
+ verifyApplicationStartInfo(
+ list.get(0), // info
+ app1Pid2, // pid
+ app1Uid, // uid
+ app1Uid, // packageUid
+ app1DefiningUid, // definingUid
+ app1ProcessName, // processName
+ ApplicationStartInfo.START_REASON_SERVICE, // reason
+ ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state
+ ApplicationStartInfo.START_TYPE_WARM, // state type
+ ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
+
+ // Case 5: Create an instance of app1 with a different user started for a broadcast
+ sleep(1);
+ app = makeProcessRecord(
+ app1PidUser2, // pid
+ app1UidUser2, // uid
+ app1UidUser2, // packageUid
+ null, // definingUid
+ app1ProcessName, // processName
+ app1PackageName); // packageName
+
+ mAppStartInfoTracker.handleProcessBroadcastStart(appStartTimestampBroadcast, app,
+ null, true /* isColdStart */);
+ list.clear();
+ mAppStartInfoTracker.getStartInfo(app1PackageName, app1UidUser2, app1PidUser2, 0, list);
+ assertEquals(list.size(), 1);
+
+ verifyApplicationStartInfo(
+ list.get(0), // info
+ app1PidUser2, // pid
+ app1UidUser2, // uid
+ app1UidUser2, // packageUid
+ null, // definingUid
+ app1ProcessName, // processName
+ ApplicationStartInfo.START_REASON_BROADCAST, // reason
+ ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state
+ ApplicationStartInfo.START_TYPE_COLD, // state type
+ ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
+
+ // Case 6: User 2 gets removed
+ mAppStartInfoTracker.onPackageRemoved(app1PackageName, app1UidUser2, false);
+ list.clear();
+ mAppStartInfoTracker.getStartInfo(app1PackageName, app1UidUser2, app1PidUser2, 0, list);
+ assertEquals(list.size(), 0);
+
+ list.clear();
+ mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1PidUser2, 0, list);
+ assertEquals(list.size(), 2);
+
+
+ // Case 7: Create a process from another package started for a content provider
+ final int app2UidUser2 = 1010234;
+ final int app2PidUser2 = 12348;
+ final String app2ProcessName = "com.android.test.stub2:process";
+ final String app2PackageName = "com.android.test.stub2";
+
+ sleep(1);
+
+ app = makeProcessRecord(
+ app2PidUser2, // pid
+ app2UidUser2, // uid
+ app2UidUser2, // packageUid
+ null, // definingUid
+ app2ProcessName, // processName
+ app2PackageName); // packageName
+
+ mAppStartInfoTracker.handleProcessContentProviderStart(appStartTimestampRContentProvider,
+ app, false);
+ list.clear();
+ mAppStartInfoTracker.getStartInfo(app2PackageName, app2UidUser2, app2PidUser2, 0, list);
+ assertEquals(list.size(), 1);
+
+ verifyApplicationStartInfo(
+ list.get(0), // info
+ app2PidUser2, // pid
+ app2UidUser2, // uid
+ app2UidUser2, // packageUid
+ null, // definingUid
+ app2ProcessName, // processName
+ ApplicationStartInfo.START_REASON_CONTENT_PROVIDER, // reason
+ ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state
+ ApplicationStartInfo.START_TYPE_WARM, // state type
+ ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
+
+ // Case 8: Save and load again
+ ArrayList<ApplicationStartInfo> original = new ArrayList<ApplicationStartInfo>();
+ mAppStartInfoTracker.getStartInfo(null, app1Uid, 0, 0, original);
+ assertTrue(original.size() > 0);
+
+ mAppStartInfoTracker.persistProcessStartInfo();
+ assertTrue(mAppStartInfoTracker.mProcStartInfoFile.exists());
+
+ mAppStartInfoTracker.clearProcessStartInfo(false);
+ list.clear();
+ mAppStartInfoTracker.getStartInfo(null, app1Uid, 0, 0, list);
+ assertEquals(0, list.size());
+
+ mAppStartInfoTracker.loadExistingProcessStartInfo();
+ list.clear();
+ mAppStartInfoTracker.getStartInfo(null, app1Uid, 0, 0, list);
+ assertEquals(original.size(), list.size());
+
+ for (int i = list.size() - 1; i >= 0; i--) {
+ assertTrue(list.get(i).equals(original.get(i)));
+ }
+ }
+
+ private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) {
+ try {
+ Field field = clazz.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ Field mfield = Field.class.getDeclaredField("accessFlags");
+ mfield.setAccessible(true);
+ mfield.setInt(field, mfield.getInt(field) & ~(Modifier.FINAL | Modifier.PRIVATE));
+ field.set(obj, val);
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ }
+ }
+
+ private void sleep(long ms) {
+ try {
+ Thread.sleep(ms);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ private ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, Integer definingUid,
+ String processName, String packageName) {
+ return makeProcessRecord(pid, uid, packageUid, definingUid, processName, packageName, mAms);
+ }
+
+ @SuppressWarnings("GuardedBy")
+ static ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, Integer definingUid,
+ String processName, String packageName, ActivityManagerService ams) {
+ ApplicationInfo ai = new ApplicationInfo();
+ ai.packageName = packageName;
+ ProcessRecord app = new ProcessRecord(ams, ai, processName, uid);
+ app.setPid(pid);
+ app.info.uid = packageUid;
+ if (definingUid != null) {
+ app.setHostingRecord(HostingRecord.byAppZygote(COMPONENT, "", definingUid, ""));
+ }
+ return app;
+ }
+
+ private static Intent buildIntent(ComponentName componentName) throws Exception {
+ Intent intent = new Intent();
+ intent.setComponent(componentName);
+ intent.setPackage(componentName.getPackageName());
+ return intent;
+ }
+
+ private void verifyInProgressRecordsSize(int expectedSize) {
+ synchronized (mAppStartInfoTracker.mLock) {
+ assertEquals(mAppStartInfoTracker.mInProgRecords.size(), expectedSize);
+ }
+ }
+
+ private void verifyInProgApplicationStartInfo(int index,
+ Integer pid, Integer uid, Integer packageUid,
+ Integer definingUid, String processName,
+ Integer reason, Integer startupState, Integer startType, Integer launchMode) {
+ synchronized (mAppStartInfoTracker.mLock) {
+ verifyApplicationStartInfo(mAppStartInfoTracker.mInProgRecords.valueAt(index),
+ pid, uid, packageUid, definingUid, processName, reason, startupState,
+ startType, launchMode);
+ }
+ }
+
+ private void verifyApplicationStartInfo(ApplicationStartInfo info,
+ Integer pid, Integer uid, Integer packageUid,
+ Integer definingUid, String processName,
+ Integer reason, Integer startupState, Integer startType, Integer launchMode) {
+ assertNotNull(info);
+
+ if (pid != null) {
+ assertEquals(pid.intValue(), info.getPid());
+ }
+ if (uid != null) {
+ assertEquals(uid.intValue(), info.getRealUid());
+ }
+ if (packageUid != null) {
+ assertEquals(packageUid.intValue(), info.getPackageUid());
+ }
+ if (definingUid != null) {
+ assertEquals(definingUid.intValue(), info.getDefiningUid());
+ }
+ if (processName != null) {
+ assertTrue(TextUtils.equals(processName, info.getProcessName()));
+ }
+ if (reason != null) {
+ assertEquals(reason.intValue(), info.getReason());
+ }
+ if (startupState != null) {
+ assertEquals(startupState.intValue(), info.getStartupState());
+ }
+ if (startType != null) {
+ assertEquals(startType.intValue(), info.getStartType());
+ }
+ if (launchMode != null) {
+ assertEquals(launchMode.intValue(), info.getLaunchMode());
+ }
+ }
+
+ private class TestInjector extends Injector {
+ TestInjector(Context context) {
+ super(context);
+ }
+
+ @Override
+ public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile,
+ Handler handler) {
+ return mAppOpsService;
+ }
+
+ @Override
+ public Handler getUiHandler(ActivityManagerService service) {
+ return mHandler;
+ }
+
+ @Override
+ public ProcessList getProcessList(ActivityManagerService service) {
+ return mProcessList;
+ }
+ }
+
+ static class ServiceThreadRule implements TestRule {
+
+ private ServiceThread mThread;
+
+ ServiceThread getThread() {
+ return mThread;
+ }
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ mThread = new ServiceThread("TestServiceThread",
+ Process.THREAD_PRIORITY_DEFAULT, true /* allowIo */);
+ mThread.start();
+ try {
+ base.evaluate();
+ } finally {
+ mThread.getThreadHandler().runWithScissors(mThread::quit, 0 /* timeout */);
+ }
+ }
+ };
+ }
+ }
+
+ // TODO: [b/302724778] Remove manual JNI load
+ static {
+ System.loadLibrary("mockingservicestestjni");
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index 650c473..284e491 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -26,15 +26,18 @@
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
+import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX;
+import static com.android.server.job.controllers.FlexibilityController.FLEXIBLE_CONSTRAINTS;
import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS;
import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS;
import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_APPLIED_CONSTRAINTS;
import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_DEADLINE_PROXIMITY_LIMIT;
import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FALLBACK_FLEXIBILITY_DEADLINE;
import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS;
-import static com.android.server.job.controllers.FlexibilityController.FLEXIBLE_CONSTRAINTS;
import static com.android.server.job.controllers.FlexibilityController.SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING;
@@ -50,24 +53,32 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.AlarmManager;
import android.app.AppGlobals;
import android.app.job.JobInfo;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.net.NetworkRequest;
import android.os.Looper;
+import android.os.PowerManager;
import android.provider.DeviceConfig;
import android.util.ArraySet;
+import android.util.EmptyArray;
import com.android.server.AppSchedulingModuleThread;
+import com.android.server.DeviceIdleInternal;
import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.JobStore;
@@ -77,6 +88,7 @@
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.MockitoSession;
@@ -95,6 +107,7 @@
private static final long FROZEN_TIME = 100L;
private MockitoSession mMockingSession;
+ private BroadcastReceiver mBroadcastReceiver;
private FlexibilityController mFlexibilityController;
private DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder;
private JobStore mJobStore;
@@ -106,6 +119,8 @@
@Mock
private Context mContext;
@Mock
+ private DeviceIdleInternal mDeviceIdleInternal;
+ @Mock
private JobSchedulerService mJobSchedulerService;
@Mock
private PrefetchController mPrefetchController;
@@ -128,10 +143,13 @@
// Called in FlexibilityController constructor.
when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager);
+ doNothing().when(mAlarmManager).setExact(anyInt(), anyLong(), anyString(), any(), any());
when(mContext.getPackageManager()).thenReturn(mPackageManager);
when(mPackageManager.hasSystemFeature(
PackageManager.FEATURE_AUTOMOTIVE)).thenReturn(false);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_EMBEDDED)).thenReturn(false);
+ doReturn(mDeviceIdleInternal)
+ .when(() -> LocalServices.getService(DeviceIdleInternal.class));
// Used in FlexibilityController.FcConstants.
doAnswer((Answer<Void>) invocationOnMock -> null)
.when(() -> DeviceConfig.addOnPropertiesChangedListener(
@@ -146,7 +164,7 @@
eq(DeviceConfig.NAMESPACE_JOB_SCHEDULER), ArgumentMatchers.<String>any()));
//used to get jobs by UID
mJobStore = JobStore.initAndGetForTesting(mContext, mContext.getFilesDir());
- when(mJobSchedulerService.getJobStore()).thenReturn(mJobStore);
+ doReturn(mJobStore).when(mJobSchedulerService).getJobStore();
// Used in JobStatus.
doReturn(mock(PackageManagerInternal.class))
.when(() -> LocalServices.getService(PackageManagerInternal.class));
@@ -156,6 +174,8 @@
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(Instant.ofEpochMilli(FROZEN_TIME), ZoneOffset.UTC);
// Initialize real objects.
+ ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
mFlexibilityController = new FlexibilityController(mJobSchedulerService,
mPrefetchController);
mFcConfig = mFlexibilityController.getFcConfig();
@@ -166,6 +186,11 @@
setDeviceConfigLong(KEY_DEADLINE_PROXIMITY_LIMIT, 0L);
setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, FLEXIBLE_CONSTRAINTS);
waitForQuietModuleThread();
+
+ verify(mContext).registerReceiver(receiverCaptor.capture(),
+ ArgumentMatchers.argThat(filter ->
+ filter.hasAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED)));
+ mBroadcastReceiver = receiverCaptor.getValue();
}
@After
@@ -212,6 +237,7 @@
JobStatus js = JobStatus.createFromJobInfo(
jobInfo, 1000, SOURCE_PACKAGE, SOURCE_USER_ID, "FCTest", testTag);
js.enqueueTime = FROZEN_TIME;
+ js.setStandbyBucket(ACTIVE_INDEX);
if (js.hasFlexibilityConstraint()) {
js.setNumAppliedFlexibleConstraints(Integer.bitCount(
mFlexibilityController.getRelevantAppliedConstraintsLocked(js)));
@@ -400,6 +426,8 @@
@Test
public void testGetNextConstraintDropTimeElapsedLocked() {
+ setDeviceConfigLong(KEY_FALLBACK_FLEXIBILITY_DEADLINE, 200 * HOUR_IN_MILLIS);
+
long nextTimeToDropNumConstraints;
// no delay, deadline
@@ -431,15 +459,18 @@
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(130400100, nextTimeToDropNumConstraints);
+ assertEquals(FROZEN_TIME + 800000L + (200 * HOUR_IN_MILLIS) / 2,
+ nextTimeToDropNumConstraints);
js.setNumDroppedFlexibleConstraints(1);
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(156320100L, nextTimeToDropNumConstraints);
+ assertEquals(FROZEN_TIME + 800000L + (200 * HOUR_IN_MILLIS) * 6 / 10,
+ nextTimeToDropNumConstraints);
js.setNumDroppedFlexibleConstraints(2);
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(182240100L, nextTimeToDropNumConstraints);
+ assertEquals(FROZEN_TIME + 800000L + (200 * HOUR_IN_MILLIS) * 7 / 10,
+ nextTimeToDropNumConstraints);
// no delay, no deadline
jb = createJob(0);
@@ -447,15 +478,15 @@
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(129600100, nextTimeToDropNumConstraints);
+ assertEquals(FROZEN_TIME + (200 * HOUR_IN_MILLIS) / 2, nextTimeToDropNumConstraints);
js.setNumDroppedFlexibleConstraints(1);
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(155520100L, nextTimeToDropNumConstraints);
+ assertEquals(FROZEN_TIME + (200 * HOUR_IN_MILLIS) * 6 / 10, nextTimeToDropNumConstraints);
js.setNumDroppedFlexibleConstraints(2);
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(181440100L, nextTimeToDropNumConstraints);
+ assertEquals(FROZEN_TIME + (200 * HOUR_IN_MILLIS) * 7 / 10, nextTimeToDropNumConstraints);
// delay, deadline
jb = createJob(0)
@@ -598,10 +629,10 @@
@Test
public void testGetLifeCycleBeginningElapsedLocked_Prefetch() {
// prefetch with lifecycle
- when(mPrefetchController.getLaunchTimeThresholdMs()).thenReturn(700L);
+ doReturn(700L).when(mPrefetchController).getLaunchTimeThresholdMs();
JobInfo.Builder jb = createJob(0).setPrefetch(true);
JobStatus js = createJobStatus("time", jb);
- when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(900L);
+ doReturn(900L).when(mPrefetchController).getNextEstimatedLaunchTimeLocked(js);
assertEquals(900L - 700L, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
// prefetch with enqueue
jb = createJob(0).setPrefetch(true);
@@ -616,7 +647,7 @@
// prefetch without estimate
mFlexibilityController.mPrefetchLifeCycleStart
.add(js.getUserId(), js.getSourcePackageName(), 500L);
- when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(Long.MAX_VALUE);
+ doReturn(Long.MAX_VALUE).when(mPrefetchController).getNextEstimatedLaunchTimeLocked(js);
jb = createJob(0).setPrefetch(true);
js = createJobStatus("time", jb);
assertEquals(500L, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
@@ -642,12 +673,12 @@
// prefetch no estimate
JobInfo.Builder jb = createJob(0).setPrefetch(true);
JobStatus js = createJobStatus("time", jb);
- when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(Long.MAX_VALUE);
+ doReturn(Long.MAX_VALUE).when(mPrefetchController).getNextEstimatedLaunchTimeLocked(js);
assertEquals(Long.MAX_VALUE, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
// prefetch with estimate
jb = createJob(0).setPrefetch(true);
js = createJobStatus("time", jb);
- when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(1000L);
+ doReturn(1000L).when(mPrefetchController).getNextEstimatedLaunchTimeLocked(js);
assertEquals(1000L, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
}
@@ -696,7 +727,7 @@
// Stop satisfied constraints from causing a false positive.
js.setNumAppliedFlexibleConstraints(100);
synchronized (mFlexibilityController.mLock) {
- when(mJobSchedulerService.isCurrentlyRunningLocked(js)).thenReturn(true);
+ doReturn(true).when(mJobSchedulerService).isCurrentlyRunningLocked(js);
assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
}
}
@@ -847,14 +878,84 @@
}
@Test
+ public void testAllowlistedAppBypass() {
+ setPowerWhitelistExceptIdle();
+ mFlexibilityController.onSystemServicesReady();
+
+ JobStatus jsHigh = createJobStatus("testAllowlistedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_HIGH));
+ JobStatus jsDefault = createJobStatus("testAllowlistedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT));
+ JobStatus jsLow = createJobStatus("testAllowlistedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_LOW));
+ JobStatus jsMin = createJobStatus("testAllowlistedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_MIN));
+ jsHigh.setStandbyBucket(EXEMPTED_INDEX);
+ jsDefault.setStandbyBucket(EXEMPTED_INDEX);
+ jsLow.setStandbyBucket(EXEMPTED_INDEX);
+ jsMin.setStandbyBucket(EXEMPTED_INDEX);
+
+ setPowerWhitelistExceptIdle();
+ synchronized (mFlexibilityController.mLock) {
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHigh));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefault));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLow));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMin));
+ }
+
+ setPowerWhitelistExceptIdle(SOURCE_PACKAGE);
+ synchronized (mFlexibilityController.mLock) {
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHigh));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefault));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLow));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMin));
+ }
+ }
+
+ @Test
+ public void testForegroundAppBypass() {
+ JobStatus jsHigh = createJobStatus("testAllowlistedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_HIGH));
+ JobStatus jsDefault = createJobStatus("testAllowlistedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT));
+ JobStatus jsLow = createJobStatus("testAllowlistedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_LOW));
+ JobStatus jsMin = createJobStatus("testAllowlistedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_MIN));
+
+ doReturn(JobInfo.BIAS_DEFAULT).when(mJobSchedulerService).getUidBias(mSourceUid);
+ synchronized (mFlexibilityController.mLock) {
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHigh));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefault));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLow));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMin));
+ }
+
+ setUidBias(mSourceUid, JobInfo.BIAS_BOUND_FOREGROUND_SERVICE);
+ synchronized (mFlexibilityController.mLock) {
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHigh));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefault));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLow));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMin));
+ }
+
+ setUidBias(mSourceUid, JobInfo.BIAS_FOREGROUND_SERVICE);
+ synchronized (mFlexibilityController.mLock) {
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHigh));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefault));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLow));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMin));
+ }
+ }
+
+ @Test
public void testTopAppBypass() {
- JobInfo.Builder jb = createJob(0);
+ JobInfo.Builder jb = createJob(0).setPriority(JobInfo.PRIORITY_MIN);
JobStatus js = createJobStatus("testTopAppBypass", jb);
mJobStore.add(js);
// Needed because if before and after Uid bias is the same, nothing happens.
- when(mJobSchedulerService.getUidBias(mSourceUid))
- .thenReturn(JobInfo.BIAS_FOREGROUND_SERVICE);
+ doReturn(JobInfo.BIAS_DEFAULT).when(mJobSchedulerService).getUidBias(mSourceUid);
synchronized (mFlexibilityController.mLock) {
mFlexibilityController.maybeStartTrackingJobLocked(js, null);
@@ -865,7 +966,7 @@
assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
assertTrue(js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE));
- setUidBias(mSourceUid, JobInfo.BIAS_FOREGROUND_SERVICE);
+ setUidBias(mSourceUid, JobInfo.BIAS_SYNC_INITIALIZATION);
assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
assertFalse(js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE));
@@ -1187,9 +1288,9 @@
JobInfo.Builder jb = createJob(22).setPrefetch(true);
JobStatus js = createJobStatus("onPrefetchCacheUpdated", jb);
jobs.add(js);
- when(mPrefetchController.getLaunchTimeThresholdMs()).thenReturn(7 * HOUR_IN_MILLIS);
- when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(
- 1150L + mFlexibilityController.mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS);
+ doReturn(7 * HOUR_IN_MILLIS).when(mPrefetchController).getLaunchTimeThresholdMs();
+ doReturn(1150L + mFlexibilityController.mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS)
+ .when(mPrefetchController).getNextEstimatedLaunchTimeLocked(js);
mFlexibilityController.maybeStartTrackingJobLocked(js, null);
@@ -1231,7 +1332,7 @@
final ArraySet<String> pkgs = new ArraySet<>();
pkgs.add(js.getSourcePackageName());
- when(mJobSchedulerService.getPackagesForUidLocked(mSourceUid)).thenReturn(pkgs);
+ doReturn(pkgs).when(mJobSchedulerService).getPackagesForUidLocked(mSourceUid);
setUidBias(mSourceUid, BIAS_TOP_APP);
setUidBias(mSourceUid, BIAS_FOREGROUND_SERVICE);
@@ -1245,7 +1346,6 @@
setUidBias(mSourceUid, BIAS_FOREGROUND_SERVICE);
assertEquals(100L, (long) mFlexibilityController
.mPrefetchLifeCycleStart.get(js.getSourceUserId(), js.getSourcePackageName()));
-
}
@Test
@@ -1259,7 +1359,7 @@
}
private void runTestUnsupportedDevice(String feature) {
- when(mPackageManager.hasSystemFeature(feature)).thenReturn(true);
+ doReturn(true).when(mPackageManager).hasSystemFeature(feature);
mFlexibilityController =
new FlexibilityController(mJobSchedulerService, mPrefetchController);
assertFalse(mFlexibilityController.isEnabled());
@@ -1279,6 +1379,16 @@
}
}
+ private void setPowerWhitelistExceptIdle(String... packages) {
+ doReturn(packages == null ? EmptyArray.STRING : packages)
+ .when(mDeviceIdleInternal).getFullPowerWhitelistExceptIdle();
+ if (mBroadcastReceiver != null) {
+ mBroadcastReceiver.onReceive(mContext,
+ new Intent(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED));
+ waitForQuietModuleThread();
+ }
+ }
+
private void setUidBias(int uid, int bias) {
int prevBias = mJobSchedulerService.getUidBias(uid);
doReturn(bias).when(mJobSchedulerService).getUidBias(uid);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
index 0403c64..ec7e359 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -233,7 +233,7 @@
Exception e = assertThrows(
SecurityException.class,
() -> mArchiveManager.requestArchive(PACKAGE, "different", mIntentSender,
- UserHandle.CURRENT, 0));
+ UserHandle.CURRENT));
assertThat(e).hasMessageThat().isEqualTo(
String.format(
"The UID %s of callerPackageName set by the caller doesn't match the "
@@ -250,7 +250,7 @@
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
- UserHandle.CURRENT, 0));
+ UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
String.format("Package %s not found.", PACKAGE));
@@ -260,8 +260,7 @@
public void archiveApp_packageNotInstalledForUser() throws IntentSender.SendIntentException {
mPackageSetting.modifyUserState(UserHandle.CURRENT.getIdentifier()).setInstalled(false);
- mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT,
- 0);
+ mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
rule.mocks().getHandler().flush();
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -291,7 +290,7 @@
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
- UserHandle.CURRENT, 0));
+ UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo("No installer found");
}
@@ -305,7 +304,7 @@
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
- UserHandle.CURRENT, 0));
+ UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
"Installer does not support unarchival");
@@ -319,7 +318,7 @@
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
- UserHandle.CURRENT, 0));
+ UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
TextUtils.formatSimple("The app %s does not have a main activity.", PACKAGE));
@@ -331,8 +330,7 @@
doThrow(e).when(mArchiveManager).storeIcon(eq(PACKAGE),
any(LauncherActivityInfo.class), eq(mUserId), anyInt(), anyInt());
- mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT,
- 0);
+ mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
rule.mocks().getHandler().flush();
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -355,7 +353,7 @@
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
- UserHandle.CURRENT, 0));
+ UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
TextUtils.formatSimple("The app %s is opted out of archiving.", PACKAGE));
@@ -363,8 +361,7 @@
@Test
public void archiveApp_withNoAdditionalFlags_success() {
- mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT,
- 0);
+ mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
rule.mocks().getHandler().flush();
verify(mInstallerService).uninstall(
@@ -386,14 +383,13 @@
@Test
public void archiveApp_withAdditionalFlags_success() {
- mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT,
- PackageManager.DELETE_SHOW_DIALOG);
+ mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
rule.mocks().getHandler().flush();
verify(mInstallerService).uninstall(
eq(new VersionedPackage(PACKAGE, PackageManager.VERSION_CODE_HIGHEST)),
eq(CALLER_PACKAGE),
- eq(DELETE_ARCHIVE | DELETE_KEEP_DATA | PackageManager.DELETE_SHOW_DIALOG),
+ eq(DELETE_ARCHIVE | DELETE_KEEP_DATA),
eq(mIntentSender),
eq(UserHandle.CURRENT.getIdentifier()), anyInt());
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index fd6aa0c..e6298ee 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -19,9 +19,11 @@
import static android.os.UserManager.DISALLOW_SMS;
import static android.os.UserManager.DISALLOW_USER_SWITCH;
import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
+import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -30,20 +32,27 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
+import android.app.KeyguardManager;
import android.content.Context;
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
+import android.multiuser.Flags;
+import android.os.PowerManager;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageManager;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.util.Log;
import android.util.Pair;
@@ -52,6 +61,7 @@
import androidx.test.annotation.UiThreadTest;
+import com.android.dx.mockito.inline.extended.MockedVoidMethod;
import com.android.internal.widget.LockSettingsInternal;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.testing.ExtendedMockitoRule;
@@ -65,6 +75,7 @@
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -115,8 +126,11 @@
.spyStatic(LocalServices.class)
.spyStatic(SystemProperties.class)
.mockStatic(Settings.Global.class)
+ .mockStatic(Settings.Secure.class)
.build();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private final Object mPackagesLock = new Object();
private final Context mRealContext = androidx.test.InstrumentationRegistry.getInstrumentation()
.getTargetContext();
@@ -133,6 +147,8 @@
private @Mock StorageManager mStorageManager;
private @Mock LockSettingsInternal mLockSettingsInternal;
private @Mock PackageManagerInternal mPackageManagerInternal;
+ private @Mock KeyguardManager mKeyguardManager;
+ private @Mock PowerManager mPowerManager;
/**
* Reference to the {@link UserManagerService} being tested.
@@ -156,6 +172,8 @@
when(mDeviceStorageMonitorInternal.isMemoryLow()).thenReturn(false);
mockGetLocalService(DeviceStorageMonitorInternal.class, mDeviceStorageMonitorInternal);
when(mSpiedContext.getSystemService(StorageManager.class)).thenReturn(mStorageManager);
+ when(mSpiedContext.getSystemService(KeyguardManager.class)).thenReturn(mKeyguardManager);
+ when(mSpiedContext.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
mockGetLocalService(LockSettingsInternal.class, mLockSettingsInternal);
mockGetLocalService(PackageManagerInternal.class, mPackageManagerInternal);
doNothing().when(mSpiedContext).sendBroadcastAsUser(any(), any(), any());
@@ -550,6 +568,143 @@
assertTrue(hasRestrictionsInUserXMLFile(user.id));
}
+ @Test
+ public void testAutoLockOnDeviceLockForPrivateProfile() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+ UserManagerService mSpiedUms = spy(mUms);
+ UserInfo privateProfileUser =
+ mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+ USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
+ mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK);
+ Mockito.doNothing().when(mSpiedUms).setQuietModeEnabledAsync(
+ eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true), any(),
+ any());
+
+ mSpiedUms.tryAutoLockingPrivateSpaceOnKeyguardChanged(true);
+
+ Mockito.verify(mSpiedUms).setQuietModeEnabledAsync(
+ eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true),
+ any(), any());
+ }
+
+ @Test
+ public void testAutoLockOnDeviceLockForPrivateProfile_keyguardUnlocked() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+ UserManagerService mSpiedUms = spy(mUms);
+ UserInfo privateProfileUser =
+ mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+ USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
+ mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK);
+
+ mSpiedUms.tryAutoLockingPrivateSpaceOnKeyguardChanged(false);
+
+ // Verify that no operation to disable quiet mode is not called
+ Mockito.verify(mSpiedUms, never()).setQuietModeEnabledAsync(
+ eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true),
+ any(), any());
+ }
+
+ @Test
+ public void testAutoLockOnDeviceLockForPrivateProfile_flagDisabled() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+ UserManagerService mSpiedUms = spy(mUms);
+ UserInfo privateProfileUser =
+ mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+ USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
+
+ mSpiedUms.tryAutoLockingPrivateSpaceOnKeyguardChanged(true);
+
+ // Verify that no auto-lock operations take place
+ verify((MockedVoidMethod) () -> Settings.Secure.getInt(any(),
+ eq(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK), anyInt()), never());
+ Mockito.verify(mSpiedUms, never()).setQuietModeEnabledAsync(
+ eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true),
+ any(), any());
+ }
+
+ @Test
+ public void testAutoLockAfterInactityForPrivateProfile() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+ UserManagerService mSpiedUms = spy(mUms);
+ mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY);
+ when(mPowerManager.isInteractive()).thenReturn(false);
+
+ UserInfo privateProfileUser =
+ mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+ USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
+ Mockito.doNothing().when(mSpiedUms).scheduleMessageToAutoLockPrivateSpace(
+ eq(privateProfileUser.getUserHandle().getIdentifier()), any(),
+ anyLong());
+
+
+ mSpiedUms.maybeScheduleMessageToAutoLockPrivateSpace();
+
+ Mockito.verify(mSpiedUms).scheduleMessageToAutoLockPrivateSpace(
+ eq(privateProfileUser.getUserHandle().getIdentifier()), any(), anyLong());
+ }
+
+ @Test
+ public void testSetOrUpdateAutoLockPreference_noPrivateProfile() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+
+ mUms.setOrUpdateAutoLockPreferenceForPrivateProfile(
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY);
+
+ Mockito.verify(mSpiedContext, never()).registerReceiver(any(), any(), any(), any());
+ Mockito.verify(mSpiedContext, never()).unregisterReceiver(any());
+ Mockito.verify(mKeyguardManager, never()).removeKeyguardLockedStateListener((any()));
+ Mockito.verify(mKeyguardManager, never()).addKeyguardLockedStateListener(any(), any());
+ }
+
+ @Test
+ public void testSetOrUpdateAutoLockPreference() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+ mUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+ USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
+
+ // Set the preference to auto lock on device lock
+ mUms.setOrUpdateAutoLockPreferenceForPrivateProfile(
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK);
+
+ // Verify that keyguard state listener was added
+ Mockito.verify(mKeyguardManager).addKeyguardLockedStateListener(any(), any());
+ //Verity that keyguard state listener was not removed
+ Mockito.verify(mKeyguardManager, never()).removeKeyguardLockedStateListener(any());
+ // Broadcasts are already unregistered when UserManagerService starts and the flag
+ // isDeviceInactivityBroadcastReceiverRegistered is false
+ Mockito.verify(mSpiedContext, never()).registerReceiver(any(), any(), any(), any());
+ Mockito.verify(mSpiedContext, never()).unregisterReceiver(any());
+
+ Mockito.clearInvocations(mKeyguardManager);
+ Mockito.clearInvocations(mSpiedContext);
+
+ // Now set the preference to auto-lock on inactivity
+ mUms.setOrUpdateAutoLockPreferenceForPrivateProfile(
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY);
+
+ // Verify that inactivity broadcasts are registered
+ Mockito.verify(mSpiedContext, times(2)).registerReceiver(any(), any(), any(), any());
+ // Verify that keyguard state listener is removed
+ Mockito.verify(mKeyguardManager).removeKeyguardLockedStateListener(any());
+ // Verify that all other operations don't take place
+ Mockito.verify(mSpiedContext, never()).unregisterReceiver(any());
+ Mockito.verify(mKeyguardManager, never()).addKeyguardLockedStateListener(any(), any());
+
+ Mockito.clearInvocations(mKeyguardManager);
+ Mockito.clearInvocations(mSpiedContext);
+
+ // Finally, set the preference to don't auto-lock
+ mUms.setOrUpdateAutoLockPreferenceForPrivateProfile(
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER);
+
+ // Verify that inactivity broadcasts are unregistered and keyguard listener was removed
+ Mockito.verify(mSpiedContext).unregisterReceiver(any());
+ Mockito.verify(mKeyguardManager).removeKeyguardLockedStateListener(any());
+ // Verify that no broadcasts were registered and no listeners were added
+ Mockito.verify(mSpiedContext, never()).registerReceiver(any(), any(), any(), any());
+ Mockito.verify(mKeyguardManager, never()).addKeyguardLockedStateListener(any(), any());
+ }
+
/**
* Returns true if the user's XML file has Default restrictions
* @param userId Id of the user.
@@ -632,6 +787,12 @@
SystemProperties.getBoolean(eq("fw.show_multiuserui"), anyBoolean()));
}
+ private void mockAutoLockForPrivateSpace(int val) {
+ doReturn(val).when(() ->
+ Settings.Secure.getIntForUser(any(), eq(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK),
+ anyInt(), anyInt()));
+ }
+
private void mockCurrentUser(@UserIdInt int userId) {
mockGetLocalService(ActivityManagerInternal.class, mActivityManagerInternal);
diff --git a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
index 37ca09d..b415682 100644
--- a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
@@ -47,6 +47,10 @@
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.SensorProperties;
+import android.hardware.face.FaceManager;
+import android.hardware.face.FaceSensorProperties;
+import android.hardware.fingerprint.FingerprintManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
@@ -69,6 +73,7 @@
import androidx.test.core.app.ApplicationProvider;
import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker;
import com.android.modules.utils.testing.ExtendedMockitoRule;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -105,6 +110,8 @@
private static final String URI_SCHEME_PACKAGE = "package";
private static final int TEST_USER_ID = 50;
+ private static final UserInfo TEST_USER =
+ new UserInfo(TEST_USER_ID, "user", UserInfo.FLAG_FULL);
private static final int PARENT_USER_ID = 60;
private static final int PROFILE_USER_ID = 70;
private static final long[] PARENT_BIOMETRIC_SIDS = new long[] { 600L, 601L };
@@ -117,6 +124,8 @@
private @Mock ActivityManager mActivityManager;
private @Mock BiometricManager mBiometricManager;
private @Mock DevicePolicyManager mDevicePolicyManager;
+ private @Mock FaceManager mFaceManager;
+ private @Mock FingerprintManager mFingerprintManager;
private @Mock IKeystoreAuthorization mKeystoreAuthorization;
private @Mock LockPatternUtils mLockPatternUtils;
private @Mock PackageManager mPackageManager;
@@ -133,6 +142,9 @@
when(mActivityManager.isUserRunning(TEST_USER_ID)).thenReturn(true);
doReturn(mock(IActivityManager.class)).when(() -> ActivityManager.getService());
+ when(mFaceManager.getSensorProperties()).thenReturn(List.of());
+ when(mFingerprintManager.getSensorProperties()).thenReturn(List.of());
+
doReturn(mKeystoreAuthorization).when(() -> Authorization.getService());
when(mLockPatternUtils.getDevicePolicyManager()).thenReturn(mDevicePolicyManager);
@@ -161,13 +173,16 @@
when(mPackageManager.checkPermission(any(), any())).thenReturn(
PackageManager.PERMISSION_GRANTED);
- when(mUserManager.getAliveUsers()).thenReturn(
- List.of(new UserInfo(TEST_USER_ID, "user", UserInfo.FLAG_FULL)));
+ when(mUserManager.getAliveUsers()).thenReturn(List.of(TEST_USER));
+ when(mUserManager.getEnabledProfileIds(TEST_USER_ID)).thenReturn(new int[0]);
+ when(mUserManager.getUserInfo(TEST_USER_ID)).thenReturn(TEST_USER);
when(mWindowManager.isKeyguardLocked()).thenReturn(true);
mMockContext.addMockSystemService(ActivityManager.class, mActivityManager);
mMockContext.addMockSystemService(BiometricManager.class, mBiometricManager);
+ mMockContext.addMockSystemService(FaceManager.class, mFaceManager);
+ mMockContext.addMockSystemService(FingerprintManager.class, mFingerprintManager);
mMockContext.setMockPackageManager(mPackageManager);
mMockContext.addMockSystemService(UserManager.class, mUserManager);
doReturn(mWindowManager).when(() -> WindowManagerGlobal.getWindowManagerService());
@@ -362,9 +377,9 @@
when(mWindowManager.isKeyguardLocked()).thenReturn(true);
mTrustManager.reportKeyguardShowingChanged();
verify(mKeystoreAuthorization)
- .onDeviceLocked(eq(PARENT_USER_ID), eq(PARENT_BIOMETRIC_SIDS));
+ .onDeviceLocked(eq(PARENT_USER_ID), eq(PARENT_BIOMETRIC_SIDS), eq(false));
verify(mKeystoreAuthorization)
- .onDeviceLocked(eq(PROFILE_USER_ID), eq(PARENT_BIOMETRIC_SIDS));
+ .onDeviceLocked(eq(PROFILE_USER_ID), eq(PARENT_BIOMETRIC_SIDS), eq(false));
}
// Tests that when the device is locked for a managed profile with a *separate* challenge, the
@@ -381,7 +396,188 @@
mTrustManager.setDeviceLockedForUser(PROFILE_USER_ID, true);
verify(mKeystoreAuthorization)
- .onDeviceLocked(eq(PROFILE_USER_ID), eq(PROFILE_BIOMETRIC_SIDS));
+ .onDeviceLocked(eq(PROFILE_USER_ID), eq(PROFILE_BIOMETRIC_SIDS), eq(false));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+ public void testKeystoreWeakUnlockEnabled_whenWeakFingerprintIsSetupAndAllowed()
+ throws Exception {
+ setupStrongAuthTrackerToAllowEverything();
+ setupFingerprint(SensorProperties.STRENGTH_WEAK);
+ verifyWeakUnlockEnabled();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+ public void testKeystoreWeakUnlockEnabled_whenWeakFaceIsSetupAndAllowed() throws Exception {
+ setupStrongAuthTrackerToAllowEverything();
+ setupFace(SensorProperties.STRENGTH_WEAK);
+ verifyWeakUnlockEnabled();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+ public void testKeystoreWeakUnlockEnabled_whenConvenienceFingerprintIsSetupAndAllowed()
+ throws Exception {
+ setupStrongAuthTrackerToAllowEverything();
+ setupFingerprint(SensorProperties.STRENGTH_CONVENIENCE);
+ verifyWeakUnlockEnabled();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+ public void testKeystoreWeakUnlockEnabled_whenConvenienceFaceIsSetupAndAllowed()
+ throws Exception {
+ setupStrongAuthTrackerToAllowEverything();
+ setupFace(SensorProperties.STRENGTH_CONVENIENCE);
+ verifyWeakUnlockEnabled();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+ public void testKeystoreWeakUnlockDisabled_whenStrongAuthRequired() throws Exception {
+ setupStrongAuthTracker(StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, true);
+ setupFace(SensorProperties.STRENGTH_WEAK);
+ verifyWeakUnlockDisabled();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+ public void testKeystoreWeakUnlockDisabled_whenNonStrongBiometricNotAllowed() throws Exception {
+ setupStrongAuthTracker(StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED,
+ /* isNonStrongBiometricAllowed= */ false);
+ setupFace(SensorProperties.STRENGTH_WEAK);
+ verifyWeakUnlockDisabled();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+ public void testKeystoreWeakUnlockDisabled_whenWeakFingerprintSensorIsPresentButNotEnrolled()
+ throws Exception {
+ setupStrongAuthTrackerToAllowEverything();
+ setupFingerprint(SensorProperties.STRENGTH_WEAK, /* enrolled= */ false);
+ verifyWeakUnlockDisabled();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+ public void testKeystoreWeakUnlockDisabled_whenWeakFaceSensorIsPresentButNotEnrolled()
+ throws Exception {
+ setupStrongAuthTrackerToAllowEverything();
+ setupFace(SensorProperties.STRENGTH_WEAK, /* enrolled= */ false);
+ verifyWeakUnlockDisabled();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+ public void
+ testKeystoreWeakUnlockDisabled_whenWeakFingerprintIsSetupButForbiddenByDevicePolicy()
+ throws Exception {
+ setupStrongAuthTrackerToAllowEverything();
+ setupFingerprint(SensorProperties.STRENGTH_WEAK);
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, TEST_USER_ID))
+ .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT);
+ verifyWeakUnlockDisabled();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+ public void testKeystoreWeakUnlockDisabled_whenWeakFaceIsSetupButForbiddenByDevicePolicy()
+ throws Exception {
+ setupStrongAuthTrackerToAllowEverything();
+ setupFace(SensorProperties.STRENGTH_WEAK);
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, TEST_USER_ID))
+ .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FACE);
+ verifyWeakUnlockDisabled();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+ public void testKeystoreWeakUnlockDisabled_whenOnlyStrongFingerprintIsSetup() throws Exception {
+ setupStrongAuthTrackerToAllowEverything();
+ setupFingerprint(SensorProperties.STRENGTH_STRONG);
+ verifyWeakUnlockDisabled();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+ public void testKeystoreWeakUnlockDisabled_whenOnlyStrongFaceIsSetup() throws Exception {
+ setupStrongAuthTrackerToAllowEverything();
+ setupFace(SensorProperties.STRENGTH_STRONG);
+ verifyWeakUnlockDisabled();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+ public void testKeystoreWeakUnlockDisabled_whenNoBiometricsAreSetup() throws Exception {
+ setupStrongAuthTrackerToAllowEverything();
+ verifyWeakUnlockDisabled();
+ }
+
+ private void setupStrongAuthTrackerToAllowEverything() throws Exception {
+ setupStrongAuthTracker(StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED, true);
+ }
+
+ private void setupStrongAuthTracker(int strongAuthFlags, boolean isNonStrongBiometricAllowed)
+ throws Exception {
+ bootService();
+ mService.onUserSwitching(null, new SystemService.TargetUser(TEST_USER));
+
+ ArgumentCaptor<StrongAuthTracker> strongAuthTracker =
+ ArgumentCaptor.forClass(StrongAuthTracker.class);
+ verify(mLockPatternUtils).registerStrongAuthTracker(strongAuthTracker.capture());
+ strongAuthTracker.getValue().getStub().onStrongAuthRequiredChanged(
+ strongAuthFlags, TEST_USER_ID);
+ strongAuthTracker.getValue().getStub().onIsNonStrongBiometricAllowedChanged(
+ isNonStrongBiometricAllowed, TEST_USER_ID);
+ mService.waitForIdle();
+ }
+
+ private void setupFingerprint(int strength) {
+ setupFingerprint(strength, /* enrolled= */ true);
+ }
+
+ private void setupFingerprint(int strength, boolean enrolled) {
+ int sensorId = 100;
+ List<SensorProperties.ComponentInfo> componentInfo = List.of();
+ SensorProperties sensor = new SensorProperties(sensorId, strength, componentInfo);
+ when(mFingerprintManager.getSensorProperties()).thenReturn(List.of(sensor));
+ when(mFingerprintManager.hasEnrolledTemplates(TEST_USER_ID)).thenReturn(enrolled);
+ }
+
+ private void setupFace(int strength) {
+ setupFace(strength, /* enrolled= */ true);
+ }
+
+ private void setupFace(int strength, boolean enrolled) {
+ int sensorId = 100;
+ List<SensorProperties.ComponentInfo> componentInfo = List.of();
+ FaceSensorProperties sensor = new FaceSensorProperties(
+ sensorId, strength, componentInfo, FaceSensorProperties.TYPE_RGB);
+ when(mFaceManager.getSensorProperties()).thenReturn(List.of(sensor));
+ when(mFaceManager.hasEnrolledTemplates(TEST_USER_ID)).thenReturn(enrolled);
+ }
+
+ private void verifyWeakUnlockEnabled() throws Exception {
+ verifyWeakUnlockValue(true);
+ }
+
+ private void verifyWeakUnlockDisabled() throws Exception {
+ verifyWeakUnlockValue(false);
+ }
+
+ // Simulates a device unlock and a device lock, then verifies that the expected
+ // weakUnlockEnabled flag was passed to Keystore's onDeviceLocked method.
+ private void verifyWeakUnlockValue(boolean expectedWeakUnlockEnabled) throws Exception {
+ when(mWindowManager.isKeyguardLocked()).thenReturn(false);
+ mTrustManager.reportKeyguardShowingChanged();
+ verify(mKeystoreAuthorization).onDeviceUnlocked(TEST_USER_ID, null);
+
+ when(mWindowManager.isKeyguardLocked()).thenReturn(true);
+ mTrustManager.reportKeyguardShowingChanged();
+ verify(mKeystoreAuthorization).onDeviceLocked(eq(TEST_USER_ID), any(),
+ eq(expectedWeakUnlockEnabled));
}
private void setupMocksForProfile(boolean unifiedChallenge) {
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 0045026..0831086 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -29,8 +29,6 @@
"src/**/*.java",
"src/**/*.kt",
- "test-apps/JobTestApp/src/**/*.java",
-
"test-apps/SuspendTestApp/src/**/*.java",
],
static_libs: [
@@ -124,7 +122,6 @@
},
data: [
- ":JobTestApp",
":SimpleServiceTestApp1",
":SimpleServiceTestApp2",
":SimpleServiceTestApp3",
diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml
index b1d5039..27c522d 100644
--- a/services/tests/servicestests/AndroidTest.xml
+++ b/services/tests/servicestests/AndroidTest.xml
@@ -29,7 +29,6 @@
<option name="cleanup-apks" value="true" />
<option name="install-arg" value="-t" />
<option name="test-file-name" value="FrameworksServicesTests.apk" />
- <option name="test-file-name" value="JobTestApp.apk" />
<option name="test-file-name" value="SuspendTestApp.apk" />
<option name="test-file-name" value="SimpleServiceTestApp1.apk" />
<option name="test-file-name" value="SimpleServiceTestApp2.apk" />
diff --git a/services/tests/servicestests/src/com/android/server/BootReceiverTest.java b/services/tests/servicestests/src/com/android/server/BootReceiverTest.java
deleted file mode 100644
index 523c5c0..0000000
--- a/services/tests/servicestests/src/com/android/server/BootReceiverTest.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.test.AndroidTestCase;
-
-import com.android.server.os.TombstoneProtos;
-import com.android.server.os.TombstoneProtos.Tombstone;
-
-public class BootReceiverTest extends AndroidTestCase {
- private static final String TAG = "BootReceiverTest";
-
- public void testRemoveMemoryFromTombstone() {
- Tombstone tombstoneBase = Tombstone.newBuilder()
- .setBuildFingerprint("build_fingerprint")
- .setRevision("revision")
- .setPid(123)
- .setTid(23)
- .setUid(34)
- .setSelinuxLabel("selinux_label")
- .addCommandLine("cmd1")
- .addCommandLine("cmd2")
- .addCommandLine("cmd3")
- .setProcessUptime(300)
- .setAbortMessage("abort")
- .addCauses(TombstoneProtos.Cause.newBuilder()
- .setHumanReadable("cause1")
- .setMemoryError(TombstoneProtos.MemoryError.newBuilder()
- .setTool(TombstoneProtos.MemoryError.Tool.SCUDO)
- .setType(TombstoneProtos.MemoryError.Type.DOUBLE_FREE)))
- .addLogBuffers(TombstoneProtos.LogBuffer.newBuilder().setName("name").addLogs(
- TombstoneProtos.LogMessage.newBuilder()
- .setTimestamp("123")
- .setMessage("message")))
- .addOpenFds(TombstoneProtos.FD.newBuilder().setFd(1).setPath("path"))
- .build();
-
- Tombstone tombstoneWithoutMemory = tombstoneBase.toBuilder()
- .putThreads(1, TombstoneProtos.Thread.newBuilder()
- .setId(1)
- .setName("thread1")
- .addRegisters(TombstoneProtos.Register.newBuilder().setName("r1").setU64(1))
- .addRegisters(TombstoneProtos.Register.newBuilder().setName("r2").setU64(2))
- .addBacktraceNote("backtracenote1")
- .addUnreadableElfFiles("files1")
- .setTaggedAddrCtrl(1)
- .setPacEnabledKeys(10)
- .build())
- .build();
-
- Tombstone tombstoneWithMemory = tombstoneBase.toBuilder()
- .addMemoryMappings(TombstoneProtos.MemoryMapping.newBuilder()
- .setBeginAddress(1)
- .setEndAddress(100)
- .setOffset(10)
- .setRead(true)
- .setWrite(true)
- .setExecute(false)
- .setMappingName("mapping")
- .setBuildId("build")
- .setLoadBias(70))
- .putThreads(1, TombstoneProtos.Thread.newBuilder()
- .setId(1)
- .setName("thread1")
- .addRegisters(TombstoneProtos.Register.newBuilder().setName("r1").setU64(1))
- .addRegisters(TombstoneProtos.Register.newBuilder().setName("r2").setU64(2))
- .addBacktraceNote("backtracenote1")
- .addUnreadableElfFiles("files1")
- .addMemoryDump(TombstoneProtos.MemoryDump.newBuilder()
- .setRegisterName("register1")
- .setMappingName("mapping")
- .setBeginAddress(10))
- .setTaggedAddrCtrl(1)
- .setPacEnabledKeys(10)
- .build())
- .build();
-
- assertThat(BootReceiver.removeMemoryFromTombstone(tombstoneWithMemory))
- .isEqualTo(tombstoneWithoutMemory);
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java b/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java
index c0051c6..eee3752 100644
--- a/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java
@@ -133,7 +133,8 @@
verify(mAnrApp.mErrorState, timeout(TIMEOUT_MS)).appNotResponding(
eq(activityShortComponentName), eq(appInfo), eq(parentShortComponentName),
eq(parentProcess), eq(aboveSystem), eq(timeoutRecord), eq(mAuxExecutorService),
- eq(false) /* onlyDumpSelf */, eq(false) /*isContinuousAnr*/, eq(mEarlyDumpFuture));
+ anyBoolean() /* onlyDumpSelf */, eq(false) /*isContinuousAnr*/,
+ eq(mEarlyDumpFuture));
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java
index feb6bd9..467c15d 100644
--- a/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java
@@ -54,7 +54,7 @@
mBgThread.start();
File systemDir = context.getCacheDir();
Handler handler = new Handler(mBgThread.getLooper());
- mBatteryStatsService = new BatteryStatsService(context, systemDir, handler);
+ mBatteryStatsService = new BatteryStatsService(context, systemDir);
}
@After
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
index 1b9e6fb..a8eace0 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
@@ -153,6 +153,15 @@
}
@Test
+ public void testGetIsHardwareIgnoringTouches() throws RemoteException {
+ mListener.onHardwareIgnoreTouchesChanged(true);
+ assertThat(mProvider.isHardwareIgnoringTouches()).isTrue();
+
+ mListener.onHardwareIgnoreTouchesChanged(false);
+ assertThat(mProvider.isHardwareIgnoringTouches()).isFalse();
+ }
+
+ @Test
public void testGetDockedState() {
final List<Integer> states = List.of(Intent.EXTRA_DOCK_STATE_DESK,
Intent.EXTRA_DOCK_STATE_CAR, Intent.EXTRA_DOCK_STATE_UNDOCKED);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricFrameworkStatsLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricFrameworkStatsLoggerTest.java
index 5cff48d..4119352 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricFrameworkStatsLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricFrameworkStatsLoggerTest.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
+import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.common.AuthenticateReason;
import android.hardware.biometrics.common.OperationContext;
@@ -48,11 +49,13 @@
public void testConvertsWakeReason_whenPowerReason() {
final OperationContext context = new OperationContext();
context.wakeReason = WakeReason.WAKE_MOTION;
- final OperationContextExt ctx = new OperationContextExt(context, false);
+ final OperationContextExt ctx = new OperationContextExt(context, false,
+ BiometricAuthenticator.TYPE_NONE);
final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx);
final int[] reasonDetails = BiometricFrameworkStatsLogger
- .toProtoWakeReasonDetails(new OperationContextExt(context, false));
+ .toProtoWakeReasonDetails(
+ new OperationContextExt(context, false, BiometricAuthenticator.TYPE_NONE));
assertThat(reason).isEqualTo(BiometricsProtoEnums.WAKE_REASON_WAKE_MOTION);
assertThat(reasonDetails).isEmpty();
@@ -63,7 +66,8 @@
final OperationContext context = new OperationContext();
context.authenticateReason = AuthenticateReason.faceAuthenticateReason(
AuthenticateReason.Face.ASSISTANT_VISIBLE);
- final OperationContextExt ctx = new OperationContextExt(context, false);
+ final OperationContextExt ctx = new OperationContextExt(context, false,
+ BiometricAuthenticator.TYPE_NONE);
final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx);
final int[] reasonDetails = BiometricFrameworkStatsLogger
@@ -79,7 +83,8 @@
final OperationContext context = new OperationContext();
context.authenticateReason = AuthenticateReason.vendorAuthenticateReason(
new AuthenticateReason.Vendor());
- final OperationContextExt ctx = new OperationContextExt(context, false);
+ final OperationContextExt ctx = new OperationContextExt(context, false,
+ BiometricAuthenticator.TYPE_NONE);
final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx);
final int[] reasonDetails = BiometricFrameworkStatsLogger
@@ -96,7 +101,8 @@
context.wakeReason = WakeReason.WAKE_KEY;
context.authenticateReason = AuthenticateReason.faceAuthenticateReason(
AuthenticateReason.Face.PRIMARY_BOUNCER_SHOWN);
- final OperationContextExt ctx = new OperationContextExt(context, false);
+ final OperationContextExt ctx = new OperationContextExt(context, false,
+ BiometricAuthenticator.TYPE_NONE);
final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx);
final int[] reasonDetails = BiometricFrameworkStatsLogger
@@ -113,7 +119,8 @@
context.wakeReason = WakeReason.LID;
context.authenticateReason = AuthenticateReason.vendorAuthenticateReason(
new AuthenticateReason.Vendor());
- final OperationContextExt ctx = new OperationContextExt(context, false);
+ final OperationContextExt ctx = new OperationContextExt(context, false,
+ BiometricAuthenticator.TYPE_NONE);
final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx);
final int[] reasonDetails = BiometricFrameworkStatsLogger
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/OperationContextExtTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/OperationContextExtTest.java
index 32284fd..767b426 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/OperationContextExtTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/OperationContextExtTest.java
@@ -18,17 +18,19 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.when;
+
import android.content.Intent;
import android.hardware.biometrics.AuthenticateOptions;
+import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.IBiometricContextListener;
import android.hardware.biometrics.common.DisplayState;
import android.hardware.biometrics.common.OperationContext;
import android.hardware.biometrics.common.OperationReason;
+import android.hardware.biometrics.common.OperationState;
import android.platform.test.annotations.Presubmit;
import android.view.Surface;
-import static org.mockito.Mockito.when;
-
import androidx.test.filters.SmallTest;
import com.android.internal.logging.InstanceId;
@@ -58,7 +60,7 @@
final OperationContext aidlContext = newAidlContext();
- context = new OperationContextExt(aidlContext, false);
+ context = new OperationContextExt(aidlContext, false, BiometricAuthenticator.TYPE_NONE);
assertThat(context.toAidlContext()).isSameInstanceAs(aidlContext);
final int id = 5;
@@ -96,7 +98,8 @@
);
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
- final OperationContextExt context = new OperationContextExt(newAidlContext(), true);
+ final OperationContextExt context = new OperationContextExt(newAidlContext(), true,
+ BiometricAuthenticator.TYPE_NONE);
when(mBiometricContext.getDisplayState()).thenReturn(entry.getKey());
assertThat(context.update(mBiometricContext, context.isCrypto()).getDisplayState())
.isEqualTo(entry.getValue());
@@ -124,7 +127,7 @@
updatesFromSource(null, OperationReason.UNKNOWN);
}
- private void updatesFromSource(BiometricContextSessionInfo sessionInfo, int sessionType) {
+ private void updatesFromSource(BiometricContextSessionInfo sessionInfo, int sessionType) {
final int rotation = Surface.ROTATION_270;
final int foldState = IBiometricContextListener.FoldState.HALF_OPENED;
final int dockState = Intent.EXTRA_DOCK_STATE_CAR;
@@ -135,9 +138,11 @@
when(mBiometricContext.getDockedState()).thenReturn(dockState);
when(mBiometricContext.isDisplayOn()).thenReturn(true);
when(mBiometricContext.getDisplayState()).thenReturn(displayState);
+ when(mBiometricContext.isHardwareIgnoringTouches()).thenReturn(true);
final OperationContextExt context = new OperationContextExt(newAidlContext(),
- sessionType == OperationReason.BIOMETRIC_PROMPT);
+ sessionType == OperationReason.BIOMETRIC_PROMPT,
+ BiometricAuthenticator.TYPE_FINGERPRINT);
assertThat(context.update(mBiometricContext, context.isCrypto())).isSameInstanceAs(context);
@@ -154,6 +159,46 @@
assertThat(context.getOrientation()).isEqualTo(rotation);
assertThat(context.isDisplayOn()).isTrue();
assertThat(context.getDisplayState()).isEqualTo(DisplayState.AOD);
+ assertThat(
+ context.getOperationState().getFingerprintOperationState().isHardwareIgnoringTouches
+ ).isTrue();
+ }
+
+ @Test
+ public void hasNullOperationState() {
+ OperationContextExt context = new OperationContextExt(false);
+ assertThat(context.toAidlContext()).isNotNull();
+
+ final OperationContext aidlContext = newAidlContext();
+
+ context = new OperationContextExt(aidlContext, false, BiometricAuthenticator.TYPE_NONE);
+ assertThat(context.getOperationState()).isNull();
+ }
+
+ @Test
+ public void hasFaceOperationState() {
+ OperationContextExt context = new OperationContextExt(false);
+ assertThat(context.toAidlContext()).isNotNull();
+
+ final OperationContext aidlContext = newAidlContext();
+
+ context = new OperationContextExt(aidlContext, false,
+ BiometricAuthenticator.TYPE_FACE);
+ assertThat(context.getOperationState().getTag()).isEqualTo(
+ OperationState.faceOperationState);
+ }
+
+ @Test
+ public void hasFingerprintOperationState() {
+ OperationContextExt context = new OperationContextExt(false);
+ assertThat(context.toAidlContext()).isNotNull();
+
+ final OperationContext aidlContext = newAidlContext();
+
+ context = new OperationContextExt(aidlContext, false,
+ BiometricAuthenticator.TYPE_FINGERPRINT);
+ assertThat(context.getOperationState().getTag()).isEqualTo(
+ OperationState.fingerprintOperationState);
}
private static OperationContext newAidlContext() {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java
index 2d9d868..4604b31 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java
@@ -72,6 +72,7 @@
mToken, mClientCallback);
client.start(mSchedulerCallback);
assertTrue(client.mHalOperationRunning);
+ verify(mClientCallback).getModality();
verify(mSchedulerCallback).onClientStarted(eq(client));
// Pretend that it got canceled by the user.
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index e1f490a..9ff29d2 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -608,6 +608,20 @@
}
@Test
+ public void testIsInputDeviceOwnedByVirtualDevice() {
+ assertThat(mLocalService.isInputDeviceOwnedByVirtualDevice(INPUT_DEVICE_ID)).isFalse();
+
+ final int fd = 1;
+ mInputController.addDeviceForTesting(BINDER, fd,
+ InputController.InputDeviceDescriptor.TYPE_KEYBOARD, DISPLAY_ID_1, PHYS,
+ DEVICE_NAME_1, INPUT_DEVICE_ID);
+ assertThat(mLocalService.isInputDeviceOwnedByVirtualDevice(INPUT_DEVICE_ID)).isTrue();
+
+ mInputController.unregisterInputDevice(BINDER);
+ assertThat(mLocalService.isInputDeviceOwnedByVirtualDevice(INPUT_DEVICE_ID)).isFalse();
+ }
+
+ @Test
public void getDeviceIdsForUid_noRunningApps_returnsNull() {
assertThat(mLocalService.getDeviceIdsForUid(UID_1)).isEmpty();
assertThat(mVdmNative.getDeviceIdsForUid(UID_1)).isEmpty();
@@ -815,6 +829,40 @@
}
@Test
+ public void getDisplayNameForPersistentDeviceId_nonExistentPeristentId_returnsNull() {
+ assertThat(mVdm.getDisplayNameForPersistentDeviceId("nonExistentPersistentId")).isNull();
+ }
+
+ @Test
+ public void getDisplayNameForPersistentDeviceId_defaultDevicePeristentId_returnsNull() {
+ assertThat(mVdm.getDisplayNameForPersistentDeviceId(
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT))
+ .isNull();
+ }
+
+ @Test
+ public void getDisplayNameForPersistentDeviceId_validVirtualDevice_returnsCorrectId() {
+ mVdms.onCdmAssociationsChanged(List.of(mAssociationInfo));
+ CharSequence persistentIdDisplayName =
+ mVdm.getDisplayNameForPersistentDeviceId(mDeviceImpl.getPersistentDeviceId());
+ assertThat(persistentIdDisplayName.toString())
+ .isEqualTo(mAssociationInfo.getDisplayName().toString());
+ }
+
+ @Test
+ public void getDisplayNameForPersistentDeviceId_noVirtualDevice_returnsCorrectId() {
+ CharSequence displayName = "New display name for the new association";
+ mVdms.onCdmAssociationsChanged(List.of(
+ createAssociationInfo(2, AssociationRequest.DEVICE_PROFILE_APP_STREAMING,
+ displayName)));
+
+ CharSequence persistentIdDisplayName =
+ mVdm.getDisplayNameForPersistentDeviceId(
+ VirtualDeviceImpl.createPersistentDeviceId(2));
+ assertThat(persistentIdDisplayName.toString()).isEqualTo(displayName.toString());
+ }
+
+ @Test
public void onAppsOnVirtualDeviceChanged_singleVirtualDevice_listenersNotified() {
ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
mLocalService.registerAppsOnVirtualDeviceListener(mAppsOnVirtualDeviceListener);
@@ -1957,7 +2005,7 @@
mRunningAppsChangedCallback,
params,
new DisplayManagerGlobal(mIDisplayManager),
- new VirtualCameraController());
+ new VirtualCameraController(DEVICE_POLICY_DEFAULT));
mVdms.addVirtualDevice(virtualDeviceImpl);
assertThat(virtualDeviceImpl.getAssociationId()).isEqualTo(mAssociationInfo.getId());
assertThat(virtualDeviceImpl.getPersistentDeviceId())
@@ -1980,8 +2028,14 @@
}
private AssociationInfo createAssociationInfo(int associationId, String deviceProfile) {
+ return createAssociationInfo(
+ associationId, deviceProfile, /* displayName= */ deviceProfile);
+ }
+
+ private AssociationInfo createAssociationInfo(int associationId, String deviceProfile,
+ CharSequence displayName) {
return new AssociationInfo(associationId, /* userId= */ 0, /* packageName=*/ null,
- /* tag= */ null, MacAddress.BROADCAST_ADDRESS, /* displayName= */ "", deviceProfile,
+ /* tag= */ null, MacAddress.BROADCAST_ADDRESS, displayName, deviceProfile,
/* associatedDevice= */ null, /* selfManaged= */ true,
/* notifyOnDeviceNearby= */ false, /* revoked= */ false, /* pending= */ false,
/* timeApprovedMs= */0, /* lastTimeConnectedMs= */0, /* systemDataSyncFlags= */ -1);
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
index 9b28b81..3e4f1df 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
@@ -16,27 +16,35 @@
package com.android.server.companion.virtual.camera;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.camera.VirtualCameraConfig.SENSOR_ORIENTATION_0;
+import static android.companion.virtual.camera.VirtualCameraConfig.SENSOR_ORIENTATION_90;
+import static android.graphics.ImageFormat.YUV_420_888;
+import static android.graphics.PixelFormat.RGBA_8888;
+import static android.hardware.camera2.CameraMetadata.LENS_FACING_BACK;
+import static android.hardware.camera2.CameraMetadata.LENS_FACING_FRONT;
+
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.annotation.NonNull;
import android.companion.virtual.camera.VirtualCameraCallback;
import android.companion.virtual.camera.VirtualCameraConfig;
-import android.companion.virtual.camera.VirtualCameraStreamConfig;
import android.companion.virtualcamera.IVirtualCameraService;
import android.companion.virtualcamera.VirtualCameraConfiguration;
-import android.graphics.ImageFormat;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Looper;
import android.platform.test.annotations.Presubmit;
-import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
-import android.view.Surface;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
import org.junit.After;
import org.junit.Before;
@@ -49,21 +57,30 @@
import java.util.List;
@Presubmit
-@RunWith(AndroidTestingRunner.class)
+@RunWith(JUnitParamsRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class VirtualCameraControllerTest {
private static final String CAMERA_NAME_1 = "Virtual camera 1";
private static final int CAMERA_WIDTH_1 = 100;
private static final int CAMERA_HEIGHT_1 = 200;
+ private static final int CAMERA_FORMAT_1 = YUV_420_888;
+ private static final int CAMERA_MAX_FPS_1 = 30;
+ private static final int CAMERA_SENSOR_ORIENTATION_1 = SENSOR_ORIENTATION_0;
+ private static final int CAMERA_LENS_FACING_1 = LENS_FACING_BACK;
private static final String CAMERA_NAME_2 = "Virtual camera 2";
private static final int CAMERA_WIDTH_2 = 400;
private static final int CAMERA_HEIGHT_2 = 600;
- private static final int CAMERA_FORMAT = ImageFormat.YUV_420_888;
+ private static final int CAMERA_FORMAT_2 = RGBA_8888;
+ private static final int CAMERA_MAX_FPS_2 = 60;
+ private static final int CAMERA_SENSOR_ORIENTATION_2 = SENSOR_ORIENTATION_90;
+ private static final int CAMERA_LENS_FACING_2 = LENS_FACING_FRONT;
@Mock
private IVirtualCameraService mVirtualCameraServiceMock;
+ @Mock
+ private VirtualCameraCallback mVirtualCameraCallbackMock;
private VirtualCameraController mVirtualCameraController;
private final HandlerExecutor mCallbackHandler =
@@ -72,7 +89,8 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mVirtualCameraController = new VirtualCameraController(mVirtualCameraServiceMock);
+ mVirtualCameraController = new VirtualCameraController(mVirtualCameraServiceMock,
+ DEVICE_POLICY_CUSTOM);
when(mVirtualCameraServiceMock.registerCamera(any(), any())).thenReturn(true);
}
@@ -81,10 +99,12 @@
mVirtualCameraController.close();
}
+ @Parameters(method = "getAllLensFacingDirections")
@Test
- public void registerCamera_registersCamera() throws Exception {
+ public void registerCamera_registersCamera(int lensFacing) throws Exception {
mVirtualCameraController.registerCamera(createVirtualCameraConfig(
- CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_NAME_1));
+ CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_MAX_FPS_1, CAMERA_NAME_1,
+ CAMERA_SENSOR_ORIENTATION_1, lensFacing));
ArgumentCaptor<VirtualCameraConfiguration> configurationCaptor =
ArgumentCaptor.forClass(VirtualCameraConfiguration.class);
@@ -92,13 +112,15 @@
VirtualCameraConfiguration virtualCameraConfiguration = configurationCaptor.getValue();
assertThat(virtualCameraConfiguration.supportedStreamConfigs.length).isEqualTo(1);
assertVirtualCameraConfiguration(virtualCameraConfiguration, CAMERA_WIDTH_1,
- CAMERA_HEIGHT_1, CAMERA_FORMAT);
+ CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_MAX_FPS_1, CAMERA_SENSOR_ORIENTATION_1,
+ lensFacing);
}
@Test
public void unregisterCamera_unregistersCamera() throws Exception {
VirtualCameraConfig config = createVirtualCameraConfig(
- CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_NAME_1);
+ CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_MAX_FPS_1, CAMERA_NAME_1,
+ CAMERA_SENSOR_ORIENTATION_1, CAMERA_LENS_FACING_1);
mVirtualCameraController.registerCamera(config);
mVirtualCameraController.unregisterCamera(config);
@@ -109,9 +131,11 @@
@Test
public void close_unregistersAllCameras() throws Exception {
mVirtualCameraController.registerCamera(createVirtualCameraConfig(
- CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_NAME_1));
+ CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_MAX_FPS_1, CAMERA_NAME_1,
+ CAMERA_SENSOR_ORIENTATION_1, CAMERA_LENS_FACING_1));
mVirtualCameraController.registerCamera(createVirtualCameraConfig(
- CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT, CAMERA_NAME_2));
+ CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT_2, CAMERA_MAX_FPS_2, CAMERA_NAME_2,
+ CAMERA_SENSOR_ORIENTATION_2, CAMERA_LENS_FACING_2));
mVirtualCameraController.close();
@@ -123,38 +147,66 @@
configurationCaptor.getAllValues();
assertThat(virtualCameraConfigurations).hasSize(2);
assertVirtualCameraConfiguration(virtualCameraConfigurations.get(0), CAMERA_WIDTH_1,
- CAMERA_HEIGHT_1, CAMERA_FORMAT);
+ CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_MAX_FPS_1, CAMERA_SENSOR_ORIENTATION_1,
+ CAMERA_LENS_FACING_1);
assertVirtualCameraConfiguration(virtualCameraConfigurations.get(1), CAMERA_WIDTH_2,
- CAMERA_HEIGHT_2, CAMERA_FORMAT);
+ CAMERA_HEIGHT_2, CAMERA_FORMAT_2, CAMERA_MAX_FPS_2, CAMERA_SENSOR_ORIENTATION_2,
+ CAMERA_LENS_FACING_2);
+ }
+
+ @Parameters(method = "getAllLensFacingDirections")
+ @Test
+ public void registerMultipleSameLensFacingCameras_withCustomCameraPolicy_throwsException(
+ int lensFacing) {
+ mVirtualCameraController.registerCamera(createVirtualCameraConfig(
+ CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_MAX_FPS_1, CAMERA_NAME_1,
+ CAMERA_SENSOR_ORIENTATION_1, lensFacing));
+ assertThrows(IllegalArgumentException.class,
+ () -> mVirtualCameraController.registerCamera(createVirtualCameraConfig(
+ CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT_2, CAMERA_MAX_FPS_2,
+ CAMERA_NAME_2, CAMERA_SENSOR_ORIENTATION_2, lensFacing)));
+ }
+
+ @Parameters(method = "getAllLensFacingDirections")
+ @Test
+ public void registerCamera_withDefaultCameraPolicy_throwsException(int lensFacing) {
+ mVirtualCameraController.close();
+ mVirtualCameraController = new VirtualCameraController(
+ mVirtualCameraServiceMock, DEVICE_POLICY_DEFAULT);
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mVirtualCameraController.registerCamera(createVirtualCameraConfig(
+ CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_MAX_FPS_1,
+ CAMERA_NAME_1, CAMERA_SENSOR_ORIENTATION_1, lensFacing)));
}
private VirtualCameraConfig createVirtualCameraConfig(
- int width, int height, int format, String displayName) {
+ int width, int height, int format, int maximumFramesPerSecond,
+ String name, int sensorOrientation, int lensFacing) {
return new VirtualCameraConfig.Builder()
- .addStreamConfig(width, height, format)
- .setName(displayName)
- .setVirtualCameraCallback(mCallbackHandler, createNoOpCallback())
+ .addStreamConfig(width, height, format, maximumFramesPerSecond)
+ .setName(name)
+ .setVirtualCameraCallback(mCallbackHandler, mVirtualCameraCallbackMock)
+ .setSensorOrientation(sensorOrientation)
+ .setLensFacing(lensFacing)
.build();
}
private static void assertVirtualCameraConfiguration(
- VirtualCameraConfiguration configuration, int width, int height, int format) {
+ VirtualCameraConfiguration configuration, int width, int height, int format,
+ int maxFps, int sensorOrientation, int lensFacing) {
assertThat(configuration.supportedStreamConfigs[0].width).isEqualTo(width);
assertThat(configuration.supportedStreamConfigs[0].height).isEqualTo(height);
assertThat(configuration.supportedStreamConfigs[0].pixelFormat).isEqualTo(format);
+ assertThat(configuration.supportedStreamConfigs[0].maxFps).isEqualTo(maxFps);
+ assertThat(configuration.sensorOrientation).isEqualTo(sensorOrientation);
+ assertThat(configuration.lensFacing).isEqualTo(lensFacing);
}
- private static VirtualCameraCallback createNoOpCallback() {
- return new VirtualCameraCallback() {
-
- @Override
- public void onStreamConfigured(
- int streamId,
- @NonNull Surface surface,
- @NonNull VirtualCameraStreamConfig streamConfig) {}
-
- @Override
- public void onStreamClosed(int streamId) {}
+ private static Integer[] getAllLensFacingDirections() {
+ return new Integer[] {
+ LENS_FACING_BACK,
+ LENS_FACING_FRONT
};
}
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraStreamConfigTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraStreamConfigTest.java
index d9a38eb..206c111 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraStreamConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraStreamConfigTest.java
@@ -35,19 +35,20 @@
private static final int VGA_WIDTH = 640;
private static final int VGA_HEIGHT = 480;
+ private static final int MAX_FPS_1 = 30;
private static final int QVGA_WIDTH = 320;
private static final int QVGA_HEIGHT = 240;
+ private static final int MAX_FPS_2 = 60;
@Test
public void testEquals() {
VirtualCameraStreamConfig vgaYuvStreamConfig = new VirtualCameraStreamConfig(VGA_WIDTH,
- VGA_HEIGHT,
- ImageFormat.YUV_420_888);
+ VGA_HEIGHT, ImageFormat.YUV_420_888, MAX_FPS_1);
VirtualCameraStreamConfig qvgaYuvStreamConfig = new VirtualCameraStreamConfig(QVGA_WIDTH,
- QVGA_HEIGHT, ImageFormat.YUV_420_888);
+ QVGA_HEIGHT, ImageFormat.YUV_420_888, MAX_FPS_2);
VirtualCameraStreamConfig vgaRgbaStreamConfig = new VirtualCameraStreamConfig(VGA_WIDTH,
- VGA_HEIGHT, PixelFormat.RGBA_8888);
+ VGA_HEIGHT, PixelFormat.RGBA_8888, MAX_FPS_1);
new EqualsTester()
.addEqualityGroup(vgaYuvStreamConfig, reparcel(vgaYuvStreamConfig))
@@ -66,6 +67,4 @@
parcel.recycle();
}
}
-
-
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 543fa57..6207349 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -1775,6 +1775,11 @@
@Test
public void wakeUp_hotPlugIn_invokesDeviceDiscoveryOnce() {
+ // There might be a leftover HotplugDetectionAction that can interfere with the test.
+ mHdmiCecLocalDevicePlayback.removeAction(HotplugDetectionAction.class);
+
+ long pollingDelay = TimeUnit.SECONDS.toMillis(60);
+ mHdmiCecController.setPollDevicesDelay(pollingDelay);
mNativeWrapper.setPollAddressResponse(Constants.ADDR_PLAYBACK_2, SendMessageResult.SUCCESS);
mHdmiControlService.onWakeUp(HdmiControlService.WAKE_UP_SCREEN_ON);
mTestLooper.dispatchAll();
@@ -1783,6 +1788,14 @@
mTestLooper.dispatchAll();
assertThat(mHdmiCecLocalDevicePlayback.getActions(DeviceDiscoveryAction.class)).hasSize(1);
+ mTestLooper.moveTimeForward(pollingDelay);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage givePhysicalAddress = HdmiCecMessageBuilder.buildGivePhysicalAddress(
+ Constants.ADDR_PLAYBACK_1,
+ Constants.ADDR_PLAYBACK_2);
+ assertThat(mNativeWrapper.getResultMessages().stream()
+ .filter(message -> message.equals(givePhysicalAddress)).count()).isEqualTo(1);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSettingsTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSettingsTest.java
new file mode 100644
index 0000000..a55d1c4
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSettingsTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+
+import android.text.TextUtils;
+import android.util.IntArray;
+
+import androidx.annotation.NonNull;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class InputMethodSettingsTest {
+ private static void verifyUpdateEnabledImeString(@NonNull String expectedEnabledImeStr,
+ @NonNull String initialEnabledImeStr, @NonNull String imeId,
+ @NonNull String enabledSubtypeHashCodesStr) {
+ assertEquals(expectedEnabledImeStr,
+ InputMethodSettings.updateEnabledImeString(initialEnabledImeStr,
+ imeId, createSubtypeHashCodeArrayFromStr(enabledSubtypeHashCodesStr)));
+ }
+
+ private static IntArray createSubtypeHashCodeArrayFromStr(String subtypeHashCodesStr) {
+ final IntArray subtypes = new IntArray();
+ final TextUtils.SimpleStringSplitter imeSubtypeSplitter =
+ new TextUtils.SimpleStringSplitter(';');
+ if (TextUtils.isEmpty(subtypeHashCodesStr)) {
+ return subtypes;
+ }
+ imeSubtypeSplitter.setString(subtypeHashCodesStr);
+ while (imeSubtypeSplitter.hasNext()) {
+ subtypes.add(Integer.parseInt(imeSubtypeSplitter.next()));
+ }
+ return subtypes;
+ }
+
+ @Test
+ public void updateEnabledImeStringTest() {
+ // No change cases
+ verifyUpdateEnabledImeString(
+ "com.android/.ime1",
+ "com.android/.ime1", "com.android/.ime1", "");
+ verifyUpdateEnabledImeString(
+ "com.android/.ime1",
+ "com.android/.ime1", "com.android/.ime2", "");
+
+ // To enable subtypes
+ verifyUpdateEnabledImeString(
+ "com.android/.ime1",
+ "com.android/.ime1", "com.android/.ime2", "");
+ verifyUpdateEnabledImeString(
+ "com.android/.ime1;1",
+ "com.android/.ime1", "com.android/.ime1", "1");
+
+ verifyUpdateEnabledImeString(
+ "com.android/.ime1;1;2;3",
+ "com.android/.ime1", "com.android/.ime1", "1;2;3");
+
+ verifyUpdateEnabledImeString(
+ "com.android/.ime1;1;2;3:com.android/.ime2",
+ "com.android/.ime1:com.android/.ime2", "com.android/.ime1", "1;2;3");
+ verifyUpdateEnabledImeString(
+ "com.android/.ime0:com.android/.ime1;1;2;3",
+ "com.android/.ime0:com.android/.ime1", "com.android/.ime1", "1;2;3");
+ verifyUpdateEnabledImeString(
+ "com.android/.ime0:com.android/.ime1;1;2;3:com.android/.ime2",
+ "com.android/.ime0:com.android/.ime1:com.android/.ime2", "com.android/.ime1",
+ "1;2;3");
+
+ // To reset enabled subtypes
+ verifyUpdateEnabledImeString(
+ "com.android/.ime1",
+ "com.android/.ime1;1", "com.android/.ime1", "");
+ verifyUpdateEnabledImeString(
+ "com.android/.ime1",
+ "com.android/.ime1;1;2;3", "com.android/.ime1", "");
+ verifyUpdateEnabledImeString(
+ "com.android/.ime1:com.android/.ime2",
+ "com.android/.ime1;1;2;3:com.android/.ime2", "com.android/.ime1", "");
+
+ verifyUpdateEnabledImeString(
+ "com.android/.ime0:com.android/.ime1",
+ "com.android/.ime0:com.android/.ime1;1;2;3", "com.android/.ime1", "");
+ verifyUpdateEnabledImeString(
+ "com.android/.ime0:com.android/.ime1:com.android/.ime2",
+ "com.android/.ime0:com.android/.ime1;1;2;3:com.android/.ime2", "com.android/.ime1",
+ "");
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
index 6b85a32..9688ef6 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
@@ -25,28 +25,16 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import android.content.ContentResolver;
import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.IContentProvider;
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Configuration;
-import android.content.res.Resources;
import android.os.Build;
import android.os.LocaleList;
import android.os.Parcel;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.test.mock.MockContentResolver;
-import android.text.TextUtils;
import android.util.ArrayMap;
-import android.util.IntArray;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
@@ -1221,85 +1209,6 @@
StartInputFlags.VIEW_HAS_FOCUS | StartInputFlags.IS_TEXT_EDITOR));
}
- private static IntArray createSubtypeHashCodeArrayFromStr(String subtypeHashCodesStr) {
- final IntArray subtypes = new IntArray();
- final TextUtils.SimpleStringSplitter imeSubtypeSplitter =
- new TextUtils.SimpleStringSplitter(';');
- if (TextUtils.isEmpty(subtypeHashCodesStr)) {
- return subtypes;
- }
- imeSubtypeSplitter.setString(subtypeHashCodesStr);
- while (imeSubtypeSplitter.hasNext()) {
- subtypes.add(Integer.parseInt(imeSubtypeSplitter.next()));
- }
- return subtypes;
- }
-
- private static void verifyUpdateEnabledImeString(@NonNull String expectedEnabledImeStr,
- @NonNull String initialEnabledImeStr, @NonNull String imeId,
- @NonNull String enabledSubtypeHashCodesStr) {
- assertEquals(expectedEnabledImeStr,
- InputMethodUtils.InputMethodSettings.updateEnabledImeString(initialEnabledImeStr,
- imeId, createSubtypeHashCodeArrayFromStr(enabledSubtypeHashCodesStr)));
- }
-
- private static TestContext createMockContext(int userId) {
- return new TestContext(InstrumentationRegistry.getInstrumentation()
- .getTargetContext(), userId);
- }
-
- private static class TestContext extends ContextWrapper {
- private int mUserId;
- private ContentResolver mResolver;
- private Resources mResources;
-
- private static TestContext sSecondaryUserContext;
-
- TestContext(@NonNull Context context, int userId) {
- super(context);
- mUserId = userId;
- mResolver = mock(MockContentResolver.class);
- when(mResolver.acquireProvider(Settings.Secure.CONTENT_URI)).thenReturn(
- mock(IContentProvider.class));
- mResources = mock(Resources.class);
-
- final Configuration configuration = new Configuration();
- if (userId == 0) {
- configuration.setLocale(LOCALE_EN_US);
- } else {
- configuration.setLocale(LOCALE_FR_CA);
- }
- doReturn(configuration).when(mResources).getConfiguration();
- }
-
- @Override
- public Context createContextAsUser(UserHandle user, int flags) {
- if (user.getIdentifier() != UserHandle.USER_SYSTEM) {
- return sSecondaryUserContext = new TestContext(this, user.getIdentifier());
- }
- return this;
- }
-
- @Override
- public int getUserId() {
- return mUserId;
- }
-
- @Override
- public ContentResolver getContentResolver() {
- return mResolver;
- }
-
- @Override
- public Resources getResources() {
- return mResources;
- }
-
- static Context getSecondaryUserContext() {
- return sSecondaryUserContext;
- }
- }
-
private static void verifySplitEnabledImeStr(@NonNull String enabledImeStr,
@NonNull String... expected) {
final ArrayList<String> actual = new ArrayList<>();
@@ -1347,57 +1256,4 @@
"com.android/.ime1:com.android/.ime2", "com.android/.ime3"))
.isEqualTo("com.android/.ime1:com.android/.ime2:com.android/.ime3");
}
-
- @Test
- public void updateEnabledImeStringTest() {
- // No change cases
- verifyUpdateEnabledImeString(
- "com.android/.ime1",
- "com.android/.ime1", "com.android/.ime1", "");
- verifyUpdateEnabledImeString(
- "com.android/.ime1",
- "com.android/.ime1", "com.android/.ime2", "");
-
- // To enable subtypes
- verifyUpdateEnabledImeString(
- "com.android/.ime1",
- "com.android/.ime1", "com.android/.ime2", "");
- verifyUpdateEnabledImeString(
- "com.android/.ime1;1",
- "com.android/.ime1", "com.android/.ime1", "1");
-
- verifyUpdateEnabledImeString(
- "com.android/.ime1;1;2;3",
- "com.android/.ime1", "com.android/.ime1", "1;2;3");
-
- verifyUpdateEnabledImeString(
- "com.android/.ime1;1;2;3:com.android/.ime2",
- "com.android/.ime1:com.android/.ime2", "com.android/.ime1", "1;2;3");
- verifyUpdateEnabledImeString(
- "com.android/.ime0:com.android/.ime1;1;2;3",
- "com.android/.ime0:com.android/.ime1", "com.android/.ime1", "1;2;3");
- verifyUpdateEnabledImeString(
- "com.android/.ime0:com.android/.ime1;1;2;3:com.android/.ime2",
- "com.android/.ime0:com.android/.ime1:com.android/.ime2", "com.android/.ime1",
- "1;2;3");
-
- // To reset enabled subtypes
- verifyUpdateEnabledImeString(
- "com.android/.ime1",
- "com.android/.ime1;1", "com.android/.ime1", "");
- verifyUpdateEnabledImeString(
- "com.android/.ime1",
- "com.android/.ime1;1;2;3", "com.android/.ime1", "");
- verifyUpdateEnabledImeString(
- "com.android/.ime1:com.android/.ime2",
- "com.android/.ime1;1;2;3:com.android/.ime2", "com.android/.ime1", "");
-
- verifyUpdateEnabledImeString(
- "com.android/.ime0:com.android/.ime1",
- "com.android/.ime0:com.android/.ime1;1;2;3", "com.android/.ime1", "");
- verifyUpdateEnabledImeString(
- "com.android/.ime0:com.android/.ime1:com.android/.ime2",
- "com.android/.ime0:com.android/.ime1;1;2;3:com.android/.ime2", "com.android/.ime1",
- "");
- }
}
diff --git a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
deleted file mode 100644
index e871fc5..0000000
--- a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.job;
-
-import static com.android.servicestests.apps.jobtestapp.TestJobService.ACTION_JOB_STARTED;
-import static com.android.servicestests.apps.jobtestapp.TestJobService.ACTION_JOB_STOPPED;
-import static com.android.servicestests.apps.jobtestapp.TestJobService.JOB_PARAMS_EXTRA_KEY;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.app.ActivityManager;
-import android.app.AppOpsManager;
-import android.app.IActivityManager;
-import android.app.job.JobParameters;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.IDeviceIdleController;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.servicestests.apps.jobtestapp.TestJobActivity;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests that background restrictions on jobs work as expected.
- * This test requires test-apps/JobTestApp to be installed on the device.
- * To run this test from root of checkout:
- * <pre>
- * mmm -j32 frameworks/base/services/tests/servicestests/
- * adb install -r $OUT/data/app/JobTestApp/JobTestApp.apk
- * adb install -r $OUT/data/app/FrameworksServicesTests/FrameworksServicesTests.apk
- * adb shell am instrument -e class 'com.android.server.job.BackgroundRestrictionsTest' -w \
- * com.android.frameworks.servicestests
- * </pre>
- */
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class BackgroundRestrictionsTest {
- private static final String TAG = BackgroundRestrictionsTest.class.getSimpleName();
- private static final String TEST_APP_PACKAGE = "com.android.servicestests.apps.jobtestapp";
- private static final String TEST_APP_ACTIVITY = TEST_APP_PACKAGE + ".TestJobActivity";
- private static final long POLL_INTERVAL = 500;
- private static final long DEFAULT_WAIT_TIMEOUT = 10_000;
-
- private Context mContext;
- private AppOpsManager mAppOpsManager;
- private IDeviceIdleController mDeviceIdleController;
- private IActivityManager mIActivityManager;
- private volatile int mTestJobId = -1;
- private int mTestPackageUid;
- /* accesses must be synchronized on itself */
- private final TestJobStatus mTestJobStatus = new TestJobStatus();
- private final BroadcastReceiver mJobStateChangeReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final JobParameters params = intent.getParcelableExtra(JOB_PARAMS_EXTRA_KEY);
- Log.d(TAG, "Received action " + intent.getAction());
- synchronized (mTestJobStatus) {
- switch (intent.getAction()) {
- case ACTION_JOB_STARTED:
- mTestJobStatus.running = true;
- mTestJobStatus.jobId = params.getJobId();
- mTestJobStatus.stopReason = JobParameters.STOP_REASON_UNDEFINED;
- break;
- case ACTION_JOB_STOPPED:
- mTestJobStatus.running = false;
- mTestJobStatus.jobId = params.getJobId();
- mTestJobStatus.stopReason = params.getStopReason();
- break;
- }
- }
- }
- };
-
- @Before
- public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getTargetContext();
- mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
- mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
- ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
- mIActivityManager = ActivityManager.getService();
- mTestPackageUid = mContext.getPackageManager().getPackageUid(TEST_APP_PACKAGE, 0);
- mTestJobStatus.reset();
- final IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(ACTION_JOB_STARTED);
- intentFilter.addAction(ACTION_JOB_STOPPED);
- mContext.registerReceiver(mJobStateChangeReceiver, intentFilter,
- Context.RECEIVER_EXPORTED_UNAUDITED);
- setAppOpsModeAllowed(true);
- setPowerExemption(false);
- }
-
- private void scheduleTestJob() {
- mTestJobId = (int) (SystemClock.uptimeMillis() / 1000);
- final Intent scheduleJobIntent = new Intent(TestJobActivity.ACTION_START_JOB);
- scheduleJobIntent.putExtra(TestJobActivity.EXTRA_JOB_ID_KEY, mTestJobId);
- scheduleJobIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- scheduleJobIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_ACTIVITY));
- mContext.startActivity(scheduleJobIntent);
- }
-
- private void scheduleAndAssertJobStarted() throws Exception {
- scheduleTestJob();
- Thread.sleep(TestJobActivity.JOB_MINIMUM_LATENCY);
- assertTrue("Job did not start after scheduling", awaitJobStart(DEFAULT_WAIT_TIMEOUT));
- }
-
- @FlakyTest
- @Test
- public void testPowerExemption() throws Exception {
- scheduleAndAssertJobStarted();
- setAppOpsModeAllowed(false);
- mIActivityManager.makePackageIdle(TEST_APP_PACKAGE, UserHandle.USER_CURRENT);
- assertTrue("Job did not stop after putting app under bg-restriction",
- awaitJobStop(DEFAULT_WAIT_TIMEOUT,
- JobParameters.STOP_REASON_BACKGROUND_RESTRICTION));
-
- setPowerExemption(true);
- scheduleTestJob();
- Thread.sleep(TestJobActivity.JOB_MINIMUM_LATENCY);
- assertTrue("Job did not start when the app was in the power exemption list",
- awaitJobStart(DEFAULT_WAIT_TIMEOUT));
-
- setPowerExemption(false);
- assertTrue("Job did not stop after removing from the power exemption list",
- awaitJobStop(DEFAULT_WAIT_TIMEOUT,
- JobParameters.STOP_REASON_BACKGROUND_RESTRICTION));
-
- scheduleTestJob();
- Thread.sleep(TestJobActivity.JOB_MINIMUM_LATENCY);
- assertFalse("Job started under bg-restrictions", awaitJobStart(DEFAULT_WAIT_TIMEOUT));
- setPowerExemption(true);
- assertTrue("Job did not start when the app was in the power exemption list",
- awaitJobStart(DEFAULT_WAIT_TIMEOUT));
- }
-
- @After
- public void tearDown() throws Exception {
- final Intent cancelJobsIntent = new Intent(TestJobActivity.ACTION_CANCEL_JOBS);
- cancelJobsIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_ACTIVITY));
- cancelJobsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivity(cancelJobsIntent);
- mContext.unregisterReceiver(mJobStateChangeReceiver);
- Thread.sleep(500); // To avoid race with register in the next setUp
- setAppOpsModeAllowed(true);
- setPowerExemption(false);
- }
-
- private void setPowerExemption(boolean exempt) throws RemoteException {
- if (exempt) {
- mDeviceIdleController.addPowerSaveWhitelistApp(TEST_APP_PACKAGE);
- } else {
- mDeviceIdleController.removePowerSaveWhitelistApp(TEST_APP_PACKAGE);
- }
- }
-
- private void setAppOpsModeAllowed(boolean allow) {
- mAppOpsManager.setMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mTestPackageUid,
- TEST_APP_PACKAGE, allow ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED);
- }
-
- private boolean awaitJobStart(long timeout) throws InterruptedException {
- return waitUntilTrue(timeout, () -> {
- synchronized (mTestJobStatus) {
- return (mTestJobStatus.jobId == mTestJobId) && mTestJobStatus.running;
- }
- });
- }
-
- private boolean awaitJobStop(long timeout, @JobParameters.StopReason int expectedStopReason)
- throws InterruptedException {
- return waitUntilTrue(timeout, () -> {
- synchronized (mTestJobStatus) {
- return (mTestJobStatus.jobId == mTestJobId) && !mTestJobStatus.running
- && (expectedStopReason == JobParameters.STOP_REASON_UNDEFINED
- || mTestJobStatus.stopReason == expectedStopReason);
- }
- });
- }
-
- private boolean waitUntilTrue(long timeout, Condition condition) throws InterruptedException {
- final long deadLine = SystemClock.uptimeMillis() + timeout;
- do {
- Thread.sleep(POLL_INTERVAL);
- } while (!condition.isTrue() && SystemClock.uptimeMillis() < deadLine);
- return condition.isTrue();
- }
-
- private static final class TestJobStatus {
- int jobId;
- int stopReason;
- boolean running;
-
- private void reset() {
- running = false;
- stopReason = jobId = 0;
- }
- }
-
- private interface Condition {
- boolean isTrue();
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 0f5fb91..d50affb 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -406,8 +406,8 @@
public void testPushDynamicShortcut() {
// Change the max number of shortcuts.
- mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=5");
-
+ mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=5,"
+ + ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=1");
setCaller(CALLING_PACKAGE_1, USER_0);
final ShortcutInfo s1 = makeShortcut("s1");
@@ -545,6 +545,57 @@
eq(CALLING_PACKAGE_1), eq("s9"), eq(USER_0));
}
+ public void testPushDynamicShortcut_CallsToUsageStatsManagerAreThrottled()
+ throws InterruptedException {
+ mService.updateConfigurationLocked(
+ ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=500");
+
+ // Verify calls to UsageStatsManagerInternal#reportShortcutUsage are throttled.
+ setCaller(CALLING_PACKAGE_1, USER_0);
+ {
+ final ShortcutInfo si = makeShortcut("s0");
+ mManager.pushDynamicShortcut(si);
+ }
+ verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage(
+ eq(CALLING_PACKAGE_1), eq("s0"), eq(USER_0));
+ Mockito.reset(mMockUsageStatsManagerInternal);
+ for (int i = 2; i <= 10; i++) {
+ final ShortcutInfo si = makeShortcut("s" + i);
+ mManager.pushDynamicShortcut(si);
+ }
+ verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage(
+ any(), any(), anyInt());
+
+ // Verify pkg2 isn't blocked by pkg1, but consecutive calls from pkg2 are throttled as well.
+ setCaller(CALLING_PACKAGE_2, USER_0);
+ {
+ final ShortcutInfo si = makeShortcut("s1");
+ mManager.pushDynamicShortcut(si);
+ }
+ verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage(
+ eq(CALLING_PACKAGE_2), eq("s1"), eq(USER_0));
+ Mockito.reset(mMockUsageStatsManagerInternal);
+ for (int i = 2; i <= 10; i++) {
+ final ShortcutInfo si = makeShortcut("s" + i);
+ mManager.pushDynamicShortcut(si);
+ }
+ verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage(
+ any(), any(), anyInt());
+
+ Mockito.reset(mMockUsageStatsManagerInternal);
+ // Let time passes which resets the throttle
+ Thread.sleep(505);
+ // Verify UsageStatsManagerInternal#reportShortcutUsed can be called again
+ setCaller(CALLING_PACKAGE_1, USER_0);
+ mManager.pushDynamicShortcut(makeShortcut("s10"));
+ setCaller(CALLING_PACKAGE_2, USER_0);
+ mManager.pushDynamicShortcut(makeShortcut("s10"));
+ verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage(
+ eq(CALLING_PACKAGE_1), any(), eq(USER_0));
+ verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage(
+ eq(CALLING_PACKAGE_2), any(), eq(USER_0));
+ }
+
public void testUnlimitedCalls() {
setCaller(CALLING_PACKAGE_1, USER_0);
diff --git a/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java
index 769ec5f..3218586 100644
--- a/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java
@@ -345,15 +345,18 @@
intent, UID_PRIMARY_CAMERA, PKG_SOCIAL, USER_PRIMARY), service);
// Verify that everything is good with the world
- assertTrue(mService.checkUriPermission(expectedGrant, UID_PRIMARY_SOCIAL, FLAG_READ));
+ assertTrue(mService.checkUriPermission(expectedGrant, UID_PRIMARY_SOCIAL, FLAG_READ,
+ /* isFullAccessForContentUri */ false));
// Finish activity; service should hold permission
activity.removeUriPermissions();
- assertTrue(mService.checkUriPermission(expectedGrant, UID_PRIMARY_SOCIAL, FLAG_READ));
+ assertTrue(mService.checkUriPermission(expectedGrant, UID_PRIMARY_SOCIAL, FLAG_READ,
+ /* isFullAccessForContentUri */ false));
// And finishing service should wrap things up
service.removeUriPermissions();
- assertFalse(mService.checkUriPermission(expectedGrant, UID_PRIMARY_SOCIAL, FLAG_READ));
+ assertFalse(mService.checkUriPermission(expectedGrant, UID_PRIMARY_SOCIAL, FLAG_READ,
+ /* isFullAccessForContentUri */ false));
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
index 3530e38..ae0a758 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
@@ -85,7 +85,7 @@
private void enablePackageForUser(String packageName, boolean enable, int userId) {
Map<Integer, PackageInfo> userPackages = mPackages.get(packageName);
if (userPackages == null) {
- throw new IllegalArgumentException("There is no package called " + packageName);
+ return;
}
PackageInfo packageInfo = userPackages.get(userId);
packageInfo.applicationInfo.enabled = enable;
diff --git a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
index 32082e3..5a06327 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
@@ -127,12 +127,21 @@
private void checkCertainPackageUsedAfterWebViewBootPreparation(String expectedProviderName,
WebViewProviderInfo[] webviewPackages) {
checkCertainPackageUsedAfterWebViewBootPreparation(
- expectedProviderName, webviewPackages, 1);
+ expectedProviderName, webviewPackages, 1, null);
}
private void checkCertainPackageUsedAfterWebViewBootPreparation(String expectedProviderName,
- WebViewProviderInfo[] webviewPackages, int numRelros) {
+ WebViewProviderInfo[] webviewPackages, String userSetting) {
+ checkCertainPackageUsedAfterWebViewBootPreparation(
+ expectedProviderName, webviewPackages, 1, userSetting);
+ }
+
+ private void checkCertainPackageUsedAfterWebViewBootPreparation(String expectedProviderName,
+ WebViewProviderInfo[] webviewPackages, int numRelros, String userSetting) {
setupWithPackagesAndRelroCount(webviewPackages, numRelros);
+ if (userSetting != null) {
+ mTestSystemImpl.updateUserSetting(null, userSetting);
+ }
// Add (enabled and valid) package infos for each provider
setEnabledAndValidPackageInfos(webviewPackages);
@@ -280,7 +289,7 @@
singlePackage,
new WebViewProviderInfo[] {
new WebViewProviderInfo(singlePackage, "", true /*def av*/, false, null)},
- 2);
+ 2, null);
}
// Ensure that package with valid signatures is chosen rather than package with invalid
@@ -295,14 +304,16 @@
Signature invalidPackageSignature = new Signature("33");
WebViewProviderInfo[] packages = new WebViewProviderInfo[] {
- new WebViewProviderInfo(invalidPackage, "", true, false, new String[]{
- Base64.encodeToString(
- invalidExpectedSignature.toByteArray(), Base64.DEFAULT)}),
new WebViewProviderInfo(validPackage, "", true, false, new String[]{
Base64.encodeToString(
- validSignature.toByteArray(), Base64.DEFAULT)})
+ validSignature.toByteArray(), Base64.DEFAULT)}),
+ new WebViewProviderInfo(invalidPackage, "", true, false, new String[]{
+ Base64.encodeToString(
+ invalidExpectedSignature.toByteArray(), Base64.DEFAULT)})
};
setupWithPackagesNonDebuggable(packages);
+ // Start with the setting pointing to the invalid package
+ mTestSystemImpl.updateUserSetting(null, invalidPackage);
mTestSystemImpl.setPackageInfo(createPackageInfo(invalidPackage, true /* enabled */,
true /* valid */, true /* installed */, new Signature[]{invalidPackageSignature}
, 0 /* updateTime */));
@@ -339,7 +350,9 @@
}
@Test
- public void testFailListingEmptyWebviewPackages() {
+ @RequiresFlagsDisabled("android.webkit.update_service_v2")
+ // If the flag is set, will throw an exception because of no available by default provider.
+ public void testEmptyConfig() {
WebViewProviderInfo[] packages = new WebViewProviderInfo[0];
setupWithPackages(packages);
setEnabledAndValidPackageInfos(packages);
@@ -352,14 +365,26 @@
WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider();
assertEquals(WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES, response.status);
assertEquals(null, mWebViewUpdateServiceImpl.getCurrentWebViewPackage());
+ }
- // Now install a package
+ @Test
+ public void testFailListingEmptyWebviewPackages() {
String singlePackage = "singlePackage";
- packages = new WebViewProviderInfo[]{
+ WebViewProviderInfo[] packages = new WebViewProviderInfo[]{
new WebViewProviderInfo(singlePackage, "", true, false, null)};
setupWithPackages(packages);
- setEnabledAndValidPackageInfos(packages);
+ runWebViewBootPreparationOnMainSync();
+
+ Mockito.verify(mTestSystemImpl, Mockito.never()).onWebViewProviderChanged(
+ Matchers.anyObject());
+
+ WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider();
+ assertEquals(WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES, response.status);
+ assertEquals(null, mWebViewUpdateServiceImpl.getCurrentWebViewPackage());
+
+ // Now install the package
+ setEnabledAndValidPackageInfos(packages);
mWebViewUpdateServiceImpl.packageStateChanged(singlePackage,
WebViewUpdateService.PACKAGE_ADDED, TestSystemImpl.PRIMARY_USER_ID);
@@ -370,7 +395,7 @@
// Remove the package again
mTestSystemImpl.removePackageInfo(singlePackage);
mWebViewUpdateServiceImpl.packageStateChanged(singlePackage,
- WebViewUpdateService.PACKAGE_ADDED, TestSystemImpl.PRIMARY_USER_ID);
+ WebViewUpdateService.PACKAGE_REMOVED, TestSystemImpl.PRIMARY_USER_ID);
// Package removed - ensure our interface states that there is no package
response = mWebViewUpdateServiceImpl.waitForAndGetProvider();
@@ -455,6 +480,8 @@
new WebViewProviderInfo(firstPackage, "", true, false, null),
new WebViewProviderInfo(secondPackage, "", true, false, null)};
setupWithPackages(packages);
+ // Start with the setting pointing to the second package
+ mTestSystemImpl.updateUserSetting(null, secondPackage);
// Have all packages be enabled, so that we can change provider however we want to
setEnabledAndValidPackageInfos(packages);
@@ -463,9 +490,9 @@
runWebViewBootPreparationOnMainSync();
Mockito.verify(mTestSystemImpl).onWebViewProviderChanged(
- Mockito.argThat(new IsPackageInfoWithName(firstPackage)));
+ Mockito.argThat(new IsPackageInfoWithName(secondPackage)));
- assertEquals(firstPackage,
+ assertEquals(secondPackage,
mWebViewUpdateServiceImpl.getCurrentWebViewPackage().packageName);
new Thread(new Runnable() {
@@ -474,12 +501,13 @@
WebViewProviderResponse threadResponse =
mWebViewUpdateServiceImpl.waitForAndGetProvider();
assertEquals(WebViewFactory.LIBLOAD_SUCCESS, threadResponse.status);
- assertEquals(secondPackage, threadResponse.packageInfo.packageName);
- // Verify that we killed the first package if we performed a settings change -
- // otherwise we had to disable the first package, in which case its dependents
+ assertEquals(firstPackage, threadResponse.packageInfo.packageName);
+ // Verify that we killed the second package if we performed a settings change -
+ // otherwise we had to disable the second package, in which case its dependents
// should have been killed by the framework.
if (settingsChange) {
- Mockito.verify(mTestSystemImpl).killPackageDependents(Mockito.eq(firstPackage));
+ Mockito.verify(mTestSystemImpl)
+ .killPackageDependents(Mockito.eq(secondPackage));
}
countdown.countDown();
}
@@ -490,32 +518,36 @@
}
if (settingsChange) {
- mWebViewUpdateServiceImpl.changeProviderAndSetting(secondPackage);
+ mWebViewUpdateServiceImpl.changeProviderAndSetting(firstPackage);
} else {
- // Enable the second provider
- mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, true /* enabled */,
+ // Enable the first provider
+ mTestSystemImpl.setPackageInfo(createPackageInfo(firstPackage, true /* enabled */,
true /* valid */, true /* installed */));
mWebViewUpdateServiceImpl.packageStateChanged(
- secondPackage, WebViewUpdateService.PACKAGE_CHANGED, TestSystemImpl.PRIMARY_USER_ID);
+ firstPackage,
+ WebViewUpdateService.PACKAGE_CHANGED,
+ TestSystemImpl.PRIMARY_USER_ID);
// Ensure we haven't changed package yet.
- assertEquals(firstPackage,
+ assertEquals(secondPackage,
mWebViewUpdateServiceImpl.getCurrentWebViewPackage().packageName);
- // Switch provider by disabling the first one
- mTestSystemImpl.setPackageInfo(createPackageInfo(firstPackage, false /* enabled */,
+ // Switch provider by disabling the second one
+ mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, false /* enabled */,
true /* valid */, true /* installed */));
mWebViewUpdateServiceImpl.packageStateChanged(
- firstPackage, WebViewUpdateService.PACKAGE_CHANGED, TestSystemImpl.PRIMARY_USER_ID);
+ secondPackage,
+ WebViewUpdateService.PACKAGE_CHANGED,
+ TestSystemImpl.PRIMARY_USER_ID);
}
mWebViewUpdateServiceImpl.notifyRelroCreationCompleted();
- // first package done, should start on second
+ // second package done, should start on first
Mockito.verify(mTestSystemImpl).onWebViewProviderChanged(
- Mockito.argThat(new IsPackageInfoWithName(secondPackage)));
+ Mockito.argThat(new IsPackageInfoWithName(firstPackage)));
mWebViewUpdateServiceImpl.notifyRelroCreationCompleted();
- // second package done, the other thread should now be unblocked
+ // first package done, the other thread should now be unblocked
try {
countdown.await();
} catch (InterruptedException e) {
@@ -526,6 +558,7 @@
* Scenario for testing re-enabling a fallback package.
*/
@Test
+ @RequiresFlagsDisabled("android.webkit.update_service_v2")
public void testFallbackPackageEnabling() {
String testPackage = "testFallback";
WebViewProviderInfo[] packages = new WebViewProviderInfo[] {
@@ -555,6 +588,9 @@
* 3. Primary should be used
*/
@Test
+ @RequiresFlagsDisabled("android.webkit.update_service_v2")
+ // If the flag is set, we don't automitally switch to secondary package unless it is
+ // chosen directly.
public void testInstallingPrimaryPackage() {
String primaryPackage = "primary";
String secondaryPackage = "secondary";
@@ -586,16 +622,16 @@
}
@Test
- public void testRemovingPrimarySelectsSecondarySingleUser() {
+ public void testRemovingSecondarySelectsPrimarySingleUser() {
for (PackageRemovalType removalType : REMOVAL_TYPES) {
- checkRemovingPrimarySelectsSecondary(false /* multiUser */, removalType);
+ checkRemovingSecondarySelectsPrimary(false /* multiUser */, removalType);
}
}
@Test
- public void testRemovingPrimarySelectsSecondaryMultiUser() {
+ public void testRemovingSecondarySelectsPrimaryMultiUser() {
for (PackageRemovalType removalType : REMOVAL_TYPES) {
- checkRemovingPrimarySelectsSecondary(true /* multiUser */, removalType);
+ checkRemovingSecondarySelectsPrimary(true /* multiUser */, removalType);
}
}
@@ -609,7 +645,7 @@
private PackageRemovalType[] REMOVAL_TYPES = PackageRemovalType.class.getEnumConstants();
- public void checkRemovingPrimarySelectsSecondary(boolean multiUser,
+ private void checkRemovingSecondarySelectsPrimary(boolean multiUser,
PackageRemovalType removalType) {
String primaryPackage = "primary";
String secondaryPackage = "secondary";
@@ -620,6 +656,8 @@
secondaryPackage, "", true /* default available */, false /* fallback */,
null)};
setupWithPackages(packages);
+ // Start with the setting pointing to the secondary package
+ mTestSystemImpl.updateUserSetting(null, secondaryPackage);
int secondaryUserId = 10;
int userIdToChangePackageFor = multiUser ? secondaryUserId : TestSystemImpl.PRIMARY_USER_ID;
if (multiUser) {
@@ -629,31 +667,31 @@
setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, packages);
runWebViewBootPreparationOnMainSync();
- checkPreparationPhasesForPackage(primaryPackage, 1);
+ checkPreparationPhasesForPackage(secondaryPackage, 1);
boolean enabled = !(removalType == PackageRemovalType.DISABLE);
boolean installed = !(removalType == PackageRemovalType.UNINSTALL);
boolean hidden = (removalType == PackageRemovalType.HIDE);
- // Disable primary package and ensure secondary becomes used
+ // Disable secondary package and ensure primary becomes used
mTestSystemImpl.setPackageInfoForUser(userIdToChangePackageFor,
- createPackageInfo(primaryPackage, enabled /* enabled */, true /* valid */,
+ createPackageInfo(secondaryPackage, enabled /* enabled */, true /* valid */,
installed /* installed */, null /* signature */, 0 /* updateTime */,
hidden /* hidden */));
- mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage,
+ mWebViewUpdateServiceImpl.packageStateChanged(secondaryPackage,
removalType == PackageRemovalType.DISABLE
? WebViewUpdateService.PACKAGE_CHANGED : WebViewUpdateService.PACKAGE_REMOVED,
userIdToChangePackageFor); // USER ID
- checkPreparationPhasesForPackage(secondaryPackage, 1);
+ checkPreparationPhasesForPackage(primaryPackage, 1);
- // Again enable primary package and verify primary is used
+ // Again enable secondary package and verify secondary is used
mTestSystemImpl.setPackageInfoForUser(userIdToChangePackageFor,
- createPackageInfo(primaryPackage, true /* enabled */, true /* valid */,
+ createPackageInfo(secondaryPackage, true /* enabled */, true /* valid */,
true /* installed */));
- mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage,
+ mWebViewUpdateServiceImpl.packageStateChanged(secondaryPackage,
removalType == PackageRemovalType.DISABLE
? WebViewUpdateService.PACKAGE_CHANGED : WebViewUpdateService.PACKAGE_ADDED,
userIdToChangePackageFor);
- checkPreparationPhasesForPackage(primaryPackage, 2);
+ checkPreparationPhasesForPackage(secondaryPackage, 2);
}
/**
@@ -671,18 +709,20 @@
secondaryPackage, "", true /* default available */, false /* fallback */,
null)};
setupWithPackages(packages);
+ // Start with the setting pointing to the secondary package
+ mTestSystemImpl.updateUserSetting(null, secondaryPackage);
setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, packages);
int newUser = 100;
mTestSystemImpl.addUser(newUser);
- // Let the primary package be uninstalled for the new user
- mTestSystemImpl.setPackageInfoForUser(newUser,
- createPackageInfo(primaryPackage, true /* enabled */, true /* valid */,
- false /* installed */));
+ // Let the secondary package be uninstalled for the new user
mTestSystemImpl.setPackageInfoForUser(newUser,
createPackageInfo(secondaryPackage, true /* enabled */, true /* valid */,
+ false /* installed */));
+ mTestSystemImpl.setPackageInfoForUser(newUser,
+ createPackageInfo(primaryPackage, true /* enabled */, true /* valid */,
true /* installed */));
mWebViewUpdateServiceImpl.handleNewUser(newUser);
- checkPreparationPhasesForPackage(secondaryPackage, 1 /* numRelros */);
+ checkPreparationPhasesForPackage(primaryPackage, 1 /* numRelros */);
}
/**
@@ -780,9 +820,9 @@
String chosenPackage = "chosenPackage";
String nonChosenPackage = "non-chosenPackage";
WebViewProviderInfo[] packages = new WebViewProviderInfo[] {
- new WebViewProviderInfo(chosenPackage, "", true /* default available */,
- false /* fallback */, null),
new WebViewProviderInfo(nonChosenPackage, "", true /* default available */,
+ false /* fallback */, null),
+ new WebViewProviderInfo(chosenPackage, "", true /* default available */,
false /* fallback */, null)};
setupWithPackages(packages);
@@ -810,6 +850,9 @@
}
@Test
+ @RequiresFlagsDisabled("android.webkit.update_service_v2")
+ // If the flag is set, we don't automitally switch to second package unless it is chosen
+ // directly.
public void testRecoverFailedListingWebViewPackagesAddedPackage() {
checkRecoverAfterFailListingWebviewPackages(false);
}
@@ -874,22 +917,22 @@
false /* fallback */, null),
new WebViewProviderInfo(secondPackage, "", true /* default available */,
false /* fallback */, null)};
- checkCertainPackageUsedAfterWebViewBootPreparation(firstPackage, packages);
+ checkCertainPackageUsedAfterWebViewBootPreparation(secondPackage, packages, secondPackage);
// Replace or remove the current webview package
if (replaced) {
mTestSystemImpl.setPackageInfo(
- createPackageInfo(firstPackage, true /* enabled */, false /* valid */,
+ createPackageInfo(secondPackage, true /* enabled */, false /* valid */,
true /* installed */));
- mWebViewUpdateServiceImpl.packageStateChanged(firstPackage,
+ mWebViewUpdateServiceImpl.packageStateChanged(secondPackage,
WebViewUpdateService.PACKAGE_ADDED_REPLACED, TestSystemImpl.PRIMARY_USER_ID);
} else {
- mTestSystemImpl.removePackageInfo(firstPackage);
- mWebViewUpdateServiceImpl.packageStateChanged(firstPackage,
+ mTestSystemImpl.removePackageInfo(secondPackage);
+ mWebViewUpdateServiceImpl.packageStateChanged(secondPackage,
WebViewUpdateService.PACKAGE_REMOVED, TestSystemImpl.PRIMARY_USER_ID);
}
- checkPreparationPhasesForPackage(secondPackage, 1);
+ checkPreparationPhasesForPackage(firstPackage, 1);
Mockito.verify(mTestSystemImpl, Mockito.never()).killPackageDependents(
Mockito.anyObject());
@@ -1073,10 +1116,12 @@
}
/**
- * Ensure that the update service does use an uninstalled package when that is the only
+ * Ensure that the update service does not use an uninstalled package even if it is the only
* package available.
*/
@Test
+ @RequiresFlagsDisabled("android.webkit.update_service_v2")
+ // If the flag is set, we return the package even if it is not installed.
public void testWithSingleUninstalledPackage() {
String testPackageName = "test.package.name";
WebViewProviderInfo[] webviewPackages = new WebViewProviderInfo[] {
@@ -1115,12 +1160,14 @@
String installedPackage = "installedPackage";
String uninstalledPackage = "uninstalledPackage";
WebViewProviderInfo[] webviewPackages = new WebViewProviderInfo[] {
- new WebViewProviderInfo(uninstalledPackage, "", true /* available by default */,
- false /* fallback */, null),
new WebViewProviderInfo(installedPackage, "", true /* available by default */,
+ false /* fallback */, null),
+ new WebViewProviderInfo(uninstalledPackage, "", true /* available by default */,
false /* fallback */, null)};
setupWithPackages(webviewPackages);
+ // Start with the setting pointing to the uninstalled package
+ mTestSystemImpl.updateUserSetting(null, uninstalledPackage);
int secondaryUserId = 5;
if (multiUser) {
mTestSystemImpl.addUser(secondaryUserId);
@@ -1128,7 +1175,7 @@
setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, webviewPackages);
mTestSystemImpl.setPackageInfoForUser(secondaryUserId, createPackageInfo(
installedPackage, true /* enabled */, true /* valid */, true /* installed */));
- // Hide or uninstall the primary package for the second user
+ // Hide or uninstall the secondary package for the second user
mTestSystemImpl.setPackageInfo(createPackageInfo(uninstalledPackage, true /* enabled */,
true /* valid */, (testUninstalled ? false : true) /* installed */,
null /* signatures */, 0 /* updateTime */, (testHidden ? true : false)));
@@ -1166,12 +1213,14 @@
String installedPackage = "installedPackage";
String uninstalledPackage = "uninstalledPackage";
WebViewProviderInfo[] webviewPackages = new WebViewProviderInfo[] {
- new WebViewProviderInfo(uninstalledPackage, "", true /* available by default */,
- false /* fallback */, null),
new WebViewProviderInfo(installedPackage, "", true /* available by default */,
+ false /* fallback */, null),
+ new WebViewProviderInfo(uninstalledPackage, "", true /* available by default */,
false /* fallback */, null)};
setupWithPackages(webviewPackages);
+ // Start with the setting pointing to the uninstalled package
+ mTestSystemImpl.updateUserSetting(null, uninstalledPackage);
int secondaryUserId = 412;
mTestSystemImpl.addUser(secondaryUserId);
@@ -1221,12 +1270,14 @@
String installedPackage = "installedPackage";
String uninstalledPackage = "uninstalledPackage";
WebViewProviderInfo[] webviewPackages = new WebViewProviderInfo[] {
- new WebViewProviderInfo(uninstalledPackage, "", true /* available by default */,
- false /* fallback */, null),
new WebViewProviderInfo(installedPackage, "", true /* available by default */,
+ false /* fallback */, null),
+ new WebViewProviderInfo(uninstalledPackage, "", true /* available by default */,
false /* fallback */, null)};
setupWithPackages(webviewPackages);
+ // Start with the setting pointing to the uninstalled package
+ mTestSystemImpl.updateUserSetting(null, uninstalledPackage);
int secondaryUserId = 4;
mTestSystemImpl.addUser(secondaryUserId);
@@ -1433,11 +1484,16 @@
new WebViewProviderInfo(newSdkPackage.packageName, "", true, false, null);
WebViewProviderInfo currentSdkProviderInfo =
new WebViewProviderInfo(currentSdkPackage.packageName, "", true, false, null);
- WebViewProviderInfo[] packages = new WebViewProviderInfo[] {
- new WebViewProviderInfo(oldSdkPackage.packageName, "", true, false, null),
- currentSdkProviderInfo, newSdkProviderInfo};
+ WebViewProviderInfo[] packages =
+ new WebViewProviderInfo[] {
+ currentSdkProviderInfo,
+ new WebViewProviderInfo(oldSdkPackage.packageName, "", true, false, null),
+ newSdkProviderInfo
+ };
setupWithPackages(packages);
-;
+ // Start with the setting pointing to the invalid package
+ mTestSystemImpl.updateUserSetting(null, oldSdkPackage.packageName);
+
mTestSystemImpl.setPackageInfo(newSdkPackage);
mTestSystemImpl.setPackageInfo(currentSdkPackage);
mTestSystemImpl.setPackageInfo(oldSdkPackage);
@@ -1467,4 +1523,74 @@
assertEquals(
defaultPackage1, mWebViewUpdateServiceImpl.getDefaultWebViewPackage().packageName);
}
+
+ @Test
+ @RequiresFlagsEnabled("android.webkit.update_service_v2")
+ public void testDefaultWebViewPackageEnabling() {
+ String testPackage = "testDefault";
+ WebViewProviderInfo[] packages =
+ new WebViewProviderInfo[] {
+ new WebViewProviderInfo(
+ testPackage,
+ "",
+ true /* default available */,
+ false /* fallback */,
+ null)
+ };
+ setupWithPackages(packages);
+ mTestSystemImpl.setPackageInfo(
+ createPackageInfo(
+ testPackage, false /* enabled */, true /* valid */, true /* installed */));
+
+ // Check that the boot time logic re-enables the default package.
+ runWebViewBootPreparationOnMainSync();
+ Mockito.verify(mTestSystemImpl)
+ .enablePackageForAllUsers(
+ Matchers.anyObject(), Mockito.eq(testPackage), Mockito.eq(true));
+ }
+
+ private void testDefaultPackageChosen(PackageInfo packageInfo) {
+ WebViewProviderInfo[] packages =
+ new WebViewProviderInfo[] {
+ new WebViewProviderInfo(packageInfo.packageName, "", true, false, null)
+ };
+ setupWithPackages(packages);
+ mTestSystemImpl.setPackageInfo(packageInfo);
+
+ runWebViewBootPreparationOnMainSync();
+ mWebViewUpdateServiceImpl.notifyRelroCreationCompleted();
+
+ assertEquals(
+ packageInfo.packageName,
+ mWebViewUpdateServiceImpl.getCurrentWebViewPackage().packageName);
+
+ WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider();
+ assertEquals(packageInfo.packageName, response.packageInfo.packageName);
+ }
+
+ @Test
+ @RequiresFlagsEnabled("android.webkit.update_service_v2")
+ public void testDisabledDefaultPackageChosen() {
+ PackageInfo disabledPackage =
+ createPackageInfo(
+ "disabledPackage",
+ false /* enabled */,
+ true /* valid */,
+ true /* installed */);
+
+ testDefaultPackageChosen(disabledPackage);
+ }
+
+ @Test
+ @RequiresFlagsEnabled("android.webkit.update_service_v2")
+ public void testUninstalledDefaultPackageChosen() {
+ PackageInfo uninstalledPackage =
+ createPackageInfo(
+ "uninstalledPackage",
+ true /* enabled */,
+ true /* valid */,
+ false /* installed */);
+
+ testDefaultPackageChosen(uninstalledPackage);
+ }
}
diff --git a/services/tests/servicestests/test-apps/JobTestApp/Android.bp b/services/tests/servicestests/test-apps/JobTestApp/Android.bp
deleted file mode 100644
index 6458bcd..0000000
--- a/services/tests/servicestests/test-apps/JobTestApp/Android.bp
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (C) 2017 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_license"],
-}
-
-android_test_helper_app {
- name: "JobTestApp",
-
- sdk_version: "current",
-
- srcs: ["**/*.java"],
-
- dex_preopt: {
- enabled: false,
- },
- optimize: {
- enabled: false,
- },
-}
diff --git a/services/tests/servicestests/test-apps/JobTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/JobTestApp/AndroidManifest.xml
deleted file mode 100644
index ac35805..0000000
--- a/services/tests/servicestests/test-apps/JobTestApp/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.servicestests.apps.jobtestapp">
-
- <application>
- <service android:name=".TestJobService"
- android:permission="android.permission.BIND_JOB_SERVICE" />
- <activity android:name=".TestJobActivity"
- android:exported="true" />
- </application>
-
-</manifest>
\ No newline at end of file
diff --git a/services/tests/servicestests/test-apps/JobTestApp/OWNERS b/services/tests/servicestests/test-apps/JobTestApp/OWNERS
deleted file mode 100644
index 6f207fb1..0000000
--- a/services/tests/servicestests/test-apps/JobTestApp/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /apex/jobscheduler/OWNERS
diff --git a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java
deleted file mode 100644
index 99eb196..0000000
--- a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.servicestests.apps.jobtestapp;
-
-import android.app.Activity;
-import android.app.job.JobInfo;
-import android.app.job.JobScheduler;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.util.Log;
-
-public class TestJobActivity extends Activity {
- private static final String TAG = TestJobActivity.class.getSimpleName();
- private static final String PACKAGE_NAME = "com.android.servicestests.apps.jobtestapp";
-
- public static final String EXTRA_JOB_ID_KEY = PACKAGE_NAME + ".extra.JOB_ID";
- public static final String ACTION_START_JOB = PACKAGE_NAME + ".action.START_JOB";
- public static final String ACTION_CANCEL_JOBS = PACKAGE_NAME + ".action.CANCEL_JOBS";
- public static final int JOB_INITIAL_BACKOFF = 10_000;
- public static final int JOB_MINIMUM_LATENCY = 5_000;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- ComponentName jobServiceComponent = new ComponentName(this, TestJobService.class);
- JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
- final Intent intent = getIntent();
- switch (intent.getAction()) {
- case ACTION_CANCEL_JOBS:
- jobScheduler.cancelAll();
- Log.d(TAG, "Cancelled all jobs for " + getPackageName());
- break;
- case ACTION_START_JOB:
- final int jobId = intent.getIntExtra(EXTRA_JOB_ID_KEY, hashCode());
- JobInfo.Builder jobBuilder = new JobInfo.Builder(jobId, jobServiceComponent)
- .setBackoffCriteria(JOB_INITIAL_BACKOFF, JobInfo.BACKOFF_POLICY_LINEAR)
- .setMinimumLatency(JOB_MINIMUM_LATENCY)
- .setOverrideDeadline(JOB_MINIMUM_LATENCY);
- final int result = jobScheduler.schedule(jobBuilder.build());
- if (result != JobScheduler.RESULT_SUCCESS) {
- Log.e(TAG, "Could not schedule job " + jobId);
- } else {
- Log.d(TAG, "Successfully scheduled job with id " + jobId);
- }
- break;
- default:
- Log.e(TAG, "Unknown action " + intent.getAction());
- }
- finish();
- }
-}
\ No newline at end of file
diff --git a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java
deleted file mode 100644
index b8585f2..0000000
--- a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.servicestests.apps.jobtestapp;
-
-import android.annotation.TargetApi;
-import android.app.job.JobParameters;
-import android.app.job.JobService;
-import android.content.Intent;
-import android.util.Log;
-
-@TargetApi(24)
-public class TestJobService extends JobService {
- private static final String TAG = TestJobService.class.getSimpleName();
- private static final String PACKAGE_NAME = "com.android.servicestests.apps.jobtestapp";
- public static final String ACTION_JOB_STARTED = PACKAGE_NAME + ".action.JOB_STARTED";
- public static final String ACTION_JOB_STOPPED = PACKAGE_NAME + ".action.JOB_STOPPED";
- public static final String JOB_PARAMS_EXTRA_KEY = PACKAGE_NAME + ".extra.JOB_PARAMETERS";
-
- @Override
- public boolean onStartJob(JobParameters params) {
- Log.i(TAG, "Test job executing: " + params.getJobId());
- Intent reportJobStartIntent = new Intent(ACTION_JOB_STARTED);
- reportJobStartIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- sendBroadcast(reportJobStartIntent);
- return true;
- }
-
- @Override
- public boolean onStopJob(JobParameters params) {
- Log.i(TAG, "Test job stopped executing: " + params.getJobId());
- Intent reportJobStopIntent = new Intent(ACTION_JOB_STOPPED);
- reportJobStopIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- sendBroadcast(reportJobStopIntent);
- // Deadline constraint is dropped on reschedule, so it's more reliable to use a new job.
- return false;
- }
-}
diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp
index 4e1c72a..2f29d10 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -47,6 +47,7 @@
"flag-junit",
"notification_flags_lib",
"platform-test-rules",
+ "SettingsLib",
],
libs: [
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index db46532..839cf7c 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -17,6 +17,9 @@
package com.android.server;
import static android.Manifest.permission.MODIFY_DAY_NIGHT_MODE;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_DAY;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_NIGHT;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
import static android.app.UiModeManager.MODE_NIGHT_AUTO;
import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME;
@@ -32,6 +35,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static junit.framework.Assert.fail;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertTrue;
@@ -65,6 +69,7 @@
import android.Manifest;
import android.app.Activity;
import android.app.AlarmManager;
+import android.app.Flags;
import android.app.IOnProjectionStateChangedListener;
import android.app.IUiModeManager;
import android.content.BroadcastReceiver;
@@ -84,6 +89,8 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.test.FakePermissionEnforcer;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.service.dreams.DreamManagerInternal;
import android.test.mock.MockContentResolver;
@@ -98,6 +105,7 @@
import org.junit.Before;
import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -109,6 +117,7 @@
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.List;
+import java.util.Map;
import java.util.function.Consumer;
@RunWith(AndroidTestingRunner.class)
@@ -159,6 +168,11 @@
private TwilightListener mTwilightListener;
private FakePermissionEnforcer mPermissionEnforcer;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(
+ SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT);
+
+
@Before
public void setUp() {
// The AIDL stub will use PermissionEnforcer to check permission from the caller.
@@ -1437,6 +1451,51 @@
verify(mInjector).startDreamWhenDockedIfAppropriate(mContext);
}
+ private void testAttentionModeThemeOverlay(boolean modeNight) throws RemoteException {
+ //setup
+ if (modeNight) {
+ mService.setNightMode(MODE_NIGHT_YES);
+ assertTrue(mUiManagerService.getConfiguration().isNightModeActive());
+ } else {
+ mService.setNightMode(MODE_NIGHT_NO);
+ assertFalse(mUiManagerService.getConfiguration().isNightModeActive());
+ }
+
+ // attention modes with expected night modes
+ Map<Integer, Boolean> modes = Map.of(
+ MODE_ATTENTION_THEME_OVERLAY_OFF, modeNight,
+ MODE_ATTENTION_THEME_OVERLAY_DAY, false,
+ MODE_ATTENTION_THEME_OVERLAY_NIGHT, true
+ );
+
+ // test
+ for (int aMode : modes.keySet()) {
+ try {
+ mService.setAttentionModeThemeOverlay(aMode);
+
+ int appliedAMode = mService.getAttentionModeThemeOverlay();
+ boolean nMode = modes.get(aMode);
+
+ assertEquals(aMode, appliedAMode);
+ assertEquals(isNightModeActivated(), nMode);
+ } catch (RemoteException e) {
+ fail("Error communicating with server: " + e.getMessage());
+ }
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void testAttentionModeThemeOverlay_nightModeDisabled() throws RemoteException {
+ testAttentionModeThemeOverlay(false);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void testAttentionModeThemeOverlay_nightModeEnabled() throws RemoteException {
+ testAttentionModeThemeOverlay(true);
+ }
+
private void triggerDockIntent() {
final Intent dockedIntent =
new Intent(Intent.ACTION_DOCK_EVENT)
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
index 30843d2..3797dbb 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
@@ -16,7 +16,8 @@
package com.android.server.notification;
-import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_NIGHT;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_APP;
import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER;
@@ -121,28 +122,49 @@
verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
verify(mColorDisplayManager).setSaturationLevel(eq(0));
verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
- verify(mUiModeManager).setNightModeActivatedForCustomMode(
- eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true));
+ verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT));
}
@Test
- public void apply_removesPreviouslyAppliedEffects() {
+ public void apply_removesEffects() {
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
ZenDeviceEffects previousEffects = new ZenDeviceEffects.Builder()
.setShouldSuppressAmbientDisplay(true)
.setShouldDimWallpaper(true)
+ .setShouldDisplayGrayscale(true)
+ .setShouldUseNightMode(true)
.build();
mApplier.apply(previousEffects, UPDATE_ORIGIN_USER);
verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
+ verify(mColorDisplayManager).setSaturationLevel(eq(0));
verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
+ verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT));
ZenDeviceEffects noEffects = new ZenDeviceEffects.Builder().build();
mApplier.apply(noEffects, UPDATE_ORIGIN_USER);
verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(false));
+ verify(mColorDisplayManager).setSaturationLevel(eq(100));
verify(mWallpaperManager).setWallpaperDimAmount(eq(0.0f));
- verifyZeroInteractions(mColorDisplayManager, mUiModeManager);
+ verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_OFF));
+ }
+
+ @Test
+ public void apply_removesOnlyPreviouslyAppliedEffects() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+ ZenDeviceEffects previousEffects = new ZenDeviceEffects.Builder()
+ .setShouldSuppressAmbientDisplay(true)
+ .build();
+ mApplier.apply(previousEffects, UPDATE_ORIGIN_USER);
+ verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
+
+ ZenDeviceEffects noEffects = new ZenDeviceEffects.Builder().build();
+ mApplier.apply(noEffects, UPDATE_ORIGIN_USER);
+
+ verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(false));
+ verifyZeroInteractions(mColorDisplayManager, mWallpaperManager, mUiModeManager);
}
@Test
@@ -150,6 +172,7 @@
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mContext.addMockSystemService(ColorDisplayManager.class, null);
mContext.addMockSystemService(WallpaperManager.class, null);
+ mApplier = new DefaultDeviceEffectsApplier(mContext);
ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
.setShouldSuppressAmbientDisplay(true)
@@ -177,7 +200,7 @@
verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
verify(mPowerManager, never()).suppressAmbientDisplay(anyString(), anyBoolean());
- verify(mUiModeManager, never()).setNightModeActivatedForCustomMode(anyInt(), anyBoolean());
+ verify(mUiModeManager, never()).setAttentionModeThemeOverlay(anyInt());
}
@Test
@@ -223,8 +246,7 @@
screenOffReceiver.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
// So the effect is applied, and we stopped listening for this event.
- verify(mUiModeManager).setNightModeActivatedForCustomMode(
- eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true));
+ verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT));
verify(mContext).unregisterReceiver(eq(screenOffReceiver));
}
@@ -239,8 +261,7 @@
origin.value());
// Effect was applied, and no broadcast receiver was registered.
- verify(mUiModeManager).setNightModeActivatedForCustomMode(
- eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true));
+ verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT));
verify(mContext, never()).registerReceiver(any(), any(), anyInt());
}
@@ -256,8 +277,7 @@
origin.value());
// Effect was applied, and no broadcast receiver was registered.
- verify(mUiModeManager).setNightModeActivatedForCustomMode(
- eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true));
+ verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT));
verify(mContext, never()).registerReceiver(any(), any(), anyInt());
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java
index c10c3c2..9b25f58 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java
@@ -183,7 +183,6 @@
assertThat(mHistoryManager.doesHistoryExistForUser(mProfileId)).isFalse();
verify(mDb, times(2)).disableHistory();
}
-
@Test
public void testAddProfile_historyEnabledInPrimary() {
// create a history
@@ -610,4 +609,14 @@
assertThat(mHistoryManager.isHistoryEnabled(USER_SYSTEM)).isFalse();
}
+ @Test
+ public void testDelayedPackageRemoval_userLocked() {
+ String pkg = "pkg";
+ mHistoryManager.onPackageRemoved(USER_SYSTEM, pkg);
+ mHistoryManager.onUserUnlocked(USER_SYSTEM);
+ mHistoryManager.onUserStopped(USER_SYSTEM);
+ mHistoryManager.onPackageRemoved(USER_SYSTEM, pkg);
+
+ // no exception, yay
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
index 53ca704..bf850cf 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -44,8 +44,10 @@
import android.content.pm.ShortcutInfo;
import android.graphics.Bitmap;
import android.graphics.drawable.Icon;
+import android.os.BadParcelableException;
import android.os.Binder;
import android.os.Build;
+import android.os.DeadObjectException;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -99,6 +101,20 @@
}
@Test
+ public void testGetActiveNotifications_handlesBinderErrors() throws RemoteException {
+ TestListenerService service = new TestListenerService();
+ INotificationManager noMan = service.getNoMan();
+ when(noMan.getActiveNotificationsFromListener(any(), any(), anyInt()))
+ .thenThrow(new BadParcelableException("oops", new DeadObjectException("")));
+
+ assertNotNull(service.getActiveNotifications());
+ assertNotNull(service.getActiveNotifications(NotificationListenerService.TRIM_FULL));
+ assertNotNull(service.getActiveNotifications(new String[0]));
+ assertNull(service.getActiveNotifications(
+ new String[0], NotificationListenerService.TRIM_LIGHT));
+ }
+
+ @Test
public void testGetActiveNotifications_preP_mapsExtraPeople() throws RemoteException {
TestListenerService service = new TestListenerService();
service.attachBaseContext(mContext);
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 c1f35cc..723ac15 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -13792,8 +13792,7 @@
NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
mBinderService.setNotificationPolicy("package", policy, false);
- verify(zenHelper).applyGlobalPolicyAsImplicitZenRule(eq("package"), anyInt(), eq(policy),
- eq(ZenModeConfig.UPDATE_ORIGIN_APP));
+ verify(zenHelper).applyGlobalPolicyAsImplicitZenRule(eq("package"), anyInt(), eq(policy));
}
@Test
@@ -13859,7 +13858,7 @@
verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
} else {
verify(zenModeHelper).applyGlobalPolicyAsImplicitZenRule(anyString(), anyInt(),
- eq(policy), anyInt());
+ eq(policy));
}
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
index 08af09c..0e20daf 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
@@ -143,12 +143,12 @@
Policy.policyState(false, true), 0);
ZenPolicy zenPolicy = notificationPolicyToZenPolicy(policy);
- assertThat(zenPolicy.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_PRIORITY);
+ assertThat(zenPolicy.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_ALLOW);
Policy notAllowed = new Policy(0, 0, 0, 0,
Policy.policyState(false, false), 0);
ZenPolicy zenPolicyNotAllowed = notificationPolicyToZenPolicy(notAllowed);
- assertThat(zenPolicyNotAllowed.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_NONE);
+ assertThat(zenPolicyNotAllowed.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_DISALLOW);
}
@Test
@@ -158,12 +158,11 @@
Policy.policyState(false, true), 0);
ZenPolicy zenPolicy = notificationPolicyToZenPolicy(policy);
- assertThat(zenPolicy.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_UNSET);
+ assertThat(zenPolicy.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_UNSET);
Policy notAllowed = new Policy(0, 0, 0, 0,
Policy.policyState(false, false), 0);
ZenPolicy zenPolicyNotAllowed = notificationPolicyToZenPolicy(notAllowed);
- assertThat(zenPolicyNotAllowed.getAllowedChannels())
- .isEqualTo(ZenPolicy.CHANNEL_TYPE_UNSET);
+ assertThat(zenPolicyNotAllowed.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_UNSET);
}
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java
index 3d8ec2e..f604f1e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java
@@ -52,7 +52,6 @@
.setShouldMaximizeDoze(true)
.setShouldUseNightMode(false)
.setShouldSuppressAmbientDisplay(false).setShouldSuppressAmbientDisplay(true)
- .setUserModifiedFields(8)
.build();
assertThat(deviceEffects.shouldDimWallpaper()).isTrue();
@@ -65,7 +64,6 @@
assertThat(deviceEffects.shouldMinimizeRadioUsage()).isFalse();
assertThat(deviceEffects.shouldUseNightMode()).isFalse();
assertThat(deviceEffects.shouldSuppressAmbientDisplay()).isTrue();
- assertThat(deviceEffects.getUserModifiedFields()).isEqualTo(8);
}
@Test
@@ -97,7 +95,6 @@
.setShouldMinimizeRadioUsage(true)
.setShouldUseNightMode(true)
.setShouldSuppressAmbientDisplay(true)
- .setUserModifiedFields(6)
.build();
Parcel parcel = Parcel.obtain();
@@ -116,7 +113,6 @@
assertThat(copy.shouldUseNightMode()).isTrue();
assertThat(copy.shouldSuppressAmbientDisplay()).isTrue();
assertThat(copy.shouldDisplayGrayscale()).isFalse();
- assertThat(copy.getUserModifiedFields()).isEqualTo(6);
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
index 177d645..e523e79f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -60,6 +60,7 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.time.Instant;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -163,7 +164,7 @@
.allowConversations(CONVERSATION_SENDERS_IMPORTANT)
.showLights(false)
.showInAmbientDisplay(false)
- .allowChannels(ZenPolicy.CHANNEL_TYPE_NONE)
+ .allowPriorityChannels(false)
.build();
Policy originalPolicy = config.toNotificationPolicy();
@@ -254,7 +255,7 @@
.allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS)
.allowMessages(ZenPolicy.PEOPLE_TYPE_STARRED)
.allowConversations(ZenPolicy.CONVERSATION_SENDERS_NONE)
- .allowChannels(ZenPolicy.CHANNEL_TYPE_NONE)
+ .allowPriorityChannels(false)
.build();
ZenModeConfig config = getMutedAllConfig();
@@ -283,8 +284,7 @@
actual.getPriorityConversationSenders());
assertEquals(expected.getPriorityCallSenders(), actual.getPriorityCallSenders());
assertEquals(expected.getPriorityMessageSenders(), actual.getPriorityMessageSenders());
- assertEquals(expected.getAllowedChannels(), actual.getAllowedChannels());
- assertEquals(expected.getUserModifiedFields(), actual.getUserModifiedFields());
+ assertEquals(expected.getPriorityChannels(), actual.getPriorityChannels());
}
@Test
@@ -341,45 +341,32 @@
ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
rule.zenPolicy = null;
rule.zenDeviceEffects = null;
-
assertThat(rule.canBeUpdatedByApp()).isTrue();
rule.userModifiedFields = 1;
+
assertThat(rule.canBeUpdatedByApp()).isFalse();
}
@Test
public void testCanBeUpdatedByApp_policyModified() throws Exception {
- ZenPolicy.Builder policyBuilder = new ZenPolicy.Builder().setUserModifiedFields(0);
- ZenPolicy policy = policyBuilder.build();
-
ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
- rule.zenPolicy = policy;
-
- assertThat(rule.userModifiedFields).isEqualTo(0);
+ rule.zenPolicy = new ZenPolicy();
assertThat(rule.canBeUpdatedByApp()).isTrue();
- policy = policyBuilder.setUserModifiedFields(1).build();
- assertThat(policy.getUserModifiedFields()).isEqualTo(1);
- rule.zenPolicy = policy;
+ rule.zenPolicyUserModifiedFields = 1;
+
assertThat(rule.canBeUpdatedByApp()).isFalse();
}
@Test
public void testCanBeUpdatedByApp_deviceEffectsModified() throws Exception {
- ZenDeviceEffects.Builder deviceEffectsBuilder =
- new ZenDeviceEffects.Builder().setUserModifiedFields(0);
- ZenDeviceEffects deviceEffects = deviceEffectsBuilder.build();
-
ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
- rule.zenDeviceEffects = deviceEffects;
-
- assertThat(rule.userModifiedFields).isEqualTo(0);
+ rule.zenDeviceEffects = new ZenDeviceEffects.Builder().build();
assertThat(rule.canBeUpdatedByApp()).isTrue();
- deviceEffects = deviceEffectsBuilder.setUserModifiedFields(1).build();
- assertThat(deviceEffects.getUserModifiedFields()).isEqualTo(1);
- rule.zenDeviceEffects = deviceEffects;
+ rule.zenDeviceEffectsUserModifiedFields = 1;
+
assertThat(rule.canBeUpdatedByApp()).isFalse();
}
@@ -405,8 +392,11 @@
rule.allowManualInvocation = ALLOW_MANUAL;
rule.type = TYPE;
rule.userModifiedFields = 16;
+ rule.zenPolicyUserModifiedFields = 5;
+ rule.zenDeviceEffectsUserModifiedFields = 2;
rule.iconResName = ICON_RES_NAME;
rule.triggerDescription = TRIGGER_DESC;
+ rule.deletionInstant = Instant.ofEpochMilli(1701790147000L);
Parcel parcel = Parcel.obtain();
rule.writeToParcel(parcel, 0);
@@ -430,11 +420,15 @@
assertEquals(rule.iconResName, parceled.iconResName);
assertEquals(rule.type, parceled.type);
assertEquals(rule.userModifiedFields, parceled.userModifiedFields);
+ assertEquals(rule.zenPolicyUserModifiedFields, parceled.zenPolicyUserModifiedFields);
+ assertEquals(rule.zenDeviceEffectsUserModifiedFields,
+ parceled.zenDeviceEffectsUserModifiedFields);
assertEquals(rule.triggerDescription, parceled.triggerDescription);
assertEquals(rule.zenPolicy, parceled.zenPolicy);
+ assertEquals(rule.deletionInstant, parceled.deletionInstant);
+
assertEquals(rule, parceled);
assertEquals(rule.hashCode(), parceled.hashCode());
-
}
@Test
@@ -508,8 +502,11 @@
rule.allowManualInvocation = ALLOW_MANUAL;
rule.type = TYPE;
rule.userModifiedFields = 4;
+ rule.zenPolicyUserModifiedFields = 5;
+ rule.zenDeviceEffectsUserModifiedFields = 2;
rule.iconResName = ICON_RES_NAME;
rule.triggerDescription = TRIGGER_DESC;
+ rule.deletionInstant = Instant.ofEpochMilli(1701790147000L);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
writeRuleXml(rule, baos);
@@ -537,8 +534,12 @@
assertEquals(rule.allowManualInvocation, fromXml.allowManualInvocation);
assertEquals(rule.type, fromXml.type);
assertEquals(rule.userModifiedFields, fromXml.userModifiedFields);
+ assertEquals(rule.zenPolicyUserModifiedFields, fromXml.zenPolicyUserModifiedFields);
+ assertEquals(rule.zenDeviceEffectsUserModifiedFields,
+ fromXml.zenDeviceEffectsUserModifiedFields);
assertEquals(rule.triggerDescription, fromXml.triggerDescription);
assertEquals(rule.iconResName, fromXml.iconResName);
+ assertEquals(rule.deletionInstant, fromXml.deletionInstant);
}
@Test
@@ -689,10 +690,9 @@
.allowSystem(true)
.allowReminders(false)
.allowEvents(true)
- .allowChannels(ZenPolicy.CHANNEL_TYPE_NONE)
+ .allowPriorityChannels(false)
.hideAllVisualEffects()
.showVisualEffect(ZenPolicy.VISUAL_EFFECT_AMBIENT, true)
- .setUserModifiedFields(4)
.build();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -716,7 +716,7 @@
assertEquals(policy.getPriorityCategorySystem(), fromXml.getPriorityCategorySystem());
assertEquals(policy.getPriorityCategoryReminders(), fromXml.getPriorityCategoryReminders());
assertEquals(policy.getPriorityCategoryEvents(), fromXml.getPriorityCategoryEvents());
- assertEquals(policy.getAllowedChannels(), fromXml.getAllowedChannels());
+ assertEquals(policy.getPriorityChannels(), fromXml.getPriorityChannels());
assertEquals(policy.getVisualEffectFullScreenIntent(),
fromXml.getVisualEffectFullScreenIntent());
@@ -727,7 +727,6 @@
assertEquals(policy.getVisualEffectAmbient(), fromXml.getVisualEffectAmbient());
assertEquals(policy.getVisualEffectNotificationList(),
fromXml.getVisualEffectNotificationList());
- assertEquals(policy.getUserModifiedFields(), fromXml.getUserModifiedFields());
}
private ZenModeConfig getMutedRingerConfig() {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
index 7e92e42..2e64645 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
@@ -65,18 +65,24 @@
@TestableLooper.RunWithLooper
public class ZenModeDiffTest extends UiServiceTestCase {
// Base set of exempt fields independent of fields that are enabled/disabled via flags.
- // version is not included in the diff; manual & automatic rules have special handling
+ // version is not included in the diff; manual & automatic rules have special handling;
+ // deleted rules are not included in the diff.
public static final Set<String> ZEN_MODE_CONFIG_EXEMPT_FIELDS =
- Set.of("version", "manualRule", "automaticRules");
+ android.app.Flags.modesApi()
+ ? Set.of("version", "manualRule", "automaticRules", "deletedRules")
+ : Set.of("version", "manualRule", "automaticRules");
// Differences for flagged fields are only generated if the flag is enabled.
- // TODO: b/310620812 - Remove this exempt list when flag is inlined.
+ // "Metadata" fields (userModifiedFields & co, deletionInstant) are not compared.
private static final Set<String> ZEN_RULE_EXEMPT_FIELDS =
android.app.Flags.modesApi()
- ? Set.of()
+ ? Set.of("userModifiedFields", "zenPolicyUserModifiedFields",
+ "zenDeviceEffectsUserModifiedFields", "deletionInstant")
: Set.of(RuleDiff.FIELD_TYPE, RuleDiff.FIELD_TRIGGER_DESCRIPTION,
RuleDiff.FIELD_ICON_RES, RuleDiff.FIELD_ALLOW_MANUAL,
- RuleDiff.FIELD_ZEN_DEVICE_EFFECTS, RuleDiff.FIELD_USER_MODIFIED_FIELDS);
+ RuleDiff.FIELD_ZEN_DEVICE_EFFECTS, "userModifiedFields",
+ "zenPolicyUserModifiedFields", "zenDeviceEffectsUserModifiedFields",
+ "deletionInstant");
// allowPriorityChannels is flagged by android.app.modes_api
public static final Set<String> ZEN_MODE_CONFIG_FLAGGED_FIELDS =
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
index 29208f4..7d6e12c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
@@ -546,13 +546,13 @@
// Create a policy to allow channels through, which means shouldIntercept is false
ZenModeConfig config = new ZenModeConfig();
Policy policy = config.toNotificationPolicy(new ZenPolicy.Builder()
- .allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY)
+ .allowPriorityChannels(true)
.build());
assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
// Now create a policy which does not allow priority channels:
policy = config.toNotificationPolicy(new ZenPolicy.Builder()
- .allowChannels(ZenPolicy.CHANNEL_TYPE_NONE)
+ .allowPriorityChannels(false)
.build());
assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 0224ff3..2486838 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -43,8 +43,10 @@
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+import static android.provider.Settings.Global.ZEN_MODE_NO_INTERRUPTIONS;
import static android.provider.Settings.Global.ZEN_MODE_OFF;
import static android.service.notification.Condition.SOURCE_SCHEDULE;
import static android.service.notification.Condition.SOURCE_USER_ACTION;
@@ -66,6 +68,7 @@
import static com.android.os.dnd.DNDProtoEnums.STATE_DISALLOW;
import static com.android.server.notification.ZenModeHelper.RULE_LIMIT_PER_PACKAGE;
+import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertEquals;
@@ -92,6 +95,7 @@
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.withSettings;
+import android.Manifest;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.AppGlobals;
@@ -104,6 +108,7 @@
import android.content.ContentResolver;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
@@ -116,6 +121,7 @@
import android.net.Uri;
import android.os.Parcel;
import android.os.Process;
+import android.os.SimpleClock;
import android.os.UserHandle;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
@@ -172,12 +178,16 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
@SmallTest
@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
@@ -232,6 +242,7 @@
@Mock PackageManager mPackageManager;
private Resources mResources;
private TestableLooper mTestableLooper;
+ private final TestClock mTestClock = new TestClock();
private ZenModeHelper mZenModeHelper;
private ContentResolver mContentResolver;
@Mock DeviceEffectsApplier mDeviceEffectsApplier;
@@ -269,7 +280,7 @@
mConditionProviders.addSystemProvider(new CountdownConditionProvider());
mConditionProviders.addSystemProvider(new ScheduleConditionProvider());
mZenModeEventLogger = new ZenModeEventLoggerFake(mPackageManager);
- mZenModeHelper = new ZenModeHelper(mContext, mTestableLooper.getLooper(),
+ mZenModeHelper = new ZenModeHelper(mContext, mTestableLooper.getLooper(), mTestClock,
mConditionProviders, mTestFlagResolver, mZenModeEventLogger);
ResolveInfo ri = new ResolveInfo();
@@ -286,6 +297,8 @@
when(appInfoSpy.loadLabel(any())).thenReturn(CUSTOM_APP_LABEL);
when(mPackageManager.getApplicationInfo(eq(CUSTOM_PKG_NAME), anyInt()))
.thenReturn(appInfoSpy);
+ when(mPackageManager.getApplicationInfo(eq(mContext.getPackageName()), anyInt()))
+ .thenReturn(appInfoSpy);
mZenModeHelper.mPm = mPackageManager;
mZenModeEventLogger.reset();
@@ -1142,7 +1155,7 @@
.allowAlarms(true)
.allowRepeatCallers(false)
.allowCalls(PEOPLE_TYPE_STARRED)
- .allowChannels(ZenPolicy.CHANNEL_TYPE_NONE)
+ .allowPriorityChannels(false)
.build();
mZenModeHelper.mConfig.automaticRules.put(rule.id, rule);
List<StatsEvent> events = new LinkedList<>();
@@ -1165,13 +1178,35 @@
assertThat(policy.getAllowCallsFrom().getNumber())
.isEqualTo(DNDProtoEnums.PEOPLE_STARRED);
assertThat(policy.getAllowChannels().getNumber())
- .isEqualTo(DNDProtoEnums.CHANNEL_TYPE_NONE);
+ .isEqualTo(DNDProtoEnums.CHANNEL_POLICY_NONE);
}
}
assertTrue("couldn't find custom rule", foundCustomEvent);
}
@Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void testProtoWithAutoRuleWithModifiedFields() throws Exception {
+ setupZenConfig();
+ mZenModeHelper.mConfig.automaticRules = new ArrayMap<>();
+ ZenRule rule = createCustomAutomaticRule(ZEN_MODE_IMPORTANT_INTERRUPTIONS, CUSTOM_RULE_ID);
+ rule.userModifiedFields = AutomaticZenRule.FIELD_NAME;
+ rule.zenPolicyUserModifiedFields = ZenPolicy.FIELD_PRIORITY_CATEGORY_MEDIA;
+ rule.zenDeviceEffectsUserModifiedFields = ZenDeviceEffects.FIELD_GRAYSCALE;
+ mZenModeHelper.mConfig.automaticRules.put(rule.id, rule);
+
+ List<StatsEvent> events = new ArrayList<>();
+ mZenModeHelper.pullRules(events);
+
+ assertThat(events).hasSize(2); // Global config + 1 automatic rule
+ DNDModeProto ruleProto = StatsEventTestUtils.convertToAtom(events.get(1)).getDndModeRule();
+ assertThat(ruleProto.getRuleModifiedFields()).isEqualTo(rule.userModifiedFields);
+ assertThat(ruleProto.getPolicyModifiedFields()).isEqualTo(rule.zenPolicyUserModifiedFields);
+ assertThat(ruleProto.getDeviceEffectsModifiedFields()).isEqualTo(
+ rule.zenDeviceEffectsUserModifiedFields);
+ }
+
+ @Test
public void ruleUidsCached() throws Exception {
setupZenConfig();
// one enabled automatic rule
@@ -1198,7 +1233,7 @@
@Test
public void ruleUidAutomaticZenRuleRemovedUpdatesCache() throws Exception {
when(mContext.checkCallingPermission(anyString()))
- .thenReturn(PackageManager.PERMISSION_GRANTED);
+ .thenReturn(PERMISSION_GRANTED);
setupZenConfig();
// one enabled automatic rule
@@ -1780,7 +1815,7 @@
public void testDoNotUpdateModifiedDefaultAutoRule() {
// mDefaultConfig is set to default config in setup by getDefaultConfigParser
when(mContext.checkCallingPermission(anyString()))
- .thenReturn(PackageManager.PERMISSION_GRANTED);
+ .thenReturn(PERMISSION_GRANTED);
// shouldn't update rule that's been modified
ZenModeConfig.ZenRule updatedDefaultRule = new ZenModeConfig.ZenRule();
@@ -1806,7 +1841,7 @@
public void testDoNotUpdateEnabledDefaultAutoRule() {
// mDefaultConfig is set to default config in setup by getDefaultConfigParser
when(mContext.checkCallingPermission(anyString()))
- .thenReturn(PackageManager.PERMISSION_GRANTED);
+ .thenReturn(PERMISSION_GRANTED);
// shouldn't update the rule that's enabled
ZenModeConfig.ZenRule updatedDefaultRule = new ZenModeConfig.ZenRule();
@@ -1833,7 +1868,7 @@
// mDefaultConfig is set to default config in setup by getDefaultConfigParser
final String defaultRuleName = "rule name test";
when(mContext.checkCallingPermission(anyString()))
- .thenReturn(PackageManager.PERMISSION_GRANTED);
+ .thenReturn(PERMISSION_GRANTED);
// will update rule that is not enabled and modified
ZenModeConfig.ZenRule customDefaultRule = new ZenModeConfig.ZenRule();
@@ -2227,12 +2262,7 @@
AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
- // savedRule.getDeviceEffects() is equal to zde, except for the userModifiedFields.
- // So we clear before comparing.
- ZenDeviceEffects savedEffects = new ZenDeviceEffects.Builder(savedRule.getDeviceEffects())
- .setUserModifiedFields(0).build();
-
- assertThat(savedEffects).isEqualTo(zde);
+ assertThat(savedRule.getDeviceEffects()).isEqualTo(zde);
}
@Test
@@ -2322,12 +2352,7 @@
AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
- // savedRule.getDeviceEffects() is equal to updateFromUser, except for the
- // userModifiedFields, so we clear before comparing.
- ZenDeviceEffects savedEffects = new ZenDeviceEffects.Builder(savedRule.getDeviceEffects())
- .setUserModifiedFields(0).build();
-
- assertThat(savedEffects).isEqualTo(updateFromUser);
+ assertThat(savedRule.getDeviceEffects()).isEqualTo(updateFromUser);
}
@Test
@@ -3089,7 +3114,7 @@
DNDPolicyProto origDndProto = mZenModeEventLogger.getPolicyProto(0);
checkDndProtoMatchesSetupZenConfig(origDndProto);
assertThat(origDndProto.getAllowChannels().getNumber())
- .isEqualTo(DNDProtoEnums.CHANNEL_TYPE_PRIORITY);
+ .isEqualTo(DNDProtoEnums.CHANNEL_POLICY_PRIORITY);
// Second message where we change the policy:
// - DND_POLICY_CHANGED (indicates only the policy changed and nothing else)
@@ -3101,7 +3126,7 @@
.isEqualTo(DNDProtoEnums.UNKNOWN_RULE);
DNDPolicyProto dndProto = mZenModeEventLogger.getPolicyProto(1);
assertThat(dndProto.getAllowChannels().getNumber())
- .isEqualTo(DNDProtoEnums.CHANNEL_TYPE_NONE);
+ .isEqualTo(DNDProtoEnums.CHANNEL_POLICY_NONE);
}
@Test
@@ -3290,7 +3315,7 @@
// one rule, custom policy, allows channels
ZenPolicy customPolicy = new ZenPolicy.Builder()
- .allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY)
+ .allowPriorityChannels(true)
.build();
AutomaticZenRule zenRule = new AutomaticZenRule("name",
@@ -3312,7 +3337,7 @@
// add new rule with policy that disallows channels
ZenPolicy strictPolicy = new ZenPolicy.Builder()
- .allowChannels(ZenPolicy.CHANNEL_TYPE_NONE)
+ .allowPriorityChannels(false)
.build();
AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
@@ -3402,7 +3427,6 @@
rule.allowManualInvocation = ALLOW_MANUAL;
rule.type = TYPE;
- rule.userModifiedFields = AutomaticZenRule.FIELD_NAME;
rule.iconResName = ICON_RES_NAME;
rule.triggerDescription = TRIGGER_DESC;
@@ -3417,7 +3441,6 @@
assertEquals(POLICY, actual.getZenPolicy());
assertEquals(CONFIG_ACTIVITY, actual.getConfigurationActivity());
assertEquals(TYPE, actual.getType());
- assertEquals(AutomaticZenRule.FIELD_NAME, actual.getUserModifiedFields());
assertEquals(ALLOW_MANUAL, actual.isManualInvocationAllowed());
assertEquals(CREATION_TIME, actual.getCreationTime());
assertEquals(OWNER.getPackageName(), actual.getPackageName());
@@ -3444,29 +3467,31 @@
.setManualInvocationAllowed(ALLOW_MANUAL)
.build();
- ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
+ String ruleId = mZenModeHelper.addAutomaticZenRule(OWNER.getPackageName(), azr,
+ UPDATE_ORIGIN_APP, "add", CUSTOM_PKG_UID);
- mZenModeHelper.populateZenRule(OWNER.getPackageName(), azr, rule, UPDATE_ORIGIN_APP, true);
+ ZenModeConfig.ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
- assertEquals(NAME, rule.name);
- assertEquals(OWNER, rule.component);
- assertEquals(CONDITION_ID, rule.conditionId);
- assertEquals(INTERRUPTION_FILTER_ZR, rule.zenMode);
- assertEquals(ENABLED, rule.enabled);
- assertEquals(POLICY, rule.zenPolicy);
- assertEquals(CONFIG_ACTIVITY, rule.configurationActivity);
- assertEquals(TYPE, rule.type);
- assertEquals(ALLOW_MANUAL, rule.allowManualInvocation);
- assertEquals(OWNER.getPackageName(), rule.getPkg());
- assertEquals(ICON_RES_NAME, rule.iconResName);
+ assertThat(storedRule).isNotNull();
+ assertEquals(NAME, storedRule.name);
+ assertEquals(OWNER, storedRule.component);
+ assertEquals(CONDITION_ID, storedRule.conditionId);
+ assertEquals(INTERRUPTION_FILTER_ZR, storedRule.zenMode);
+ assertEquals(ENABLED, storedRule.enabled);
+ assertEquals(POLICY, storedRule.zenPolicy);
+ assertEquals(CONFIG_ACTIVITY, storedRule.configurationActivity);
+ assertEquals(TYPE, storedRule.type);
+ assertEquals(ALLOW_MANUAL, storedRule.allowManualInvocation);
+ assertEquals(OWNER.getPackageName(), storedRule.getPkg());
+ assertEquals(ICON_RES_NAME, storedRule.iconResName);
// Because the origin of the update is the app, we don't expect the bitmask to change.
- assertEquals(0, rule.userModifiedFields);
- assertEquals(TRIGGER_DESC, rule.triggerDescription);
+ assertEquals(0, storedRule.userModifiedFields);
+ assertEquals(TRIGGER_DESC, storedRule.triggerDescription);
}
@Test
@EnableFlags(Flags.FLAG_MODES_API)
- public void automaticZenRuleToZenRule_updatesNameUnlessUserModified() {
+ public void updateAutomaticZenRule_fromApp_updatesNameUnlessUserModified() {
// Add a starting rule with the name OriginalName.
AutomaticZenRule azrBase = new AutomaticZenRule.Builder("OriginalName", CONDITION_ID)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
@@ -3483,7 +3508,6 @@
Process.SYSTEM_UID);
rule = mZenModeHelper.getAutomaticZenRule(ruleId);
assertThat(rule.getName()).isEqualTo("NewName");
- assertThat(rule.canUpdate()).isTrue();
// The user modifies some other field in the rule, which makes the rule as a whole not
// app modifiable.
@@ -3492,10 +3516,6 @@
.build();
mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_USER, "reason",
Process.SYSTEM_UID);
- rule = mZenModeHelper.getAutomaticZenRule(ruleId);
- assertThat(rule.getUserModifiedFields())
- .isEqualTo(AutomaticZenRule.FIELD_INTERRUPTION_FILTER);
- assertThat(rule.canUpdate()).isFalse();
// ...but the app can still modify the name, because the name itself hasn't been modified
// by the user.
@@ -3515,8 +3535,6 @@
Process.SYSTEM_UID);
rule = mZenModeHelper.getAutomaticZenRule(ruleId);
assertThat(rule.getName()).isEqualTo("UserProvidedName");
- assertThat(rule.getUserModifiedFields()).isEqualTo(AutomaticZenRule.FIELD_NAME
- | AutomaticZenRule.FIELD_INTERRUPTION_FILTER);
// The app is no longer able to modify the name.
azrUpdate = new AutomaticZenRule.Builder(rule)
@@ -3530,7 +3548,7 @@
@Test
@EnableFlags(Flags.FLAG_MODES_API)
- public void automaticZenRuleToZenRule_updatesBitmaskAndValueForUserOrigin() {
+ public void updateAutomaticZenRule_fromUser_updatesBitmaskAndValue() {
// Adds a starting rule with empty zen policies and device effects
AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
.setZenPolicy(new ZenPolicy.Builder().build())
@@ -3543,7 +3561,7 @@
// Modifies the zen policy and device effects
ZenPolicy policy = new ZenPolicy.Builder(rule.getZenPolicy())
- .allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY)
+ .allowPriorityChannels(true)
.build();
ZenDeviceEffects deviceEffects =
new ZenDeviceEffects.Builder(rule.getDeviceEffects())
@@ -3562,85 +3580,21 @@
// UPDATE_ORIGIN_USER should change the bitmask and change the values.
assertThat(rule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_PRIORITY);
- assertThat(rule.getUserModifiedFields())
- .isEqualTo(AutomaticZenRule.FIELD_INTERRUPTION_FILTER);
- assertThat(rule.getZenPolicy().getUserModifiedFields())
- .isEqualTo(ZenPolicy.FIELD_ALLOW_CHANNELS);
- assertThat(rule.getZenPolicy().getAllowedChannels())
- .isEqualTo(ZenPolicy.CHANNEL_TYPE_PRIORITY);
- assertThat(rule.getDeviceEffects().getUserModifiedFields())
- .isEqualTo(ZenDeviceEffects.FIELD_GRAYSCALE);
+ assertThat(rule.getZenPolicy().getPriorityChannels()).isEqualTo(ZenPolicy.STATE_ALLOW);
assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
+
+ ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+ assertThat(storedRule.userModifiedFields)
+ .isEqualTo(AutomaticZenRule.FIELD_INTERRUPTION_FILTER);
+ assertThat(storedRule.zenPolicyUserModifiedFields)
+ .isEqualTo(ZenPolicy.FIELD_ALLOW_CHANNELS);
+ assertThat(storedRule.zenDeviceEffectsUserModifiedFields)
+ .isEqualTo(ZenDeviceEffects.FIELD_GRAYSCALE);
}
@Test
@EnableFlags(Flags.FLAG_MODES_API)
- public void automaticZenRuleToZenRule_doesNotUpdateValuesForInitUserOrigin() {
- // Adds a starting rule with empty zen policies and device effects
- AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
- .setInterruptionFilter(INTERRUPTION_FILTER_ALL) // Already the default, no change
- .setZenPolicy(new ZenPolicy.Builder()
- .allowReminders(false)
- .build())
- .setDeviceEffects(new ZenDeviceEffects.Builder()
- .setShouldDisplayGrayscale(false)
- .build())
- .build();
- // Adds the rule using the user, to set user-modified bits.
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrBase, UPDATE_ORIGIN_USER, "reason", Process.SYSTEM_UID);
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
- assertThat(rule.canUpdate()).isFalse();
- assertThat(rule.getUserModifiedFields()).isEqualTo(AutomaticZenRule.FIELD_NAME);
-
- ZenPolicy policy = new ZenPolicy.Builder(rule.getZenPolicy())
- .allowReminders(true)
- .build();
- ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder(rule.getDeviceEffects())
- .setShouldDisplayGrayscale(true)
- .build();
- AutomaticZenRule azrUpdate = new AutomaticZenRule.Builder(rule)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(policy)
- .setDeviceEffects(deviceEffects)
- .build();
-
- // Attempts to update the rule with the AZR from origin init user.
- mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_INIT_USER, "reason",
- Process.SYSTEM_UID);
- AutomaticZenRule unchangedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
-
- // UPDATE_ORIGIN_INIT_USER does not change the bitmask or values if rule is user modified.
- // TODO: b/318506692 - Remove once we check that INIT origins can't call add/updateAZR.
- assertThat(unchangedRule.getUserModifiedFields()).isEqualTo(rule.getUserModifiedFields());
- assertThat(unchangedRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALL);
- assertThat(unchangedRule.getZenPolicy().getUserModifiedFields()).isEqualTo(
- rule.getZenPolicy().getUserModifiedFields());
- assertThat(unchangedRule.getZenPolicy().getPriorityCategoryReminders()).isEqualTo(
- ZenPolicy.STATE_DISALLOW);
- assertThat(unchangedRule.getDeviceEffects().getUserModifiedFields()).isEqualTo(
- rule.getDeviceEffects().getUserModifiedFields());
- assertThat(unchangedRule.getDeviceEffects().shouldDisplayGrayscale()).isFalse();
-
- // Creates a new rule with the AZR from origin init user.
- String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrUpdate, UPDATE_ORIGIN_INIT_USER, "reason", Process.SYSTEM_UID);
- AutomaticZenRule newRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
-
- // UPDATE_ORIGIN_INIT_USER does change the values if the rule is new,
- // but does not update the bitmask.
- assertThat(newRule.getUserModifiedFields()).isEqualTo(0);
- assertThat(newRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_PRIORITY);
- assertThat(newRule.getZenPolicy().getUserModifiedFields()).isEqualTo(0);
- assertThat(newRule.getZenPolicy().getPriorityCategoryReminders())
- .isEqualTo(ZenPolicy.STATE_ALLOW);
- assertThat(newRule.getDeviceEffects().getUserModifiedFields()).isEqualTo(0);
- assertThat(newRule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
- }
-
- @Test
- @EnableFlags(Flags.FLAG_MODES_API)
- public void automaticZenRuleToZenRule_updatesValuesForSystemUiOrigin() {
+ public void updateAutomaticZenRule_fromSystemUi_updatesValues() {
// Adds a starting rule with empty zen policies and device effects
AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
.setInterruptionFilter(INTERRUPTION_FILTER_ALL)
@@ -3676,17 +3630,19 @@
rule = mZenModeHelper.getAutomaticZenRule(ruleId);
// UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI should change the value but NOT update the bitmask.
- assertThat(rule.getUserModifiedFields()).isEqualTo(0);
- assertThat(rule.getZenPolicy().getUserModifiedFields()).isEqualTo(0);
assertThat(rule.getZenPolicy().getPriorityCategoryReminders())
.isEqualTo(ZenPolicy.STATE_ALLOW);
- assertThat(rule.getDeviceEffects().getUserModifiedFields()).isEqualTo(0);
assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
+
+ ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+ assertThat(storedRule.userModifiedFields).isEqualTo(0);
+ assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo(0);
+ assertThat(storedRule.zenDeviceEffectsUserModifiedFields).isEqualTo(0);
}
@Test
@EnableFlags(Flags.FLAG_MODES_API)
- public void automaticZenRuleToZenRule_updatesValuesIfRuleNotUserModified() {
+ public void updateAutomaticZenRule_fromApp_updatesValuesIfRuleNotUserModified() {
// Adds a starting rule with empty zen policies and device effects
AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
.setInterruptionFilter(INTERRUPTION_FILTER_ALL)
@@ -3701,7 +3657,6 @@
String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
- assertThat(rule.canUpdate()).isTrue();
ZenPolicy policy = new ZenPolicy.Builder()
.allowReminders(true)
@@ -3709,57 +3664,59 @@
ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder()
.setShouldDisplayGrayscale(true)
.build();
- AutomaticZenRule azrUpdate = new AutomaticZenRule.Builder(rule)
+ AutomaticZenRule azrUpdate = new AutomaticZenRule.Builder(rule)
.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
.setZenPolicy(policy)
.setDeviceEffects(deviceEffects)
.build();
- // Since the rule is not already user modified, UPDATE_ORIGIN_UNKNOWN can modify the rule.
+ // Since the rule is not already user modified, UPDATE_ORIGIN_APP can modify the rule.
// The bitmask is not modified.
- mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_UNKNOWN, "reason",
+ mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_APP, "reason",
Process.SYSTEM_UID);
- AutomaticZenRule unchangedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
- assertThat(unchangedRule.getUserModifiedFields()).isEqualTo(rule.getUserModifiedFields());
- assertThat(unchangedRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS);
- assertThat(unchangedRule.getZenPolicy().getUserModifiedFields()).isEqualTo(
- rule.getZenPolicy().getUserModifiedFields());
- assertThat(unchangedRule.getZenPolicy().getPriorityCategoryReminders())
+ ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+ assertThat(storedRule.userModifiedFields).isEqualTo(0);
+
+ assertThat(storedRule.zenMode).isEqualTo(ZEN_MODE_ALARMS);
+ assertThat(storedRule.zenPolicy.getPriorityCategoryReminders())
.isEqualTo(ZenPolicy.STATE_ALLOW);
- assertThat(unchangedRule.getDeviceEffects().getUserModifiedFields()).isEqualTo(
- rule.getDeviceEffects().getUserModifiedFields());
- assertThat(unchangedRule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
+ assertThat(storedRule.zenDeviceEffects.shouldDisplayGrayscale()).isTrue();
+ assertThat(storedRule.userModifiedFields).isEqualTo(0);
+ assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo(0);
+ assertThat(storedRule.zenDeviceEffectsUserModifiedFields).isEqualTo(0);
// Creates another rule, this time from user. This will have user modified bits set.
String ruleIdUser = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
azrBase, UPDATE_ORIGIN_USER, "reason", Process.SYSTEM_UID);
- AutomaticZenRule ruleUser = mZenModeHelper.getAutomaticZenRule(ruleIdUser);
- assertThat(ruleUser.canUpdate()).isFalse();
+ storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleIdUser);
+ int ruleModifiedFields = storedRule.userModifiedFields;
+ int rulePolicyModifiedFields = storedRule.zenPolicyUserModifiedFields;
+ int ruleDeviceEffectsModifiedFields = storedRule.zenDeviceEffectsUserModifiedFields;
- // Zen rule update coming from unknown origin. This cannot fully update the rule, because
+ // Zen rule update coming from the app again. This cannot fully update the rule, because
// the rule is already considered user modified.
- mZenModeHelper.updateAutomaticZenRule(ruleIdUser, azrUpdate, UPDATE_ORIGIN_UNKNOWN,
+ mZenModeHelper.updateAutomaticZenRule(ruleIdUser, azrUpdate, UPDATE_ORIGIN_APP,
"reason", Process.SYSTEM_UID);
- ruleUser = mZenModeHelper.getAutomaticZenRule(ruleIdUser);
+ AutomaticZenRule ruleUser = mZenModeHelper.getAutomaticZenRule(ruleIdUser);
- // UPDATE_ORIGIN_UNKNOWN can only change the value if the rule is not already user modified,
+ // The app can only change the value if the rule is not already user modified,
// so the rule is not changed, and neither is the bitmask.
assertThat(ruleUser.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALL);
- // Interruption Filter All is the default value, so it's not included as a modified field.
- assertThat(ruleUser.getUserModifiedFields() | AutomaticZenRule.FIELD_NAME).isGreaterThan(0);
- assertThat(ruleUser.getZenPolicy().getUserModifiedFields()
- | ZenPolicy.FIELD_PRIORITY_CATEGORY_REMINDERS).isGreaterThan(0);
assertThat(ruleUser.getZenPolicy().getPriorityCategoryReminders())
.isEqualTo(ZenPolicy.STATE_DISALLOW);
- assertThat(ruleUser.getDeviceEffects().getUserModifiedFields()
- | ZenDeviceEffects.FIELD_GRAYSCALE).isGreaterThan(0);
assertThat(ruleUser.getDeviceEffects().shouldDisplayGrayscale()).isFalse();
+
+ storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleIdUser);
+ assertThat(storedRule.userModifiedFields).isEqualTo(ruleModifiedFields);
+ assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo(rulePolicyModifiedFields);
+ assertThat(storedRule.zenDeviceEffectsUserModifiedFields).isEqualTo(
+ ruleDeviceEffectsModifiedFields);
}
@Test
@EnableFlags(Flags.FLAG_MODES_API)
- public void automaticZenRuleToZenRule_updatesValuesIfRuleNew() {
+ public void addAutomaticZenRule_updatesValues() {
// Adds a starting rule with empty zen policies and device effects
AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
@@ -3770,21 +3727,22 @@
.setShouldDisplayGrayscale(true)
.build())
.build();
- // Adds the rule using origin unknown, to show that a new rule is always allowed.
String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrBase, UPDATE_ORIGIN_UNKNOWN, "reason", Process.SYSTEM_UID);
+ azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
// The values are modified but the bitmask is not.
- assertThat(rule.canUpdate()).isTrue();
assertThat(rule.getZenPolicy().getPriorityCategoryReminders())
.isEqualTo(ZenPolicy.STATE_ALLOW);
assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
+
+ ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+ assertThat(storedRule.canBeUpdatedByApp()).isTrue();
}
@Test
@EnableFlags(Flags.FLAG_MODES_API)
- public void automaticZenRuleToZenRule_nullDeviceEffectsUpdate() {
+ public void updateAutomaticZenRule_nullDeviceEffectsUpdate() {
// Adds a starting rule with empty zen policies and device effects
AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
.setDeviceEffects(new ZenDeviceEffects.Builder().build())
@@ -3799,9 +3757,9 @@
.setDeviceEffects(null)
.build();
- // Zen rule update coming from unknown origin, but since the rule isn't already
+ // Zen rule update coming from app, but since the rule isn't already
// user modified, it can be updated.
- mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_UNKNOWN, "reason",
+ mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_APP, "reason",
Process.SYSTEM_UID);
rule = mZenModeHelper.getAutomaticZenRule(ruleId);
@@ -3811,7 +3769,7 @@
@Test
@EnableFlags(Flags.FLAG_MODES_API)
- public void automaticZenRuleToZenRule_nullPolicyUpdate() {
+ public void updateAutomaticZenRule_nullPolicyUpdate() {
// Adds a starting rule with empty zen policies and device effects
AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
.setZenPolicy(new ZenPolicy.Builder().build())
@@ -3820,16 +3778,15 @@
String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
- assertThat(rule.canUpdate()).isTrue();
AutomaticZenRule azr = new AutomaticZenRule.Builder(azrBase)
// Set zen policy to null
.setZenPolicy(null)
.build();
- // Zen rule update coming from unknown origin, but since the rule isn't already
+ // Zen rule update coming from app, but since the rule isn't already
// user modified, it can be updated.
- mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_UNKNOWN, "reason",
+ mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_APP, "reason",
Process.SYSTEM_UID);
rule = mZenModeHelper.getAutomaticZenRule(ruleId);
@@ -3851,11 +3808,10 @@
String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
- assertThat(rule.canUpdate()).isTrue();
// Create a fully populated ZenPolicy.
ZenPolicy policy = new ZenPolicy.Builder()
- .allowChannels(ZenPolicy.CHANNEL_TYPE_NONE) // Differs from the default
+ .allowPriorityChannels(false) // Differs from the default
.allowReminders(true) // Differs from the default
.allowEvents(true) // Differs from the default
.allowConversations(ZenPolicy.CONVERSATION_SENDERS_IMPORTANT)
@@ -3885,9 +3841,11 @@
// New ZenPolicy differs from the default config
assertThat(rule.getZenPolicy()).isNotNull();
- assertThat(rule.getZenPolicy().getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_NONE);
- assertThat(rule.canUpdate()).isFalse();
- assertThat(rule.getZenPolicy().getUserModifiedFields()).isEqualTo(
+ assertThat(rule.getZenPolicy().getPriorityChannels()).isEqualTo(ZenPolicy.STATE_DISALLOW);
+
+ ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+ assertThat(storedRule.canBeUpdatedByApp()).isFalse();
+ assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo(
ZenPolicy.FIELD_ALLOW_CHANNELS
| ZenPolicy.FIELD_PRIORITY_CATEGORY_REMINDERS
| ZenPolicy.FIELD_PRIORITY_CATEGORY_EVENTS
@@ -3910,7 +3868,6 @@
String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
- assertThat(rule.canUpdate()).isTrue();
ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder()
.setShouldDisplayGrayscale(true)
@@ -3927,8 +3884,10 @@
// New ZenDeviceEffects is used; all fields considered set, since previously were null.
assertThat(rule.getDeviceEffects()).isNotNull();
assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
- assertThat(rule.canUpdate()).isFalse();
- assertThat(rule.getDeviceEffects().getUserModifiedFields()).isEqualTo(
+
+ ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+ assertThat(storedRule.canBeUpdatedByApp()).isFalse();
+ assertThat(storedRule.zenDeviceEffectsUserModifiedFields).isEqualTo(
ZenDeviceEffects.FIELD_GRAYSCALE);
}
@@ -4318,6 +4277,325 @@
}
@Test
+ public void removeAndAddAutomaticZenRule_wasCustomized_isRestored() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+ // Start with a rule.
+ mZenModeHelper.mConfig.automaticRules.clear();
+ mTestClock.setNowMillis(1000);
+ AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(false).build())
+ .build();
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
+ UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).getCreationTime()).isEqualTo(1000);
+
+ // User customizes it.
+ AutomaticZenRule userUpdate = new AutomaticZenRule.Builder(rule)
+ .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
+ .setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(true).build())
+ .build();
+ mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, UPDATE_ORIGIN_USER, "userUpdate",
+ Process.SYSTEM_UID);
+
+ // App deletes it.
+ mTestClock.advanceByMillis(1000);
+ mZenModeHelper.removeAutomaticZenRule(ruleId, UPDATE_ORIGIN_APP, "delete it",
+ CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
+ assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(1);
+
+ // App adds it again.
+ mTestClock.advanceByMillis(1000);
+ String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
+ UPDATE_ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+
+ // Verify that the rule was restored:
+ // - id and creation time is the same as the original one.
+ // - ZenPolicy is the one that the user had set.
+ // - rule still has the user-modified fields.
+ AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
+ assertThat(finalRule.getCreationTime()).isEqualTo(1000); // And not 3000.
+ assertThat(newRuleId).isEqualTo(ruleId);
+ assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS);
+ assertThat(finalRule.getZenPolicy().getPriorityCategoryRepeatCallers()).isEqualTo(
+ ZenPolicy.STATE_ALLOW);
+
+ ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+ assertThat(storedRule.userModifiedFields).isEqualTo(
+ AutomaticZenRule.FIELD_INTERRUPTION_FILTER);
+ assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo(
+ ZenPolicy.FIELD_PRIORITY_CATEGORY_REPEAT_CALLERS);
+
+ // Also, we discarded the "deleted rule" since we already used it for restoration.
+ assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(0);
+ }
+
+ @Test
+ public void removeAndAddAutomaticZenRule_wasNotCustomized_isNotRestored() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+ // Start with a single rule.
+ mZenModeHelper.mConfig.automaticRules.clear();
+ mTestClock.setNowMillis(1000);
+ AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(false).build())
+ .build();
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
+ UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).getCreationTime()).isEqualTo(1000);
+
+ // App deletes it.
+ mTestClock.advanceByMillis(1000);
+ mZenModeHelper.removeAutomaticZenRule(ruleId, UPDATE_ORIGIN_APP, "delete it",
+ CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
+ assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(0);
+
+ // App adds it again.
+ mTestClock.advanceByMillis(1000);
+ String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
+ UPDATE_ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+
+ // Verify that the rule was recreated. This means id and creation time are new.
+ AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
+ assertThat(finalRule.getCreationTime()).isEqualTo(3000);
+ assertThat(newRuleId).isNotEqualTo(ruleId);
+ }
+
+ @Test
+ public void removeAndAddAutomaticZenRule_recreatedButNotByApp_isNotRestored() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+ // Start with a single rule.
+ mZenModeHelper.mConfig.automaticRules.clear();
+ mTestClock.setNowMillis(1000);
+ AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(false).build())
+ .build();
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
+ UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).getCreationTime()).isEqualTo(1000);
+
+ // User customizes it.
+ mTestClock.advanceByMillis(1000);
+ AutomaticZenRule userUpdate = new AutomaticZenRule.Builder(rule)
+ .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
+ .setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(true).build())
+ .build();
+ mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, UPDATE_ORIGIN_USER, "userUpdate",
+ Process.SYSTEM_UID);
+
+ // App deletes it.
+ mTestClock.advanceByMillis(1000);
+ mZenModeHelper.removeAutomaticZenRule(ruleId, UPDATE_ORIGIN_APP, "delete it",
+ CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
+ assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(1);
+
+ // User creates it again (unusual case, but ok).
+ mTestClock.advanceByMillis(1000);
+ String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
+ UPDATE_ORIGIN_USER, "add it anew", CUSTOM_PKG_UID);
+
+ // Verify that the rule was recreated. This means id and creation time are new, and the rule
+ // matches the latest data supplied to addAZR.
+ AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
+ assertThat(finalRule.getCreationTime()).isEqualTo(4000);
+ assertThat(newRuleId).isNotEqualTo(ruleId);
+ assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_PRIORITY);
+ assertThat(finalRule.getZenPolicy().getPriorityCategoryRepeatCallers()).isEqualTo(
+ ZenPolicy.STATE_DISALLOW);
+
+ // Also, we discarded the "deleted rule" since we're not interested in recreating it.
+ assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(0);
+ }
+
+ @Test
+ public void removeAndAddAutomaticZenRule_removedByUser_isNotRestored() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+ // Start with a single rule.
+ mZenModeHelper.mConfig.automaticRules.clear();
+ mTestClock.setNowMillis(1000);
+ AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(false).build())
+ .build();
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
+ UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).getCreationTime()).isEqualTo(1000);
+
+ // User customizes it.
+ mTestClock.advanceByMillis(1000);
+ AutomaticZenRule userUpdate = new AutomaticZenRule.Builder(rule)
+ .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
+ .setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(true).build())
+ .build();
+ mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, UPDATE_ORIGIN_USER, "userUpdate",
+ Process.SYSTEM_UID);
+
+ // User deletes it.
+ mTestClock.advanceByMillis(1000);
+ mZenModeHelper.removeAutomaticZenRule(ruleId, UPDATE_ORIGIN_USER, "delete it",
+ CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
+ assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(0);
+
+ // App creates it again.
+ mTestClock.advanceByMillis(1000);
+ String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
+ UPDATE_ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+
+ // Verify that the rule was recreated. This means id and creation time are new.
+ AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
+ assertThat(finalRule.getCreationTime()).isEqualTo(4000);
+ assertThat(newRuleId).isNotEqualTo(ruleId);
+ }
+
+ @Test
+ public void removeAutomaticZenRule_preservedForRestoringByPackageAndConditionId() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+ mContext.getTestablePermissions().setPermission(Manifest.permission.MANAGE_NOTIFICATIONS,
+ PERMISSION_GRANTED); // So that canManageAZR passes although packages don't match.
+ mZenModeHelper.mConfig.automaticRules.clear();
+
+ // Start with a bunch of customized rules where conditionUris are not unique.
+ String id1 = mZenModeHelper.addAutomaticZenRule("pkg1",
+ new AutomaticZenRule.Builder("Test1", Uri.parse("uri1")).build(), UPDATE_ORIGIN_APP,
+ "add it", CUSTOM_PKG_UID);
+ String id2 = mZenModeHelper.addAutomaticZenRule("pkg1",
+ new AutomaticZenRule.Builder("Test2", Uri.parse("uri2")).build(), UPDATE_ORIGIN_APP,
+ "add it", CUSTOM_PKG_UID);
+ String id3 = mZenModeHelper.addAutomaticZenRule("pkg1",
+ new AutomaticZenRule.Builder("Test3", Uri.parse("uri2")).build(), UPDATE_ORIGIN_APP,
+ "add it", CUSTOM_PKG_UID);
+ String id4 = mZenModeHelper.addAutomaticZenRule("pkg2",
+ new AutomaticZenRule.Builder("Test4", Uri.parse("uri1")).build(), UPDATE_ORIGIN_APP,
+ "add it", CUSTOM_PKG_UID);
+ String id5 = mZenModeHelper.addAutomaticZenRule("pkg2",
+ new AutomaticZenRule.Builder("Test5", Uri.parse("uri1")).build(), UPDATE_ORIGIN_APP,
+ "add it", CUSTOM_PKG_UID);
+ for (ZenRule zenRule : mZenModeHelper.mConfig.automaticRules.values()) {
+ zenRule.userModifiedFields = AutomaticZenRule.FIELD_INTERRUPTION_FILTER;
+ }
+
+ mZenModeHelper.removeAutomaticZenRule(id1, UPDATE_ORIGIN_APP, "begone", CUSTOM_PKG_UID);
+ mZenModeHelper.removeAutomaticZenRule(id2, UPDATE_ORIGIN_APP, "begone", CUSTOM_PKG_UID);
+ mZenModeHelper.removeAutomaticZenRule(id3, UPDATE_ORIGIN_APP, "begone", CUSTOM_PKG_UID);
+ mZenModeHelper.removeAutomaticZenRule(id4, UPDATE_ORIGIN_APP, "begone", CUSTOM_PKG_UID);
+ mZenModeHelper.removeAutomaticZenRule(id5, UPDATE_ORIGIN_APP, "begone", CUSTOM_PKG_UID);
+
+ assertThat(mZenModeHelper.mConfig.deletedRules.keySet())
+ .containsExactly("pkg1|uri1", "pkg1|uri2", "pkg2|uri1");
+ assertThat(mZenModeHelper.mConfig.deletedRules.values().stream().map(zr -> zr.name)
+ .collect(Collectors.toList()))
+ .containsExactly("Test1", "Test3", "Test5");
+ }
+
+ @Test
+ public void removeAllZenRules_preservedForRestoring() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+ mZenModeHelper.mConfig.automaticRules.clear();
+
+ mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ new AutomaticZenRule.Builder("Test1", Uri.parse("uri1")).build(), UPDATE_ORIGIN_APP,
+ "add it", CUSTOM_PKG_UID);
+ mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ new AutomaticZenRule.Builder("Test2", Uri.parse("uri2")).build(), UPDATE_ORIGIN_APP,
+ "add it", CUSTOM_PKG_UID);
+
+ for (ZenRule zenRule : mZenModeHelper.mConfig.automaticRules.values()) {
+ zenRule.userModifiedFields = AutomaticZenRule.FIELD_INTERRUPTION_FILTER;
+ }
+
+ mZenModeHelper.removeAutomaticZenRules(mContext.getPackageName(), UPDATE_ORIGIN_APP,
+ "begone", CUSTOM_PKG_UID);
+
+ assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(2);
+ }
+
+ @Test
+ public void removeAllZenRules_fromSystem_deletesPreservedRulesToo() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+ mZenModeHelper.mConfig.automaticRules.clear();
+
+ // Start with deleted rules from 2 different packages.
+ Instant now = Instant.ofEpochMilli(1701796461000L);
+ ZenRule pkg1Rule = newZenRule("pkg1", now.minus(1, ChronoUnit.DAYS), now);
+ ZenRule pkg2Rule = newZenRule("pkg2", now.minus(2, ChronoUnit.DAYS), now);
+ mZenModeHelper.mConfig.deletedRules.put(ZenModeConfig.deletedRuleKey(pkg1Rule), pkg1Rule);
+ mZenModeHelper.mConfig.deletedRules.put(ZenModeConfig.deletedRuleKey(pkg2Rule), pkg2Rule);
+
+ mZenModeHelper.removeAutomaticZenRules("pkg1",
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "goodbye pkg1", Process.SYSTEM_UID);
+
+ // Preserved rules from pkg1 are gone; those from pkg2 are still there.
+ assertThat(mZenModeHelper.mConfig.deletedRules.values().stream().map(r -> r.pkg)
+ .collect(Collectors.toSet())).containsExactly("pkg2");
+ }
+
+ @Test
+ public void testRuleCleanup() throws Exception {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+ Instant now = Instant.ofEpochMilli(1701796461000L);
+ Instant yesterday = now.minus(1, ChronoUnit.DAYS);
+ Instant aWeekAgo = now.minus(7, ChronoUnit.DAYS);
+ Instant twoMonthsAgo = now.minus(60, ChronoUnit.DAYS);
+ mTestClock.setNowMillis(now.toEpochMilli());
+
+ when(mPackageManager.getPackageInfo(eq("good_pkg"), anyInt()))
+ .thenReturn(new PackageInfo());
+ when(mPackageManager.getPackageInfo(eq("bad_pkg"), anyInt()))
+ .thenThrow(new PackageManager.NameNotFoundException("bad_pkg is not here"));
+
+ // Set up a config for another user containing:
+ ZenModeConfig config = new ZenModeConfig();
+ config.user = 42;
+ mZenModeHelper.mConfigs.put(42, config);
+ // okay rules (not deleted, package exists, with a range of creation dates).
+ config.automaticRules.put("ar1", newZenRule("good_pkg", now, null));
+ config.automaticRules.put("ar2", newZenRule("good_pkg", yesterday, null));
+ config.automaticRules.put("ar3", newZenRule("good_pkg", twoMonthsAgo, null));
+ // newish rules for a missing package
+ config.automaticRules.put("ar4", newZenRule("bad_pkg", yesterday, null));
+ // oldish rules belonging to a missing package
+ config.automaticRules.put("ar5", newZenRule("bad_pkg", aWeekAgo, null));
+ // rules deleted recently
+ config.deletedRules.put("del1", newZenRule("good_pkg", twoMonthsAgo, yesterday));
+ config.deletedRules.put("del2", newZenRule("good_pkg", twoMonthsAgo, aWeekAgo));
+ // rules deleted a long time ago
+ config.deletedRules.put("del3", newZenRule("good_pkg", twoMonthsAgo, twoMonthsAgo));
+ // rules for a missing package, created recently and deleted recently
+ config.deletedRules.put("del4", newZenRule("bad_pkg", yesterday, now));
+ // rules for a missing package, created a long time ago and deleted recently
+ config.deletedRules.put("del5", newZenRule("bad_pkg", twoMonthsAgo, now));
+ // rules for a missing package, created a long time ago and deleted a long time ago
+ config.deletedRules.put("del6", newZenRule("bad_pkg", twoMonthsAgo, twoMonthsAgo));
+
+ mZenModeHelper.onUserUnlocked(42); // copies config and cleans it up.
+
+ assertThat(mZenModeHelper.mConfig.automaticRules.keySet())
+ .containsExactly("ar1", "ar2", "ar3", "ar4");
+ assertThat(mZenModeHelper.mConfig.deletedRules.keySet())
+ .containsExactly("del1", "del2", "del4");
+ }
+
+ private static ZenRule newZenRule(String pkg, Instant createdAt, @Nullable Instant deletedAt) {
+ ZenRule rule = new ZenRule();
+ rule.pkg = pkg;
+ rule.creationTime = createdAt.toEpochMilli();
+ rule.deletionInstant = deletedAt;
+ // Plus stuff so that isValidAutomaticRule() passes
+ rule.name = "A rule from " + pkg + " created on " + createdAt;
+ rule.conditionId = Uri.parse(rule.name);
+ return rule;
+ }
+
+ @Test
public void applyGlobalZenModeAsImplicitZenRule_createsImplicitRuleAndActivatesIt() {
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mZenModeHelper.mConfig.automaticRules.clear();
@@ -4326,7 +4604,7 @@
ZEN_MODE_IMPORTANT_INTERRUPTIONS);
assertThat(mZenModeHelper.mConfig.automaticRules.values())
- .comparingElementsUsing(IGNORE_TIMESTAMPS)
+ .comparingElementsUsing(IGNORE_METADATA)
.containsExactly(
expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
null, true));
@@ -4346,12 +4624,75 @@
ZEN_MODE_ALARMS);
assertThat(mZenModeHelper.mConfig.automaticRules.values())
- .comparingElementsUsing(IGNORE_TIMESTAMPS)
+ .comparingElementsUsing(IGNORE_METADATA)
.containsExactly(
expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_ALARMS, null, true));
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void applyGlobalZenModeAsImplicitZenRule_ruleCustomized_doesNotUpdateRule() {
+ mZenModeHelper.mConfig.automaticRules.clear();
+ String pkg = mContext.getPackageName();
+
+ // From app, call "setInterruptionFilter" and create and implicit rule.
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID,
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet());
+ assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenMode)
+ .isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+
+ // From user, update that rule's interruption filter.
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
+ .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
+ .build();
+ mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason",
+ Process.SYSTEM_UID);
+
+ // From app, call "setInterruptionFilter" again.
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID,
+ ZEN_MODE_NO_INTERRUPTIONS);
+
+ // The app's update was ignored, and the user's update is still current, and the current
+ // mode is the one they chose.
+ assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenMode)
+ .isEqualTo(ZEN_MODE_ALARMS);
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void applyGlobalZenModeAsImplicitZenRule_ruleCustomizedButNotFilter_updatesRule() {
+ mZenModeHelper.mConfig.automaticRules.clear();
+ String pkg = mContext.getPackageName();
+
+ // From app, call "setInterruptionFilter" and create and implicit rule.
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID,
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet());
+ assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenMode)
+ .isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+
+ // From user, update something in that rule, but not the interruption filter.
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
+ .setName("Renamed")
+ .build();
+ mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason",
+ Process.SYSTEM_UID);
+
+ // From app, call "setInterruptionFilter" again.
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID,
+ ZEN_MODE_NO_INTERRUPTIONS);
+
+ // The app's update was accepted, and the current mode is the one that they wanted.
+ assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenMode)
+ .isEqualTo(ZEN_MODE_NO_INTERRUPTIONS);
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_NO_INTERRUPTIONS);
+ }
+
+ @Test
public void applyGlobalZenModeAsImplicitZenRule_modeOff_deactivatesImplicitRule() {
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mZenModeHelper.mConfig.automaticRules.clear();
@@ -4421,18 +4762,17 @@
Policy policy = new Policy(PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS,
PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED,
Policy.getAllSuppressedVisualEffects(), CONVERSATION_SENDERS_IMPORTANT);
- mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, policy,
- UPDATE_ORIGIN_APP);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, policy);
ZenPolicy expectedZenPolicy = new ZenPolicy.Builder()
.disallowAllSounds()
.allowCalls(PEOPLE_TYPE_CONTACTS)
.allowConversations(CONVERSATION_SENDERS_IMPORTANT)
.hideAllVisualEffects()
- .allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY)
+ .allowPriorityChannels(true)
.build();
assertThat(mZenModeHelper.mConfig.automaticRules.values())
- .comparingElementsUsing(IGNORE_TIMESTAMPS)
+ .comparingElementsUsing(IGNORE_METADATA)
.containsExactly(
expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
expectedZenPolicy, /* conditionActive= */ null));
@@ -4447,37 +4787,103 @@
PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED,
Policy.getAllSuppressedVisualEffects(), CONVERSATION_SENDERS_IMPORTANT);
mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
- original, UPDATE_ORIGIN_APP);
+ original);
// Change priorityCallSenders: contacts -> starred.
Policy updated = new Policy(PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS,
PRIORITY_SENDERS_STARRED, PRIORITY_SENDERS_STARRED,
Policy.getAllSuppressedVisualEffects(), CONVERSATION_SENDERS_IMPORTANT);
- mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, updated,
- UPDATE_ORIGIN_APP);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, updated);
ZenPolicy expectedZenPolicy = new ZenPolicy.Builder()
.disallowAllSounds()
.allowCalls(PEOPLE_TYPE_STARRED)
.allowConversations(CONVERSATION_SENDERS_IMPORTANT)
.hideAllVisualEffects()
- .allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY)
+ .allowPriorityChannels(true)
.build();
assertThat(mZenModeHelper.mConfig.automaticRules.values())
- .comparingElementsUsing(IGNORE_TIMESTAMPS)
+ .comparingElementsUsing(IGNORE_METADATA)
.containsExactly(
expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
expectedZenPolicy, /* conditionActive= */ null));
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void applyGlobalPolicyAsImplicitZenRule_ruleCustomized_doesNotUpdateRule() {
+ mZenModeHelper.mConfig.automaticRules.clear();
+ String pkg = mContext.getPackageName();
+
+ // From app, call "setNotificationPolicy" and create and implicit rule.
+ Policy originalPolicy = new Policy(PRIORITY_CATEGORY_MEDIA, 0, 0);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, originalPolicy);
+ String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet());
+
+ // From user, update that rule's policy.
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ ZenPolicy userUpdateZenPolicy = new ZenPolicy.Builder().disallowAllSounds()
+ .allowAlarms(true).build();
+ AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
+ .setZenPolicy(userUpdateZenPolicy)
+ .build();
+ mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason",
+ Process.SYSTEM_UID);
+
+ // From app, call "setNotificationPolicy" again.
+ Policy appUpdatePolicy = new Policy(PRIORITY_CATEGORY_SYSTEM, 0, 0);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, appUpdatePolicy);
+
+ // The app's update was ignored, and the user's update is still current.
+ assertThat(mZenModeHelper.mConfig.automaticRules.values())
+ .comparingElementsUsing(IGNORE_METADATA)
+ .containsExactly(
+ expectedImplicitRule(pkg, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ userUpdateZenPolicy,
+ /* conditionActive= */ null));
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void applyGlobalPolicyAsImplicitZenRule_ruleCustomizedButNotZenPolicy_updatesRule() {
+ mZenModeHelper.mConfig.automaticRules.clear();
+ String pkg = mContext.getPackageName();
+
+ // From app, call "setNotificationPolicy" and create and implicit rule.
+ Policy originalPolicy = new Policy(PRIORITY_CATEGORY_MEDIA, 0, 0);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, originalPolicy);
+ String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet());
+
+ // From user, update something in that rule, but not the ZenPolicy.
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
+ .setName("Rule renamed, not touching policy")
+ .build();
+ mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason",
+ Process.SYSTEM_UID);
+
+ // From app, call "setNotificationPolicy" again.
+ Policy appUpdatePolicy = new Policy(PRIORITY_CATEGORY_SYSTEM, 0, 0);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, appUpdatePolicy);
+
+ // The app's update was applied.
+ ZenPolicy appsSecondZenPolicy = new ZenPolicy.Builder()
+ .disallowAllSounds()
+ .allowSystem(true)
+ .allowPriorityChannels(true)
+ .build();
+ assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenPolicy)
+ .isEqualTo(appsSecondZenPolicy);
+ }
+
+ @Test
public void applyGlobalPolicyAsImplicitZenRule_flagOff_ignored() {
mSetFlagsRule.disableFlags(android.app.Flags.FLAG_MODES_API);
mZenModeHelper.mConfig.automaticRules.clear();
withoutWtfCrash(
() -> mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME,
- CUSTOM_PKG_UID, new Policy(0, 0, 0), UPDATE_ORIGIN_APP));
+ CUSTOM_PKG_UID, new Policy(0, 0, 0)));
assertThat(mZenModeHelper.mConfig.automaticRules).isEmpty();
}
@@ -4490,7 +4896,7 @@
Policy.getAllSuppressedVisualEffects(), STATE_FALSE,
CONVERSATION_SENDERS_IMPORTANT);
mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
- writtenPolicy, UPDATE_ORIGIN_APP);
+ writtenPolicy);
Policy readPolicy = mZenModeHelper.getNotificationPolicyFromImplicitZenRule(
CUSTOM_PKG_NAME);
@@ -4530,7 +4936,7 @@
assertThat(readPolicy.allowConversations()).isFalse();
}
- private static final Correspondence<ZenRule, ZenRule> IGNORE_TIMESTAMPS =
+ private static final Correspondence<ZenRule, ZenRule> IGNORE_METADATA =
Correspondence.transforming(zr -> {
Parcel p = Parcel.obtain();
try {
@@ -4538,12 +4944,15 @@
p.setDataPosition(0);
ZenRule copy = new ZenRule(p);
copy.creationTime = 0;
+ copy.userModifiedFields = 0;
+ copy.zenPolicyUserModifiedFields = 0;
+ copy.zenDeviceEffectsUserModifiedFields = 0;
return copy;
} finally {
p.recycle();
}
},
- "Ignoring timestamps");
+ "Ignoring timestamp and userModifiedFields");
private ZenRule expectedImplicitRule(String ownerPkg, int zenMode, ZenPolicy policy,
@Nullable Boolean conditionActive) {
@@ -4919,4 +5328,25 @@
return parser.nextTag();
}
}
+
+ private static class TestClock extends SimpleClock {
+ private long mNowMillis = 441644400000L;
+
+ private TestClock() {
+ super(ZoneOffset.UTC);
+ }
+
+ @Override
+ public long millis() {
+ return mNowMillis;
+ }
+
+ private void setNowMillis(long millis) {
+ mNowMillis = millis;
+ }
+
+ private void advanceByMillis(long millis) {
+ mNowMillis += millis;
+ }
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
index 21c96d6..4ed55df 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
@@ -213,13 +213,12 @@
ZenPolicy unset = builder.build();
// priority channels allowed
- builder.allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY);
+ builder.allowPriorityChannels(true);
ZenPolicy channelsPriority = builder.build();
// unset applied, channels setting keeps its state
channelsPriority.apply(unset);
- assertThat(channelsPriority.getAllowedChannels())
- .isEqualTo(ZenPolicy.CHANNEL_TYPE_PRIORITY);
+ assertThat(channelsPriority.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_ALLOW);
}
@Test
@@ -227,15 +226,15 @@
mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
ZenPolicy.Builder builder = new ZenPolicy.Builder();
- builder.allowChannels(ZenPolicy.CHANNEL_TYPE_NONE);
+ builder.allowPriorityChannels(false);
ZenPolicy none = builder.build();
- builder.allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY);
+ builder.allowPriorityChannels(true);
ZenPolicy priority = builder.build();
// priority channels (less strict state) cannot override a setting that sets it to none
none.apply(priority);
- assertThat(none.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_NONE);
+ assertThat(none.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_DISALLOW);
}
@Test
@@ -243,15 +242,15 @@
mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
ZenPolicy.Builder builder = new ZenPolicy.Builder();
- builder.allowChannels(ZenPolicy.CHANNEL_TYPE_NONE);
+ builder.allowPriorityChannels(false);
ZenPolicy none = builder.build();
- builder.allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY);
+ builder.allowPriorityChannels(true);
ZenPolicy priority = builder.build();
// applying a policy with channelType=none overrides priority setting
priority.apply(none);
- assertThat(priority.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_NONE);
+ assertThat(priority.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_DISALLOW);
}
@Test
@@ -261,12 +260,12 @@
ZenPolicy.Builder builder = new ZenPolicy.Builder();
ZenPolicy unset = builder.build();
- builder.allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY);
+ builder.allowPriorityChannels(true);
ZenPolicy priority = builder.build();
// applying a policy with a set channel type actually goes through
unset.apply(priority);
- assertThat(unset.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_PRIORITY);
+ assertThat(unset.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_ALLOW);
}
@Test
@@ -308,7 +307,7 @@
ZenPolicy.Builder builder = new ZenPolicy.Builder();
ZenPolicy policy = builder.build();
- assertThat(policy.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_UNSET);
+ assertThat(policy.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_UNSET);
}
@Test
@@ -622,10 +621,10 @@
// allowChannels should be unset, not be modifiable, and not show up in any output
ZenPolicy.Builder builder = new ZenPolicy.Builder();
- builder.allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY);
+ builder.allowPriorityChannels(true);
ZenPolicy policy = builder.build();
- assertThat(policy.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_UNSET);
+ assertThat(policy.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_UNSET);
assertThat(policy.toString().contains("allowChannels")).isFalse();
}
@@ -635,40 +634,14 @@
// allow priority channels
ZenPolicy.Builder builder = new ZenPolicy.Builder();
- builder.allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY);
+ builder.allowPriorityChannels(true);
ZenPolicy policy = builder.build();
- assertThat(policy.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_PRIORITY);
+ assertThat(policy.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_ALLOW);
// disallow priority channels
- builder.allowChannels(ZenPolicy.CHANNEL_TYPE_NONE);
+ builder.allowPriorityChannels(false);
policy = builder.build();
- assertThat(policy.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_NONE);
- }
-
- @Test
- public void testFromParcel() {
- ZenPolicy.Builder builder = new ZenPolicy.Builder();
- builder.setUserModifiedFields(10);
-
- ZenPolicy policy = builder.build();
- assertThat(policy.getUserModifiedFields()).isEqualTo(10);
-
- Parcel parcel = Parcel.obtain();
- policy.writeToParcel(parcel, 0);
- parcel.setDataPosition(0);
-
- ZenPolicy fromParcel = ZenPolicy.CREATOR.createFromParcel(parcel);
- assertThat(fromParcel.getUserModifiedFields()).isEqualTo(10);
- }
-
- @Test
- public void testPolicy_userModifiedFields() {
- ZenPolicy.Builder builder = new ZenPolicy.Builder();
- builder.setUserModifiedFields(10);
- assertThat(builder.build().getUserModifiedFields()).isEqualTo(10);
-
- builder.setUserModifiedFields(0);
- assertThat(builder.build().getUserModifiedFields()).isEqualTo(0);
+ assertThat(policy.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_DISALLOW);
}
@Test
@@ -676,8 +649,8 @@
ZenPolicy.Builder builder = new ZenPolicy.Builder();
ZenPolicy policy = builder.allowRepeatCallers(true).allowAlarms(false)
.showLights(true).showBadges(false)
- .allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY)
- .setUserModifiedFields(20).build();
+ .allowPriorityChannels(true)
+ .build();
ZenPolicy newPolicy = new ZenPolicy.Builder(policy).build();
@@ -689,8 +662,7 @@
assertThat(newPolicy.getVisualEffectBadge()).isEqualTo(ZenPolicy.STATE_DISALLOW);
assertThat(newPolicy.getVisualEffectPeek()).isEqualTo(ZenPolicy.STATE_UNSET);
- assertThat(newPolicy.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_PRIORITY);
- assertThat(newPolicy.getUserModifiedFields()).isEqualTo(20);
+ assertThat(newPolicy.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_ALLOW);
}
@Test
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 f5282cb..ba7b52e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -3309,7 +3309,7 @@
// keyguard to back to the app, expect IME insets is not frozen
app.mActivityRecord.commitVisibility(true, false);
mDisplayContent.updateImeInputAndControlTarget(app);
- mDisplayContent.mWmService.mRoot.performSurfacePlacement();
+ performSurfacePlacementAndWaitForWindowAnimator();
assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
@@ -3358,14 +3358,14 @@
mDisplayContent.setImeLayeringTarget(app2);
app2.mActivityRecord.commitVisibility(true, false);
mDisplayContent.updateImeInputAndControlTarget(app2);
- mDisplayContent.mWmService.mRoot.performSurfacePlacement();
+ performSurfacePlacementAndWaitForWindowAnimator();
// Verify after unfreezing app2's IME insets state, we won't dispatch visible IME insets
// to client if the app didn't request IME visible.
assertFalse(app2.mActivityRecord.mImeInsetsFrozenUntilStartInput);
if (Flags.bundleClientTransactionFlag()) {
- verify(app2.getProcess()).scheduleClientTransactionItem(
+ verify(app2.getProcess(), atLeastOnce()).scheduleClientTransactionItem(
isA(WindowStateResizeItem.class));
} else {
verify(app2.mClient, atLeastOnce()).resized(any(), anyBoolean(), any(),
@@ -3412,7 +3412,7 @@
// frozen until the input started.
mDisplayContent.setImeLayeringTarget(app1);
mDisplayContent.updateImeInputAndControlTarget(app1);
- mDisplayContent.mWmService.mRoot.performSurfacePlacement();
+ performSurfacePlacementAndWaitForWindowAnimator();
assertEquals(app1, mDisplayContent.getImeInputTarget());
assertFalse(activity1.mImeInsetsFrozenUntilStartInput);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java b/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java
index 71dbc57..2986372 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertTrue;
import android.platform.test.annotations.Presubmit;
+import android.util.ArraySet;
import androidx.test.filters.SmallTest;
@@ -28,7 +29,6 @@
import org.junit.After;
import org.junit.Test;
-import java.util.Collections;
import java.util.Set;
/**
@@ -52,18 +52,21 @@
@After
public void tearDown() {
- mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ mSensitiveContentPackages.clearBlockedApps();
}
@Test
- public void setShouldBlockScreenCaptureForApp() {
- Set<PackageInfo> blockedApps =
+ public void addBlockScreenCaptureForApps() {
+ ArraySet<PackageInfo> blockedApps = new ArraySet(
Set.of(new PackageInfo(APP_PKG_1, APP_UID_1),
new PackageInfo(APP_PKG_1, APP_UID_2),
new PackageInfo(APP_PKG_2, APP_UID_1),
- new PackageInfo(APP_PKG_2, APP_UID_2));
+ new PackageInfo(APP_PKG_2, APP_UID_2)
+ ));
- mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(blockedApps);
+ boolean modified = mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps);
+
+ assertTrue(modified);
assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1));
assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2));
@@ -79,15 +82,93 @@
}
@Test
- public void setShouldBlockScreenCaptureForApp_empty() {
- Set<PackageInfo> blockedApps =
+ public void addBlockScreenCaptureForApps_addedTwice() {
+ ArraySet<PackageInfo> blockedApps = new ArraySet(
Set.of(new PackageInfo(APP_PKG_1, APP_UID_1),
new PackageInfo(APP_PKG_1, APP_UID_2),
new PackageInfo(APP_PKG_2, APP_UID_1),
- new PackageInfo(APP_PKG_2, APP_UID_2));
+ new PackageInfo(APP_PKG_2, APP_UID_2)
+ ));
- mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(blockedApps);
- mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps);
+ boolean modified = mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps);
+
+ assertFalse(modified);
+
+ assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1));
+ assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_3));
+
+ assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_1));
+ assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_3));
+
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_1));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_2));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_3));
+ }
+
+ @Test
+ public void addBlockScreenCaptureForApps_withPartialPreviousPackages() {
+ ArraySet<PackageInfo> blockedApps = new ArraySet(
+ Set.of(new PackageInfo(APP_PKG_1, APP_UID_1),
+ new PackageInfo(APP_PKG_1, APP_UID_2),
+ new PackageInfo(APP_PKG_2, APP_UID_1),
+ new PackageInfo(APP_PKG_2, APP_UID_2)
+ ));
+
+ mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps);
+ boolean modified = mSensitiveContentPackages
+ .addBlockScreenCaptureForApps(
+ new ArraySet(Set.of(new PackageInfo(APP_PKG_3, APP_UID_1))));
+
+ assertTrue(modified);
+
+ assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1));
+ assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_3));
+
+ assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_1));
+ assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_3));
+
+ assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_1));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_2));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_3));
+ }
+
+ @Test
+ public void clearBlockedApps() {
+ ArraySet<PackageInfo> blockedApps = new ArraySet(
+ Set.of(new PackageInfo(APP_PKG_1, APP_UID_1),
+ new PackageInfo(APP_PKG_1, APP_UID_2),
+ new PackageInfo(APP_PKG_2, APP_UID_1),
+ new PackageInfo(APP_PKG_2, APP_UID_2)
+ ));
+
+ mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps);
+ boolean modified = mSensitiveContentPackages.clearBlockedApps();
+
+ assertTrue(modified);
+
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_3));
+
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_1));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_3));
+
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_1));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_2));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_3));
+ }
+
+ @Test
+ public void clearBlockedApps_alreadyEmpty() {
+ boolean modified = mSensitiveContentPackages.clearBlockedApps();
+
+ assertFalse(modified);
assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1));
assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2));
diff --git a/services/tests/wmtests/src/com/android/server/wm/SplashScreenExceptionListTest.java b/services/tests/wmtests/src/com/android/server/wm/SplashScreenExceptionListTest.java
index 8bd5473..2ea5dc4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SplashScreenExceptionListTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SplashScreenExceptionListTest.java
@@ -28,6 +28,7 @@
import android.os.Looper;
import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
+import android.util.Log;
import androidx.test.filters.MediumTest;
@@ -147,6 +148,8 @@
private void setExceptionListAndWaitForCallback(String commaSeparatedList) {
CountDownLatch latch = new CountDownLatch(1);
mOnUpdateDeviceConfig = rawList -> {
+ Log.i(getClass().getSimpleName(), "updateDeviceConfig expected="
+ + commaSeparatedList + " actual=" + rawList);
if (commaSeparatedList.equals(rawList)) {
latch.countDown();
}
@@ -154,8 +157,11 @@
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
KEY_SPLASH_SCREEN_EXCEPTION_LIST, commaSeparatedList, false);
try {
- assertTrue("Timed out waiting for DeviceConfig to be updated.",
- latch.await(1, TimeUnit.SECONDS));
+ if (!latch.await(1, TimeUnit.SECONDS)) {
+ Log.w(getClass().getSimpleName(),
+ "Timed out waiting for DeviceConfig to be updated. Force update.");
+ mList.updateDeviceConfig(commaSeparatedList);
+ }
} catch (InterruptedException e) {
Assert.fail(e.getMessage());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
index 339162a..dfea2fc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
@@ -44,7 +44,6 @@
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
-import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
import com.android.server.AnimationThread;
@@ -147,9 +146,10 @@
assertFinishCallbackNotCalled();
}
- @FlakyTest(bugId = 71719744)
@Test
public void testCancel_sneakyCancelBeforeUpdate() throws Exception {
+ final CountDownLatch animationCancelled = new CountDownLatch(1);
+
mSurfaceAnimationRunner = new SurfaceAnimationRunner(null, () -> new ValueAnimator() {
{
setFloatValues(0f, 1f);
@@ -162,6 +162,7 @@
// interleaving of multiple threads. Muahahaha
if (animation.getCurrentPlayTime() > 0) {
mSurfaceAnimationRunner.onAnimationCancelled(mMockSurface);
+ animationCancelled.countDown();
}
listener.onAnimationUpdate(animation);
});
@@ -170,11 +171,7 @@
when(mMockAnimationSpec.getDuration()).thenReturn(200L);
mSurfaceAnimationRunner.startAnimation(mMockAnimationSpec, mMockSurface, mMockTransaction,
this::finishedCallback);
-
- // We need to wait for two frames: The first frame starts the animation, the second frame
- // actually cancels the animation.
- waitUntilNextFrame();
- waitUntilNextFrame();
+ assertTrue(animationCancelled.await(1, SECONDS));
assertTrue(mSurfaceAnimationRunner.mRunningAnimations.isEmpty());
verify(mMockAnimationSpec, atLeastOnce()).apply(any(), any(), eq(0L));
}
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 b360800..961fdfb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -1822,6 +1822,35 @@
verify(fragment2).assignLayer(t, 2);
}
+ @Test
+ public void testMoveTaskFragmentsToBottomIfNeeded() {
+ 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 TaskFragment fragment3 = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ doReturn(true).when(fragment1).isMoveToBottomIfClearWhenLaunch();
+ doReturn(false).when(fragment2).isMoveToBottomIfClearWhenLaunch();
+ doReturn(true).when(fragment3).isMoveToBottomIfClearWhenLaunch();
+
+ assertEquals(unembeddedActivity, task.mChildren.get(0));
+ assertEquals(fragment1, task.mChildren.get(1));
+ assertEquals(fragment2, task.mChildren.get(2));
+ assertEquals(fragment3, task.mChildren.get(3));
+
+ final int[] finishCount = {0};
+ task.moveTaskFragmentsToBottomIfNeeded(unembeddedActivity, finishCount);
+
+ // fragment1 and fragment3 should be moved to the bottom of the task
+ assertEquals(fragment1, task.mChildren.get(0));
+ assertEquals(fragment3, task.mChildren.get(1));
+ assertEquals(unembeddedActivity, task.mChildren.get(2));
+ assertEquals(fragment2, task.mChildren.get(3));
+ assertEquals(2, finishCount[0]);
+ }
+
private Task getTestTask() {
return new TaskBuilder(mSupervisor).setCreateActivity(true).build();
}
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 6216acb..73d386a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -32,6 +32,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.window.flags.Flags.multiCrop;
import static com.google.common.truth.Truth.assertThat;
@@ -39,6 +40,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -484,6 +486,7 @@
@Test
public void testUpdateWallpaperOffset_resize_shouldCenterEnabled() {
+ assumeFalse(multiCrop());
final DisplayContent dc = new TestDisplayContent.Builder(mAtm, INITIAL_WIDTH,
INITIAL_HEIGHT).build();
dc.mWallpaperController.setShouldOffsetWallpaperCenter(true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index a1cc8d5..fe9d837 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -115,7 +115,6 @@
import org.mockito.ArgumentCaptor;
import java.util.ArrayList;
-import java.util.Collections;
/**
* Build/Install/Run:
@@ -139,7 +138,7 @@
@After
public void tearDown() {
- mWm.mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ mWm.mSensitiveContentPackages.clearBlockedApps();
}
@Test
@@ -824,7 +823,7 @@
}
@Test
- public void setShouldBlockScreenCaptureForApp() {
+ public void addBlockScreenCaptureForApps() {
String testPackage = "test";
int ownerId1 = 20;
int ownerId2 = 21;
@@ -833,7 +832,7 @@
blockedPackages.add(blockedPackage);
WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
- wmInternal.setShouldBlockScreenCaptureForApp(blockedPackages);
+ wmInternal.addBlockScreenCaptureForApps(blockedPackages);
assertTrue(mWm.mSensitiveContentPackages
.shouldBlockScreenCaptureForApp(testPackage, ownerId1));
@@ -843,7 +842,47 @@
}
@Test
- public void setShouldBlockScreenCaptureForApp_emptySet_clearsCache() {
+ public void addBlockScreenCaptureForApps_duplicate_verifyNoRefresh() {
+ String testPackage = "test";
+ int ownerId1 = 20;
+ int ownerId2 = 21;
+ PackageInfo blockedPackage = new PackageInfo(testPackage, ownerId1);
+ ArraySet<PackageInfo> blockedPackages = new ArraySet();
+ blockedPackages.add(blockedPackage);
+
+ WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+ wmInternal.addBlockScreenCaptureForApps(blockedPackages);
+ wmInternal.addBlockScreenCaptureForApps(blockedPackages);
+
+ verify(mWm, times(1)).refreshScreenCaptureDisabled();
+ }
+
+ @Test
+ public void addBlockScreenCaptureForApps_notDuplicate_verifyRefresh() {
+ String testPackage = "test";
+ int ownerId1 = 20;
+ int ownerId2 = 21;
+ PackageInfo blockedPackage = new PackageInfo(testPackage, ownerId1);
+ PackageInfo blockedPackage2 = new PackageInfo(testPackage, ownerId2);
+ ArraySet<PackageInfo> blockedPackages = new ArraySet();
+ blockedPackages.add(blockedPackage);
+ ArraySet<PackageInfo> blockedPackages2 = new ArraySet();
+ blockedPackages2.add(blockedPackage);
+ blockedPackages2.add(blockedPackage2);
+
+ WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+ wmInternal.addBlockScreenCaptureForApps(blockedPackages);
+ wmInternal.addBlockScreenCaptureForApps(blockedPackages2);
+
+ assertTrue(mWm.mSensitiveContentPackages
+ .shouldBlockScreenCaptureForApp(testPackage, ownerId1));
+ assertTrue(mWm.mSensitiveContentPackages
+ .shouldBlockScreenCaptureForApp(testPackage, ownerId2));
+ verify(mWm, times(2)).refreshScreenCaptureDisabled();
+ }
+
+ @Test
+ public void clearBlockedApps_clearsCache() {
String testPackage = "test";
int ownerId1 = 20;
PackageInfo blockedPackage = new PackageInfo(testPackage, ownerId1);
@@ -851,8 +890,8 @@
blockedPackages.add(blockedPackage);
WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
- wmInternal.setShouldBlockScreenCaptureForApp(blockedPackages);
- wmInternal.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ wmInternal.addBlockScreenCaptureForApps(blockedPackages);
+ wmInternal.clearBlockedApps();
assertFalse(mWm.mSensitiveContentPackages
.shouldBlockScreenCaptureForApp(testPackage, ownerId1));
@@ -860,6 +899,14 @@
}
@Test
+ public void clearBlockedApps_alreadyEmpty_verifyNoRefresh() {
+ WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+ wmInternal.clearBlockedApps();
+
+ verify(mWm, never()).refreshScreenCaptureDisabled();
+ }
+
+ @Test
public void testisLetterboxBackgroundMultiColored() {
assertThat(setupLetterboxConfigurationWithBackgroundType(
LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING)).isTrue();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index fb4edfa..a0562aa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -136,7 +136,7 @@
@After
public void tearDown() {
- mWm.mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ mWm.mSensitiveContentPackages.clearBlockedApps();
}
@Test
@@ -1398,7 +1398,7 @@
PackageInfo blockedPackage = new PackageInfo(testPackage, ownerId1);
ArraySet<PackageInfo> blockedPackages = new ArraySet();
blockedPackages.add(blockedPackage);
- mWm.mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(blockedPackages);
+ mWm.mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedPackages);
assertTrue(window1.isSecureLocked());
assertFalse(window2.isSecureLocked());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 60e84b0..114b9c3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -119,6 +119,7 @@
import com.android.internal.policy.AttributeCache;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils;
import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry;
import org.junit.After;
@@ -286,6 +287,18 @@
mAtm.mWindowManager.mLetterboxConfiguration
.setIsDisplayAspectRatioEnabledForFixedOrientationLetterbox(false);
+ // Setup WallpaperController crop utils with a simple center-align strategy
+ WallpaperCropUtils cropUtils = (displaySize, bitmapSize, suggestedCrops, rtl) -> {
+ Rect crop = new Rect(0, 0, displaySize.x, displaySize.y);
+ crop.scale(Math.min(
+ ((float) bitmapSize.x) / displaySize.x,
+ ((float) bitmapSize.y) / displaySize.y));
+ crop.offset((bitmapSize.x - crop.width()) / 2, (bitmapSize.y - crop.height()) / 2);
+ return crop;
+ };
+ mDisplayContent.mWallpaperController.setWallpaperCropUtils(cropUtils);
+ mDefaultDisplay.mWallpaperController.setWallpaperCropUtils(cropUtils);
+
checkDeviceSpecificOverridesNotApplied();
}
@@ -1054,6 +1067,21 @@
}
/**
+ * Performs surface placement and waits for WindowAnimator to complete the frame. It is used
+ * to execute the callbacks if the surface placement is expected to add some callbacks via
+ * {@link WindowAnimator#addAfterPrepareSurfacesRunnable}.
+ */
+ void performSurfacePlacementAndWaitForWindowAnimator() {
+ mWm.mAnimator.ready();
+ if (!mWm.mWindowPlacerLocked.isTraversalScheduled()) {
+ mRootWindowContainer.performSurfacePlacement();
+ } else {
+ waitHandlerIdle(mWm.mAnimationHandler);
+ }
+ waitUntilWindowAnimatorIdle();
+ }
+
+ /**
* Avoids rotating screen disturbed by some conditions. It is usually used for the default
* display that is not the instance of {@link TestDisplayContent} (it bypasses the conditions).
*
diff --git a/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java b/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java
index 336bfdd..a8fd6f2 100644
--- a/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java
+++ b/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java
@@ -35,11 +35,13 @@
import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.Keep;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.RingBuffer;
import com.android.server.usage.BroadcastResponseStatsTracker.NotificationEventType;
+import java.util.function.IntFunction;
+import java.util.function.Supplier;
+
public class BroadcastResponseStatsLogger {
private static final int MAX_LOG_SIZE =
@@ -49,10 +51,10 @@
@GuardedBy("mLock")
private final LogBuffer mBroadcastEventsBuffer = new LogBuffer(
- BroadcastEvent.class, MAX_LOG_SIZE);
+ BroadcastEvent::new, BroadcastEvent[]::new, MAX_LOG_SIZE);
@GuardedBy("mLock")
private final LogBuffer mNotificationEventsBuffer = new LogBuffer(
- NotificationEvent.class, MAX_LOG_SIZE);
+ NotificationEvent::new, NotificationEvent[]::new, MAX_LOG_SIZE);
void logBroadcastDispatchEvent(int sourceUid, @NonNull String targetPackage,
UserHandle targetUser, long idForResponseEvent,
@@ -96,8 +98,8 @@
private static final class LogBuffer<T extends Data> extends RingBuffer<T> {
- LogBuffer(Class<T> classType, int capacity) {
- super(classType, capacity);
+ LogBuffer(Supplier<T> newItem, IntFunction<T[]> newBacking, int capacity) {
+ super(newItem, newBacking, capacity);
}
void logBroadcastDispatchEvent(int sourceUid, @NonNull String targetPackage,
@@ -179,8 +181,7 @@
}
}
- @Keep
- public static final class BroadcastEvent implements Data {
+ private static final class BroadcastEvent implements Data {
public int sourceUid;
public int targetUserId;
public int targetUidProcessState;
@@ -200,8 +201,7 @@
}
}
- @Keep
- public static final class NotificationEvent implements Data {
+ private static final class NotificationEvent implements Data {
public int type;
public String packageName;
public int userId;
@@ -218,7 +218,7 @@
}
}
- public interface Data {
+ private interface Data {
void reset();
}
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 08f719e..9fc64fe 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -2440,7 +2440,7 @@
}
return queryEventsHelper(userId, query.getBeginTimeMillis(),
- query.getEndTimeMillis(), callingPackage, query.getEventTypeFilter());
+ query.getEndTimeMillis(), callingPackage, query.getEventTypes());
}
@Override
diff --git a/services/usb/lint-baseline.xml b/services/usb/lint-baseline.xml
index c2c0a35..62a2ee5 100644
--- a/services/usb/lint-baseline.xml
+++ b/services/usb/lint-baseline.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 7.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="7.1.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
<issue
id="NonUserGetterCalled"
@@ -12,4 +12,4 @@
column="42"/>
</issue>
-</issues>
+</issues>
\ No newline at end of file
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java b/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java
index 8773cab..49ad461 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java
@@ -24,6 +24,7 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.telephony.flags.Flags;
import java.util.ArrayList;
import java.util.List;
@@ -61,7 +62,8 @@
SubscriptionManager subscriptionManager,
TelephonyManager telephonyManager,
Callback callback) {
- mSubscriptionManager = Objects.requireNonNull(subscriptionManager);
+ mSubscriptionManager = Objects.requireNonNull(subscriptionManager)
+ .createForAllUserProfiles();
mTelephonyManager = Objects.requireNonNull(telephonyManager);
mCallback = Objects.requireNonNull(callback);
mSubscriptionManager.addOnSubscriptionsChangedListener(
@@ -117,12 +119,28 @@
private boolean checkCallStatus() {
List<SubscriptionInfo> infoList = mSubscriptionManager.getActiveSubscriptionInfoList();
if (infoList == null) return false;
- return infoList.stream()
- .filter(s -> (s.getSubscriptionId() != SubscriptionManager.INVALID_SUBSCRIPTION_ID))
- .anyMatch(s -> isCallOngoingFromState(
- mTelephonyManager
- .createForSubscriptionId(s.getSubscriptionId())
- .getCallStateForSubscription()));
+ if (!Flags.enforceTelephonyFeatureMapping()) {
+ return infoList.stream()
+ .filter(s -> (s.getSubscriptionId()
+ != SubscriptionManager.INVALID_SUBSCRIPTION_ID))
+ .anyMatch(s -> isCallOngoingFromState(
+ mTelephonyManager
+ .createForSubscriptionId(s.getSubscriptionId())
+ .getCallStateForSubscription()));
+ } else {
+ return infoList.stream()
+ .filter(s -> (s.getSubscriptionId()
+ != SubscriptionManager.INVALID_SUBSCRIPTION_ID))
+ .anyMatch(s -> {
+ try {
+ return isCallOngoingFromState(mTelephonyManager
+ .createForSubscriptionId(s.getSubscriptionId())
+ .getCallStateForSubscription());
+ } catch (UnsupportedOperationException e) {
+ return false;
+ }
+ });
+ }
}
private void updateTelephonyListeners() {
diff --git a/telecomm/java/android/telecom/CallControl.java b/telecomm/java/android/telecom/CallControl.java
index 24d3918..fe699af 100644
--- a/telecomm/java/android/telecom/CallControl.java
+++ b/telecomm/java/android/telecom/CallControl.java
@@ -19,6 +19,7 @@
import static android.telecom.CallException.TRANSACTION_EXCEPTION_KEY;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -32,6 +33,7 @@
import com.android.internal.telecom.ClientTransactionalServiceRepository;
import com.android.internal.telecom.ICallControl;
+import com.android.server.telecom.flags.Flags;
import java.util.List;
import java.util.Objects;
@@ -292,6 +294,43 @@
}
/**
+ * Request a new mute state. Note: {@link CallEventCallback#onMuteStateChanged(boolean)}
+ * will be called every time the mute state is changed and can be used to track the current
+ * mute state.
+ *
+ * @param isMuted The new mute state. Passing in a {@link Boolean#TRUE} for the isMuted
+ * parameter will mute the call. {@link Boolean#FALSE} will unmute the call.
+ * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
+ * will be called on.
+ * @param callback The {@link OutcomeReceiver} that will be completed on the Telecom side
+ * that details success or failure of the requested operation.
+ *
+ * {@link OutcomeReceiver#onResult} will be called if Telecom has
+ * successfully changed the mute state.
+ *
+ * {@link OutcomeReceiver#onError} will be called if Telecom has failed to
+ * switch to the mute state. A {@link CallException} will be
+ * passed that details why the operation failed.
+ */
+ @FlaggedApi(Flags.FLAG_SET_MUTE_STATE)
+ public void setMuteState(boolean isMuted, @CallbackExecutor @NonNull Executor executor,
+ @NonNull OutcomeReceiver<Void, CallException> callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ if (mServerInterface != null) {
+ try {
+ mServerInterface.setMuteState(isMuted,
+ new CallControlResultReceiver("setMuteState", executor, callback));
+
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ } else {
+ throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ }
+ }
+
+ /**
* Raises an event to the {@link android.telecom.InCallService} implementations tracking this
* call via {@link android.telecom.Call.Callback#onConnectionEvent(Call, String, Bundle)}.
* These events and the associated extra keys for the {@code Bundle} parameter are mutually
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 57b13e9..e81f482 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1049,8 +1049,17 @@
public static final int PRESENTATION_UNAVAILABLE = 5;
+ /**
+ * Controls audio route for video calls.
+ * 0 - Use the default audio routing strategy.
+ * 1 - Disable the speaker. Route the audio to Headset or Bluetooth
+ * or Earpiece, based on the default audio routing strategy.
+ * @hide
+ */
+ public static final String PROPERTY_VIDEOCALL_AUDIO_OUTPUT = "persist.radio.call.audio.output";
+
/*
- * Values for the adb property "persist.radio.videocall.audio.output"
+ * Values for the adb property "persist.radio.call.audio.output"
*/
/** @hide */
public static final int AUDIO_OUTPUT_ENABLE_SPEAKER = 0;
diff --git a/telecomm/java/com/android/internal/telecom/ICallControl.aidl b/telecomm/java/com/android/internal/telecom/ICallControl.aidl
index 5e2c923..372e4a12 100644
--- a/telecomm/java/com/android/internal/telecom/ICallControl.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallControl.aidl
@@ -32,5 +32,6 @@
void disconnect(String callId, in DisconnectCause disconnectCause, in ResultReceiver callback);
void startCallStreaming(String callId, in ResultReceiver callback);
void requestCallEndpointChange(in CallEndpoint callEndpoint, in ResultReceiver callback);
+ void setMuteState(boolean isMuted, in ResultReceiver callback);
void sendEvent(String callId, String event, in Bundle extras);
}
\ No newline at end of file
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index 250c3a5..86eed2f 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -196,7 +196,7 @@
// We have READ_PHONE_STATE permission, so return true as long as the AppOps bit hasn't been
// revoked.
AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
- return appOps.noteOp(AppOpsManager.OPSTR_READ_PHONE_STATE, uid, callingPackage,
+ return appOps.noteOpNoThrow(AppOpsManager.OPSTR_READ_PHONE_STATE, uid, callingPackage,
callingFeatureId, null) == AppOpsManager.MODE_ALLOWED;
}
@@ -249,7 +249,7 @@
// We have READ_PHONE_STATE permission, so return true as long as the AppOps bit hasn't been
// revoked.
AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
- return appOps.noteOp(AppOpsManager.OPSTR_READ_PHONE_STATE, uid, callingPackage,
+ return appOps.noteOpNoThrow(AppOpsManager.OPSTR_READ_PHONE_STATE, uid, callingPackage,
callingFeatureId, null) == AppOpsManager.MODE_ALLOWED;
}
@@ -521,7 +521,7 @@
// We have READ_CALL_LOG permission, so return true as long as the AppOps bit hasn't been
// revoked.
AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
- return appOps.noteOp(AppOpsManager.OPSTR_READ_CALL_LOG, uid, callingPackage,
+ return appOps.noteOpNoThrow(AppOpsManager.OPSTR_READ_CALL_LOG, uid, callingPackage,
callingPackageName, null) == AppOpsManager.MODE_ALLOWED;
}
@@ -601,8 +601,9 @@
*
* @return true if caller has ACCESS_LAST_KNOWN_CELL_ID permission else false.
*/
+ @RequiresPermission(Manifest.permission.ACCESS_LAST_KNOWN_CELL_ID)
public static boolean checkLastKnownCellIdAccessPermission(Context context) {
- return context.checkCallingOrSelfPermission("android.permission.ACCESS_LAST_KNOWN_CELL_ID")
+ return context.checkCallingOrSelfPermission(Manifest.permission.ACCESS_LAST_KNOWN_CELL_ID)
== PackageManager.PERMISSION_GRANTED;
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index c7b84a3..1badf67 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9683,6 +9683,38 @@
"parameters_used_for_ntn_lte_signal_bar_int";
/**
+ * Indicating whether plmns associated with carrier satellite can be exposed to user when
+ * manually scanning available cellular network.
+ * If key is {@code true}, satellite plmn should not be exposed to user and should be
+ * automatically set, {@code false} otherwise. Default value is {@code true}.
+ *
+ * @hide
+ */
+ public static final String KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL =
+ "remove_satellite_plmn_in_manual_network_scan_bool";
+
+ /**
+ * An integer key holds the time interval for refreshing or re-querying the satellite
+ * entitlement status from the entitlement server to ensure it is the latest.
+ *
+ * The default value is 30 days (1 month).
+ */
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ public static final String KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT =
+ "satellite_entitlement_status_refresh_days_int";
+
+ /**
+ * This configuration enables device to query the entitlement server to get the satellite
+ * configuration.
+ * This will need agreement the carrier before enabling this flag.
+ *
+ * The default value is false.
+ */
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ public static final String KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL =
+ "satellite_entitlement_supported_bool";
+
+ /**
* Indicating whether DUN APN should be disabled when the device is roaming. In that case,
* the default APN (i.e. internet) will be used for tethering.
*
@@ -10787,6 +10819,9 @@
});
sDefaults.putInt(KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT,
CellSignalStrengthLte.USE_RSRP);
+ sDefaults.putBoolean(KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL, true);
+ sDefaults.putInt(KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT, 30);
+ sDefaults.putBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, false);
sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false);
sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, "");
sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false);
@@ -10955,6 +10990,9 @@
* @return A {@link PersistableBundle} containing the config for the given subId, or default
* values for an invalid subId.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
+ *
* @deprecated Use {@link #getConfigForSubId(int, String...)} instead.
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@@ -11002,6 +11040,9 @@
* @return A {@link PersistableBundle} with key/value mapping for the specified configuration
* on success, or an empty (but never null) bundle on failure (for example, when the calling app
* has no permission).
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@RequiresPermission(anyOf = {
Manifest.permission.READ_PHONE_STATE,
@@ -11047,6 +11088,9 @@
* @param overrideValues Key-value pairs of the values that are to be overridden. If set to
* {@code null}, this will remove all previous overrides and set the
* carrier configuration back to production values.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@@ -11104,6 +11148,10 @@
*
* @see #getConfigForSubId
* @see #getConfig(String...)
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
+ *
* @deprecated use {@link #getConfig(String...)} instead.
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@@ -11138,6 +11186,9 @@
* configs on success, or an empty (but never null) bundle on failure.
* @see #getConfigForSubId(int, String...)
* @see SubscriptionManager#getDefaultSubscriptionId()
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@RequiresPermission(anyOf = {
Manifest.permission.READ_PHONE_STATE,
@@ -11189,6 +11240,9 @@
*
* <p>This method returns before the reload has completed, and {@link
* android.service.carrier.CarrierService#onLoadConfig} will be called from an arbitrary thread.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@@ -11212,6 +11266,8 @@
* <p>Depending on simState, the config may be cleared or loaded from config app. This is only
* used by SubscriptionInfoUpdater.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@SystemApi
@@ -11234,6 +11290,8 @@
* Gets the package name for a default carrier service.
* @return the package name for a default carrier service; empty string if not available.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@NonNull
@@ -11287,6 +11345,9 @@
* @param subId the subscription ID, normally obtained from {@link SubscriptionManager}.
*
* @see #getConfigForSubId
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(Manifest.permission.READ_PHONE_STATE)
diff --git a/telephony/java/android/telephony/CarrierInfo.java b/telephony/java/android/telephony/CarrierInfo.java
new file mode 100644
index 0000000..da77a45
--- /dev/null
+++ b/telephony/java/android/telephony/CarrierInfo.java
@@ -0,0 +1,236 @@
+/*
+ * 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.telephony;
+
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.telephony.Rlog;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * CarrierInfo that is used to represent the carrier lock information details.
+ *
+ * @hide
+ */
+public final class CarrierInfo implements Parcelable {
+
+ /**
+ * Used to create a {@link CarrierInfo} from a {@link Parcel}.
+ *
+ * @hide
+ */
+ public static final @android.annotation.NonNull Creator<CarrierInfo> CREATOR =
+ new Creator<CarrierInfo>() {
+ /**
+ * Create a new instance of the Parcelable class, instantiating it
+ * from the given Parcel whose data had previously been written by
+ * {@link Parcelable#writeToParcel Parcelable.writeToParcel()}.
+ *
+ * @param source The Parcel to read the object's data from.
+ * @return Returns a new instance of the Parcelable class.
+ */
+ @Override
+ public CarrierInfo createFromParcel(Parcel source) {
+ return new CarrierInfo(source);
+ }
+
+ /**
+ * Create a new array of the Parcelable class.
+ *
+ * @param size Size of the array.
+ * @return Returns an array of the Parcelable class, with every entry
+ * initialized to null.
+ */
+ @Override
+ public CarrierInfo[] newArray(int size) {
+ return new CarrierInfo[size];
+ }
+
+ };
+ @NonNull
+ private String mMcc;
+ @NonNull
+ private String mMnc;
+ @Nullable
+ private String mSpn;
+ @Nullable
+ private String mGid1;
+ @Nullable
+ private String mGid2;
+ @Nullable
+ private String mImsiPrefix;
+ /** Ehplmn is String combination of MCC,MNC */
+ @Nullable
+ private List<String> mEhplmn;
+ @Nullable
+ private String mIccid;
+ @Nullable
+ private String mImpi;
+
+ /** @hide */
+ @NonNull
+ public String getMcc() {
+ return mMcc;
+ }
+
+ /** @hide */
+ @NonNull
+ public String getMnc() {
+ return mMnc;
+ }
+
+ /** @hide */
+ @Nullable
+ public String getSpn() {
+ return mSpn;
+ }
+
+ /** @hide */
+ @Nullable
+ public String getGid1() {
+ return mGid1;
+ }
+
+ /** @hide */
+ @Nullable
+ public String getGid2() {
+ return mGid2;
+ }
+
+ /** @hide */
+ @Nullable
+ public String getImsiPrefix() {
+ return mImsiPrefix;
+ }
+
+ /** @hide */
+ @Nullable
+ public String getIccid() {
+ return mIccid;
+ }
+
+ /** @hide */
+ @Nullable
+ public String getImpi() {
+ return mImpi;
+ }
+
+ /**
+ * Returns the list of EHPLMN.
+ *
+ * @return List of String that represent Ehplmn.
+ * @hide
+ */
+ @NonNull
+ public List<String> getEhplmn() {
+ return mEhplmn;
+ }
+
+ /** @hide */
+ public CarrierInfo(@NonNull String mcc, @NonNull String mnc, @Nullable String spn,
+ @Nullable String gid1, @Nullable String gid2, @Nullable String imsi,
+ @Nullable String iccid, @Nullable String impi, @Nullable List<String> plmnArrayList) {
+ mMcc = mcc;
+ mMnc = mnc;
+ mSpn = spn;
+ mGid1 = gid1;
+ mGid2 = gid2;
+ mImsiPrefix = imsi;
+ mIccid = iccid;
+ mImpi = impi;
+ mEhplmn = plmnArrayList;
+ }
+
+ /**
+ * Describe the kinds of special objects contained in this Parcelable
+ * instance's marshaled representation. For example, if the object will
+ * include a file descriptor in the output of {@link #writeToParcel(Parcel, int)},
+ * the return value of this method must include the
+ * {@link #CONTENTS_FILE_DESCRIPTOR} bit.
+ *
+ * @return a bitmask indicating the set of special object types marshaled
+ * by this Parcelable object instance.
+ * @hide
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Flatten this object in to a Parcel.
+ *
+ * @param dest The Parcel in which the object should be written.
+ * @param flags Additional flags about how the object should be written.
+ * May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
+ * @hide
+ */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString8(mMcc);
+ dest.writeString8(mMnc);
+ dest.writeString8(mSpn);
+ dest.writeString8(mGid1);
+ dest.writeString8(mGid2);
+ dest.writeString8(mImsiPrefix);
+ dest.writeString8(mIccid);
+ dest.writeString8(mImpi);
+ dest.writeStringList(mEhplmn);
+ }
+
+ /** @hide */
+ public CarrierInfo(Parcel in) {
+ mEhplmn = new ArrayList<String>();
+ mMcc = in.readString8();
+ mMnc = in.readString8();
+ mSpn = in.readString8();
+ mGid1 = in.readString8();
+ mGid2 = in.readString8();
+ mImsiPrefix = in.readString8();
+ mIccid = in.readString8();
+ mImpi = in.readString8();
+ in.readStringList(mEhplmn);
+ }
+
+
+ /** @hide */
+ @android.annotation.NonNull
+ @Override
+ public String toString() {
+ return "CarrierInfo MCC = " + mMcc + " MNC = " + mMnc + " SPN = " + mSpn + " GID1 = "
+ + mGid1 + " GID2 = " + mGid2 + " IMSI = " + getPrintableImsi() + " ICCID = "
+ + SubscriptionInfo.getPrintableId(mIccid) + " IMPI = " + mImpi + " EHPLMN = [ "
+ + getEhplmn_toString() + " ]";
+ }
+
+ private String getEhplmn_toString() {
+ return String.join(" ", mEhplmn);
+ }
+
+ private String getPrintableImsi() {
+ boolean enablePiiLog = Rlog.isLoggable("CarrierInfo", Log.VERBOSE);
+ return ((mImsiPrefix != null && mImsiPrefix.length() > 6) ? mImsiPrefix.substring(0, 6)
+ + Rlog.pii(enablePiiLog, mImsiPrefix.substring(6)) : mImsiPrefix);
+ }
+}
diff --git a/telephony/java/android/telephony/CarrierRestrictionRules.java b/telephony/java/android/telephony/CarrierRestrictionRules.java
index cc768bc..2b0d626 100644
--- a/telephony/java/android/telephony/CarrierRestrictionRules.java
+++ b/telephony/java/android/telephony/CarrierRestrictionRules.java
@@ -84,13 +84,75 @@
/** The same configuration is applied to all SIM slots independently. */
public static final int MULTISIM_POLICY_NONE = 0;
- /** Any SIM card can be used as far as one SIM card matching the configuration is present. */
+ /**
+ * Indicates that any SIM card can be used as far as one valid card is present in the device.
+ * For the modem, a SIM card is valid when its content (i.e. MCC, MNC, GID, SPN) matches the
+ * carrier restriction configuration.
+ */
public static final int MULTISIM_POLICY_ONE_VALID_SIM_MUST_BE_PRESENT = 1;
+ /**
+ * Indicates that the SIM lock policy applies uniformly to all sim slots.
+ * @hide
+ */
+ public static final int MULTISIM_POLICY_APPLY_TO_ALL_SLOTS = 2;
+
+ /**
+ * The SIM lock configuration applies exclusively to sim slot 1, leaving
+ * all other sim slots unlocked irrespective of the SIM card in slot 1
+ * @hide
+ */
+ public static final int MULTISIM_POLICY_APPLY_TO_ONLY_SLOT_1 = 3;
+
+ /**
+ * Valid sim cards must be present on sim slot1 in order
+ * to use other sim slots.
+ * @hide
+ */
+ public static final int MULTISIM_POLICY_VALID_SIM_MUST_PRESENT_ON_SLOT_1 = 4;
+
+ /**
+ * Valid sim card must be present on slot1 and it must be in full service
+ * in order to use other sim slots.
+ * @hide
+ */
+ public static final int MULTISIM_POLICY_ACTIVE_SERVICE_ON_SLOT_1_TO_UNBLOCK_OTHER_SLOTS = 5;
+
+ /**
+ * Valid sim card be present on any slot and it must be in full service
+ * in order to use other sim slots.
+ * @hide
+ */
+ public static final int MULTISIM_POLICY_ACTIVE_SERVICE_ON_ANY_SLOT_TO_UNBLOCK_OTHER_SLOTS = 6;
+
+ /**
+ * Valid sim cards must be present on all slots. If any SIM cards become
+ * invalid then device would set other SIM cards as invalid as well.
+ * @hide
+ */
+ public static final int MULTISIM_POLICY_ALL_SIMS_MUST_BE_VALID = 7;
+
+ /**
+ * In case there is no match policy listed above.
+ * @hide
+ */
+ public static final int MULTISIM_POLICY_SLOT_POLICY_OTHER = 8;
+
+
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = "MULTISIM_POLICY_",
- value = {MULTISIM_POLICY_NONE, MULTISIM_POLICY_ONE_VALID_SIM_MUST_BE_PRESENT})
+ value = {MULTISIM_POLICY_NONE,
+ MULTISIM_POLICY_ONE_VALID_SIM_MUST_BE_PRESENT,
+ MULTISIM_POLICY_APPLY_TO_ALL_SLOTS,
+ MULTISIM_POLICY_APPLY_TO_ONLY_SLOT_1,
+ MULTISIM_POLICY_VALID_SIM_MUST_PRESENT_ON_SLOT_1,
+ MULTISIM_POLICY_ACTIVE_SERVICE_ON_SLOT_1_TO_UNBLOCK_OTHER_SLOTS,
+ MULTISIM_POLICY_ACTIVE_SERVICE_ON_ANY_SLOT_TO_UNBLOCK_OTHER_SLOTS,
+ MULTISIM_POLICY_ALL_SIMS_MUST_BE_VALID,
+ MULTISIM_POLICY_SLOT_POLICY_OTHER
+ })
public @interface MultiSimPolicy {}
/** @hide */
@@ -104,6 +166,8 @@
private List<CarrierIdentifier> mAllowedCarriers;
private List<CarrierIdentifier> mExcludedCarriers;
+ private List<CarrierInfo> mAllowedCarrierInfo;
+ private List<CarrierInfo> mExcludedCarrierInfo;
@CarrierRestrictionDefault
private int mCarrierRestrictionDefault;
@MultiSimPolicy
@@ -114,6 +178,8 @@
private CarrierRestrictionRules() {
mAllowedCarriers = new ArrayList<CarrierIdentifier>();
mExcludedCarriers = new ArrayList<CarrierIdentifier>();
+ mAllowedCarrierInfo = new ArrayList<CarrierInfo>();
+ mExcludedCarrierInfo = new ArrayList<CarrierInfo>();
mCarrierRestrictionDefault = CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED;
mMultiSimPolicy = MULTISIM_POLICY_NONE;
mCarrierRestrictionStatus = TelephonyManager.CARRIER_RESTRICTION_STATUS_UNKNOWN;
@@ -122,12 +188,17 @@
private CarrierRestrictionRules(Parcel in) {
mAllowedCarriers = new ArrayList<CarrierIdentifier>();
mExcludedCarriers = new ArrayList<CarrierIdentifier>();
-
+ mAllowedCarrierInfo = new ArrayList<CarrierInfo>();
+ mExcludedCarrierInfo = new ArrayList<CarrierInfo>();
in.readTypedList(mAllowedCarriers, CarrierIdentifier.CREATOR);
in.readTypedList(mExcludedCarriers, CarrierIdentifier.CREATOR);
mCarrierRestrictionDefault = in.readInt();
mMultiSimPolicy = in.readInt();
mCarrierRestrictionStatus = in.readInt();
+ if (Flags.carrierRestrictionRulesEnhancement()) {
+ in.readTypedList(mAllowedCarrierInfo, CarrierInfo.CREATOR);
+ in.readTypedList(mExcludedCarrierInfo, CarrierInfo.CREATOR);
+ }
}
/**
@@ -165,6 +236,25 @@
}
/**
+ * Retrieves list of excluded carrierInfos
+ *
+ * @return the list of excluded carrierInfos
+ * @hide
+ */
+ public @NonNull List<CarrierInfo> getExcludedCarriersInfoList() {
+ return mExcludedCarrierInfo;
+ }
+
+ /**
+ * Retrieves list of excluded carrierInfos
+ *
+ * @return the list of excluded carrierInfos
+ * @hide
+ */
+ public @NonNull List<CarrierInfo> getAllowedCarriersInfoList() {
+ return mAllowedCarrierInfo;
+ }
+ /**
* Retrieves the default behavior of carrier restrictions
*/
public @CarrierRestrictionDefault int getDefaultCarrierRestriction() {
@@ -326,6 +416,10 @@
out.writeInt(mCarrierRestrictionDefault);
out.writeInt(mMultiSimPolicy);
out.writeInt(mCarrierRestrictionStatus);
+ if (Flags.carrierRestrictionRulesEnhancement()) {
+ out.writeTypedList(mAllowedCarrierInfo);
+ out.writeTypedList(mExcludedCarrierInfo);
+ }
}
/**
@@ -357,7 +451,16 @@
public String toString() {
return "CarrierRestrictionRules(allowed:" + mAllowedCarriers + ", excluded:"
+ mExcludedCarriers + ", default:" + mCarrierRestrictionDefault
- + ", multisim policy:" + mMultiSimPolicy + ")";
+ + ", multisim policy:" + mMultiSimPolicy + getCarrierInfoList() + ")";
+ }
+
+ private String getCarrierInfoList() {
+ if (Flags.carrierRestrictionRulesEnhancement()) {
+ return ", allowedCarrierInfoList:" + mAllowedCarrierInfo
+ + ", excludedCarrierInfoList:" + mExcludedCarrierInfo;
+ } else {
+ return "";
+ }
}
/**
@@ -382,6 +485,12 @@
mRules.mAllowedCarriers.clear();
mRules.mExcludedCarriers.clear();
mRules.mCarrierRestrictionDefault = CARRIER_RESTRICTION_DEFAULT_ALLOWED;
+ if (Flags.carrierRestrictionRulesEnhancement()) {
+ mRules.mCarrierRestrictionStatus =
+ TelephonyManager.CARRIER_RESTRICTION_STATUS_NOT_RESTRICTED;
+ mRules.mAllowedCarrierInfo.clear();
+ mRules.mExcludedCarrierInfo.clear();
+ }
return this;
}
@@ -439,5 +548,29 @@
mRules.mCarrierRestrictionStatus = carrierRestrictionStatus;
return this;
}
+
+ /**
+ * Set list of allowed carrierInfo
+ *
+ * @param allowedCarrierInfo list of allowed CarrierInfo
+ * @hide
+ */
+ public @NonNull Builder setAllowedCarrierInfo(
+ @NonNull List<CarrierInfo> allowedCarrierInfo) {
+ mRules.mAllowedCarrierInfo = new ArrayList<CarrierInfo>(allowedCarrierInfo);
+ return this;
+ }
+
+ /**
+ * Set list of allowed carrierInfo
+ *
+ * @param excludedCarrierInfo list of allowed CarrierInfo
+ * @hide
+ */
+ public @NonNull Builder setExcludedCarrierInfo(
+ @NonNull List<CarrierInfo> excludedCarrierInfo) {
+ mRules.mExcludedCarrierInfo = new ArrayList<CarrierInfo>(excludedCarrierInfo);
+ return this;
+ }
}
}
diff --git a/telephony/java/android/telephony/SecurityAlgorithmUpdate.java b/telephony/java/android/telephony/SecurityAlgorithmUpdate.java
index c902016..57209eb 100644
--- a/telephony/java/android/telephony/SecurityAlgorithmUpdate.java
+++ b/telephony/java/android/telephony/SecurityAlgorithmUpdate.java
@@ -129,11 +129,15 @@
public static final int CONNECTION_EVENT_NAS_SIGNALLING_LTE = 4;
public static final int CONNECTION_EVENT_AS_SIGNALLING_LTE = 5;
public static final int CONNECTION_EVENT_VOLTE_SIP = 6;
- public static final int CONNECTION_EVENT_VOLTE_RTP = 7;
- public static final int CONNECTION_EVENT_NAS_SIGNALLING_5G = 8;
- public static final int CONNECTION_EVENT_AS_SIGNALLING_5G = 9;
- public static final int CONNECTION_EVENT_VONR_SIP = 10;
- public static final int CONNECTION_EVENT_VONR_RTP = 11;
+ public static final int CONNECTION_EVENT_VOLTE_SIP_SOS = 7;
+ public static final int CONNECTION_EVENT_VOLTE_RTP = 8;
+ public static final int CONNECTION_EVENT_VOLTE_RTP_SOS = 9;
+ public static final int CONNECTION_EVENT_NAS_SIGNALLING_5G = 10;
+ public static final int CONNECTION_EVENT_AS_SIGNALLING_5G = 11;
+ public static final int CONNECTION_EVENT_VONR_SIP = 12;
+ public static final int CONNECTION_EVENT_VONR_SIP_SOS = 13;
+ public static final int CONNECTION_EVENT_VONR_RTP = 14;
+ public static final int CONNECTION_EVENT_VONR_RTP_SOS = 15;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -141,9 +145,11 @@
CONNECTION_EVENT_PS_SIGNALLING_GPRS, CONNECTION_EVENT_CS_SIGNALLING_3G,
CONNECTION_EVENT_PS_SIGNALLING_3G, CONNECTION_EVENT_NAS_SIGNALLING_LTE,
CONNECTION_EVENT_AS_SIGNALLING_LTE, CONNECTION_EVENT_VOLTE_SIP,
- CONNECTION_EVENT_VOLTE_RTP, CONNECTION_EVENT_NAS_SIGNALLING_5G,
+ CONNECTION_EVENT_VOLTE_SIP_SOS, CONNECTION_EVENT_VOLTE_RTP,
+ CONNECTION_EVENT_VOLTE_RTP_SOS, CONNECTION_EVENT_NAS_SIGNALLING_5G,
CONNECTION_EVENT_AS_SIGNALLING_5G, CONNECTION_EVENT_VONR_SIP,
- CONNECTION_EVENT_VONR_RTP})
+ CONNECTION_EVENT_VONR_SIP_SOS, CONNECTION_EVENT_VONR_RTP,
+ CONNECTION_EVENT_VONR_RTP_SOS})
public @interface ConnectionEvent {
}
@@ -169,6 +175,7 @@
public static final int SECURITY_ALGORITHM_NEA1 = 56;
public static final int SECURITY_ALGORITHM_NEA2 = 57;
public static final int SECURITY_ALGORITHM_NEA3 = 58;
+ public static final int SECURITY_ALGORITHM_SIP_NO_IPSEC_CONFIG = 66;
public static final int SECURITY_ALGORITHM_IMS_NULL = 67;
public static final int SECURITY_ALGORITHM_SIP_NULL = 68;
public static final int SECURITY_ALGORITHM_AES_GCM = 69;
@@ -178,6 +185,7 @@
public static final int SECURITY_ALGORITHM_AES_EDE3_CBC = 73;
public static final int SECURITY_ALGORITHM_HMAC_SHA1_96 = 74;
public static final int SECURITY_ALGORITHM_HMAC_MD5_96 = 75;
+ public static final int SECURITY_ALGORITHM_RTP = 85;
public static final int SECURITY_ALGORITHM_SRTP_NULL = 86;
public static final int SECURITY_ALGORITHM_SRTP_AES_COUNTER = 87;
public static final int SECURITY_ALGORITHM_SRTP_AES_F8 = 88;
@@ -199,15 +207,16 @@
SECURITY_ALGORITHM_UEA2, SECURITY_ALGORITHM_EEA0, SECURITY_ALGORITHM_EEA1,
SECURITY_ALGORITHM_EEA2, SECURITY_ALGORITHM_EEA3, SECURITY_ALGORITHM_NEA0,
SECURITY_ALGORITHM_NEA1, SECURITY_ALGORITHM_NEA2, SECURITY_ALGORITHM_NEA3,
- SECURITY_ALGORITHM_IMS_NULL, SECURITY_ALGORITHM_SIP_NULL, SECURITY_ALGORITHM_AES_GCM,
+ SECURITY_ALGORITHM_SIP_NO_IPSEC_CONFIG, SECURITY_ALGORITHM_IMS_NULL,
+ SECURITY_ALGORITHM_SIP_NULL, SECURITY_ALGORITHM_AES_GCM,
SECURITY_ALGORITHM_AES_GMAC, SECURITY_ALGORITHM_AES_CBC,
SECURITY_ALGORITHM_DES_EDE3_CBC, SECURITY_ALGORITHM_AES_EDE3_CBC,
SECURITY_ALGORITHM_HMAC_SHA1_96, SECURITY_ALGORITHM_HMAC_MD5_96,
- SECURITY_ALGORITHM_SRTP_NULL, SECURITY_ALGORITHM_SRTP_AES_COUNTER,
- SECURITY_ALGORITHM_SRTP_AES_F8, SECURITY_ALGORITHM_SRTP_HMAC_SHA1,
- SECURITY_ALGORITHM_ENCR_AES_GCM_16, SECURITY_ALGORITHM_ENCR_AES_CBC,
- SECURITY_ALGORITHM_AUTH_HMAC_SHA2_256_128, SECURITY_ALGORITHM_UNKNOWN,
- SECURITY_ALGORITHM_OTHER, SECURITY_ALGORITHM_ORYX})
+ SECURITY_ALGORITHM_RTP, SECURITY_ALGORITHM_SRTP_NULL,
+ SECURITY_ALGORITHM_SRTP_AES_COUNTER, SECURITY_ALGORITHM_SRTP_AES_F8,
+ SECURITY_ALGORITHM_SRTP_HMAC_SHA1, SECURITY_ALGORITHM_ENCR_AES_GCM_16,
+ SECURITY_ALGORITHM_ENCR_AES_CBC, SECURITY_ALGORITHM_AUTH_HMAC_SHA2_256_128,
+ SECURITY_ALGORITHM_UNKNOWN, SECURITY_ALGORITHM_OTHER, SECURITY_ALGORITHM_ORYX})
public @interface SecurityAlgorithm {
}
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 5615602..a5c6d57 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1315,7 +1315,7 @@
* A source of phone number: the EF-MSISDN (see 3GPP TS 31.102),
* or EF-MDN for CDMA (see 3GPP2 C.P0065-B), from UICC application.
*
- * <p>The availability and a of the number depends on the carrier.
+ * <p>The availability and accuracy of the number depends on the carrier.
* The number may be updated by over-the-air update to UICC applications
* from the carrier, or by other means with physical access to the SIM.
*/
@@ -1557,12 +1557,21 @@
* caller can see all subscription across user profiles as it does today today even if it's
* {@code false}.
*/
- private boolean mIsForAllUserProfiles = false;
+ private final boolean mIsForAllUserProfiles;
/** @hide */
@UnsupportedAppUsage
public SubscriptionManager(Context context) {
- if (DBG) logd("SubscriptionManager created");
+ this(context, false /*isForAllUserProfiles*/);
+ }
+
+ /** Constructor */
+ private SubscriptionManager(Context context, boolean isForAllUserProfiles) {
+ if (DBG) {
+ logd("SubscriptionManager created "
+ + (isForAllUserProfiles ? "for all user profile" : ""));
+ }
+ mIsForAllUserProfiles = isForAllUserProfiles;
mContext = context;
}
@@ -1998,7 +2007,7 @@
}
/**
- * Convert this subscription manager instance into one that can see all subscriptions across
+ * Create a new subscription manager instance that can see all subscriptions across
* user profiles.
*
* @return a SubscriptionManager that can see all subscriptions regardless its user profile
@@ -2008,13 +2017,12 @@
* @see #getActiveSubscriptionInfoCount
* @see UserHandle
*/
- @FlaggedApi(Flags.FLAG_WORK_PROFILE_API_SPLIT)
+ @FlaggedApi(Flags.FLAG_ENFORCE_SUBSCRIPTION_USER_FILTER)
// @RequiresPermission(TODO(b/308809058))
// The permission check for accessing all subscriptions will be enforced upon calling the
// individual APIs linked above.
@NonNull public SubscriptionManager createForAllUserProfiles() {
- mIsForAllUserProfiles = true;
- return this;
+ return new SubscriptionManager(mContext, true/*isForAllUserProfiles*/);
}
/**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 1b47dfe..9ec5f7a 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -6445,10 +6445,6 @@
* targeting API level 31+.
*
* @return the current call state.
- *
- * @throws UnsupportedOperationException If the device does not have
- * {@link PackageManager#FEATURE_TELECOM}.
- *
* @deprecated Use {@link #getCallStateForSubscription} to retrieve the call state for a
* specific telephony subscription (which allows carrier privileged apps),
* {@link TelephonyCallback.CallStateListener} for real-time call state updates, or
@@ -6456,7 +6452,6 @@
* device.
*/
@RequiresPermission(value = android.Manifest.permission.READ_PHONE_STATE, conditional = true)
- @RequiresFeature(PackageManager.FEATURE_TELECOM)
@Deprecated
public @CallState int getCallState() {
if (mContext != null) {
@@ -10782,9 +10777,7 @@
}
/**
- * @throws UnsupportedOperationException If the device does not have
- * {@link PackageManager#FEATURE_TELECOM}.
- * @deprecated Use {@link android.telecom.TelecomManager#isInCall} instead
+ * @deprecated Use {@link android.telecom.TelecomManager#isInCall} instead
* @hide
*/
@Deprecated
@@ -10793,15 +10786,12 @@
android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
android.Manifest.permission.READ_PHONE_STATE
})
- @RequiresFeature(PackageManager.FEATURE_TELECOM)
public boolean isOffhook() {
TelecomManager tm = (TelecomManager) mContext.getSystemService(TELECOM_SERVICE);
return tm.isInCall();
}
/**
- * @throws UnsupportedOperationException If the device does not have
- * {@link PackageManager#FEATURE_TELECOM}.
* @deprecated Use {@link android.telecom.TelecomManager#isRinging} instead
* @hide
*/
@@ -10811,15 +10801,12 @@
android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
android.Manifest.permission.READ_PHONE_STATE
})
- @RequiresFeature(PackageManager.FEATURE_TELECOM)
public boolean isRinging() {
TelecomManager tm = (TelecomManager) mContext.getSystemService(TELECOM_SERVICE);
return tm.isRinging();
}
/**
- * @throws UnsupportedOperationException If the device does not have
- * {@link PackageManager#FEATURE_TELECOM}.
* @deprecated Use {@link android.telecom.TelecomManager#isInCall} instead
* @hide
*/
@@ -10829,7 +10816,6 @@
android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
android.Manifest.permission.READ_PHONE_STATE
})
- @RequiresFeature(PackageManager.FEATURE_TELECOM)
public boolean isIdle() {
TelecomManager tm = (TelecomManager) mContext.getSystemService(TELECOM_SERVICE);
return !tm.isInCall();
@@ -12044,11 +12030,8 @@
*
* @return {@code true} if the device supports TTY mode, and {@code false} otherwise.
*
- * @throws UnsupportedOperationException If the device does not have
- * {@link PackageManager#FEATURE_TELECOM}.
*/
@Deprecated
- @RequiresFeature(PackageManager.FEATURE_TELECOM)
public boolean isTtyModeSupported() {
try {
TelecomManager telecomManager = null;
@@ -18500,8 +18483,8 @@
/**
* Get last known cell identity.
- * Require {@link android.Manifest.permission#ACCESS_FINE_LOCATION} and
- * com.android.phone.permission.ACCESS_LAST_KNOWN_CELL_ID, otherwise throws SecurityException.
+ * Require appropriate permissions, otherwise throws SecurityException.
+ *
* If there is current registered network this value will be same as the registered cell
* identity. If the device goes out of service the previous cell identity is cached and
* will be returned. If the cache age of the Cell identity is more than 24 hours
@@ -18509,6 +18492,8 @@
* @return last known cell identity {@CellIdentity}.
* @hide
*/
+ @SystemApi
+ @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
@RequiresPermission(allOf = {Manifest.permission.ACCESS_FINE_LOCATION,
"com.android.phone.permission.ACCESS_LAST_KNOWN_CELL_ID"})
public @Nullable CellIdentity getLastKnownCellIdentity() {
@@ -18586,13 +18571,11 @@
* an overall "device can make voice calls" boolean, they can use {@link
* ServiceState#getNetworkRegistrationInfo} to check CS registration state.
*
- * <p>TODO(b/215240050) In the future, this API will be removed and replaced with a new superset
- * API to disentangle the "true" {@link ServiceState} meaning of "this is the connection status
- * to the tower" from IMS registration state and over-the-top voice calling capabilities.
- *
* @hide
*/
@TestApi
+ @SystemApi
+ @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
@RequiresPermission(Manifest.permission.BIND_TELECOM_CONNECTION_SERVICE)
public void setVoiceServiceStateOverride(boolean hasService) {
try {
@@ -18923,6 +18906,62 @@
}
/**
+ * Enables or disables notifications sent when cellular null cipher or integrity algorithms
+ * are in use by the cellular modem.
+ *
+ * @throws IllegalStateException if the Telephony process is not currently available
+ * @throws SecurityException if the caller does not have the required privileges
+ * @throws UnsupportedOperationException if the modem does not support reporting on ciphering
+ * and integrity algorithms in use
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY)
+ @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+ @SystemApi
+ public void setEnableNullCipherNotifications(boolean enable) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ telephony.setEnableNullCipherNotifications(enable);
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ Rlog.e(TAG, "setEnableNullCipherNotifications RemoteException", ex);
+ ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get whether notifications are enabled for null cipher or integrity algorithms in use by the
+ * cellular modem.
+ *
+ * @throws IllegalStateException if the Telephony process is not currently available
+ * @throws SecurityException if the caller does not have the required privileges
+ * @throws UnsupportedOperationException if the modem does not support reporting on ciphering
+ * and integrity algorithms in use
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY)
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @SystemApi
+ public boolean isNullCipherNotificationsEnabled() {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony.isNullCipherNotificationsEnabled();
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ Rlog.e(TAG, "isNullCipherNotificationsEnabled RemoteException", ex);
+ ex.rethrowFromSystemServer();
+ }
+ return false;
+ }
+
+
+ /**
* Get current cell broadcast message identifier ranges.
*
* @throws SecurityException if the caller does not have the required permission
diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java
index 9dd83d1..b6f9e1f 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -451,7 +451,7 @@
/**
* Return the network validation status that was initiated by {@link
- * DataService.DataServiceProvider#requestValidation}
+ * DataService.DataServiceProvider#requestNetworkValidation}
*
* @return The network validation status of data connection.
*/
@@ -931,7 +931,7 @@
/**
* Set the network validation status that corresponds to the state of the network validation
- * request started by {@link DataService.DataServiceProvider#requestValidation}
+ * request started by {@link DataService.DataServiceProvider#requestNetworkValidation}
*
* @param status The network validation status.
* @return The same instance of the builder.
diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java
index 80e91a3..f04e1c9 100644
--- a/telephony/java/android/telephony/data/DataService.java
+++ b/telephony/java/android/telephony/data/DataService.java
@@ -415,13 +415,13 @@
* request validation to the DataService and checks if the request has been submitted.
*/
@FlaggedApi(Flags.FLAG_NETWORK_VALIDATION)
- public void requestValidation(int cid,
+ public void requestNetworkValidation(int cid,
@NonNull @CallbackExecutor Executor executor,
@NonNull @DataServiceCallback.ResultCode Consumer<Integer> resultCodeCallback) {
Objects.requireNonNull(executor, "executor cannot be null");
Objects.requireNonNull(resultCodeCallback, "resultCodeCallback cannot be null");
- Log.d(TAG, "requestValidation: " + cid);
+ Log.d(TAG, "requestNetworkValidation: " + cid);
// The default implementation is to return unsupported.
executor.execute(() -> resultCodeCallback
@@ -741,7 +741,7 @@
case DATA_SERVICE_REQUEST_VALIDATION:
if (serviceProvider == null) break;
ValidationRequest validationRequest = (ValidationRequest) message.obj;
- serviceProvider.requestValidation(
+ serviceProvider.requestNetworkValidation(
validationRequest.cid,
validationRequest.executor,
FunctionalUtils
@@ -924,9 +924,10 @@
}
@Override
- public void requestValidation(int slotIndex, int cid, IIntegerConsumer resultCodeCallback) {
+ public void requestNetworkValidation(int slotIndex, int cid,
+ IIntegerConsumer resultCodeCallback) {
if (resultCodeCallback == null) {
- loge("requestValidation: resultCodeCallback is null");
+ loge("requestNetworkValidation: resultCodeCallback is null");
return;
}
ValidationRequest validationRequest =
diff --git a/telephony/java/android/telephony/data/IDataService.aidl b/telephony/java/android/telephony/data/IDataService.aidl
index 15f8881..c36c302 100644
--- a/telephony/java/android/telephony/data/IDataService.aidl
+++ b/telephony/java/android/telephony/data/IDataService.aidl
@@ -48,5 +48,5 @@
void cancelHandover(int slotId, int cid, IDataServiceCallback callback);
void registerForUnthrottleApn(int slotIndex, IDataServiceCallback callback);
void unregisterForUnthrottleApn(int slotIndex, IDataServiceCallback callback);
- void requestValidation(int slotId, int cid, IIntegerConsumer callback);
+ void requestNetworkValidation(int slotId, int cid, IIntegerConsumer callback);
}
diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java
index 4c37f7d..b84ff29 100644
--- a/telephony/java/android/telephony/ims/ImsService.java
+++ b/telephony/java/android/telephony/ims/ImsService.java
@@ -16,6 +16,7 @@
package android.telephony.ims;
+import android.annotation.FlaggedApi;
import android.annotation.LongDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -46,6 +47,7 @@
import com.android.ims.internal.IImsFeatureStatusCallback;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.Flags;
import com.android.internal.telephony.util.TelephonyUtils;
import java.lang.annotation.Retention;
@@ -152,12 +154,36 @@
public static final long CAPABILITY_TERMINAL_BASED_CALL_WAITING = 1 << 2;
/**
+ * This ImsService supports the capability to manage calls on multiple subscriptions at the same
+ * time.
+ * <p>
+ * When set, this ImsService supports managing calls on multiple subscriptions at the same time
+ * for all WLAN network configurations. Telephony will allow new outgoing/incoming IMS calls to
+ * be set up on other subscriptions while there is an ongoing call. The ImsService must also
+ * support managing calls on WWAN + WWAN configurations whenever the modem also reports
+ * simultaneous calling availability, which can be listened to using the
+ * {@link android.telephony.TelephonyCallback.SimultaneousCellularCallingSupportListener} API.
+ * Telephony will only allow additional ongoing/incoming IMS calls on another subscription to be
+ * set up on WWAN + WWAN configurations when the modem reports that simultaneous cellular
+ * calling is allowed at the current time on both subscriptions where there are ongoing calls.
+ * <p>
+ * When unset (default), this ImsService can not support calls on multiple subscriptions at the
+ * same time for any WLAN or WWAN configurations, so pending outgoing call placed on another
+ * cellular subscription while there is an ongoing call will be cancelled by Telephony.
+ * Similarly, any incoming call notification on another cellular subscription while there is an
+ * ongoing call will be rejected.
+ * @hide TODO: move this to system API when we have a backing implementation + CTS testing
+ */
+ @FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS)
+ public static final long CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING = 1 << 3;
+
+ /**
* Used for internal correctness checks of capabilities set by the ImsService implementation and
* tracks the index of the largest defined flag in the capabilities long.
* @hide
*/
public static final long CAPABILITY_MAX_INDEX =
- Long.numberOfTrailingZeros(CAPABILITY_TERMINAL_BASED_CALL_WAITING);
+ Long.numberOfTrailingZeros(CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING);
/**
* @hide
diff --git a/telephony/java/android/telephony/ims/RegistrationManager.java b/telephony/java/android/telephony/ims/RegistrationManager.java
index 54ceaed..9f83da9 100644
--- a/telephony/java/android/telephony/ims/RegistrationManager.java
+++ b/telephony/java/android/telephony/ims/RegistrationManager.java
@@ -84,7 +84,7 @@
SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK,
SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK_WITH_TIMEOUT,
SUGGESTED_ACTION_TRIGGER_RAT_BLOCK,
- SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCK
+ SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCKS
})
@Retention(RetentionPolicy.SOURCE)
public @interface SuggestedAction {}
@@ -116,9 +116,10 @@
/**
* Indicates that the IMS registration on current RAT failed multiple times.
- * The radio shall block the current RAT and search for other available RATs in the
- * background. If no other RAT is available that meets the carrier requirements, the
- * radio may remain on the current RAT for internet service. The radio clears all
+ * The radio shall block the {@link ImsRegistrationImplBase.ImsRegistrationTech}
+ * included with this and search for other available RATs in the background.
+ * If no other RAT is available that meets the carrier requirements, the
+ * radio may remain on the blocked RAT for internet service. The radio clears all
* RATs marked as unavailable if the IMS service is registered to the carrier network.
* @hide
*/
@@ -133,7 +134,7 @@
*/
@SystemApi
@FlaggedApi(Flags.FLAG_ADD_RAT_RELATED_SUGGESTED_ACTION_TO_IMS_REGISTRATION)
- int SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCK = 4;
+ int SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCKS = 4;
/**@hide*/
// Translate ImsRegistrationImplBase API to new AccessNetworkConstant because WLAN
diff --git a/telephony/java/android/telephony/satellite/AntennaPosition.java b/telephony/java/android/telephony/satellite/AntennaPosition.java
index 8842886..d6440fc 100644
--- a/telephony/java/android/telephony/satellite/AntennaPosition.java
+++ b/telephony/java/android/telephony/satellite/AntennaPosition.java
@@ -35,10 +35,10 @@
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
public final class AntennaPosition implements Parcelable {
/** Antenna direction used for satellite communication. */
- @NonNull AntennaDirection mAntennaDirection;
+ @NonNull private AntennaDirection mAntennaDirection;
/** Enum corresponding to device hold position to be used by the end user. */
- @SatelliteManager.DeviceHoldPosition int mSuggestedHoldPosition;
+ @SatelliteManager.DeviceHoldPosition private int mSuggestedHoldPosition;
/**
* @hide
diff --git a/telephony/java/android/telephony/satellite/ISatelliteStateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteModemStateCallback.aidl
similarity index 94%
rename from telephony/java/android/telephony/satellite/ISatelliteStateCallback.aidl
rename to telephony/java/android/telephony/satellite/ISatelliteModemStateCallback.aidl
index cd9d81e..9ff73e2 100644
--- a/telephony/java/android/telephony/satellite/ISatelliteStateCallback.aidl
+++ b/telephony/java/android/telephony/satellite/ISatelliteModemStateCallback.aidl
@@ -20,7 +20,7 @@
* Interface for satellite state change callback.
* @hide
*/
-oneway interface ISatelliteStateCallback {
+oneway interface ISatelliteModemStateCallback {
/**
* Indicates that the satellite modem state has changed.
*
diff --git a/telephony/java/android/telephony/satellite/PointingInfo.java b/telephony/java/android/telephony/satellite/PointingInfo.java
index 022a856..9440b65 100644
--- a/telephony/java/android/telephony/satellite/PointingInfo.java
+++ b/telephony/java/android/telephony/satellite/PointingInfo.java
@@ -17,6 +17,7 @@
package android.telephony.satellite;
import android.annotation.FlaggedApi;
+import android.annotation.FloatRange;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
@@ -108,11 +109,19 @@
return sb.toString();
}
+ /**
+ * Returns the azimuth of the satellite, in degrees.
+ */
+ @FloatRange(from = -180, to = 180)
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
public float getSatelliteAzimuthDegrees() {
return mSatelliteAzimuthDegrees;
}
+ /**
+ * Returns the elevation of the satellite, in degrees.
+ */
+ @FloatRange(from = -90, to = 90)
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
public float getSatelliteElevationDegrees() {
return mSatelliteElevationDegrees;
diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
index b5763c3..8e79ca5 100644
--- a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
@@ -22,10 +22,15 @@
import com.android.internal.telephony.flags.Flags;
+import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
* A callback class for listening to satellite datagrams.
+ * {@link SatelliteDatagramCallback} is registered to telephony when an app which invokes
+ * {@link SatelliteManager#registerForSatelliteDatagram(Executor, SatelliteDatagramCallback)},
+ * and {@link #onSatelliteDatagramReceived(long, SatelliteDatagram, int, Consumer)} will be invoked
+ * when a new datagram is received from satellite.
*
* @hide
*/
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index e09bd20..70047a6 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -49,8 +49,10 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.time.Duration;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
+import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -75,8 +77,9 @@
private static final ConcurrentHashMap<SatelliteProvisionStateCallback,
ISatelliteProvisionStateCallback> sSatelliteProvisionStateCallbackMap =
new ConcurrentHashMap<>();
- private static final ConcurrentHashMap<SatelliteStateCallback, ISatelliteStateCallback>
- sSatelliteStateCallbackMap = new ConcurrentHashMap<>();
+ private static final ConcurrentHashMap<SatelliteModemStateCallback,
+ ISatelliteModemStateCallback>
+ sSatelliteModemStateCallbackMap = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<SatelliteTransmissionUpdateCallback,
ISatelliteTransmissionUpdateCallback> sSatelliteTransmissionUpdateCallbackMap =
new ConcurrentHashMap<>();
@@ -624,6 +627,11 @@
/**
* Request to get whether the satellite service is supported on the device.
*
+ * <p>
+ * Note: This API only checks whether the device supports the satellite feature. The result will
+ * not be affected by whether the device is provisioned.
+ * </p>
+ *
* @param executor The executor on which the callback will be called.
* @param callback The callback object to which the result will be delivered.
* If the request is successful, {@link OutcomeReceiver#onResult(Object)}
@@ -920,10 +928,19 @@
@FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1;
+ /**
+ * Satellite communication restricted by entitlement server. This can be triggered based on
+ * the EntitlementStatus value received from the entitlement server to enable or disable
+ * satellite.
+ */
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT = 2;
+
/** @hide */
@IntDef(prefix = "SATELLITE_COMMUNICATION_RESTRICTION_REASON_", value = {
SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER,
- SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION
+ SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION,
+ SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT
})
@Retention(RetentionPolicy.SOURCE)
public @interface SatelliteCommunicationRestrictionReason {}
@@ -1301,21 +1318,22 @@
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@SatelliteResult public int registerForSatelliteModemStateChanged(
@NonNull @CallbackExecutor Executor executor,
- @NonNull SatelliteStateCallback callback) {
+ @NonNull SatelliteModemStateCallback callback) {
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- ISatelliteStateCallback internalCallback = new ISatelliteStateCallback.Stub() {
+ ISatelliteModemStateCallback internalCallback =
+ new ISatelliteModemStateCallback.Stub() {
@Override
public void onSatelliteModemStateChanged(int state) {
executor.execute(() -> Binder.withCleanCallingIdentity(() ->
callback.onSatelliteModemStateChanged(state)));
}
};
- sSatelliteStateCallbackMap.put(callback, internalCallback);
+ sSatelliteModemStateCallbackMap.put(callback, internalCallback);
return telephony.registerForSatelliteModemStateChanged(mSubId, internalCallback);
} else {
throw new IllegalStateException("telephony service is null.");
@@ -1332,16 +1350,18 @@
* If callback was not registered before, the request will be ignored.
*
* @param callback The callback that was passed to
- * {@link #registerForSatelliteModemStateChanged(Executor, SatelliteStateCallback)}.
+ * {@link #registerForSatelliteModemStateChanged(Executor, SatelliteModemStateCallback)}.
*
* @throws SecurityException if the caller doesn't have required permission.
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
- public void unregisterForSatelliteModemStateChanged(@NonNull SatelliteStateCallback callback) {
+ public void unregisterForSatelliteModemStateChanged(
+ @NonNull SatelliteModemStateCallback callback) {
Objects.requireNonNull(callback);
- ISatelliteStateCallback internalCallback = sSatelliteStateCallbackMap.remove(callback);
+ ISatelliteModemStateCallback internalCallback = sSatelliteModemStateCallbackMap.remove(
+ callback);
try {
ITelephony telephony = getITelephony();
@@ -2133,6 +2153,35 @@
}
}
+ /**
+ * Get all satellite PLMNs for which attach is enable for carrier.
+ *
+ * @param subId subId The subscription ID of the carrier.
+ *
+ * @return List of plmn for carrier satellite service. If no plmn is available, empty list will
+ * be returned.
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ @NonNull public List<String> getAllSatellitePlmnsForCarrier(int subId) {
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ throw new IllegalArgumentException("Invalid subscription ID");
+ }
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony.getAllSatellitePlmnsForCarrier(subId);
+ } else {
+ throw new IllegalStateException("Telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("getAllSatellitePlmnsForCarrier() RemoteException: " + ex);
+ ex.rethrowFromSystemServer();
+ }
+ return new ArrayList<>();
+ }
+
private static ITelephony getITelephony() {
ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
.getTelephonyServiceManager()
diff --git a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteModemStateCallback.java
similarity index 96%
rename from telephony/java/android/telephony/satellite/SatelliteStateCallback.java
rename to telephony/java/android/telephony/satellite/SatelliteModemStateCallback.java
index bfe6e10..8d33c88 100644
--- a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteModemStateCallback.java
@@ -28,7 +28,7 @@
*/
@SystemApi
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
-public interface SatelliteStateCallback {
+public interface SatelliteModemStateCallback {
/**
* Called when satellite modem state changes.
* @param state The new satellite modem state.
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 9b5ee0c..acbf354 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -72,7 +72,7 @@
import android.telephony.satellite.ISatelliteDatagramCallback;
import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
import android.telephony.satellite.ISatelliteProvisionStateCallback;
-import android.telephony.satellite.ISatelliteStateCallback;
+import android.telephony.satellite.ISatelliteModemStateCallback;
import android.telephony.satellite.NtnSignalStrength;
import android.telephony.satellite.SatelliteCapabilities;
import android.telephony.satellite.SatelliteDatagram;
@@ -2896,7 +2896,7 @@
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- int registerForSatelliteModemStateChanged(int subId, ISatelliteStateCallback callback);
+ int registerForSatelliteModemStateChanged(int subId, ISatelliteModemStateCallback callback);
/**
* Unregisters for modem state changed from satellite modem.
@@ -2907,7 +2907,7 @@
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void unregisterForSatelliteModemStateChanged(int subId, ISatelliteStateCallback callback);
+ void unregisterForSatelliteModemStateChanged(int subId, ISatelliteModemStateCallback callback);
/**
* Register to receive incoming datagrams over satellite.
@@ -3230,4 +3230,45 @@
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)")
boolean isCellularIdentifierDisclosureNotificationsEnabled();
+
+ /**
+ * Enables or disables notifications sent when cellular null cipher or integrity algorithms
+ * are in use by the cellular modem.
+ *
+ * @throws IllegalStateException if the Telephony process is not currently available
+ * @throws SecurityException if the caller does not have the required privileges
+ * @throws UnsupportedOperationException if the modem does not support reporting on ciphering
+ * and integrity algorithms in use
+ * @hide
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.MODIFY_PHONE_STATE)")
+ void setEnableNullCipherNotifications(boolean enable);
+
+ /**
+ * Get whether notifications are enabled for null cipher or integrity algorithms in use by the
+ * cellular modem.
+ *
+ * @throws IllegalStateException if the Telephony process is not currently available
+ * @throws SecurityException if the caller does not have the required privileges
+ * @throws UnsupportedOperationException if the modem does not support reporting on ciphering
+ * and integrity algorithms in use
+ * @hide
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)")
+ boolean isNullCipherNotificationsEnabled();
+
+ /**
+ * Get the aggregated satellite plmn list. This API collects plmn data from multiple sources,
+ * including carrier config, entitlement server, and config update.
+ *
+ * @param subId subId The subscription ID of the carrier.
+ *
+ * @return List of plmns for carrier satellite service. If no plmn is available, empty list will
+ * be returned.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ List<String> getAllSatellitePlmnsForCarrier(int subId);
}
diff --git a/test-base/Android.bp b/test-base/Android.bp
index 527159a..70a9540 100644
--- a/test-base/Android.bp
+++ b/test-base/Android.bp
@@ -120,12 +120,13 @@
path: "src",
}
-// Make the current.txt available for use by the cts/tests/signature tests.
+// Make the current.txt available for use by the cts/tests/signature and /vendor tests.
// ========================================================================
filegroup {
name: "android-test-base-current.txt",
visibility: [
"//cts/tests/signature/api",
+ "//vendor:__subpackages__",
],
srcs: [
"api/current.txt",
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index 2ff7413..f37d2d1 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -77,12 +77,13 @@
auto_gen_config: true,
}
-// Make the current.txt available for use by the cts/tests/signature tests.
+// Make the current.txt available for use by the cts/tests/signature and /vendor tests.
// ========================================================================
filegroup {
name: "android-test-mock-current.txt",
visibility: [
"//cts/tests/signature/api",
+ "//vendor:__subpackages__",
],
srcs: [
"api/current.txt",
diff --git a/test-mock/src/android/test/mock/MockContext.java b/test-mock/src/android/test/mock/MockContext.java
index f5f9d97..cf38bea 100644
--- a/test-mock/src/android/test/mock/MockContext.java
+++ b/test-mock/src/android/test/mock/MockContext.java
@@ -822,12 +822,6 @@
throw new UnsupportedOperationException();
}
- /** {@hide} */
- @Override
- public int getUserId() {
- throw new UnsupportedOperationException();
- }
-
@Override
public Context createConfigurationContext(Configuration overrideConfiguration) {
throw new UnsupportedOperationException();
diff --git a/test-runner/Android.bp b/test-runner/Android.bp
index 13a5dac..21e09d3 100644
--- a/test-runner/Android.bp
+++ b/test-runner/Android.bp
@@ -79,12 +79,13 @@
],
}
-// Make the current.txt available for use by the cts/tests/signature tests.
+// Make the current.txt available for use by the cts/tests/signature and /vendor tests.
// ========================================================================
filegroup {
name: "android-test-runner-current.txt",
visibility: [
"//cts/tests/signature/api",
+ "//vendor:__subpackages__",
],
srcs: [
"api/current.txt",
diff --git a/tests/Camera2Tests/CameraToo/tests/Android.bp b/tests/Camera2Tests/CameraToo/tests/Android.bp
new file mode 100644
index 0000000..8339a2c
--- /dev/null
+++ b/tests/Camera2Tests/CameraToo/tests/Android.bp
@@ -0,0 +1,34 @@
+// 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 {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: [
+ "frameworks_base_license",
+ ],
+}
+
+android_test {
+ name: "CameraTooTests",
+ instrumentation_for: "CameraToo",
+ srcs: ["src/**/*.java"],
+ sdk_version: "current",
+ static_libs: [
+ "androidx.test.rules",
+ "mockito-target-minus-junit4",
+ ],
+ data: [
+ ":CameraToo",
+ ],
+}
diff --git a/tests/Camera2Tests/CameraToo/tests/Android.mk b/tests/Camera2Tests/CameraToo/tests/Android.mk
deleted file mode 100644
index dfa64f1..0000000
--- a/tests/Camera2Tests/CameraToo/tests/Android.mk
+++ /dev/null
@@ -1,28 +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.
-
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := CameraTooTests
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../NOTICE
-LOCAL_INSTRUMENTATION_FOR := CameraToo
-LOCAL_SDK_VERSION := current
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules mockito-target-minus-junit4
-
-include $(BUILD_PACKAGE)
diff --git a/tests/Camera2Tests/CameraToo/tests/AndroidTest.xml b/tests/Camera2Tests/CameraToo/tests/AndroidTest.xml
new file mode 100644
index 0000000..884c095
--- /dev/null
+++ b/tests/Camera2Tests/CameraToo/tests/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs CameraToo tests.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CameraTooTests.apk" />
+ <option name="test-file-name" value="CameraToo.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.example.android.camera2.cameratoo.tests" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
index 80c1e5be..217659e 100644
--- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
+++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
@@ -43,9 +43,7 @@
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Comparator;
import java.util.List;
-import java.util.Optional;
import java.util.stream.Collectors;
/**
@@ -178,8 +176,14 @@
this.deviceFrameRate = deviceFrameRate;
}
+ FrameRateTimeoutException(List<Float> expectedFrameRates, float deviceFrameRate) {
+ this.expectedFrameRates = expectedFrameRates;
+ this.deviceFrameRate = deviceFrameRate;
+ }
+
public float expectedFrameRate;
public float deviceFrameRate;
+ public List<Float> expectedFrameRates;
}
public enum Api {
@@ -502,31 +506,37 @@
}
private void waitForStableFrameRate(TestSurface... surfaces) throws InterruptedException {
- verifyCompatibleAndStableFrameRate(0, surfaces);
+ verifyFrameRates(List.of(), surfaces);
}
private void verifyExactAndStableFrameRate(
float expectedFrameRate,
TestSurface... surfaces) throws InterruptedException {
- verifyFrameRate(expectedFrameRate, false, surfaces);
+ verifyFrameRate(List.of(expectedFrameRate), false, surfaces);
}
private void verifyCompatibleAndStableFrameRate(
float expectedFrameRate,
TestSurface... surfaces) throws InterruptedException {
- verifyFrameRate(expectedFrameRate, true, surfaces);
+ verifyFrameRate(List.of(expectedFrameRate), true, surfaces);
}
- // Set expectedFrameRate to 0.0 to verify only stable frame rate.
- private void verifyFrameRate(
- float expectedFrameRate, boolean multiplesAllowed,
+ /** Verify stable frame rate at one of the expectedFrameRates. */
+ private void verifyFrameRates(List<Float> expectedFrameRates, TestSurface... surfaces)
+ throws InterruptedException {
+ verifyFrameRate(expectedFrameRates, true, surfaces);
+ }
+
+ // Set expectedFrameRates to empty to verify only stable frame rate.
+ private void verifyFrameRate(List<Float> expectedFrameRates, boolean multiplesAllowed,
TestSurface... surfaces) throws InterruptedException {
Log.i(TAG, "Verifying compatible and stable frame rate");
long nowNanos = System.nanoTime();
long gracePeriodEndTimeNanos =
nowNanos + FRAME_RATE_SWITCH_GRACE_PERIOD_SECONDS * 1_000_000_000L;
while (true) {
- if (expectedFrameRate > FRAME_RATE_TOLERANCE) { // expectedFrameRate > 0
+ if (expectedFrameRates.size() == 1) {
+ float expectedFrameRate = expectedFrameRates.get(0);
// Wait until we switch to a compatible frame rate.
Log.i(TAG,
String.format(
@@ -557,11 +567,25 @@
while (endTimeNanos > nowNanos) {
int numModeChangedEvents = mModeChangedEvents.size();
if (waitForEvents(endTimeNanos, surfaces)) {
- Log.i(TAG,
- String.format("Stable frame rate %.2f verified",
- multiplesAllowed ? mDisplayModeRefreshRate
- : mDisplayRefreshRate));
- return;
+ // Verify any expected frame rate since there are multiple that will suffice.
+ // Mainly to account for running tests on real devices, where other non-test
+ // layers may affect the outcome.
+ if (expectedFrameRates.size() > 1) {
+ for (float expectedFrameRate : expectedFrameRates) {
+ if (isFrameRateMultiple(mDisplayModeRefreshRate, expectedFrameRate)) {
+ return;
+ }
+ }
+ // The frame rate is stable but it is not one of the expected frame rates.
+ throw new FrameRateTimeoutException(
+ expectedFrameRates, mDisplayModeRefreshRate);
+ } else {
+ Log.i(TAG,
+ String.format("Stable frame rate %.2f verified",
+ multiplesAllowed ? mDisplayModeRefreshRate
+ : mDisplayRefreshRate));
+ return;
+ }
}
nowNanos = System.nanoTime();
if (mModeChangedEvents.size() > numModeChangedEvents) {
@@ -623,12 +647,28 @@
// caused the timeout failure. Wait for a bit to see if we get notified
// of a precondition violation, and if so, retry the test. Otherwise
// fail.
- assertTrue(String.format(
- "Timed out waiting for a stable and compatible frame"
- + " rate. expected=%.2f received=%.2f."
- + " Stack trace: " + stackTrace,
- exc.expectedFrameRate, exc.deviceFrameRate),
- waitForPreconditionViolation());
+ if (exc.expectedFrameRates.isEmpty()) {
+ assertTrue(
+ String.format(
+ "Timed out waiting for a stable and compatible"
+ + " frame rate."
+ + " expected=%.2f received=%.2f."
+ + " Stack trace: " + stackTrace,
+ exc.expectedFrameRate, exc.deviceFrameRate),
+ waitForPreconditionViolation());
+ } else {
+ assertTrue(
+ String.format(
+ "Timed out waiting for a stable and compatible"
+ + " frame rate."
+ + " expected={%.2f} received=%.2f."
+ + " Stack trace: " + stackTrace,
+ exc.expectedFrameRates.stream()
+ .map(Object::toString)
+ .collect(Collectors.joining(", ")),
+ exc.deviceFrameRate),
+ waitForPreconditionViolation());
+ }
}
if (!testPassed) {
@@ -679,10 +719,15 @@
"**** Running testSurfaceControlFrameRateCompatibility with compatibility "
+ compatibility);
- float expectedFrameRate = getExpectedFrameRateForCompatibility(compatibility);
+ List<Float> expectedFrameRates = getExpectedFrameRateForCompatibility(compatibility);
+ Log.i(TAG,
+ "Expected frame rates: "
+ + expectedFrameRates.stream()
+ .map(Object::toString)
+ .collect(Collectors.joining(", ")));
int initialNumEvents = mModeChangedEvents.size();
surface.setFrameRate(30.f, compatibility);
- verifyExactAndStableFrameRate(expectedFrameRate, surface);
+ verifyFrameRates(expectedFrameRates, surface);
verifyModeSwitchesDontChangeResolution(initialNumEvents, mModeChangedEvents.size());
});
}
@@ -699,10 +744,10 @@
runOneSurfaceTest((TestSurface surface) -> {
Log.i(TAG, "**** Running testSurfaceControlFrameRateCategory for category " + category);
- float expectedFrameRate = getExpectedFrameRateForCategory(category);
+ List<Float> expectedFrameRates = getExpectedFrameRateForCategory(category);
int initialNumEvents = mModeChangedEvents.size();
surface.setFrameRateCategory(category);
- verifyCompatibleAndStableFrameRate(expectedFrameRate, surface);
+ verifyFrameRates(expectedFrameRates, surface);
verifyModeSwitchesDontChangeResolution(initialNumEvents, mModeChangedEvents.size());
});
}
@@ -771,41 +816,41 @@
"frame rate strategy=" + parentStrategy);
}
- private float getExpectedFrameRateForCompatibility(int compatibility) {
+ private List<Float> getExpectedFrameRateForCompatibility(int compatibility) {
assumeTrue("**** testSurfaceControlFrameRateCompatibility SKIPPED for compatibility "
+ compatibility,
compatibility == Surface.FRAME_RATE_COMPATIBILITY_GTE);
Display display = getDisplay();
- Optional<Float> expectedFrameRate = getRefreshRates(display.getMode(), display)
- .stream()
- .filter(rate -> rate >= 30.f)
- .min(Comparator.naturalOrder());
+ List<Float> expectedFrameRates = getRefreshRates(display.getMode(), display)
+ .stream()
+ .filter(rate -> rate >= 30.f)
+ .collect(Collectors.toList());
assumeTrue("**** testSurfaceControlFrameRateCompatibility SKIPPED because no refresh rate "
+ "is >= 30",
- expectedFrameRate.isPresent());
- return expectedFrameRate.get();
+ !expectedFrameRates.isEmpty());
+ return expectedFrameRates;
}
- private float getExpectedFrameRateForCategory(int category) {
+ private List<Float> getExpectedFrameRateForCategory(int category) {
Display display = getDisplay();
List<Float> frameRates = getRefreshRates(display.getMode(), display);
if (category == Surface.FRAME_RATE_CATEGORY_DEFAULT) {
// Max due to default vote and no other frame rate specifications.
- return Collections.max(frameRates);
+ return List.of(Collections.max(frameRates));
} else if (category == Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE) {
- return Collections.min(frameRates);
+ return frameRates;
}
FpsRange categoryRange = convertCategory(category);
- Optional<Float> expectedFrameRate = frameRates.stream()
- .filter(fps -> categoryRange.includes(fps))
- .min(Comparator.naturalOrder());
+ List<Float> expectedFrameRates = frameRates.stream()
+ .filter(fps -> categoryRange.includes(fps))
+ .collect(Collectors.toList());
assumeTrue("**** testSurfaceControlFrameRateCategory SKIPPED for category " + category,
- expectedFrameRate.isPresent());
- return expectedFrameRate.get();
+ !expectedFrameRates.isEmpty());
+ return expectedFrameRates;
}
private FpsRange convertCategory(int category) {
diff --git a/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
index b0ca4d2..79d3a10 100644
--- a/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
+++ b/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
@@ -17,7 +17,10 @@
package com.android.server.wm.flicker.rotation
import android.platform.test.annotations.Presubmit
+import android.tools.common.flicker.subject.layers.LayerTraceEntrySubject
import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.common.traces.component.IComponentMatcher
+import android.tools.common.traces.surfaceflinger.Display
import android.tools.device.apphelpers.StandardAppHelper
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
@@ -57,9 +60,8 @@
@Test
open fun appLayerRotates_StartingPos() {
flicker.assertLayersStart {
- this.entry.displays.map { display ->
- this.visibleRegion(testApp).coversExactly(display.layerStackSpace)
- }
+ val display = getDisplay(testApp)
+ this.visibleRegion(testApp).coversAtLeast(display.layerStackSpace)
}
}
@@ -68,12 +70,20 @@
@Test
open fun appLayerRotates_EndingPos() {
flicker.assertLayersEnd {
- this.entry.displays.map { display ->
- this.visibleRegion(testApp).coversExactly(display.layerStackSpace)
- }
+ val display = getDisplay(testApp)
+ this.visibleRegion(testApp).coversAtLeast(display.layerStackSpace)
}
}
+ private fun LayerTraceEntrySubject.getDisplay(componentMatcher: IComponentMatcher): Display {
+ val stackId = this.layer {
+ componentMatcher.layerMatchesAnyOf(it) && it.isVisible
+ }?.layer?.stackId ?: -1
+
+ return this.entry.displays.firstOrNull { it.layerStackId == stackId }
+ ?: error("Unable to find visible layer for $componentMatcher")
+ }
+
override fun cujCompleted() {
super.cujCompleted()
appLayerRotates_StartingPos()
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java
index 29cbf01..c44d943 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java
@@ -24,8 +24,8 @@
import android.view.View;
import android.widget.ToggleButton;
+import androidx.window.embedding.ActivityEmbeddingController;
import androidx.window.embedding.SplitAttributes;
-import androidx.window.embedding.SplitAttributesCalculatorParams;
import androidx.window.embedding.SplitController;
/**
@@ -66,7 +66,9 @@
@Override
public void onClick(View v) {
// This triggers a recalcuation of splitatributes.
- mSplitController.invalidateTopVisibleSplitAttributes();
+ ActivityEmbeddingController
+ .getInstance(ActivityEmbeddingSecondaryActivity.this)
+ .invalidateVisibleActivityStacks();
}
});
findViewById(R.id.secondary_enter_pip_button).setOnClickListener(
diff --git a/tests/FsVerityTest/AndroidTest.xml b/tests/FsVerityTest/AndroidTest.xml
index d2537f6..f2d7990 100644
--- a/tests/FsVerityTest/AndroidTest.xml
+++ b/tests/FsVerityTest/AndroidTest.xml
@@ -15,6 +15,7 @@
-->
<configuration description="fs-verity end-to-end test">
<option name="test-suite-tag" value="apct" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user"/>
<object type="module_controller" class="com.android.tradefed.testtype.suite.module.ShippingApiLevelModuleController">
<!-- fs-verity is required since R/30 -->
diff --git a/tests/FsVerityTest/src/com/android/fsverity/FsVerityHostTest.java b/tests/FsVerityTest/src/com/android/fsverity/FsVerityHostTest.java
index be479f2..1b02792 100644
--- a/tests/FsVerityTest/src/com/android/fsverity/FsVerityHostTest.java
+++ b/tests/FsVerityTest/src/com/android/fsverity/FsVerityHostTest.java
@@ -25,6 +25,7 @@
import android.security.Flags;
import com.android.blockdevicewriter.BlockDeviceWriter;
+import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
@@ -52,7 +53,6 @@
private static final String TARGET_PACKAGE = "com.android.fsverity";
private static final String BASENAME = "test.file";
- private static final String TARGET_PATH = "/data/data/" + TARGET_PACKAGE + "/files/" + BASENAME;
@Rule
public final CheckFlagsRule mCheckFlagsRule =
@@ -63,11 +63,11 @@
prepareTest(10000);
ITestDevice device = getDevice();
- BlockDeviceWriter.damageFileAgainstBlockDevice(device, TARGET_PATH, 0);
- BlockDeviceWriter.damageFileAgainstBlockDevice(device, TARGET_PATH, 8192);
+ BlockDeviceWriter.damageFileAgainstBlockDevice(device, getTargetFilePath(), 0);
+ BlockDeviceWriter.damageFileAgainstBlockDevice(device, getTargetFilePath(), 8192);
BlockDeviceWriter.dropCaches(device);
- verifyRead(TARGET_PATH, "0,2");
+ verifyRead(getTargetFilePath(), "0,2");
}
@Test
@@ -75,12 +75,17 @@
prepareTest(128 * 4096 + 1);
ITestDevice device = getDevice();
- BlockDeviceWriter.damageFileAgainstBlockDevice(device, TARGET_PATH, 4096);
- BlockDeviceWriter.damageFileAgainstBlockDevice(device, TARGET_PATH, 100 * 4096);
- BlockDeviceWriter.damageFileAgainstBlockDevice(device, TARGET_PATH, 128 * 4096 + 1);
+ BlockDeviceWriter.damageFileAgainstBlockDevice(device, getTargetFilePath(), 4096);
+ BlockDeviceWriter.damageFileAgainstBlockDevice(device, getTargetFilePath(), 100 * 4096);
+ BlockDeviceWriter.damageFileAgainstBlockDevice(device, getTargetFilePath(), 128 * 4096 + 1);
BlockDeviceWriter.dropCaches(device);
- verifyRead(TARGET_PATH, "1,100,128");
+ verifyRead(getTargetFilePath(), "1,100,128");
+ }
+
+ private String getTargetFilePath() throws DeviceNotAvailableException {
+ return "/data/user/" + getDevice().getCurrentUser() + "/" + TARGET_PACKAGE + "/files/"
+ + BASENAME;
}
private void prepareTest(int fileSize) throws Exception {
@@ -97,7 +102,7 @@
options.setTestClassName(TARGET_PACKAGE + ".Helper");
options.setTestMethodName("verifyFileRead");
options.addInstrumentationArg("brokenBlockIndicesCsv", indicesCsv);
- options.addInstrumentationArg("filePath", TARGET_PATH);
+ options.addInstrumentationArg("filePath", getTargetFilePath());
assertThat(runDeviceTests(options)).isTrue();
}
}
diff --git a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
index bbd4567..7343ba1c 100644
--- a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -16,6 +16,7 @@
package com.android.server.input
+import android.app.NotificationManager
import android.content.Context
import android.content.ContextWrapper
import android.content.pm.ActivityInfo
@@ -129,6 +130,8 @@
@Mock
private lateinit var packageManager: PackageManager
+ @Mock
+ private lateinit var notificationManager: NotificationManager
private lateinit var keyboardLayoutManager: KeyboardLayoutManager
private lateinit var imeInfo: InputMethodInfo
@@ -163,6 +166,8 @@
keyboardLayoutManager = Mockito.spy(
KeyboardLayoutManager(context, native, dataStore, testLooper.looper)
)
+ Mockito.`when`(context.getSystemService(Mockito.eq(Context.NOTIFICATION_SERVICE)))
+ .thenReturn(notificationManager)
setupInputDevices()
setupBroadcastReceiver()
setupIme()
@@ -946,6 +951,48 @@
}
}
+ @Test
+ fun testNotificationShown_onInputDeviceChanged() {
+ val imeInfos = listOf(KeyboardLayoutManager.ImeInfo(0, imeInfo, createImeSubtype()))
+ Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping
+ Mockito.doReturn(false).`when`(keyboardLayoutManager).isVirtualDevice(
+ ArgumentMatchers.eq(keyboardDevice.id)
+ )
+ NewSettingsApiFlag(true).use {
+ keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id)
+ ExtendedMockito.verify(
+ notificationManager,
+ Mockito.times(1)
+ ).notifyAsUser(
+ ArgumentMatchers.isNull(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any(),
+ ArgumentMatchers.any()
+ )
+ }
+ }
+
+ @Test
+ fun testNotificationNotShown_onInputDeviceChanged_forVirtualDevice() {
+ val imeInfos = listOf(KeyboardLayoutManager.ImeInfo(0, imeInfo, createImeSubtype()))
+ Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping
+ Mockito.doReturn(true).`when`(keyboardLayoutManager).isVirtualDevice(
+ ArgumentMatchers.eq(keyboardDevice.id)
+ )
+ NewSettingsApiFlag(true).use {
+ keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id)
+ ExtendedMockito.verify(
+ notificationManager,
+ Mockito.never()
+ ).notifyAsUser(
+ ArgumentMatchers.isNull(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any(),
+ ArgumentMatchers.any()
+ )
+ }
+ }
+
private fun assertCorrectLayout(
device: InputDevice,
imeSubtype: InputMethodSubtype,
diff --git a/tests/SurfaceControlViewHostTest/AndroidManifest.xml b/tests/SurfaceControlViewHostTest/AndroidManifest.xml
index e50cbc5..71f01ac 100644
--- a/tests/SurfaceControlViewHostTest/AndroidManifest.xml
+++ b/tests/SurfaceControlViewHostTest/AndroidManifest.xml
@@ -32,6 +32,16 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+
+ <activity android:name="SurfaceInputTestActivity"
+ android:label="Surface Input Test"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
<service android:name=".EmbeddedWindowService"
android:process="com.android.test.viewembed.embedded_process"/>
</application>
diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java
index abc15b4..5aaf30a 100644
--- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java
+++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java
@@ -23,15 +23,21 @@
import android.app.Service;
import android.content.Context;
import android.content.Intent;
+import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
+import android.util.Log;
+import android.view.Choreographer;
import android.view.Display;
import android.view.Gravity;
+import android.view.Surface;
+import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
import android.view.WindowManager;
import android.widget.FrameLayout;
@@ -43,6 +49,9 @@
private Handler mHandler;
+ private IBinder mInputToken;
+ private SurfaceControl mSurfaceControl;
+
@Override
public void onCreate() {
super.onCreate();
@@ -101,9 +110,49 @@
}
});
}
+
@Override
public void relayout(WindowManager.LayoutParams lp) {
mHandler.post(() -> mVr.relayout(lp));
}
+
+ @Override
+ public void attachEmbeddedSurfaceControl(SurfaceControl parentSc, int displayId,
+ IBinder hostToken) {
+ mHandler.post(() -> {
+ Paint paint = new Paint();
+ paint.setTextSize(40);
+ paint.setColor(Color.WHITE);
+
+ mSurfaceControl = new SurfaceControl.Builder().setName("Child SurfaceControl")
+ .setParent(parentSc).setBufferSize(500, 500).build();
+ new SurfaceControl.Transaction().show(mSurfaceControl).apply();
+
+ Surface surface = new Surface(mSurfaceControl);
+ Canvas c = surface.lockCanvas(null);
+ c.drawColor(Color.BLUE);
+ c.drawText("Remote", 250, 250, paint);
+ surface.unlockCanvasAndPost(c);
+ WindowManager wm = getSystemService(WindowManager.class);
+ mInputToken = wm.registerBatchedSurfaceControlInputReceiver(displayId, hostToken,
+ mSurfaceControl,
+ Choreographer.getInstance(), event -> {
+ Log.d(TAG, "onInputEvent-remote " + event);
+ return false;
+ });
+
+ });
+ }
+
+ @Override
+ public void tearDownEmbeddedSurfaceControl() {
+ if (mSurfaceControl != null) {
+ new SurfaceControl.Transaction().remove(mSurfaceControl);
+ }
+ if (mInputToken != null) {
+ WindowManager wm = getSystemService(WindowManager.class);
+ wm.unregisterSurfaceControlInputReceiver(mInputToken);
+ }
+ }
}
}
diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/IAttachEmbeddedWindow.aidl b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/IAttachEmbeddedWindow.aidl
index 9e9faf0..6b65b40e 100644
--- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/IAttachEmbeddedWindow.aidl
+++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/IAttachEmbeddedWindow.aidl
@@ -19,8 +19,11 @@
import android.os.IBinder;
import com.android.test.viewembed.IAttachEmbeddedWindowCallback;
import android.view.WindowManager.LayoutParams;
+import android.view.SurfaceControl;
interface IAttachEmbeddedWindow {
void attachEmbedded(IBinder hostToken, int width, int height, in IAttachEmbeddedWindowCallback callback);
void relayout(in LayoutParams lp);
+ oneway void attachEmbeddedSurfaceControl(in SurfaceControl parentSurfaceControl, int displayId, IBinder hostToken);
+ oneway void tearDownEmbeddedSurfaceControl();
}
\ No newline at end of file
diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java
new file mode 100644
index 0000000..e5f8f47
--- /dev/null
+++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java
@@ -0,0 +1,217 @@
+/*
+ * 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.test.viewembed;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.AttachedSurfaceControl;
+import android.view.Choreographer;
+import android.view.Gravity;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+import android.widget.LinearLayout;
+
+/**
+ * Used to manually test that {@link android.view.SurfaceControlInputReceiver} API works.
+ */
+public class SurfaceInputTestActivity extends Activity {
+
+ private static final String TAG = "SurfaceInputTestActivity";
+ private SurfaceView mLocalSurfaceView;
+ private SurfaceView mRemoteSurfaceView;
+ private IBinder mInputToken;
+ private IAttachEmbeddedWindow mIAttachEmbeddedWindow;
+ private SurfaceControl mParentSurfaceControl;
+
+ private final ServiceConnection mConnection = new ServiceConnection() {
+ // Called when the connection with the service is established
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ Log.d(TAG, "Service Connected");
+ mIAttachEmbeddedWindow = IAttachEmbeddedWindow.Stub.asInterface(service);
+ loadEmbedded();
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ Log.d(TAG, "Service Disconnected");
+ mIAttachEmbeddedWindow = null;
+ }
+ };
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ ViewTreeObserver viewTreeObserver = getWindow().getDecorView().getViewTreeObserver();
+ viewTreeObserver.addOnPreDrawListener(
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ addLocalChildSurfaceControl(getWindow().getRootSurfaceControl());
+ viewTreeObserver.removeOnPreDrawListener(this);
+ return true;
+ }
+ });
+ }
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ LinearLayout content = new LinearLayout(this);
+ mLocalSurfaceView = new SurfaceView(this);
+ content.addView(mLocalSurfaceView, new LinearLayout.LayoutParams(
+ 500, 500, Gravity.CENTER_HORIZONTAL | Gravity.TOP));
+
+ mRemoteSurfaceView = new SurfaceView(this);
+ content.addView(mRemoteSurfaceView, new LinearLayout.LayoutParams(
+ 500, 500, Gravity.CENTER_HORIZONTAL | Gravity.TOP));
+
+ setContentView(content);
+
+ mLocalSurfaceView.setZOrderOnTop(true);
+ mLocalSurfaceView.getHolder().addCallback(mLocalSurfaceViewCallback);
+
+ mRemoteSurfaceView.setZOrderOnTop(true);
+ mRemoteSurfaceView.getHolder().addCallback(mRemoteSurfaceViewHolder);
+
+ Intent intent = new Intent(this, EmbeddedWindowService.class);
+ intent.setAction(IAttachEmbeddedWindow.class.getName());
+ Log.d(TAG, "bindService");
+ bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ getWindowManager().unregisterSurfaceControlInputReceiver(mInputToken);
+ }
+
+ private void addLocalChildSurfaceControl(AttachedSurfaceControl attachedSurfaceControl) {
+ SurfaceControl surfaceControl = new SurfaceControl.Builder().setName("LocalSC")
+ .setBufferSize(100, 100).build();
+ attachedSurfaceControl.buildReparentTransaction(surfaceControl)
+ .setVisibility(surfaceControl, true)
+ .setCrop(surfaceControl, new Rect(0, 0, 100, 100))
+ .setPosition(surfaceControl, 250, 1000)
+ .setLayer(surfaceControl, 1).apply();
+
+ Paint paint = new Paint();
+ paint.setColor(Color.WHITE);
+ paint.setTextSize(20);
+
+ Surface surface = new Surface(surfaceControl);
+ Canvas c = surface.lockCanvas(null);
+ c.drawColor(Color.GREEN);
+ c.drawText("Local SC", 0, 0, paint);
+ surface.unlockCanvasAndPost(c);
+ WindowManager wm = getSystemService(WindowManager.class);
+ mInputToken = wm.registerBatchedSurfaceControlInputReceiver(getDisplayId(),
+ attachedSurfaceControl.getHostToken(), surfaceControl,
+ Choreographer.getInstance(), event -> {
+ Log.d(TAG, "onInputEvent-sc " + event);
+ return false;
+ });
+ }
+
+ private final SurfaceHolder.Callback mLocalSurfaceViewCallback = new SurfaceHolder.Callback() {
+ private IBinder mInputToken;
+
+ @Override
+ public void surfaceCreated(@NonNull SurfaceHolder holder) {
+ Paint paint = new Paint();
+ paint.setColor(Color.WHITE);
+ paint.setTextSize(40);
+
+ Canvas c = holder.lockCanvas();
+ c.drawColor(Color.RED);
+ c.drawText("Local", 250, 250, paint);
+ holder.unlockCanvasAndPost(c);
+
+ WindowManager wm = getSystemService(WindowManager.class);
+ mInputToken = wm.registerBatchedSurfaceControlInputReceiver(getDisplayId(),
+ mLocalSurfaceView.getHostToken(), mLocalSurfaceView.getSurfaceControl(),
+ Choreographer.getInstance(), event -> {
+ Log.d(TAG, "onInputEvent-local " + event);
+ return false;
+ });
+ }
+
+ @Override
+ public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width,
+ int height) {
+
+ }
+
+ @Override
+ public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
+ if (mInputToken != null) {
+ getWindowManager().unregisterSurfaceControlInputReceiver(mInputToken);
+ }
+ }
+ };
+
+ private final SurfaceHolder.Callback mRemoteSurfaceViewHolder = new SurfaceHolder.Callback() {
+ @Override
+ public void surfaceCreated(@NonNull SurfaceHolder holder) {
+ mParentSurfaceControl = mRemoteSurfaceView.getSurfaceControl();
+ loadEmbedded();
+ }
+
+ @Override
+ public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width,
+ int height) {
+ }
+
+ @Override
+ public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
+ if (mIAttachEmbeddedWindow != null) {
+ try {
+ mIAttachEmbeddedWindow.tearDownEmbeddedSurfaceControl();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to tear down embedded SurfaceControl", e);
+ }
+ }
+ }
+ };
+
+ private void loadEmbedded() {
+ if (mParentSurfaceControl == null || mIAttachEmbeddedWindow == null) {
+ return;
+ }
+ try {
+ mIAttachEmbeddedWindow.attachEmbeddedSurfaceControl(mParentSurfaceControl,
+ getDisplayId(), mRemoteSurfaceView.getHostToken());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to load embedded SurfaceControl", e);
+ }
+ }
+}
diff --git a/tests/testables/src/android/testing/TestableContext.java b/tests/testables/src/android/testing/TestableContext.java
index 0f04d6a..fa8fdfa 100644
--- a/tests/testables/src/android/testing/TestableContext.java
+++ b/tests/testables/src/android/testing/TestableContext.java
@@ -62,8 +62,9 @@
*/
public class TestableContext extends ContextWrapper implements TestRule {
- private final TestableContentResolver mTestableContentResolver;
- private final TestableSettingsProvider mSettingsProvider;
+ private TestableContentResolver mTestableContentResolver;
+ private TestableSettingsProvider mSettingsProvider;
+ private RuntimeException mSettingsProviderFailure;
private ArrayList<MockServiceResolver> mMockServiceResolvers;
private ArrayMap<String, Object> mMockSystemServices;
@@ -83,12 +84,24 @@
public TestableContext(Context base, LeakCheck check) {
super(base);
- mTestableContentResolver = new TestableContentResolver(base);
- ContentProviderClient settings = base.getContentResolver()
- .acquireContentProviderClient(Settings.AUTHORITY);
- mSettingsProvider = TestableSettingsProvider.getFakeSettingsProvider(settings);
- mTestableContentResolver.addProvider(Settings.AUTHORITY, mSettingsProvider);
- mSettingsProvider.clearValuesAndCheck(TestableContext.this);
+
+ // Configure TestableSettingsProvider when possible; if we fail to initialize some
+ // underlying infrastructure then remember the error and report it later when a test
+ // attempts to interact with it
+ try {
+ ContentProviderClient settings = base.getContentResolver()
+ .acquireContentProviderClient(Settings.AUTHORITY);
+ mSettingsProvider = TestableSettingsProvider.getFakeSettingsProvider(settings);
+ mTestableContentResolver = new TestableContentResolver(base);
+ mTestableContentResolver.addProvider(Settings.AUTHORITY, mSettingsProvider);
+ mSettingsProvider.clearValuesAndCheck(TestableContext.this);
+ mSettingsProviderFailure = null;
+ } catch (Throwable t) {
+ mTestableContentResolver = null;
+ mSettingsProvider = null;
+ mSettingsProviderFailure = new RuntimeException(
+ "Failed to initialize TestableSettingsProvider", t);
+ }
mReceiver = check != null ? check.getTracker("receiver") : null;
mService = check != null ? check.getTracker("service") : null;
mComponent = check != null ? check.getTracker("component") : null;
@@ -171,11 +184,17 @@
}
TestableSettingsProvider getSettingsProvider() {
+ if (mSettingsProviderFailure != null) {
+ throw mSettingsProviderFailure;
+ }
return mSettingsProvider;
}
@Override
public TestableContentResolver getContentResolver() {
+ if (mSettingsProviderFailure != null) {
+ throw mSettingsProviderFailure;
+ }
return mTestableContentResolver;
}
@@ -515,12 +534,16 @@
return new TestWatcher() {
@Override
protected void succeeded(Description description) {
- mSettingsProvider.clearValuesAndCheck(TestableContext.this);
+ if (mSettingsProvider != null) {
+ mSettingsProvider.clearValuesAndCheck(TestableContext.this);
+ }
}
@Override
protected void failed(Throwable e, Description description) {
- mSettingsProvider.clearValuesAndCheck(TestableContext.this);
+ if (mSettingsProvider != null) {
+ mSettingsProvider.clearValuesAndCheck(TestableContext.this);
+ }
}
}.apply(base, description);
}
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index 82e40b1..60c25b7 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -32,6 +32,7 @@
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -76,7 +77,7 @@
}
private TestableLooper(TestLooperManager wrapper, Looper l) {
- mQueueWrapper = wrapper;
+ mQueueWrapper = Objects.requireNonNull(wrapper);
setupQueue(l);
}
@@ -282,65 +283,94 @@
return InstrumentationRegistry.getInstrumentation().acquireLooperManager(l);
}
- private static final Map<Object, TestableLooper> sLoopers = new ArrayMap<>();
+ private static final Map<Object, TestableLooperHolder> sLoopers = new ArrayMap<>();
/**
* For use with {@link RunWithLooper}, used to get the TestableLooper that was
* automatically created for this test.
*/
public static TestableLooper get(Object test) {
- return sLoopers.get(test);
+ final TestableLooperHolder looperHolder = sLoopers.get(test);
+ return (looperHolder != null) ? looperHolder.mTestableLooper : null;
}
public static void remove(Object test) {
sLoopers.remove(test);
}
- static class LooperFrameworkMethod extends FrameworkMethod {
+ /**
+ * Holder object that contains {@link TestableLooper} so that its initialization can be
+ * deferred until a test case is actually run, instead of forcing it to be created at
+ * {@link FrameworkMethod} construction time.
+ *
+ * This deferral is important because some test environments may configure
+ * {@link Looper#getMainLooper()} as part of a {@code Rule} instead of assuming it's globally
+ * initialized and unconditionally available.
+ */
+ private static class TestableLooperHolder {
+ private final boolean mSetAsMain;
+ private final Object mTest;
+
+ private TestableLooper mTestableLooper;
+ private Looper mLooper;
+ private Handler mHandler;
private HandlerThread mHandlerThread;
- private final TestableLooper mTestableLooper;
- private final Looper mLooper;
- private final Handler mHandler;
+ public TestableLooperHolder(boolean setAsMain, Object test) {
+ mSetAsMain = setAsMain;
+ mTest = test;
+ }
- public LooperFrameworkMethod(FrameworkMethod base, boolean setAsMain, Object test) {
- super(base.getMethod());
+ public void ensureInit() {
+ if (mLooper != null) return;
try {
- mLooper = setAsMain ? Looper.getMainLooper() : createLooper();
+ mLooper = mSetAsMain ? Looper.getMainLooper() : createLooper();
mTestableLooper = new TestableLooper(mLooper, false);
- if (!setAsMain) {
- mTestableLooper.getLooper().getThread().setName(test.getClass().getName());
+ if (!mSetAsMain) {
+ mTestableLooper.getLooper().getThread().setName(mTest.getClass().getName());
}
} catch (Exception e) {
throw new RuntimeException(e);
}
- sLoopers.put(test, mTestableLooper);
mHandler = new Handler(mLooper);
}
- public LooperFrameworkMethod(TestableLooper other, FrameworkMethod base) {
+ private Looper createLooper() {
+ // TODO: Find way to share these.
+ mHandlerThread = new HandlerThread(TestableLooper.class.getSimpleName());
+ mHandlerThread.start();
+ return mHandlerThread.getLooper();
+ }
+ }
+
+ static class LooperFrameworkMethod extends FrameworkMethod {
+ private TestableLooperHolder mLooperHolder;
+
+ public LooperFrameworkMethod(FrameworkMethod base, TestableLooperHolder looperHolder) {
super(base.getMethod());
- mLooper = other.mLooper;
- mTestableLooper = other;
- mHandler = Handler.createAsync(mLooper);
+ mLooperHolder = looperHolder;
}
public static FrameworkMethod get(FrameworkMethod base, boolean setAsMain, Object test) {
- if (sLoopers.containsKey(test)) {
- return new LooperFrameworkMethod(sLoopers.get(test), base);
+ TestableLooperHolder looperHolder = sLoopers.get(test);
+ if (looperHolder == null) {
+ looperHolder = new TestableLooperHolder(setAsMain, test);
+ sLoopers.put(test, looperHolder);
}
- return new LooperFrameworkMethod(base, setAsMain, test);
+ return new LooperFrameworkMethod(base, looperHolder);
}
@Override
public Object invokeExplosively(Object target, Object... params) throws Throwable {
- if (Looper.myLooper() == mLooper) {
+ mLooperHolder.ensureInit();
+ if (Looper.myLooper() == mLooperHolder.mLooper) {
// Already on the right thread from another statement, just execute then.
return super.invokeExplosively(target, params);
}
- boolean set = mTestableLooper.mQueueWrapper == null;
+ boolean set = mLooperHolder.mTestableLooper.mQueueWrapper == null;
if (set) {
- mTestableLooper.mQueueWrapper = acquireLooperManager(mLooper);
+ mLooperHolder.mTestableLooper.mQueueWrapper = acquireLooperManager(
+ mLooperHolder.mLooper);
}
try {
Object[] ret = new Object[1];
@@ -352,11 +382,11 @@
throw new LooperException(throwable);
}
};
- Message m = Message.obtain(mHandler, execute);
+ Message m = Message.obtain(mLooperHolder.mHandler, execute);
// Dispatch our message.
try {
- mTestableLooper.mQueueWrapper.execute(m);
+ mLooperHolder.mTestableLooper.mQueueWrapper.execute(m);
} catch (LooperException e) {
throw e.getSource();
} catch (RuntimeException re) {
@@ -373,27 +403,20 @@
return ret[0];
} finally {
if (set) {
- mTestableLooper.mQueueWrapper.release();
- mTestableLooper.mQueueWrapper = null;
- if (HOLD_MAIN_THREAD && mLooper == Looper.getMainLooper()) {
+ mLooperHolder.mTestableLooper.mQueueWrapper.release();
+ mLooperHolder.mTestableLooper.mQueueWrapper = null;
+ if (HOLD_MAIN_THREAD && mLooperHolder.mLooper == Looper.getMainLooper()) {
TestableInstrumentation.releaseMain();
}
}
}
}
- private Looper createLooper() {
- // TODO: Find way to share these.
- mHandlerThread = new HandlerThread(TestableLooper.class.getSimpleName());
- mHandlerThread.start();
- return mHandlerThread.getLooper();
- }
-
@Override
protected void finalize() throws Throwable {
super.finalize();
- if (mHandlerThread != null) {
- mHandlerThread.quit();
+ if (mLooperHolder.mHandlerThread != null) {
+ mLooperHolder.mHandlerThread.quit();
}
}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index f846164..20b7f1f 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -269,6 +269,7 @@
@Test
public void testCreatedTransformsAreApplied() throws Exception {
verifyVcnTransformsApplied(mGatewayConnection, false /* expectForwardTransform */);
+ verify(mUnderlyingNetworkController).updateInboundTransform(any(), any());
}
@Test
@@ -327,6 +328,8 @@
eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(direction), anyInt(), any());
}
+ verify(mUnderlyingNetworkController).updateInboundTransform(any(), any());
+
assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
final List<ChildSaProposal> saProposals =
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
index 692c8a8..49665f7 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
@@ -27,15 +27,18 @@
import static android.net.vcn.VcnGatewayConnectionConfig.VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY;
import static com.android.server.vcn.VcnGatewayConnection.DUMMY_ADDR;
+import static com.android.server.vcn.VcnGatewayConnection.SAFEMODE_TIMEOUT_SECONDS;
import static com.android.server.vcn.VcnGatewayConnection.VcnChildSessionConfiguration;
import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession;
import static com.android.server.vcn.VcnGatewayConnection.VcnNetworkAgent;
+import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
@@ -55,6 +58,7 @@
import android.net.TelephonyNetworkSpecifier;
import android.net.vcn.VcnGatewayConnectionConfig;
import android.net.vcn.VcnGatewayConnectionConfigTest;
+import android.net.vcn.VcnManager;
import android.net.vcn.VcnTransportInfo;
import android.net.wifi.WifiInfo;
import android.os.ParcelUuid;
@@ -81,6 +85,7 @@
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
/** Tests for TelephonySubscriptionTracker */
@RunWith(AndroidJUnit4.class)
@@ -352,4 +357,71 @@
any(Executor.class),
any(ConnectivityDiagnosticsCallback.class));
}
+
+ private void verifyGetSafeModeTimeoutMs(
+ boolean isInTestMode,
+ boolean isConfigTimeoutSupported,
+ PersistableBundleWrapper carrierConfig,
+ long expectedTimeoutMs)
+ throws Exception {
+ doReturn(isInTestMode).when(mVcnContext).isInTestMode();
+ doReturn(isConfigTimeoutSupported).when(mVcnContext).isFlagSafeModeTimeoutConfigEnabled();
+
+ final TelephonySubscriptionSnapshot snapshot = mock(TelephonySubscriptionSnapshot.class);
+ doReturn(carrierConfig).when(snapshot).getCarrierConfigForSubGrp(TEST_SUB_GRP);
+
+ final long result =
+ VcnGatewayConnection.getSafeModeTimeoutMs(mVcnContext, snapshot, TEST_SUB_GRP);
+
+ assertEquals(expectedTimeoutMs, result);
+ }
+
+ @Test
+ public void testGetSafeModeTimeoutMs_configTimeoutUnsupported() throws Exception {
+ verifyGetSafeModeTimeoutMs(
+ false /* isInTestMode */,
+ false /* isConfigTimeoutSupported */,
+ null /* carrierConfig */,
+ TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS));
+ }
+
+ @Test
+ public void testGetSafeModeTimeoutMs_configTimeoutSupported() throws Exception {
+ final int carrierConfigTimeoutSeconds = 20;
+ final PersistableBundleWrapper carrierConfig = mock(PersistableBundleWrapper.class);
+ doReturn(carrierConfigTimeoutSeconds)
+ .when(carrierConfig)
+ .getInt(eq(VcnManager.VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY), anyInt());
+
+ verifyGetSafeModeTimeoutMs(
+ false /* isInTestMode */,
+ true /* isConfigTimeoutSupported */,
+ carrierConfig,
+ TimeUnit.SECONDS.toMillis(carrierConfigTimeoutSeconds));
+ }
+
+ @Test
+ public void testGetSafeModeTimeoutMs_configTimeoutSupported_carrierConfigNull()
+ throws Exception {
+ verifyGetSafeModeTimeoutMs(
+ false /* isInTestMode */,
+ true /* isConfigTimeoutSupported */,
+ null /* carrierConfig */,
+ TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS));
+ }
+
+ @Test
+ public void testGetSafeModeTimeoutMs_configTimeoutOverrideTestModeDefault() throws Exception {
+ final int carrierConfigTimeoutSeconds = 20;
+ final PersistableBundleWrapper carrierConfig = mock(PersistableBundleWrapper.class);
+ doReturn(carrierConfigTimeoutSeconds)
+ .when(carrierConfig)
+ .getInt(eq(VcnManager.VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY), anyInt());
+
+ verifyGetSafeModeTimeoutMs(
+ true /* isInTestMode */,
+ true /* isConfigTimeoutSupported */,
+ carrierConfig,
+ TimeUnit.SECONDS.toMillis(carrierConfigTimeoutSeconds));
+ }
}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index edced87..e29e462 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -67,6 +67,8 @@
import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
import com.android.server.vcn.Vcn.VcnGatewayStatusCallback;
import com.android.server.vcn.VcnGatewayConnection.VcnChildSessionCallback;
+import com.android.server.vcn.VcnGatewayConnection.VcnIkeSession;
+import com.android.server.vcn.VcnGatewayConnection.VcnNetworkAgent;
import com.android.server.vcn.VcnGatewayConnection.VcnWakeLock;
import com.android.server.vcn.routeselection.UnderlyingNetworkController;
import com.android.server.vcn.routeselection.UnderlyingNetworkRecord;
@@ -118,13 +120,7 @@
NetworkCapabilities networkCapabilities,
LinkProperties linkProperties,
boolean isBlocked) {
- return new UnderlyingNetworkRecord(
- network,
- networkCapabilities,
- linkProperties,
- isBlocked,
- false /* isSelected */,
- 0 /* priorityClass */);
+ return new UnderlyingNetworkRecord(network, networkCapabilities, linkProperties, isBlocked);
}
protected static final String TEST_TCP_BUFFER_SIZES_1 = "1,2,3,4";
@@ -226,6 +222,9 @@
doReturn(mTestLooper.getLooper()).when(mVcnContext).getLooper();
doReturn(mVcnNetworkProvider).when(mVcnContext).getVcnNetworkProvider();
doReturn(mFeatureFlags).when(mVcnContext).getFeatureFlags();
+ doReturn(true).when(mVcnContext).isFlagSafeModeTimeoutConfigEnabled();
+ doReturn(true).when(mVcnContext).isFlagIpSecTransformStateEnabled();
+ doReturn(true).when(mVcnContext).isFlagNetworkMetricMonitorEnabled();
doReturn(mUnderlyingNetworkController)
.when(mDeps)
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
new file mode 100644
index 0000000..9daba6a
--- /dev/null
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
@@ -0,0 +1,419 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.vcn.routeselection;
+
+import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY;
+import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY;
+
+import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.PACKET_LOSS_UNAVALAIBLE;
+import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.net.IpSecTransformState;
+import android.os.OutcomeReceiver;
+import android.os.PowerManager;
+
+import com.android.server.vcn.routeselection.IpSecPacketLossDetector.PacketLossCalculator;
+import com.android.server.vcn.routeselection.NetworkMetricMonitor.IpSecTransformWrapper;
+import com.android.server.vcn.routeselection.NetworkMetricMonitor.NetworkMetricMonitorCallback;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.concurrent.TimeUnit;
+
+public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase {
+ private static final String TAG = IpSecPacketLossDetectorTest.class.getSimpleName();
+
+ private static final int REPLAY_BITMAP_LEN_BYTE = 512;
+ private static final int REPLAY_BITMAP_LEN_BIT = REPLAY_BITMAP_LEN_BYTE * 8;
+ private static final int IPSEC_PACKET_LOSS_PERCENT_THRESHOLD = 5;
+ private static final long POLL_IPSEC_STATE_INTERVAL_MS = TimeUnit.SECONDS.toMillis(30L);
+
+ @Mock private IpSecTransformWrapper mIpSecTransform;
+ @Mock private NetworkMetricMonitorCallback mMetricMonitorCallback;
+ @Mock private PersistableBundleWrapper mCarrierConfig;
+ @Mock private IpSecPacketLossDetector.Dependencies mDependencies;
+ @Spy private PacketLossCalculator mPacketLossCalculator = new PacketLossCalculator();
+
+ @Captor private ArgumentCaptor<OutcomeReceiver> mTransformStateReceiverCaptor;
+ @Captor private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor;
+
+ private IpSecPacketLossDetector mIpSecPacketLossDetector;
+ private IpSecTransformState mTransformStateInitial;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ mTransformStateInitial = newTransformState(0, 0, newReplayBitmap(0));
+
+ when(mCarrierConfig.getInt(
+ eq(VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY), anyInt()))
+ .thenReturn((int) TimeUnit.MILLISECONDS.toSeconds(POLL_IPSEC_STATE_INTERVAL_MS));
+ when(mCarrierConfig.getInt(
+ eq(VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY),
+ anyInt()))
+ .thenReturn(IPSEC_PACKET_LOSS_PERCENT_THRESHOLD);
+
+ when(mDependencies.getPacketLossCalculator()).thenReturn(mPacketLossCalculator);
+
+ mIpSecPacketLossDetector =
+ new IpSecPacketLossDetector(
+ mVcnContext,
+ mNetwork,
+ mCarrierConfig,
+ mMetricMonitorCallback,
+ mDependencies);
+ }
+
+ private static IpSecTransformState newTransformState(
+ long rxSeqNo, long packtCount, byte[] replayBitmap) {
+ return new IpSecTransformState.Builder()
+ .setRxHighestSequenceNumber(rxSeqNo)
+ .setPacketCount(packtCount)
+ .setReplayBitmap(replayBitmap)
+ .build();
+ }
+
+ private static byte[] newReplayBitmap(int receivedPktCnt) {
+ final BitSet bitSet = new BitSet(REPLAY_BITMAP_LEN_BIT);
+ for (int i = 0; i < receivedPktCnt; i++) {
+ bitSet.set(i);
+ }
+ return Arrays.copyOf(bitSet.toByteArray(), REPLAY_BITMAP_LEN_BYTE);
+ }
+
+ private void verifyStopped() {
+ assertFalse(mIpSecPacketLossDetector.isStarted());
+ assertFalse(mIpSecPacketLossDetector.isValidationFailed());
+ assertNull(mIpSecPacketLossDetector.getLastTransformState());
+
+ // No event scheduled
+ mTestLooper.moveTimeForward(POLL_IPSEC_STATE_INTERVAL_MS);
+ assertNull(mTestLooper.nextMessage());
+ }
+
+ @Test
+ public void testInitialization() throws Exception {
+ assertFalse(mIpSecPacketLossDetector.isSelectedUnderlyingNetwork());
+ verifyStopped();
+ }
+
+ private OutcomeReceiver<IpSecTransformState, RuntimeException>
+ startMonitorAndCaptureStateReceiver() {
+ mIpSecPacketLossDetector.setIsSelectedUnderlyingNetwork(true /* setIsSelected */);
+ mIpSecPacketLossDetector.setInboundTransformInternal(mIpSecTransform);
+
+ // Trigger the runnable
+ mTestLooper.dispatchAll();
+
+ verify(mIpSecTransform)
+ .getIpSecTransformState(any(), mTransformStateReceiverCaptor.capture());
+ return mTransformStateReceiverCaptor.getValue();
+ }
+
+ @Test
+ public void testStartMonitor() throws Exception {
+ final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver =
+ startMonitorAndCaptureStateReceiver();
+
+ assertTrue(mIpSecPacketLossDetector.isStarted());
+ assertFalse(mIpSecPacketLossDetector.isValidationFailed());
+ assertTrue(mIpSecPacketLossDetector.isSelectedUnderlyingNetwork());
+ assertEquals(mIpSecTransform, mIpSecPacketLossDetector.getInboundTransformInternal());
+
+ // Mock receiving a state
+ xfrmStateReceiver.onResult(mTransformStateInitial);
+
+ // Verify the first polled state is stored
+ assertEquals(mTransformStateInitial, mIpSecPacketLossDetector.getLastTransformState());
+ verify(mPacketLossCalculator, never())
+ .getPacketLossRatePercentage(any(), any(), anyString());
+
+ // Verify next poll is scheduled
+ assertNull(mTestLooper.nextMessage());
+ mTestLooper.moveTimeForward(POLL_IPSEC_STATE_INTERVAL_MS);
+ assertNotNull(mTestLooper.nextMessage());
+ }
+
+ @Test
+ public void testStartedMonitor_enterDozeMoze() throws Exception {
+ final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver =
+ startMonitorAndCaptureStateReceiver();
+
+ // Mock receiving a state
+ xfrmStateReceiver.onResult(mTransformStateInitial);
+ assertEquals(mTransformStateInitial, mIpSecPacketLossDetector.getLastTransformState());
+
+ // Mock entering doze mode
+ final Intent intent = mock(Intent.class);
+ when(intent.getAction()).thenReturn(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+ when(mPowerManagerService.isDeviceIdleMode()).thenReturn(true);
+
+ verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(), any(), any(), any());
+ final BroadcastReceiver broadcastReceiver = mBroadcastReceiverCaptor.getValue();
+ broadcastReceiver.onReceive(mContext, intent);
+
+ assertNull(mIpSecPacketLossDetector.getLastTransformState());
+ }
+
+ @Test
+ public void testStartedMonitor_updateInboundTransform() throws Exception {
+ final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver =
+ startMonitorAndCaptureStateReceiver();
+
+ // Mock receiving a state
+ xfrmStateReceiver.onResult(mTransformStateInitial);
+ assertEquals(mTransformStateInitial, mIpSecPacketLossDetector.getLastTransformState());
+
+ // Update the inbound transform
+ final IpSecTransformWrapper newTransform = mock(IpSecTransformWrapper.class);
+ mIpSecPacketLossDetector.setInboundTransformInternal(newTransform);
+
+ // Verifications
+ assertNull(mIpSecPacketLossDetector.getLastTransformState());
+ mTestLooper.moveTimeForward(POLL_IPSEC_STATE_INTERVAL_MS);
+ mTestLooper.dispatchAll();
+ verify(newTransform).getIpSecTransformState(any(), any());
+ }
+
+ @Test
+ public void testStartedMonitor_updateCarrierConfig() throws Exception {
+ startMonitorAndCaptureStateReceiver();
+
+ final int additionalPollIntervalMs = (int) TimeUnit.SECONDS.toMillis(10L);
+ when(mCarrierConfig.getInt(
+ eq(VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY), anyInt()))
+ .thenReturn(
+ (int)
+ TimeUnit.MILLISECONDS.toSeconds(
+ POLL_IPSEC_STATE_INTERVAL_MS + additionalPollIntervalMs));
+ mIpSecPacketLossDetector.setCarrierConfig(mCarrierConfig);
+ mTestLooper.dispatchAll();
+
+ // The already scheduled event is still fired with the old timeout
+ mTestLooper.moveTimeForward(POLL_IPSEC_STATE_INTERVAL_MS);
+ mTestLooper.dispatchAll();
+
+ // The next scheduled event will take 10 more seconds to fire
+ mTestLooper.moveTimeForward(POLL_IPSEC_STATE_INTERVAL_MS);
+ assertNull(mTestLooper.nextMessage());
+ mTestLooper.moveTimeForward(additionalPollIntervalMs);
+ assertNotNull(mTestLooper.nextMessage());
+ }
+
+ @Test
+ public void testStopMonitor() throws Exception {
+ mIpSecPacketLossDetector.setIsSelectedUnderlyingNetwork(true /* setIsSelected */);
+ mIpSecPacketLossDetector.setInboundTransformInternal(mIpSecTransform);
+
+ assertTrue(mIpSecPacketLossDetector.isStarted());
+ assertNotNull(mTestLooper.nextMessage());
+
+ // Unselect the monitor
+ mIpSecPacketLossDetector.setIsSelectedUnderlyingNetwork(false /* setIsSelected */);
+ verifyStopped();
+ }
+
+ @Test
+ public void testClose() throws Exception {
+ mIpSecPacketLossDetector.setIsSelectedUnderlyingNetwork(true /* setIsSelected */);
+ mIpSecPacketLossDetector.setInboundTransformInternal(mIpSecTransform);
+
+ assertTrue(mIpSecPacketLossDetector.isStarted());
+ assertNotNull(mTestLooper.nextMessage());
+
+ // Stop the monitor
+ mIpSecPacketLossDetector.close();
+ verifyStopped();
+ verify(mIpSecTransform).close();
+ }
+
+ @Test
+ public void testTransformStateReceiverOnResultWhenStopped() throws Exception {
+ final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver =
+ startMonitorAndCaptureStateReceiver();
+ xfrmStateReceiver.onResult(mTransformStateInitial);
+
+ // Unselect the monitor
+ mIpSecPacketLossDetector.setIsSelectedUnderlyingNetwork(false /* setIsSelected */);
+ verifyStopped();
+
+ xfrmStateReceiver.onResult(newTransformState(1, 1, newReplayBitmap(1)));
+ verify(mPacketLossCalculator, never())
+ .getPacketLossRatePercentage(any(), any(), anyString());
+ }
+
+ @Test
+ public void testTransformStateReceiverOnError() throws Exception {
+ final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver =
+ startMonitorAndCaptureStateReceiver();
+ xfrmStateReceiver.onResult(mTransformStateInitial);
+
+ xfrmStateReceiver.onError(new RuntimeException("Test"));
+ verify(mPacketLossCalculator, never())
+ .getPacketLossRatePercentage(any(), any(), anyString());
+ }
+
+ private void checkHandleLossRate(
+ int mockPacketLossRate, boolean isLastStateExpectedToUpdate, boolean isCallbackExpected)
+ throws Exception {
+ final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver =
+ startMonitorAndCaptureStateReceiver();
+ doReturn(mockPacketLossRate)
+ .when(mPacketLossCalculator)
+ .getPacketLossRatePercentage(any(), any(), anyString());
+
+ // Mock receiving two states with mTransformStateInitial and an arbitrary transformNew
+ final IpSecTransformState transformNew = newTransformState(1, 1, newReplayBitmap(1));
+ xfrmStateReceiver.onResult(mTransformStateInitial);
+ xfrmStateReceiver.onResult(transformNew);
+
+ // Verifications
+ verify(mPacketLossCalculator)
+ .getPacketLossRatePercentage(
+ eq(mTransformStateInitial), eq(transformNew), anyString());
+
+ if (isLastStateExpectedToUpdate) {
+ assertEquals(transformNew, mIpSecPacketLossDetector.getLastTransformState());
+ } else {
+ assertEquals(mTransformStateInitial, mIpSecPacketLossDetector.getLastTransformState());
+ }
+
+ if (isCallbackExpected) {
+ verify(mMetricMonitorCallback).onValidationResultReceived();
+ } else {
+ verify(mMetricMonitorCallback, never()).onValidationResultReceived();
+ }
+ }
+
+ @Test
+ public void testHandleLossRate_validationPass() throws Exception {
+ checkHandleLossRate(
+ 2, true /* isLastStateExpectedToUpdate */, true /* isCallbackExpected */);
+ }
+
+ @Test
+ public void testHandleLossRate_validationFail() throws Exception {
+ checkHandleLossRate(
+ 22, true /* isLastStateExpectedToUpdate */, true /* isCallbackExpected */);
+ }
+
+ @Test
+ public void testHandleLossRate_resultUnavalaible() throws Exception {
+ checkHandleLossRate(
+ PACKET_LOSS_UNAVALAIBLE,
+ false /* isLastStateExpectedToUpdate */,
+ false /* isCallbackExpected */);
+ }
+
+ private void checkGetPacketLossRate(
+ IpSecTransformState oldState, IpSecTransformState newState, int expectedLossRate)
+ throws Exception {
+ assertEquals(
+ expectedLossRate,
+ mPacketLossCalculator.getPacketLossRatePercentage(oldState, newState, TAG));
+ }
+
+ private void checkGetPacketLossRate(
+ IpSecTransformState oldState,
+ int rxSeqNo,
+ int packetCount,
+ int packetInWin,
+ int expectedDataLossRate)
+ throws Exception {
+ final IpSecTransformState newState =
+ newTransformState(rxSeqNo, packetCount, newReplayBitmap(packetInWin));
+ checkGetPacketLossRate(oldState, newState, expectedDataLossRate);
+ }
+
+ @Test
+ public void testGetPacketLossRate_replayWindowUnchanged() throws Exception {
+ checkGetPacketLossRate(
+ mTransformStateInitial, mTransformStateInitial, PACKET_LOSS_UNAVALAIBLE);
+ checkGetPacketLossRate(mTransformStateInitial, 3000, 2000, 2000, PACKET_LOSS_UNAVALAIBLE);
+ }
+
+ @Test
+ public void testGetPacketLossRate_againstInitialState() throws Exception {
+ checkGetPacketLossRate(mTransformStateInitial, 7000, 7001, 4096, 0);
+ checkGetPacketLossRate(mTransformStateInitial, 7000, 6000, 4096, 15);
+ checkGetPacketLossRate(mTransformStateInitial, 7000, 6000, 4000, 14);
+ }
+
+ @Test
+ public void testGetPktLossRate_oldHiSeqSmallerThanWinSize_overlappedWithNewWin()
+ throws Exception {
+ final IpSecTransformState oldState = newTransformState(2000, 1500, newReplayBitmap(1500));
+
+ checkGetPacketLossRate(oldState, 5000, 5001, 4096, 0);
+ checkGetPacketLossRate(oldState, 5000, 4000, 4096, 29);
+ checkGetPacketLossRate(oldState, 5000, 4000, 4000, 27);
+ }
+
+ @Test
+ public void testGetPktLossRate_oldHiSeqSmallerThanWinSize_notOverlappedWithNewWin()
+ throws Exception {
+ final IpSecTransformState oldState = newTransformState(2000, 1500, newReplayBitmap(1500));
+
+ checkGetPacketLossRate(oldState, 7000, 7001, 4096, 0);
+ checkGetPacketLossRate(oldState, 7000, 5000, 4096, 37);
+ checkGetPacketLossRate(oldState, 7000, 5000, 3000, 21);
+ }
+
+ @Test
+ public void testGetPktLossRate_oldHiSeqLargerThanWinSize_overlappedWithNewWin()
+ throws Exception {
+ final IpSecTransformState oldState = newTransformState(10000, 5000, newReplayBitmap(3000));
+
+ checkGetPacketLossRate(oldState, 12000, 8096, 4096, 0);
+ checkGetPacketLossRate(oldState, 12000, 7000, 4096, 36);
+ checkGetPacketLossRate(oldState, 12000, 7000, 3000, 0);
+ }
+
+ @Test
+ public void testGetPktLossRate_oldHiSeqLargerThanWinSize_notOverlappedWithNewWin()
+ throws Exception {
+ final IpSecTransformState oldState = newTransformState(10000, 5000, newReplayBitmap(3000));
+
+ checkGetPacketLossRate(oldState, 20000, 16096, 4096, 0);
+ checkGetPacketLossRate(oldState, 20000, 14000, 4096, 19);
+ checkGetPacketLossRate(oldState, 20000, 14000, 3000, 10);
+ }
+}
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
new file mode 100644
index 0000000..6015e93
--- /dev/null
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.vcn.routeselection;
+
+import static com.android.server.vcn.VcnTestUtils.setupSystemService;
+import static com.android.server.vcn.routeselection.UnderlyingNetworkControllerTest.getLinkPropertiesWithName;
+
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.IpSecConfig;
+import android.net.IpSecTransform;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.TelephonyNetworkSpecifier;
+import android.net.vcn.FeatureFlags;
+import android.os.Handler;
+import android.os.IPowerManager;
+import android.os.IThermalService;
+import android.os.ParcelUuid;
+import android.os.PowerManager;
+import android.os.test.TestLooper;
+import android.telephony.TelephonyManager;
+
+import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+import com.android.server.vcn.VcnContext;
+import com.android.server.vcn.VcnNetworkProvider;
+
+import org.junit.Before;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Set;
+import java.util.UUID;
+
+public abstract class NetworkEvaluationTestBase {
+ protected static final String SSID = "TestWifi";
+ protected static final String SSID_OTHER = "TestWifiOther";
+ protected static final String PLMN_ID = "123456";
+ protected static final String PLMN_ID_OTHER = "234567";
+
+ protected static final int SUB_ID = 1;
+ protected static final int WIFI_RSSI = -60;
+ protected static final int WIFI_RSSI_HIGH = -50;
+ protected static final int WIFI_RSSI_LOW = -80;
+ protected static final int CARRIER_ID = 1;
+ protected static final int CARRIER_ID_OTHER = 2;
+
+ protected static final int LINK_UPSTREAM_BANDWIDTH_KBPS = 1024;
+ protected static final int LINK_DOWNSTREAM_BANDWIDTH_KBPS = 2048;
+
+ protected static final int TEST_MIN_UPSTREAM_BANDWIDTH_KBPS = 100;
+ protected static final int TEST_MIN_DOWNSTREAM_BANDWIDTH_KBPS = 200;
+
+ protected static final ParcelUuid SUB_GROUP = new ParcelUuid(new UUID(0, 0));
+
+ protected static final NetworkCapabilities WIFI_NETWORK_CAPABILITIES =
+ new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .setSignalStrength(WIFI_RSSI)
+ .setSsid(SSID)
+ .setLinkUpstreamBandwidthKbps(LINK_UPSTREAM_BANDWIDTH_KBPS)
+ .setLinkDownstreamBandwidthKbps(LINK_DOWNSTREAM_BANDWIDTH_KBPS)
+ .build();
+
+ protected static final TelephonyNetworkSpecifier TEL_NETWORK_SPECIFIER =
+ new TelephonyNetworkSpecifier.Builder().setSubscriptionId(SUB_ID).build();
+ protected static final NetworkCapabilities CELL_NETWORK_CAPABILITIES =
+ new NetworkCapabilities.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .setSubscriptionIds(Set.of(SUB_ID))
+ .setNetworkSpecifier(TEL_NETWORK_SPECIFIER)
+ .setLinkUpstreamBandwidthKbps(LINK_UPSTREAM_BANDWIDTH_KBPS)
+ .setLinkDownstreamBandwidthKbps(LINK_DOWNSTREAM_BANDWIDTH_KBPS)
+ .build();
+
+ protected static final LinkProperties LINK_PROPERTIES = getLinkPropertiesWithName("test_iface");
+
+ @Mock protected Context mContext;
+ @Mock protected Network mNetwork;
+ @Mock protected FeatureFlags mFeatureFlags;
+ @Mock protected com.android.net.flags.FeatureFlags mCoreNetFeatureFlags;
+ @Mock protected TelephonySubscriptionSnapshot mSubscriptionSnapshot;
+ @Mock protected TelephonyManager mTelephonyManager;
+ @Mock protected IPowerManager mPowerManagerService;
+
+ protected TestLooper mTestLooper;
+ protected VcnContext mVcnContext;
+ protected PowerManager mPowerManager;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ when(mNetwork.getNetId()).thenReturn(-1);
+
+ mTestLooper = new TestLooper();
+ mVcnContext =
+ spy(
+ new VcnContext(
+ mContext,
+ mTestLooper.getLooper(),
+ mock(VcnNetworkProvider.class),
+ false /* isInTestMode */));
+ doNothing().when(mVcnContext).ensureRunningOnLooperThread();
+
+ doReturn(true).when(mVcnContext).isFlagNetworkMetricMonitorEnabled();
+ doReturn(true).when(mVcnContext).isFlagIpSecTransformStateEnabled();
+
+ setupSystemService(
+ mContext, mTelephonyManager, Context.TELEPHONY_SERVICE, TelephonyManager.class);
+ when(mTelephonyManager.createForSubscriptionId(SUB_ID)).thenReturn(mTelephonyManager);
+ when(mTelephonyManager.getNetworkOperator()).thenReturn(PLMN_ID);
+ when(mTelephonyManager.getSimSpecificCarrierId()).thenReturn(CARRIER_ID);
+
+ mPowerManager =
+ new PowerManager(
+ mContext,
+ mPowerManagerService,
+ mock(IThermalService.class),
+ mock(Handler.class));
+ setupSystemService(mContext, mPowerManager, Context.POWER_SERVICE, PowerManager.class);
+ }
+
+ protected IpSecTransform makeDummyIpSecTransform() throws Exception {
+ return new IpSecTransform(mContext, new IpSecConfig());
+ }
+}
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
index 2266041..d85c515 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
@@ -24,152 +24,48 @@
import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS;
import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS;
-import static com.android.server.vcn.VcnTestUtils.setupSystemService;
import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_FALLBACK;
import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_INVALID;
import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesCellPriorityRule;
import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesPriorityRule;
import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesWifiPriorityRule;
-import static com.android.server.vcn.routeselection.UnderlyingNetworkControllerTest.getLinkPropertiesWithName;
import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
-import android.content.Context;
-import android.net.LinkProperties;
-import android.net.Network;
import android.net.NetworkCapabilities;
-import android.net.TelephonyNetworkSpecifier;
import android.net.vcn.VcnCellUnderlyingNetworkTemplate;
import android.net.vcn.VcnGatewayConnectionConfig;
import android.net.vcn.VcnManager;
import android.net.vcn.VcnUnderlyingNetworkTemplate;
import android.net.vcn.VcnWifiUnderlyingNetworkTemplate;
-import android.os.ParcelUuid;
import android.os.PersistableBundle;
-import android.os.test.TestLooper;
-import android.telephony.TelephonyManager;
import android.util.ArraySet;
-import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
-import com.android.server.vcn.VcnContext;
-import com.android.server.vcn.VcnNetworkProvider;
-
import org.junit.Before;
import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
import java.util.Collections;
import java.util.List;
import java.util.Set;
-import java.util.UUID;
-public class NetworkPriorityClassifierTest {
- private static final String SSID = "TestWifi";
- private static final String SSID_OTHER = "TestWifiOther";
- private static final String PLMN_ID = "123456";
- private static final String PLMN_ID_OTHER = "234567";
-
- private static final int SUB_ID = 1;
- private static final int WIFI_RSSI = -60;
- private static final int WIFI_RSSI_HIGH = -50;
- private static final int WIFI_RSSI_LOW = -80;
- private static final int CARRIER_ID = 1;
- private static final int CARRIER_ID_OTHER = 2;
-
- private static final int LINK_UPSTREAM_BANDWIDTH_KBPS = 1024;
- private static final int LINK_DOWNSTREAM_BANDWIDTH_KBPS = 2048;
-
- private static final int TEST_MIN_UPSTREAM_BANDWIDTH_KBPS = 100;
- private static final int TEST_MIN_DOWNSTREAM_BANDWIDTH_KBPS = 200;
-
- private static final ParcelUuid SUB_GROUP = new ParcelUuid(new UUID(0, 0));
-
- private static final NetworkCapabilities WIFI_NETWORK_CAPABILITIES =
- new NetworkCapabilities.Builder()
- .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
- .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
- .setSignalStrength(WIFI_RSSI)
- .setSsid(SSID)
- .setLinkUpstreamBandwidthKbps(LINK_UPSTREAM_BANDWIDTH_KBPS)
- .setLinkDownstreamBandwidthKbps(LINK_DOWNSTREAM_BANDWIDTH_KBPS)
- .build();
-
- private static final TelephonyNetworkSpecifier TEL_NETWORK_SPECIFIER =
- new TelephonyNetworkSpecifier.Builder().setSubscriptionId(SUB_ID).build();
- private static final NetworkCapabilities CELL_NETWORK_CAPABILITIES =
- new NetworkCapabilities.Builder()
- .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
- .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
- .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
- .setSubscriptionIds(Set.of(SUB_ID))
- .setNetworkSpecifier(TEL_NETWORK_SPECIFIER)
- .setLinkUpstreamBandwidthKbps(LINK_UPSTREAM_BANDWIDTH_KBPS)
- .setLinkDownstreamBandwidthKbps(LINK_DOWNSTREAM_BANDWIDTH_KBPS)
- .build();
-
- private static final LinkProperties LINK_PROPERTIES = getLinkPropertiesWithName("test_iface");
-
- @Mock private Network mNetwork;
- @Mock private TelephonySubscriptionSnapshot mSubscriptionSnapshot;
- @Mock private TelephonyManager mTelephonyManager;
-
- private TestLooper mTestLooper;
- private VcnContext mVcnContext;
+public class NetworkPriorityClassifierTest extends NetworkEvaluationTestBase {
private UnderlyingNetworkRecord mWifiNetworkRecord;
private UnderlyingNetworkRecord mCellNetworkRecord;
@Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
+ public void setUp() throws Exception {
+ super.setUp();
- final Context mockContext = mock(Context.class);
- mTestLooper = new TestLooper();
- mVcnContext =
- spy(
- new VcnContext(
- mockContext,
- mTestLooper.getLooper(),
- mock(VcnNetworkProvider.class),
- false /* isInTestMode */));
- doNothing().when(mVcnContext).ensureRunningOnLooperThread();
-
- setupSystemService(
- mockContext, mTelephonyManager, Context.TELEPHONY_SERVICE, TelephonyManager.class);
- when(mTelephonyManager.createForSubscriptionId(SUB_ID)).thenReturn(mTelephonyManager);
- when(mTelephonyManager.getNetworkOperator()).thenReturn(PLMN_ID);
- when(mTelephonyManager.getSimSpecificCarrierId()).thenReturn(CARRIER_ID);
-
- mWifiNetworkRecord =
- getTestNetworkRecord(
- WIFI_NETWORK_CAPABILITIES,
- VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES);
- mCellNetworkRecord =
- getTestNetworkRecord(
- CELL_NETWORK_CAPABILITIES,
- VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES);
+ mWifiNetworkRecord = getTestNetworkRecord(WIFI_NETWORK_CAPABILITIES);
+ mCellNetworkRecord = getTestNetworkRecord(CELL_NETWORK_CAPABILITIES);
}
- private UnderlyingNetworkRecord getTestNetworkRecord(
- NetworkCapabilities nc, List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates) {
- return new UnderlyingNetworkRecord(
- mNetwork,
- nc,
- LINK_PROPERTIES,
- false /* isBlocked */,
- mVcnContext,
- underlyingNetworkTemplates,
- SUB_GROUP,
- mSubscriptionSnapshot,
- null /* currentlySelected */,
- null /* carrierConfig */);
+ private UnderlyingNetworkRecord getTestNetworkRecord(NetworkCapabilities nc) {
+ return new UnderlyingNetworkRecord(mNetwork, nc, LINK_PROPERTIES, false /* isBlocked */);
}
@Test
@@ -186,14 +82,14 @@
mWifiNetworkRecord,
SUB_GROUP,
mSubscriptionSnapshot,
- null /* currentlySelecetd */,
+ false /* isSelected */,
null /* carrierConfig */));
}
private void verifyMatchesPriorityRuleForUpstreamBandwidth(
int entryUpstreamBandwidth,
int exitUpstreamBandwidth,
- UnderlyingNetworkRecord currentlySelected,
+ boolean isSelected,
boolean expectMatch) {
final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority =
new VcnWifiUnderlyingNetworkTemplate.Builder()
@@ -208,14 +104,14 @@
mWifiNetworkRecord,
SUB_GROUP,
mSubscriptionSnapshot,
- currentlySelected,
+ isSelected,
null /* carrierConfig */));
}
private void verifyMatchesPriorityRuleForDownstreamBandwidth(
int entryDownstreamBandwidth,
int exitDownstreamBandwidth,
- UnderlyingNetworkRecord currentlySelected,
+ boolean isSelected,
boolean expectMatch) {
final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority =
new VcnWifiUnderlyingNetworkTemplate.Builder()
@@ -231,7 +127,7 @@
mWifiNetworkRecord,
SUB_GROUP,
mSubscriptionSnapshot,
- currentlySelected,
+ isSelected,
null /* carrierConfig */));
}
@@ -240,7 +136,7 @@
verifyMatchesPriorityRuleForUpstreamBandwidth(
TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS,
TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS,
- null /* currentlySelected */,
+ false /* isSelected */,
true);
}
@@ -249,7 +145,7 @@
verifyMatchesPriorityRuleForUpstreamBandwidth(
LINK_UPSTREAM_BANDWIDTH_KBPS + 1,
LINK_UPSTREAM_BANDWIDTH_KBPS + 1,
- null /* currentlySelected */,
+ false /* isSelected */,
false);
}
@@ -258,7 +154,7 @@
verifyMatchesPriorityRuleForDownstreamBandwidth(
TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS,
TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS,
- null /* currentlySelected */,
+ false /* isSelected */,
true);
}
@@ -267,7 +163,7 @@
verifyMatchesPriorityRuleForDownstreamBandwidth(
LINK_DOWNSTREAM_BANDWIDTH_KBPS + 1,
LINK_DOWNSTREAM_BANDWIDTH_KBPS + 1,
- null /* currentlySelected */,
+ false /* isSelected */,
false);
}
@@ -276,7 +172,7 @@
verifyMatchesPriorityRuleForUpstreamBandwidth(
TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS,
TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS,
- mWifiNetworkRecord,
+ true /* isSelected */,
true);
}
@@ -285,7 +181,7 @@
verifyMatchesPriorityRuleForUpstreamBandwidth(
LINK_UPSTREAM_BANDWIDTH_KBPS + 1,
LINK_UPSTREAM_BANDWIDTH_KBPS + 1,
- mWifiNetworkRecord,
+ true /* isSelected */,
false);
}
@@ -294,7 +190,7 @@
verifyMatchesPriorityRuleForDownstreamBandwidth(
TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS,
TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS,
- mWifiNetworkRecord,
+ true /* isSelected */,
true);
}
@@ -303,7 +199,7 @@
verifyMatchesPriorityRuleForDownstreamBandwidth(
LINK_DOWNSTREAM_BANDWIDTH_KBPS + 1,
LINK_DOWNSTREAM_BANDWIDTH_KBPS + 1,
- mWifiNetworkRecord,
+ true /* isSelected */,
false);
}
@@ -318,14 +214,12 @@
TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS,
TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS)
.build();
- final UnderlyingNetworkRecord selectedNetworkRecord =
- isSelectedNetwork ? mWifiNetworkRecord : null;
assertEquals(
expectMatch,
checkMatchesWifiPriorityRule(
wifiNetworkPriority,
mWifiNetworkRecord,
- selectedNetworkRecord,
+ isSelectedNetwork,
carrierConfig == null
? null
: new PersistableBundleWrapper(carrierConfig)));
@@ -381,7 +275,7 @@
checkMatchesWifiPriorityRule(
wifiNetworkPriority,
mWifiNetworkRecord,
- null /* currentlySelecetd */,
+ false /* isSelected */,
null /* carrierConfig */));
}
@@ -516,7 +410,7 @@
mCellNetworkRecord,
SUB_GROUP,
mSubscriptionSnapshot,
- null /* currentlySelected */,
+ false /* isSelected */,
null /* carrierConfig */));
}
@@ -543,7 +437,16 @@
@Test
public void testCalculatePriorityClass() throws Exception {
- assertEquals(2, mCellNetworkRecord.priorityClass);
+ final int priorityClass =
+ NetworkPriorityClassifier.calculatePriorityClass(
+ mVcnContext,
+ mCellNetworkRecord,
+ VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+ SUB_GROUP,
+ mSubscriptionSnapshot,
+ false /* isSelected */,
+ null /* carrierConfig */);
+ assertEquals(2, priorityClass);
}
private void checkCalculatePriorityClassFailToMatchAny(
@@ -561,10 +464,19 @@
ncBuilder.addCapability(NET_CAPABILITY_INTERNET);
}
- final UnderlyingNetworkRecord nonDunNetworkRecord =
- getTestNetworkRecord(ncBuilder.build(), templatesRequireDun);
+ final UnderlyingNetworkRecord nonDunNetworkRecord = getTestNetworkRecord(ncBuilder.build());
- assertEquals(expectedPriorityClass, nonDunNetworkRecord.priorityClass);
+ final int priorityClass =
+ NetworkPriorityClassifier.calculatePriorityClass(
+ mVcnContext,
+ nonDunNetworkRecord,
+ templatesRequireDun,
+ SUB_GROUP,
+ mSubscriptionSnapshot,
+ false /* isSelected */,
+ null /* carrierConfig */);
+
+ assertEquals(expectedPriorityClass, priorityClass);
}
@Test
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
index 2941fde..588624b 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
@@ -29,13 +29,12 @@
import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.WIFI_EXIT_RSSI_THRESHOLD_DEFAULT;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -48,6 +47,8 @@
import android.content.Context;
import android.net.ConnectivityManager;
+import android.net.IpSecConfig;
+import android.net.IpSecTransform;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
@@ -67,9 +68,11 @@
import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
import com.android.server.vcn.VcnContext;
import com.android.server.vcn.VcnNetworkProvider;
+import com.android.server.vcn.routeselection.UnderlyingNetworkController.Dependencies;
import com.android.server.vcn.routeselection.UnderlyingNetworkController.NetworkBringupCallback;
import com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkControllerCallback;
import com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkListener;
+import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.NetworkEvaluatorCallback;
import org.junit.Before;
import org.junit.Test;
@@ -77,6 +80,7 @@
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
import java.util.ArrayList;
import java.util.Arrays;
@@ -152,12 +156,17 @@
@Mock private CarrierConfigManager mCarrierConfigManager;
@Mock private TelephonySubscriptionSnapshot mSubscriptionSnapshot;
@Mock private UnderlyingNetworkControllerCallback mNetworkControllerCb;
+ @Mock private NetworkEvaluatorCallback mEvaluatorCallback;
@Mock private Network mNetwork;
+ @Spy private Dependencies mDependencies = new Dependencies();
+
@Captor private ArgumentCaptor<UnderlyingNetworkListener> mUnderlyingNetworkListenerCaptor;
+ @Captor private ArgumentCaptor<NetworkEvaluatorCallback> mEvaluatorCallbackCaptor;
private TestLooper mTestLooper;
private VcnContext mVcnContext;
+ private UnderlyingNetworkEvaluator mNetworkEvaluator;
private UnderlyingNetworkController mUnderlyingNetworkController;
@Before
@@ -172,7 +181,7 @@
mTestLooper.getLooper(),
mVcnNetworkProvider,
false /* isInTestMode */));
- resetVcnContext();
+ resetVcnContext(mVcnContext);
setupSystemService(
mContext,
@@ -189,18 +198,36 @@
when(mSubscriptionSnapshot.getAllSubIdsInGroup(eq(SUB_GROUP))).thenReturn(INITIAL_SUB_IDS);
+ mNetworkEvaluator =
+ spy(
+ new UnderlyingNetworkEvaluator(
+ mVcnContext,
+ mNetwork,
+ VcnGatewayConnectionConfigTest.buildTestConfig()
+ .getVcnUnderlyingNetworkPriorities(),
+ SUB_GROUP,
+ mSubscriptionSnapshot,
+ null,
+ mEvaluatorCallback));
+ doReturn(mNetworkEvaluator)
+ .when(mDependencies)
+ .newUnderlyingNetworkEvaluator(any(), any(), any(), any(), any(), any(), any());
+
mUnderlyingNetworkController =
new UnderlyingNetworkController(
mVcnContext,
VcnGatewayConnectionConfigTest.buildTestConfig(),
SUB_GROUP,
mSubscriptionSnapshot,
- mNetworkControllerCb);
+ mNetworkControllerCb,
+ mDependencies);
}
- private void resetVcnContext() {
- reset(mVcnContext);
- doNothing().when(mVcnContext).ensureRunningOnLooperThread();
+ private void resetVcnContext(VcnContext vcnContext) {
+ reset(vcnContext);
+ doNothing().when(vcnContext).ensureRunningOnLooperThread();
+ doReturn(true).when(vcnContext).isFlagNetworkMetricMonitorEnabled();
+ doReturn(true).when(vcnContext).isFlagIpSecTransformStateEnabled();
}
// Package private for use in NetworkPriorityClassifierTest
@@ -226,11 +253,13 @@
final ConnectivityManager cm = mock(ConnectivityManager.class);
setupSystemService(mContext, cm, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class);
final VcnContext vcnContext =
- new VcnContext(
- mContext,
- mTestLooper.getLooper(),
- mVcnNetworkProvider,
- true /* isInTestMode */);
+ spy(
+ new VcnContext(
+ mContext,
+ mTestLooper.getLooper(),
+ mVcnNetworkProvider,
+ true /* isInTestMode */));
+ resetVcnContext(vcnContext);
new UnderlyingNetworkController(
vcnContext,
@@ -489,13 +518,7 @@
NetworkCapabilities networkCapabilities,
LinkProperties linkProperties,
boolean isBlocked) {
- return new UnderlyingNetworkRecord(
- network,
- networkCapabilities,
- linkProperties,
- isBlocked,
- false /* isSelected */,
- 0 /* priorityClass */);
+ return new UnderlyingNetworkRecord(network, networkCapabilities, linkProperties, isBlocked);
}
@Test
@@ -515,24 +538,12 @@
UnderlyingNetworkRecord recordC =
new UnderlyingNetworkRecord(
mNetwork,
- INITIAL_NETWORK_CAPABILITIES,
- INITIAL_LINK_PROPERTIES,
- false /* isBlocked */,
- true /* isSelected */,
- -1 /* priorityClass */);
- UnderlyingNetworkRecord recordD =
- getTestNetworkRecord(
- mNetwork,
UPDATED_NETWORK_CAPABILITIES,
UPDATED_LINK_PROPERTIES,
false /* isBlocked */);
assertEquals(recordA, recordB);
- assertEquals(recordA, recordC);
- assertNotEquals(recordA, recordD);
-
- assertTrue(UnderlyingNetworkRecord.isEqualIncludingPriorities(recordA, recordB));
- assertFalse(UnderlyingNetworkRecord.isEqualIncludingPriorities(recordA, recordC));
+ assertNotEquals(recordA, recordC);
}
@Test
@@ -540,6 +551,58 @@
verifyRegistrationOnAvailableAndGetCallback();
}
+ @Test
+ public void testUpdateSubscriptionSnapshotAndCarrierConfig() {
+ verifyRegistrationOnAvailableAndGetCallback();
+
+ TelephonySubscriptionSnapshot subscriptionUpdate =
+ mock(TelephonySubscriptionSnapshot.class);
+ when(subscriptionUpdate.getAllSubIdsInGroup(eq(SUB_GROUP))).thenReturn(UPDATED_SUB_IDS);
+
+ mUnderlyingNetworkController.updateSubscriptionSnapshot(subscriptionUpdate);
+
+ verify(mNetworkEvaluator).reevaluate(any(), any(), any(), any());
+ }
+
+ @Test
+ public void testUpdateIpSecTransform() {
+ verifyRegistrationOnAvailableAndGetCallback();
+
+ final UnderlyingNetworkRecord expectedRecord =
+ getTestNetworkRecord(
+ mNetwork,
+ INITIAL_NETWORK_CAPABILITIES,
+ INITIAL_LINK_PROPERTIES,
+ false /* isBlocked */);
+ final IpSecTransform expectedTransform = new IpSecTransform(mContext, new IpSecConfig());
+
+ mUnderlyingNetworkController.updateInboundTransform(expectedRecord, expectedTransform);
+ verify(mNetworkEvaluator).setInboundTransform(expectedTransform);
+ }
+
+ @Test
+ public void testOnEvaluationResultChanged() {
+ verifyRegistrationOnAvailableAndGetCallback();
+
+ // Verify #reevaluateNetworks is called by checking #getNetworkRecord
+ verify(mNetworkEvaluator).getNetworkRecord();
+
+ // Trigger the callback
+ verify(mDependencies)
+ .newUnderlyingNetworkEvaluator(
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ mEvaluatorCallbackCaptor.capture());
+ mEvaluatorCallbackCaptor.getValue().onEvaluationResultChanged();
+
+ // Verify #reevaluateNetworks is called again
+ verify(mNetworkEvaluator, times(2)).getNetworkRecord();
+ }
+
private UnderlyingNetworkListener verifyRegistrationOnAvailableAndGetCallback() {
return verifyRegistrationOnAvailableAndGetCallback(INITIAL_NETWORK_CAPABILITIES);
}
@@ -583,6 +646,7 @@
INITIAL_LINK_PROPERTIES,
false /* isBlocked */);
verifyOnSelectedUnderlyingNetworkChanged(expectedRecord);
+ verify(mNetworkEvaluator).setIsSelected(eq(true), any(), any(), any(), any());
return cb;
}
@@ -667,7 +731,7 @@
cb.onBlockedStatusChanged(mNetwork, true /* isBlocked */);
- verifyOnSelectedUnderlyingNetworkChanged(null);
+ verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(null);
}
@Test
@@ -675,6 +739,7 @@
UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback();
cb.onLost(mNetwork);
+ verify(mNetworkEvaluator).close();
verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(null);
}
@@ -713,7 +778,8 @@
VcnGatewayConnectionConfigTest.buildTestConfig(networkTemplates),
SUB_GROUP,
mSubscriptionSnapshot,
- mNetworkControllerCb);
+ mNetworkControllerCb,
+ mDependencies);
verify(cm)
.registerNetworkCallback(
@@ -724,30 +790,44 @@
return mUnderlyingNetworkListenerCaptor.getValue();
}
- private UnderlyingNetworkRecord bringupNetworkAndGetRecord(
+ private UnderlyingNetworkEvaluator bringupNetworkAndGetEvaluator(
UnderlyingNetworkListener cb,
NetworkCapabilities requestNetworkCaps,
- List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
- UnderlyingNetworkRecord currentlySelected) {
+ List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates) {
final Network network = mock(Network.class);
final NetworkCapabilities responseNetworkCaps =
buildResponseNwCaps(requestNetworkCaps, INITIAL_SUB_IDS);
+ final UnderlyingNetworkEvaluator evaluator =
+ spy(
+ new UnderlyingNetworkEvaluator(
+ mVcnContext,
+ network,
+ underlyingNetworkTemplates,
+ SUB_GROUP,
+ mSubscriptionSnapshot,
+ null,
+ mEvaluatorCallback));
+ doReturn(evaluator)
+ .when(mDependencies)
+ .newUnderlyingNetworkEvaluator(any(), any(), any(), any(), any(), any(), any());
cb.onAvailable(network);
cb.onCapabilitiesChanged(network, responseNetworkCaps);
cb.onLinkPropertiesChanged(network, INITIAL_LINK_PROPERTIES);
cb.onBlockedStatusChanged(network, false /* isFalse */);
- return new UnderlyingNetworkRecord(
- network,
- responseNetworkCaps,
- INITIAL_LINK_PROPERTIES,
- false /* isBlocked */,
- mVcnContext,
- underlyingNetworkTemplates,
- SUB_GROUP,
- mSubscriptionSnapshot,
- currentlySelected,
- null /* carrierConfig */);
+
+ return evaluator;
+ }
+
+ private void verifySelectNetwork(UnderlyingNetworkEvaluator expectedEvaluator) {
+ verifyOnSelectedUnderlyingNetworkChanged(expectedEvaluator.getNetworkRecord());
+ verify(expectedEvaluator).setIsSelected(eq(true), any(), any(), any(), any());
+ }
+
+ private void verifyNeverSelectNetwork(UnderlyingNetworkEvaluator expectedEvaluator) {
+ verify(mNetworkControllerCb, never())
+ .onSelectedUnderlyingNetworkChanged(eq(expectedEvaluator.getNetworkRecord()));
+ verify(expectedEvaluator, never()).setIsSelected(eq(true), any(), any(), any(), any());
}
@Test
@@ -759,19 +839,15 @@
UnderlyingNetworkListener cb = setupControllerAndGetNetworkListener(networkTemplates);
// Bring up CBS network
- final UnderlyingNetworkRecord cbsNetworkRecord =
- bringupNetworkAndGetRecord(
- cb,
- CBS_NETWORK_CAPABILITIES,
- networkTemplates,
- null /* currentlySelected */);
- verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(cbsNetworkRecord));
+ final UnderlyingNetworkEvaluator cbsNetworkEvaluator =
+ bringupNetworkAndGetEvaluator(cb, CBS_NETWORK_CAPABILITIES, networkTemplates);
+ verifySelectNetwork(cbsNetworkEvaluator);
// Bring up DUN network
- final UnderlyingNetworkRecord dunNetworkRecord =
- bringupNetworkAndGetRecord(
- cb, DUN_NETWORK_CAPABILITIES, networkTemplates, cbsNetworkRecord);
- verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(dunNetworkRecord));
+ final UnderlyingNetworkEvaluator dunNetworkEvaluator =
+ bringupNetworkAndGetEvaluator(cb, DUN_NETWORK_CAPABILITIES, networkTemplates);
+ verifySelectNetwork(dunNetworkEvaluator);
+ verify(cbsNetworkEvaluator).setIsSelected(eq(false), any(), any(), any(), any());
}
@Test
@@ -783,20 +859,14 @@
UnderlyingNetworkListener cb = setupControllerAndGetNetworkListener(networkTemplates);
// Bring up DUN network
- final UnderlyingNetworkRecord dunNetworkRecord =
- bringupNetworkAndGetRecord(
- cb,
- DUN_NETWORK_CAPABILITIES,
- networkTemplates,
- null /* currentlySelected */);
- verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(dunNetworkRecord));
+ final UnderlyingNetworkEvaluator dunNetworkEvaluator =
+ bringupNetworkAndGetEvaluator(cb, DUN_NETWORK_CAPABILITIES, networkTemplates);
+ verifySelectNetwork(dunNetworkEvaluator);
// Bring up CBS network
- final UnderlyingNetworkRecord cbsNetworkRecord =
- bringupNetworkAndGetRecord(
- cb, CBS_NETWORK_CAPABILITIES, networkTemplates, dunNetworkRecord);
- verify(mNetworkControllerCb, never())
- .onSelectedUnderlyingNetworkChanged(eq(cbsNetworkRecord));
+ final UnderlyingNetworkEvaluator cbsNetworkEvaluator =
+ bringupNetworkAndGetEvaluator(cb, CBS_NETWORK_CAPABILITIES, networkTemplates);
+ verifyNeverSelectNetwork(cbsNetworkEvaluator);
}
@Test
@@ -808,13 +878,9 @@
UnderlyingNetworkListener cb = setupControllerAndGetNetworkListener(networkTemplates);
// Bring up an Internet network without DUN capability
- final UnderlyingNetworkRecord networkRecord =
- bringupNetworkAndGetRecord(
- cb,
- INITIAL_NETWORK_CAPABILITIES,
- networkTemplates,
- null /* currentlySelected */);
- verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(networkRecord));
+ final UnderlyingNetworkEvaluator evaluator =
+ bringupNetworkAndGetEvaluator(cb, INITIAL_NETWORK_CAPABILITIES, networkTemplates);
+ verifySelectNetwork(evaluator);
}
@Test
@@ -825,10 +891,8 @@
new VcnCellUnderlyingNetworkTemplate.Builder().setDun(MATCH_REQUIRED).build());
UnderlyingNetworkListener cb = setupControllerAndGetNetworkListener(networkTemplates);
- bringupNetworkAndGetRecord(
- cb, CBS_NETWORK_CAPABILITIES, networkTemplates, null /* currentlySelected */);
-
- verify(mNetworkControllerCb, never())
- .onSelectedUnderlyingNetworkChanged(any(UnderlyingNetworkRecord.class));
+ final UnderlyingNetworkEvaluator evaluator =
+ bringupNetworkAndGetEvaluator(cb, CBS_NETWORK_CAPABILITIES, networkTemplates);
+ verifyNeverSelectNetwork(evaluator);
}
}
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java
new file mode 100644
index 0000000..aa81efe
--- /dev/null
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.vcn.routeselection;
+
+import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY;
+
+import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_INVALID;
+import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyObject;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.net.IpSecTransform;
+import android.net.vcn.VcnGatewayConnectionConfig;
+
+import com.android.server.vcn.routeselection.NetworkMetricMonitor.NetworkMetricMonitorCallback;
+import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.Dependencies;
+import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.NetworkEvaluatorCallback;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+
+import java.util.concurrent.TimeUnit;
+
+public class UnderlyingNetworkEvaluatorTest extends NetworkEvaluationTestBase {
+ private static final int PENALTY_TIMEOUT_MIN = 10;
+ private static final long PENALTY_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(PENALTY_TIMEOUT_MIN);
+
+ @Mock private PersistableBundleWrapper mCarrierConfig;
+ @Mock private IpSecPacketLossDetector mIpSecPacketLossDetector;
+ @Mock private Dependencies mDependencies;
+ @Mock private NetworkEvaluatorCallback mEvaluatorCallback;
+
+ @Captor private ArgumentCaptor<NetworkMetricMonitorCallback> mMetricMonitorCbCaptor;
+
+ private UnderlyingNetworkEvaluator mNetworkEvaluator;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ when(mDependencies.newIpSecPacketLossDetector(any(), any(), any(), any()))
+ .thenReturn(mIpSecPacketLossDetector);
+
+ when(mCarrierConfig.getIntArray(
+ eq(VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY), anyObject()))
+ .thenReturn(new int[] {PENALTY_TIMEOUT_MIN});
+
+ mNetworkEvaluator = newValidUnderlyingNetworkEvaluator();
+ }
+
+ private UnderlyingNetworkEvaluator newUnderlyingNetworkEvaluator() {
+ return new UnderlyingNetworkEvaluator(
+ mVcnContext,
+ mNetwork,
+ VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+ SUB_GROUP,
+ mSubscriptionSnapshot,
+ mCarrierConfig,
+ mEvaluatorCallback,
+ mDependencies);
+ }
+
+ private UnderlyingNetworkEvaluator newValidUnderlyingNetworkEvaluator() {
+ final UnderlyingNetworkEvaluator evaluator = newUnderlyingNetworkEvaluator();
+
+ evaluator.setNetworkCapabilities(
+ CELL_NETWORK_CAPABILITIES,
+ VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+ SUB_GROUP,
+ mSubscriptionSnapshot,
+ mCarrierConfig);
+ evaluator.setLinkProperties(
+ LINK_PROPERTIES,
+ VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+ SUB_GROUP,
+ mSubscriptionSnapshot,
+ mCarrierConfig);
+ evaluator.setIsBlocked(
+ false /* isBlocked */,
+ VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+ SUB_GROUP,
+ mSubscriptionSnapshot,
+ mCarrierConfig);
+
+ return evaluator;
+ }
+
+ @Test
+ public void testInitializedEvaluator() throws Exception {
+ final UnderlyingNetworkEvaluator evaluator = newUnderlyingNetworkEvaluator();
+
+ assertFalse(evaluator.isValid());
+ assertEquals(mNetwork, evaluator.getNetwork());
+ assertEquals(PRIORITY_INVALID, evaluator.getPriorityClass());
+
+ try {
+ evaluator.getNetworkRecord();
+ fail("Expected to fail because evaluator is not valid");
+ } catch (Exception expected) {
+ }
+ }
+
+ @Test
+ public void testValidEvaluator() {
+ final UnderlyingNetworkEvaluator evaluator = newUnderlyingNetworkEvaluator();
+ evaluator.setNetworkCapabilities(
+ CELL_NETWORK_CAPABILITIES,
+ VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+ SUB_GROUP,
+ mSubscriptionSnapshot,
+ mCarrierConfig);
+ evaluator.setLinkProperties(
+ LINK_PROPERTIES,
+ VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+ SUB_GROUP,
+ mSubscriptionSnapshot,
+ mCarrierConfig);
+ evaluator.setIsBlocked(
+ false /* isBlocked */,
+ VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+ SUB_GROUP,
+ mSubscriptionSnapshot,
+ mCarrierConfig);
+
+ final UnderlyingNetworkRecord expectedRecord =
+ new UnderlyingNetworkRecord(
+ mNetwork,
+ CELL_NETWORK_CAPABILITIES,
+ LINK_PROPERTIES,
+ false /* isBlocked */);
+
+ assertTrue(evaluator.isValid());
+ assertEquals(mNetwork, evaluator.getNetwork());
+ assertEquals(2, evaluator.getPriorityClass());
+ assertEquals(expectedRecord, evaluator.getNetworkRecord());
+ }
+
+ private void checkSetSelectedNetwork(boolean isSelected) {
+ mNetworkEvaluator.setIsSelected(
+ isSelected,
+ VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+ SUB_GROUP,
+ mSubscriptionSnapshot,
+ mCarrierConfig);
+ verify(mIpSecPacketLossDetector).setIsSelectedUnderlyingNetwork(isSelected);
+ }
+
+ @Test
+ public void testSetIsSelected_selected() throws Exception {
+ checkSetSelectedNetwork(true /* isSelectedExpected */);
+ }
+
+ @Test
+ public void testSetIsSelected_unselected() throws Exception {
+ checkSetSelectedNetwork(false /* isSelectedExpected */);
+ }
+
+ @Test
+ public void testSetIpSecTransform_onSelectedNetwork() throws Exception {
+ final IpSecTransform transform = makeDummyIpSecTransform();
+
+ // Make the network selected
+ mNetworkEvaluator.setIsSelected(
+ true,
+ VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+ SUB_GROUP,
+ mSubscriptionSnapshot,
+ mCarrierConfig);
+ mNetworkEvaluator.setInboundTransform(transform);
+
+ verify(mIpSecPacketLossDetector).setInboundTransform(transform);
+ }
+
+ @Test
+ public void testSetIpSecTransform_onUnSelectedNetwork() throws Exception {
+ mNetworkEvaluator.setIsSelected(
+ false,
+ VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+ SUB_GROUP,
+ mSubscriptionSnapshot,
+ mCarrierConfig);
+ mNetworkEvaluator.setInboundTransform(makeDummyIpSecTransform());
+
+ verify(mIpSecPacketLossDetector, never()).setInboundTransform(any());
+ }
+
+ @Test
+ public void close() throws Exception {
+ mNetworkEvaluator.close();
+
+ verify(mIpSecPacketLossDetector).close();
+ mTestLooper.moveTimeForward(PENALTY_TIMEOUT_MS);
+ assertNull(mTestLooper.nextMessage());
+ }
+
+ private NetworkMetricMonitorCallback getMetricMonitorCbCaptor() throws Exception {
+ verify(mDependencies)
+ .newIpSecPacketLossDetector(any(), any(), any(), mMetricMonitorCbCaptor.capture());
+
+ return mMetricMonitorCbCaptor.getValue();
+ }
+
+ private void checkPenalizeNetwork() throws Exception {
+ assertFalse(mNetworkEvaluator.isPenalized());
+
+ // Validation failed
+ when(mIpSecPacketLossDetector.isValidationFailed()).thenReturn(true);
+ getMetricMonitorCbCaptor().onValidationResultReceived();
+
+ // Verify the evaluator is penalized
+ assertTrue(mNetworkEvaluator.isPenalized());
+ verify(mEvaluatorCallback).onEvaluationResultChanged();
+ }
+
+ @Test
+ public void testRcvValidationResult_penalizeNetwork_penaltyTimeout() throws Exception {
+ checkPenalizeNetwork();
+
+ // Penalty timeout
+ mTestLooper.moveTimeForward(PENALTY_TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ // Verify the evaluator is not penalized
+ assertFalse(mNetworkEvaluator.isPenalized());
+ verify(mEvaluatorCallback, times(2)).onEvaluationResultChanged();
+ }
+
+ @Test
+ public void testRcvValidationResult_penalizeNetwork_passValidation() throws Exception {
+ checkPenalizeNetwork();
+
+ // Validation passed
+ when(mIpSecPacketLossDetector.isValidationFailed()).thenReturn(false);
+ getMetricMonitorCbCaptor().onValidationResultReceived();
+
+ // Verify the evaluator is not penalized and penalty timeout is canceled
+ assertFalse(mNetworkEvaluator.isPenalized());
+ verify(mEvaluatorCallback, times(2)).onEvaluationResultChanged();
+ mTestLooper.moveTimeForward(PENALTY_TIMEOUT_MS);
+ assertNull(mTestLooper.nextMessage());
+ }
+
+ @Test
+ public void testRcvValidationResult_penalizeNetwork_closeEvaluator() throws Exception {
+ checkPenalizeNetwork();
+
+ mNetworkEvaluator.close();
+
+ // Verify penalty timeout is canceled
+ mTestLooper.moveTimeForward(PENALTY_TIMEOUT_MS);
+ assertNull(mTestLooper.nextMessage());
+ }
+
+ @Test
+ public void testRcvValidationResult_PenaltyStateUnchanged() throws Exception {
+ assertFalse(mNetworkEvaluator.isPenalized());
+
+ // Validation passed
+ when(mIpSecPacketLossDetector.isValidationFailed()).thenReturn(false);
+ getMetricMonitorCbCaptor().onValidationResultReceived();
+
+ // Verifications
+ assertFalse(mNetworkEvaluator.isPenalized());
+ verify(mEvaluatorCallback, never()).onEvaluationResultChanged();
+ }
+
+ @Test
+ public void testSetCarrierConfig() throws Exception {
+ final int additionalTimeoutMin = 10;
+ when(mCarrierConfig.getIntArray(
+ eq(VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY), anyObject()))
+ .thenReturn(new int[] {PENALTY_TIMEOUT_MIN + additionalTimeoutMin});
+
+ // Update evaluator and penalize the network
+ mNetworkEvaluator.reevaluate(
+ VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+ SUB_GROUP,
+ mSubscriptionSnapshot,
+ mCarrierConfig);
+ checkPenalizeNetwork();
+
+ // Verify penalty timeout is changed
+ mTestLooper.moveTimeForward(PENALTY_TIMEOUT_MS);
+ assertNull(mTestLooper.nextMessage());
+ mTestLooper.moveTimeForward(TimeUnit.MINUTES.toMillis(additionalTimeoutMin));
+ assertNotNull(mTestLooper.nextMessage());
+
+ // Verify NetworkMetricMonitor is notified
+ verify(mIpSecPacketLossDetector).setCarrierConfig(any());
+ }
+
+ @Test
+ public void testCompare() throws Exception {
+ when(mIpSecPacketLossDetector.isValidationFailed()).thenReturn(true);
+ getMetricMonitorCbCaptor().onValidationResultReceived();
+
+ final UnderlyingNetworkEvaluator penalized = mNetworkEvaluator;
+ final UnderlyingNetworkEvaluator notPenalized = newValidUnderlyingNetworkEvaluator();
+
+ assertEquals(penalized.getPriorityClass(), notPenalized.getPriorityClass());
+
+ final int result =
+ UnderlyingNetworkEvaluator.getComparator(mVcnContext)
+ .compare(penalized, notPenalized);
+ assertEquals(1, result);
+ }
+}
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index f056110..1a82021 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -381,9 +381,9 @@
return true;
}
- bool FlattenTypeSpec(const ResourceTableTypeView& type,
- const std::vector<ResourceTableEntryView>& sorted_entries,
- BigBuffer* buffer) {
+ ResTable_typeSpec* FlattenTypeSpec(const ResourceTableTypeView& type,
+ const std::vector<ResourceTableEntryView>& sorted_entries,
+ BigBuffer* buffer) {
ChunkWriter type_spec_writer(buffer);
ResTable_typeSpec* spec_header =
type_spec_writer.StartChunk<ResTable_typeSpec>(RES_TABLE_TYPE_SPEC_TYPE);
@@ -391,7 +391,7 @@
if (sorted_entries.empty()) {
type_spec_writer.Finish();
- return true;
+ return spec_header;
}
// We can't just take the size of the vector. There may be holes in the
@@ -427,7 +427,7 @@
}
}
type_spec_writer.Finish();
- return true;
+ return spec_header;
}
bool FlattenTypes(BigBuffer* buffer) {
@@ -450,7 +450,8 @@
expected_type_id++;
type_pool_.MakeRef(type.named_type.to_string());
- if (!FlattenTypeSpec(type, type.entries, buffer)) {
+ const auto type_spec_header = FlattenTypeSpec(type, type.entries, buffer);
+ if (!type_spec_header) {
return false;
}
@@ -511,6 +512,10 @@
return false;
}
}
+
+ // And now we can update the type entries count in the typeSpec header.
+ type_spec_header->typesCount = android::util::HostToDevice16(uint16_t(std::min<uint32_t>(
+ config_to_entry_list_map.size(), std::numeric_limits<uint16_t>::max())));
}
return true;
}
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/Android.mk b/tools/aapt2/integration-tests/MergeOnlyTest/Android.mk
deleted file mode 100644
index 6361f9b..0000000
--- a/tools/aapt2/integration-tests/MergeOnlyTest/Android.mk
+++ /dev/null
@@ -1,2 +0,0 @@
-LOCAL_PATH := $(call my-dir)
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/App/Android.mk b/tools/aapt2/integration-tests/MergeOnlyTest/App/Android.mk
deleted file mode 100644
index 27b6068..0000000
--- a/tools/aapt2/integration-tests/MergeOnlyTest/App/Android.mk
+++ /dev/null
@@ -1,32 +0,0 @@
-//
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_NAMESPACES := true
-LOCAL_PACKAGE_NAME := AaptTestMergeOnly_App
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_EXPORT_PACKAGE_RESOURCES := true
-LOCAL_MODULE_TAGS := tests
-LOCAL_STATIC_ANDROID_LIBRARIES := \
- AaptTestMergeOnly_LeafLib \
- AaptTestMergeOnly_LocalLib
-include $(BUILD_PACKAGE)
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/Android.mk b/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/Android.mk
deleted file mode 100644
index c084849..0000000
--- a/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/Android.mk
+++ /dev/null
@@ -1,31 +0,0 @@
-//
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_NAMESPACES := true
-LOCAL_MODULE := AaptTestMergeOnly_LeafLib
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_MODULE_TAGS := tests
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_MIN_SDK_VERSION := 21
-LOCAL_AAPT_FLAGS := --merge-only
-include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/Android.mk b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/Android.mk
deleted file mode 100644
index 699ad79..0000000
--- a/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/Android.mk
+++ /dev/null
@@ -1,31 +0,0 @@
-//
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_NAMESPACES := true
-LOCAL_MODULE := AaptTestMergeOnly_LocalLib
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_MODULE_TAGS := tests
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_MIN_SDK_VERSION := 21
-LOCAL_AAPT_FLAGS := --merge-only
-include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tools/aapt2/integration-tests/NamespaceTest/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/Android.mk
deleted file mode 100644
index 6361f9b..0000000
--- a/tools/aapt2/integration-tests/NamespaceTest/Android.mk
+++ /dev/null
@@ -1,2 +0,0 @@
-LOCAL_PATH := $(call my-dir)
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk
deleted file mode 100644
index 98b7440..0000000
--- a/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_NAMESPACES := true
-LOCAL_PACKAGE_NAME := AaptTestNamespace_App
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_EXPORT_PACKAGE_RESOURCES := true
-LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_STATIC_ANDROID_LIBRARIES := \
- AaptTestNamespace_LibOne \
- AaptTestNamespace_LibTwo
-include $(BUILD_PACKAGE)
diff --git a/tools/aapt2/integration-tests/NamespaceTest/LibOne/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/LibOne/Android.mk
deleted file mode 100644
index dd41702..0000000
--- a/tools/aapt2/integration-tests/NamespaceTest/LibOne/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_NAMESPACES := true
-LOCAL_MODULE := AaptTestNamespace_LibOne
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_MODULE_TAGS := tests
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_MIN_SDK_VERSION := 21
-
-# We need this to retain the R.java generated for this library.
-LOCAL_JAR_EXCLUDE_FILES := none
-include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tools/aapt2/integration-tests/NamespaceTest/LibTwo/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/LibTwo/Android.mk
deleted file mode 100644
index 0d11bcb..0000000
--- a/tools/aapt2/integration-tests/NamespaceTest/LibTwo/Android.mk
+++ /dev/null
@@ -1,34 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_NAMESPACES := true
-LOCAL_MODULE := AaptTestNamespace_LibTwo
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_MIN_SDK_VERSION := 21
-
-# We need this to retain the R.java generated for this library.
-LOCAL_JAR_EXCLUDE_FILES := none
-include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tools/aapt2/integration-tests/NamespaceTest/Split/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/Split/Android.mk
deleted file mode 100644
index 30375728..0000000
--- a/tools/aapt2/integration-tests/NamespaceTest/Split/Android.mk
+++ /dev/null
@@ -1,32 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_NAMESPACES := true
-LOCAL_PACKAGE_NAME := AaptTestNamespace_Split
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_APK_LIBRARIES := AaptTestNamespace_App
-LOCAL_RES_LIBRARIES := AaptTestNamespace_App
-LOCAL_AAPT_FLAGS := --package-id 0x80 --rename-manifest-package com.android.aapt.namespace.app
-include $(BUILD_PACKAGE)
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/com/android/okhttp/internalandroidapi/Dns.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/com/android/okhttp/internalandroidapi/Dns.java
new file mode 100644
index 0000000..379c4ae
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/com/android/okhttp/internalandroidapi/Dns.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.okhttp.internalandroidapi;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.List;
+
+/**
+ * A domain name service that resolves IP addresses for host names.
+ * @hide
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface Dns {
+ /**
+ * Returns the IP addresses of {@code hostname}, in the order they should
+ * be attempted.
+ *
+ * @hide
+ */
+ List<InetAddress> lookup(String hostname) throws UnknownHostException;
+}