Merge "Remove ActivityStack#relativeBounds" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 705a4df..c231b30 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -57,7 +57,7 @@
":android.app.flags-aconfig-java{.generated_srcjars}",
":android.credentials.flags-aconfig-java{.generated_srcjars}",
":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}",
- ":com.android.server.flags.pinner-aconfig-java{.generated_srcjars}",
+ ":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}",
@@ -430,10 +430,7 @@
aconfig_declarations {
name: "com.android.media.flags.bettertogether-aconfig",
package: "com.android.media.flags",
- srcs: [
- "media/java/android/media/flags/media_better_together.aconfig",
- "media/java/android/media/flags/fade_manager_configuration.aconfig",
- ],
+ srcs: ["media/java/android/media/flags/media_better_together.aconfig"],
}
java_aconfig_library {
@@ -591,16 +588,16 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
-// Pinner Service
+// Server Services Flags
aconfig_declarations {
- name: "com.android.server.flags.pinner-aconfig",
+ name: "com.android.server.flags.services-aconfig",
package: "com.android.server.flags",
- srcs: ["services/core/java/com/android/server/flags/pinner.aconfig"],
+ srcs: ["services/core/java/com/android/server/flags/*.aconfig"],
}
java_aconfig_library {
- name: "com.android.server.flags.pinner-aconfig-java",
- aconfig_declarations: "com.android.server.flags.pinner-aconfig",
+ name: "com.android.server.flags.services-aconfig-java",
+ aconfig_declarations: "com.android.server.flags.services-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
diff --git a/Android.bp b/Android.bp
index 91f03e003..bb93048 100644
--- a/Android.bp
+++ b/Android.bp
@@ -97,6 +97,7 @@
// AIDL sources from external directories
":android.hardware.biometrics.common-V4-java-source",
":android.hardware.biometrics.fingerprint-V3-java-source",
+ ":android.hardware.biometrics.face-V4-java-source",
":android.hardware.gnss-V2-java-source",
":android.hardware.graphics.common-V3-java-source",
":android.hardware.keymaster-V4-java-source",
diff --git a/Ravenwood.bp b/Ravenwood.bp
index 03f3f0f..f330ad1 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -65,7 +65,7 @@
// depend on it.
java_genrule {
name: "framework-minus-apex.ravenwood",
- defaults: ["hoststubgen-for-prototype-only-genrule"],
+ defaults: ["ravenwood-internal-only-visibility-genrule"],
cmd: "cp $(in) $(out)",
srcs: [
":framework-minus-apex.ravenwood-base{ravenwood.jar}",
diff --git a/SECURITY_STATE_OWNERS b/SECURITY_STATE_OWNERS
new file mode 100644
index 0000000..30ddfe2
--- /dev/null
+++ b/SECURITY_STATE_OWNERS
@@ -0,0 +1,5 @@
+alxu@google.com
+musashi@google.com
+maunik@google.com
+davidkwak@google.com
+willcoster@google.com
\ No newline at end of file
diff --git a/apct-tests/perftests/healthconnect/Android.bp b/apct-tests/perftests/healthconnect/Android.bp
new file mode 100644
index 0000000..c2d0a6f
--- /dev/null
+++ b/apct-tests/perftests/healthconnect/Android.bp
@@ -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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "HealthConnectPerfTests",
+
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+
+ static_libs: [
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "apct-perftests-utils",
+ "collector-device-lib-platform",
+ ],
+
+ libs: ["android.test.base"],
+ platform_apis: true,
+ test_suites: ["device-tests"],
+ data: [":perfetto_artifacts"],
+ certificate: "platform",
+}
diff --git a/apct-tests/perftests/healthconnect/AndroidManifest.xml b/apct-tests/perftests/healthconnect/AndroidManifest.xml
new file mode 100644
index 0000000..6a6370b
--- /dev/null
+++ b/apct-tests/perftests/healthconnect/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.perftests.healthconnect">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.perftests.healthconnect"/>
+</manifest>
diff --git a/apct-tests/perftests/healthconnect/AndroidTest.xml b/apct-tests/perftests/healthconnect/AndroidTest.xml
new file mode 100644
index 0000000..5036202
--- /dev/null
+++ b/apct-tests/perftests/healthconnect/AndroidTest.xml
@@ -0,0 +1,61 @@
+<?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 HealthConnectPerfTests metric instrumentation.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-metric-instrumentation" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="HealthConnectPerfTests.apk" />
+ </target_preparer>
+
+ <!-- Needed for pushing the trace config file -->
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="push-file" key="trace_config_detailed.textproto" value="/data/misc/perfetto-traces/trace_config.textproto" />
+ </target_preparer>
+
+ <!-- Needed for pulling the collected trace config on to the host -->
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="pull-pattern-keys" value="perfetto_file_path" />
+ </metrics_collector>
+
+ <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
+ <option name="isolated-storage" value="false" />
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.perftests.healthconnect" />
+ <option name="hidden-api-checks" value="false"/>
+
+ <!-- Listener related args for collecting the traces and waiting for the device to stabilize. -->
+ <option name="device-listeners" value="android.device.collectors.ProcLoadListener,android.device.collectors.PerfettoListener" />
+ <!-- Guarantee that user defined RunListeners will be running before any of the default listeners defined in this runner. -->
+ <option name="instrumentation-arg" key="newRunListenerMode" value="true" />
+
+ <!-- ProcLoadListener related arguments -->
+ <!-- Wait for device last minute threshold to reach 3 with 2 minute timeout before starting the test run -->
+ <option name="instrumentation-arg" key="procload-collector:per_run" value="true" />
+ <option name="instrumentation-arg" key="proc-loadavg-threshold" value="3" />
+ <option name="instrumentation-arg" key="proc-loadavg-timeout" value="120000" />
+ <option name="instrumentation-arg" key="proc-loadavg-interval" value="10000" />
+
+ <!-- PerfettoListener related arguments -->
+ <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true" />
+ <option name="instrumentation-arg" key="perfetto_config_file" value="trace_config.textproto" />
+
+ <option name="instrumentation-arg" key="newRunListenerMode" value="true" />
+ </test>
+</configuration>
diff --git a/apct-tests/perftests/healthconnect/OWNERS b/apct-tests/perftests/healthconnect/OWNERS
new file mode 100644
index 0000000..da0b46a
--- /dev/null
+++ b/apct-tests/perftests/healthconnect/OWNERS
@@ -0,0 +1,6 @@
+# Bug component: 1219472
+
+arkivanov@google.com
+jstembridge@google.com
+pratyushmore@google.com
+itsleo@google.com
diff --git a/apct-tests/perftests/healthconnect/src/com/android/perftests/healthconnect/HealthConnectReadWritePerfTest.kt b/apct-tests/perftests/healthconnect/src/com/android/perftests/healthconnect/HealthConnectReadWritePerfTest.kt
new file mode 100644
index 0000000..21a4ca0
--- /dev/null
+++ b/apct-tests/perftests/healthconnect/src/com/android/perftests/healthconnect/HealthConnectReadWritePerfTest.kt
@@ -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 com.android.perftests.healthconnect
+
+import android.health.connect.HealthConnectManager
+import android.os.SystemClock
+import android.perftests.utils.PerfStatusReporter
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Read/write benchmark tests for [HealthConnectManager]
+ *
+ * Build/Install/Run: atest HealthConnectReadWritePerfTest
+ */
+@RunWith(AndroidJUnit4::class)
+class HealthConnectReadWritePerfTest {
+
+ @get:Rule
+ val perfStatusReporter = PerfStatusReporter()
+
+ private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
+
+ private val manager by lazy {
+ requireNotNull(context.getSystemService(HealthConnectManager::class.java))
+ }
+
+ /**
+ * A first empty test just to setup the test package and make sure it runs properly.
+ */
+ @Test
+ fun placeholder() {
+ val state = perfStatusReporter.benchmarkState
+ while (state.keepRunning()) {
+ SystemClock.sleep(100)
+ }
+ }
+}
\ No newline at end of file
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 83db4cb..d940e38 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1828,7 +1828,14 @@
/* system_measured_source_download_bytes */0,
/* system_measured_source_upload_bytes */ 0,
/* system_measured_calling_download_bytes */0,
- /* system_measured_calling_upload_bytes */ 0);
+ /* system_measured_calling_upload_bytes */ 0,
+ jobStatus.getJob().getIntervalMillis(),
+ jobStatus.getJob().getFlexMillis(),
+ jobStatus.hasFlexibilityConstraint(),
+ /* isFlexConstraintSatisfied */ false,
+ jobStatus.canApplyTransportAffinities(),
+ jobStatus.getNumAppliedFlexibleConstraints(),
+ jobStatus.getNumDroppedFlexibleConstraints());
// If the job is immediately ready to run, then we can just immediately
// put it in the pending list and try to schedule it. This is especially
@@ -2269,7 +2276,14 @@
/* system_measured_source_download_bytes */ 0,
/* system_measured_source_upload_bytes */ 0,
/* system_measured_calling_download_bytes */0,
- /* system_measured_calling_upload_bytes */ 0);
+ /* system_measured_calling_upload_bytes */ 0,
+ cancelled.getJob().getIntervalMillis(),
+ cancelled.getJob().getFlexMillis(),
+ cancelled.hasFlexibilityConstraint(),
+ cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_FLEXIBLE),
+ cancelled.canApplyTransportAffinities(),
+ cancelled.getNumAppliedFlexibleConstraints(),
+ cancelled.getNumDroppedFlexibleConstraints());
}
// If this is a replacement, bring in the new version of the job
if (incomingJob != null) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 6449edc..fe55e27 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -534,7 +534,14 @@
/* system_measured_source_download_bytes */ 0,
/* system_measured_source_upload_bytes */ 0,
/* system_measured_calling_download_bytes */ 0,
- /* system_measured_calling_upload_bytes */ 0);
+ /* system_measured_calling_upload_bytes */ 0,
+ job.getJob().getIntervalMillis(),
+ job.getJob().getFlexMillis(),
+ job.hasFlexibilityConstraint(),
+ job.isConstraintSatisfied(JobStatus.CONSTRAINT_FLEXIBLE),
+ job.canApplyTransportAffinities(),
+ job.getNumAppliedFlexibleConstraints(),
+ job.getNumDroppedFlexibleConstraints());
sEnqueuedJwiAtJobStart.logSampleWithUid(job.getUid(), job.getWorkCount());
final String sourcePackage = job.getSourcePackageName();
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
@@ -1616,7 +1623,14 @@
TrafficStats.getUidRxBytes(completedJob.getUid())
- mInitialDownloadedBytesFromCalling,
TrafficStats.getUidTxBytes(completedJob.getUid())
- - mInitialUploadedBytesFromCalling);
+ - mInitialUploadedBytesFromCalling,
+ completedJob.getJob().getIntervalMillis(),
+ completedJob.getJob().getFlexMillis(),
+ completedJob.hasFlexibilityConstraint(),
+ completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_FLEXIBLE),
+ completedJob.canApplyTransportAffinities(),
+ completedJob.getNumAppliedFlexibleConstraints(),
+ completedJob.getNumDroppedFlexibleConstraints());
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
getId());
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 0d5d11e..bdc2246 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -106,11 +106,8 @@
public static final long NO_LATEST_RUNTIME = Long.MAX_VALUE;
public static final long NO_EARLIEST_RUNTIME = 0L;
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public static final int CONSTRAINT_CHARGING = JobInfo.CONSTRAINT_FLAG_CHARGING; // 1 < 0
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE; // 1 << 2
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public static final int CONSTRAINT_BATTERY_NOT_LOW =
JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW; // 1 << 1
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
@@ -125,7 +122,7 @@
static final int CONSTRAINT_WITHIN_QUOTA = 1 << 24; // Implicit constraint
static final int CONSTRAINT_PREFETCH = 1 << 23;
static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1 << 22; // Implicit constraint
- static final int CONSTRAINT_FLEXIBLE = 1 << 21; // Implicit constraint
+ public static final int CONSTRAINT_FLEXIBLE = 1 << 21; // Implicit constraint
private static final int IMPLICIT_CONSTRAINTS = 0
| CONSTRAINT_BACKGROUND_NOT_RESTRICTED
@@ -1534,7 +1531,7 @@
/**
* Returns the number of required flexible job constraints that have been dropped with time.
- * The lower this number is the easier it is for the flexibility constraint to be satisfied.
+ * The higher this number is the easier it is for the flexibility constraint to be satisfied.
*/
public int getNumDroppedFlexibleConstraints() {
return mNumDroppedFlexibleConstraints;
@@ -1600,7 +1597,8 @@
mTransportAffinitiesSatisfied = isSatisfied;
}
- boolean canApplyTransportAffinities() {
+ /** Whether transport affinities can be applied to the job in flex scheduling. */
+ public boolean canApplyTransportAffinities() {
return mCanApplyTransportAffinities;
}
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
index 913a76a..4d4e340 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
@@ -591,6 +591,16 @@
if (idle) {
newBucket = IDLE_BUCKET_CUTOFF;
reason = REASON_MAIN_FORCED_BY_USER;
+ final AppUsageHistory appHistory = getAppUsageHistory(packageName, userId,
+ elapsedRealtime);
+ // Wipe all expiry times that could raise the bucket on reevaluation.
+ if (appHistory.bucketExpiryTimesMs != null) {
+ for (int i = appHistory.bucketExpiryTimesMs.size() - 1; i >= 0; --i) {
+ if (appHistory.bucketExpiryTimesMs.keyAt(i) < newBucket) {
+ appHistory.bucketExpiryTimesMs.removeAt(i);
+ }
+ }
+ }
} else {
newBucket = STANDBY_BUCKET_ACTIVE;
// This is to pretend that the app was just used, don't freeze the state anymore.
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index bd5c006..e589c21 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -59,6 +59,7 @@
import static com.android.server.usage.AppIdleHistory.STANDBY_BUCKET_UNKNOWN;
import android.annotation.CurrentTimeMillisLong;
+import android.annotation.DurationMillisLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -2146,6 +2147,15 @@
}
}
+ /**
+ * Flush the handler.
+ * Returns true if successfully flushed within the timeout, otherwise return false.
+ */
+ @VisibleForTesting
+ boolean flushHandler(@DurationMillisLong long timeoutMillis) {
+ return mHandler.runWithScissors(() -> {}, timeoutMillis);
+ }
+
@Override
public void flushToDisk() {
synchronized (mAppIdleLock) {
diff --git a/api/Android.bp b/api/Android.bp
index 9029d25..363197a 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -307,10 +307,6 @@
"framework-protos",
],
flags: [
- "--api-lint-ignore-prefix android.icu.",
- "--api-lint-ignore-prefix java.",
- "--api-lint-ignore-prefix junit.",
- "--api-lint-ignore-prefix org.",
"--error NoSettingsProvider",
"--error UnhiddenSystemApi",
"--error UnflaggedApi",
diff --git a/core/api/current.txt b/core/api/current.txt
index 12a6f74..a85a421 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9315,10 +9315,14 @@
public final class StorageStats implements android.os.Parcelable {
method public int describeContents();
method public long getAppBytes();
+ method @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public long getAppBytesByDataType(int);
method public long getCacheBytes();
method public long getDataBytes();
method public long getExternalCacheBytes();
method public void writeToParcel(android.os.Parcel, int);
+ field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_APK = 0; // 0x0
+ field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_DM = 1; // 0x1
+ field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_LIB = 2; // 0x2
field @NonNull public static final android.os.Parcelable.Creator<android.app.usage.StorageStats> CREATOR;
}
@@ -13418,12 +13422,12 @@
public final class SigningInfo implements android.os.Parcelable {
ctor public SigningInfo();
- ctor @FlaggedApi("android.content.pm.archiving") public SigningInfo(@IntRange(from=0) int, @Nullable java.util.Collection<android.content.pm.Signature>, @Nullable java.util.Collection<java.security.PublicKey>, @Nullable java.util.Collection<android.content.pm.Signature>);
+ ctor @FlaggedApi("android.content.pm.archiving") public SigningInfo(int, @Nullable java.util.Collection<android.content.pm.Signature>, @Nullable java.util.Collection<java.security.PublicKey>, @Nullable java.util.Collection<android.content.pm.Signature>);
ctor public SigningInfo(android.content.pm.SigningInfo);
method public int describeContents();
method public android.content.pm.Signature[] getApkContentsSigners();
method @FlaggedApi("android.content.pm.archiving") @NonNull public java.util.Collection<java.security.PublicKey> getPublicKeys();
- method @FlaggedApi("android.content.pm.archiving") @IntRange(from=0) public int getSchemeVersion();
+ method @FlaggedApi("android.content.pm.archiving") public int getSchemeVersion();
method public android.content.pm.Signature[] getSigningCertificateHistory();
method public boolean hasMultipleSigners();
method public boolean hasPastSigningCertificates();
@@ -23972,6 +23976,7 @@
method @Nullable public android.net.Uri getIconUri();
method @NonNull public String getId();
method @NonNull public CharSequence getName();
+ method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public int getSuitabilityStatus();
method public int getType();
method public int getVolume();
method public int getVolumeHandling();
@@ -23989,6 +23994,9 @@
field public static final String FEATURE_REMOTE_VIDEO_PLAYBACK = "android.media.route.feature.REMOTE_VIDEO_PLAYBACK";
field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0
field public static final int PLAYBACK_VOLUME_VARIABLE = 1; // 0x1
+ field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER = 2; // 0x2
+ field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER = 0; // 0x0
+ field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int SUITABILITY_STATUS_SUITABLE_FOR_MANUAL_TRANSFER = 1; // 0x1
field public static final int TYPE_BLE_HEADSET = 26; // 0x1a
field public static final int TYPE_BLUETOOTH_A2DP = 8; // 0x8
field public static final int TYPE_BUILTIN_SPEAKER = 2; // 0x2
@@ -24029,6 +24037,7 @@
method @NonNull public android.media.MediaRoute2Info.Builder setDescription(@Nullable CharSequence);
method @NonNull public android.media.MediaRoute2Info.Builder setExtras(@Nullable android.os.Bundle);
method @NonNull public android.media.MediaRoute2Info.Builder setIconUri(@Nullable android.net.Uri);
+ method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") @NonNull public android.media.MediaRoute2Info.Builder setSuitabilityStatus(int);
method @NonNull public android.media.MediaRoute2Info.Builder setType(int);
method @NonNull public android.media.MediaRoute2Info.Builder setVisibilityPublic();
method @NonNull public android.media.MediaRoute2Info.Builder setVisibilityRestricted(@NonNull java.util.Set<java.lang.String>);
@@ -24242,6 +24251,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();
}
public abstract static class MediaRouter2.TransferCallback {
@@ -24639,12 +24649,16 @@
method @Nullable public CharSequence getName();
method @NonNull public java.util.List<java.lang.String> getSelectableRoutes();
method @NonNull public java.util.List<java.lang.String> getSelectedRoutes();
+ method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public int getTransferReason();
method @NonNull public java.util.List<java.lang.String> getTransferableRoutes();
method public int getVolume();
method public int getVolumeHandling();
method public int getVolumeMax();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.media.RoutingSessionInfo> CREATOR;
+ field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int TRANSFER_REASON_APP = 2; // 0x2
+ field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int TRANSFER_REASON_FALLBACK = 0; // 0x0
+ field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int TRANSFER_REASON_SYSTEM_REQUEST = 1; // 0x1
}
public static final class RoutingSessionInfo.Builder {
@@ -24665,6 +24679,8 @@
method @NonNull public android.media.RoutingSessionInfo.Builder removeTransferableRoute(@NonNull String);
method @NonNull public android.media.RoutingSessionInfo.Builder setControlHints(@Nullable android.os.Bundle);
method @NonNull public android.media.RoutingSessionInfo.Builder setName(@Nullable CharSequence);
+ method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") @NonNull public android.media.RoutingSessionInfo.Builder setTransferInitiator(@Nullable android.os.UserHandle, @Nullable String);
+ method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") @NonNull public android.media.RoutingSessionInfo.Builder setTransferReason(int);
method @NonNull public android.media.RoutingSessionInfo.Builder setVolume(int);
method @NonNull public android.media.RoutingSessionInfo.Builder setVolumeHandling(int);
method @NonNull public android.media.RoutingSessionInfo.Builder setVolumeMax(int);
@@ -35874,19 +35890,19 @@
field public static final String NAMESPACE = "data2";
}
- public static final class ContactsContract.CommonDataKinds.Im implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
- method public static CharSequence getProtocolLabel(android.content.res.Resources, int, CharSequence);
- method public static int getProtocolLabelResource(int);
- method public static CharSequence getTypeLabel(android.content.res.Resources, int, @Nullable CharSequence);
- method public static int getTypeLabelResource(int);
- field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/im";
- field public static final String CUSTOM_PROTOCOL = "data6";
+ @Deprecated public static final class ContactsContract.CommonDataKinds.Im implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
+ method @Deprecated public static CharSequence getProtocolLabel(android.content.res.Resources, int, CharSequence);
+ method @Deprecated public static int getProtocolLabelResource(int);
+ method @Deprecated public static CharSequence getTypeLabel(android.content.res.Resources, int, @Nullable CharSequence);
+ method @Deprecated public static int getTypeLabelResource(int);
+ field @Deprecated public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/im";
+ field @Deprecated public static final String CUSTOM_PROTOCOL = "data6";
field public static final String EXTRA_ADDRESS_BOOK_INDEX = "android.provider.extra.ADDRESS_BOOK_INDEX";
field public static final String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "android.provider.extra.ADDRESS_BOOK_INDEX_COUNTS";
field public static final String EXTRA_ADDRESS_BOOK_INDEX_TITLES = "android.provider.extra.ADDRESS_BOOK_INDEX_TITLES";
- field public static final String PROTOCOL = "data5";
+ field @Deprecated public static final String PROTOCOL = "data5";
field @Deprecated public static final int PROTOCOL_AIM = 0; // 0x0
- field public static final int PROTOCOL_CUSTOM = -1; // 0xffffffff
+ field @Deprecated public static final int PROTOCOL_CUSTOM = -1; // 0xffffffff
field @Deprecated public static final int PROTOCOL_GOOGLE_TALK = 5; // 0x5
field @Deprecated public static final int PROTOCOL_ICQ = 6; // 0x6
field @Deprecated public static final int PROTOCOL_JABBER = 7; // 0x7
@@ -35895,9 +35911,9 @@
field @Deprecated public static final int PROTOCOL_QQ = 4; // 0x4
field @Deprecated public static final int PROTOCOL_SKYPE = 3; // 0x3
field @Deprecated public static final int PROTOCOL_YAHOO = 2; // 0x2
- field public static final int TYPE_HOME = 1; // 0x1
- field public static final int TYPE_OTHER = 3; // 0x3
- field public static final int TYPE_WORK = 2; // 0x2
+ field @Deprecated public static final int TYPE_HOME = 1; // 0x1
+ field @Deprecated public static final int TYPE_OTHER = 3; // 0x3
+ field @Deprecated public static final int TYPE_WORK = 2; // 0x2
}
public static final class ContactsContract.CommonDataKinds.Nickname implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
@@ -36012,17 +36028,17 @@
field public static final int TYPE_SPOUSE = 14; // 0xe
}
- public static final class ContactsContract.CommonDataKinds.SipAddress implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
- method public static CharSequence getTypeLabel(android.content.res.Resources, int, @Nullable CharSequence);
- method public static int getTypeLabelResource(int);
- field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/sip_address";
+ @Deprecated public static final class ContactsContract.CommonDataKinds.SipAddress implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
+ method @Deprecated public static CharSequence getTypeLabel(android.content.res.Resources, int, @Nullable CharSequence);
+ method @Deprecated public static int getTypeLabelResource(int);
+ field @Deprecated public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/sip_address";
field public static final String EXTRA_ADDRESS_BOOK_INDEX = "android.provider.extra.ADDRESS_BOOK_INDEX";
field public static final String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "android.provider.extra.ADDRESS_BOOK_INDEX_COUNTS";
field public static final String EXTRA_ADDRESS_BOOK_INDEX_TITLES = "android.provider.extra.ADDRESS_BOOK_INDEX_TITLES";
- field public static final String SIP_ADDRESS = "data1";
- field public static final int TYPE_HOME = 1; // 0x1
- field public static final int TYPE_OTHER = 3; // 0x3
- field public static final int TYPE_WORK = 2; // 0x2
+ field @Deprecated public static final String SIP_ADDRESS = "data1";
+ field @Deprecated public static final int TYPE_HOME = 1; // 0x1
+ field @Deprecated public static final int TYPE_OTHER = 3; // 0x3
+ field @Deprecated public static final int TYPE_WORK = 2; // 0x2
}
public static final class ContactsContract.CommonDataKinds.StructuredName implements android.provider.ContactsContract.DataColumnsWithJoins {
@@ -43277,6 +43293,7 @@
field public static final String KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY = "cdma_nonroaming_networks_string_array";
field public static final String KEY_CDMA_ROAMING_MODE_INT = "cdma_roaming_mode_int";
field public static final String KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY = "cdma_roaming_networks_string_array";
+ field @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public static final String KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY = "cellular_service_capabilities_int_array";
field public static final String KEY_CELLULAR_USAGE_SETTING_INT = "cellular_usage_setting_int";
field public static final String KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL = "check_pricing_with_carrier_data_roaming_bool";
field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_BOOL = "ci_action_on_sys_update_bool";
@@ -43403,6 +43420,7 @@
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT = "parameters_used_for_ntn_lte_signal_bar_int";
field public static final String KEY_PING_TEST_BEFORE_DATA_SWITCH_BOOL = "ping_test_before_data_switch_bool";
field public static final String KEY_PREFER_2G_BOOL = "prefer_2g_bool";
+ field @FlaggedApi("com.android.internal.telephony.flags.hide_prefer_3g_item") public static final String KEY_PREFER_3G_VISIBILITY_BOOL = "prefer_3g_visibility_bool";
field public static final String KEY_PREMIUM_CAPABILITY_MAXIMUM_DAILY_NOTIFICATION_COUNT_INT = "premium_capability_maximum_daily_notification_count_int";
field public static final String KEY_PREMIUM_CAPABILITY_MAXIMUM_MONTHLY_NOTIFICATION_COUNT_INT = "premium_capability_maximum_monthly_notification_count_int";
field public static final String KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG = "premium_capability_network_setup_time_millis_long";
@@ -45271,6 +45289,7 @@
method @Nullable public String getMncString();
method @Deprecated public String getNumber();
method public int getPortIndex();
+ method @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") @NonNull public java.util.Set<java.lang.Integer> getServiceCapabilities();
method public int getSimSlotIndex();
method public int getSubscriptionId();
method public int getSubscriptionType();
@@ -45352,6 +45371,9 @@
field public static final int PHONE_NUMBER_SOURCE_CARRIER = 2; // 0x2
field public static final int PHONE_NUMBER_SOURCE_IMS = 3; // 0x3
field public static final int PHONE_NUMBER_SOURCE_UICC = 1; // 0x1
+ field @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public static final int SERVICE_CAPABILITY_DATA = 3; // 0x3
+ field @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public static final int SERVICE_CAPABILITY_SMS = 2; // 0x2
+ field @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public static final int SERVICE_CAPABILITY_VOICE = 1; // 0x1
field public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0; // 0x0
field public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1; // 0x1
field public static final int USAGE_SETTING_DATA_CENTRIC = 2; // 0x2
@@ -45600,6 +45622,8 @@
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public boolean isDataEnabled();
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public boolean isDataEnabledForReason(int);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public boolean isDataRoamingEnabled();
+ method @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public boolean isDeviceSmsCapable();
+ method @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public boolean isDeviceVoiceCapable();
method public boolean isEmergencyNumber(@NonNull String);
method public boolean isHearingAidCompatibilitySupported();
method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRECISE_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public boolean isManualNetworkSelectionAllowed();
@@ -45609,9 +45633,9 @@
method @RequiresPermission(android.Manifest.permission.READ_BASIC_PHONE_STATE) public boolean isPremiumCapabilityAvailableForPurchase(int);
method public boolean isRadioInterfaceCapabilitySupported(@NonNull String);
method public boolean isRttSupported();
- method public boolean isSmsCapable();
+ method @Deprecated public boolean isSmsCapable();
method @Deprecated public boolean isTtyModeSupported();
- method public boolean isVoiceCapable();
+ method @Deprecated public boolean isVoiceCapable();
method public boolean isVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle);
method public boolean isWorldPhone();
method @Deprecated public void listen(android.telephony.PhoneStateListener, int);
@@ -53524,6 +53548,7 @@
method public android.transition.Transition getExitTransition();
method protected final int getFeatures();
method protected final int getForcedWindowFlags();
+ method @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public boolean getFrameRateBoostOnTouchEnabled();
method @Nullable public android.view.WindowInsetsController getInsetsController();
method @NonNull public abstract android.view.LayoutInflater getLayoutInflater();
method protected final int getLocalFeatures();
@@ -53601,6 +53626,7 @@
method public abstract void setFeatureInt(int, int);
method public void setFlags(int, int);
method public void setFormat(int);
+ method @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public void setFrameRateBoostOnTouchEnabled(boolean);
method public void setGravity(int);
method @RequiresPermission(android.Manifest.permission.HIDE_OVERLAY_WINDOWS) public final void setHideOverlayWindows(boolean);
method public void setIcon(@DrawableRes int);
@@ -53945,6 +53971,7 @@
method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public float getDesiredHdrHeadroom();
method public int getFitInsetsSides();
method public int getFitInsetsTypes();
+ method @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public boolean getFrameRateBoostOnTouchEnabled();
method public final CharSequence getTitle();
method public boolean isFitInsetsIgnoringVisibility();
method public boolean isHdrConversionEnabled();
@@ -53956,6 +53983,7 @@
method public void setFitInsetsIgnoringVisibility(boolean);
method public void setFitInsetsSides(int);
method public void setFitInsetsTypes(int);
+ method @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public void setFrameRateBoostOnTouchEnabled(boolean);
method public void setHdrConversionEnabled(boolean);
method public final void setTitle(CharSequence);
method public void setWallpaperTouchEventsEnabled(boolean);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 8ce3a8d..51e61e6 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3350,7 +3350,7 @@
}
@FlaggedApi("android.companion.virtual.flags.virtual_camera") public interface VirtualCameraCallback {
- method public void onProcessCaptureRequest(int, long, @Nullable android.companion.virtual.camera.VirtualCameraMetadata);
+ 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);
}
@@ -3371,12 +3371,6 @@
method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setVirtualCameraCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.camera.VirtualCameraCallback);
}
- @FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCameraMetadata implements android.os.Parcelable {
- method public int describeContents();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.camera.VirtualCameraMetadata> CREATOR;
- }
-
@FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCameraStreamConfig implements android.os.Parcelable {
ctor public VirtualCameraStreamConfig(@IntRange(from=1) int, @IntRange(from=1) int, int);
method public int describeContents();
@@ -6478,6 +6472,7 @@
method public void clearAudioServerStateCallback();
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean clearPreferredDevicesForCapturePreset(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int dispatchAudioFocusChange(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
+ method @FlaggedApi("android.media.audiopolicy.enable_fade_manager_configuration") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public int dispatchAudioFocusChangeWithFade(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy, @NonNull java.util.List<android.media.AudioFocusInfo>, @Nullable android.media.FadeManagerConfiguration);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int[] getActiveAssistantServicesUids();
method @IntRange(from=0) public long getAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int[] getAssistantServicesUids();
@@ -6677,6 +6672,69 @@
field public static final int CONTENT_ID_NONE = 0; // 0x0
}
+ @FlaggedApi("android.media.audiopolicy.enable_fade_manager_configuration") public final class FadeManagerConfiguration implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.List<android.media.AudioAttributes> getAudioAttributesWithVolumeShaperConfigs();
+ method public long getFadeInDelayForOffenders();
+ method public long getFadeInDurationForAudioAttributes(@NonNull android.media.AudioAttributes);
+ method public long getFadeInDurationForUsage(int);
+ method @Nullable public android.media.VolumeShaper.Configuration getFadeInVolumeShaperConfigForAudioAttributes(@NonNull android.media.AudioAttributes);
+ method @Nullable public android.media.VolumeShaper.Configuration getFadeInVolumeShaperConfigForUsage(int);
+ method public long getFadeOutDurationForAudioAttributes(@NonNull android.media.AudioAttributes);
+ method public long getFadeOutDurationForUsage(int);
+ method @Nullable public android.media.VolumeShaper.Configuration getFadeOutVolumeShaperConfigForAudioAttributes(@NonNull android.media.AudioAttributes);
+ method @Nullable public android.media.VolumeShaper.Configuration getFadeOutVolumeShaperConfigForUsage(int);
+ method public int getFadeState();
+ method @NonNull public java.util.List<java.lang.Integer> getFadeableUsages();
+ method @NonNull public java.util.List<android.media.AudioAttributes> getUnfadeableAudioAttributes();
+ method @NonNull public java.util.List<java.lang.Integer> getUnfadeableContentTypes();
+ method @NonNull public java.util.List<java.lang.Integer> getUnfadeablePlayerTypes();
+ method @NonNull public java.util.List<java.lang.Integer> getUnfadeableUids();
+ method public boolean isAudioAttributesUnfadeable(@NonNull android.media.AudioAttributes);
+ method public boolean isContentTypeUnfadeable(int);
+ method public boolean isFadeEnabled();
+ method public boolean isPlayerTypeUnfadeable(int);
+ method public boolean isUidUnfadeable(int);
+ method public boolean isUsageFadeable(int);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.FadeManagerConfiguration> CREATOR;
+ field public static final long DURATION_NOT_SET = 0L; // 0x0L
+ field public static final int FADE_STATE_DISABLED = 0; // 0x0
+ field public static final int FADE_STATE_ENABLED_AUTO = 2; // 0x2
+ field public static final int FADE_STATE_ENABLED_DEFAULT = 1; // 0x1
+ field public static final String TAG = "FadeManagerConfiguration";
+ field public static final int VOLUME_SHAPER_SYSTEM_FADE_ID = 2; // 0x2
+ }
+
+ public static final class FadeManagerConfiguration.Builder {
+ ctor public FadeManagerConfiguration.Builder();
+ ctor public FadeManagerConfiguration.Builder(long, long);
+ ctor public FadeManagerConfiguration.Builder(@NonNull android.media.FadeManagerConfiguration);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder addFadeableUsage(int);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder addUnfadeableAudioAttributes(@NonNull android.media.AudioAttributes);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder addUnfadeableContentType(int);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder addUnfadeableUid(int);
+ method @NonNull public android.media.FadeManagerConfiguration build();
+ method @NonNull public android.media.FadeManagerConfiguration.Builder clearFadeableUsage(int);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder clearUnfadeableAudioAttributes(@NonNull android.media.AudioAttributes);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder clearUnfadeableContentType(int);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder clearUnfadeableUid(int);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeInDelayForOffenders(long);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeInDurationForAudioAttributes(@NonNull android.media.AudioAttributes, long);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeInDurationForUsage(int, long);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeInVolumeShaperConfigForAudioAttributes(@NonNull android.media.AudioAttributes, @Nullable android.media.VolumeShaper.Configuration);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeInVolumeShaperConfigForUsage(int, @Nullable android.media.VolumeShaper.Configuration);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeOutDurationForAudioAttributes(@NonNull android.media.AudioAttributes, long);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeOutDurationForUsage(int, long);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeOutVolumeShaperConfigForAudioAttributes(@NonNull android.media.AudioAttributes, @Nullable android.media.VolumeShaper.Configuration);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeOutVolumeShaperConfigForUsage(int, @Nullable android.media.VolumeShaper.Configuration);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeState(int);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeableUsages(@NonNull java.util.List<java.lang.Integer>);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setUnfadeableAudioAttributes(@NonNull java.util.List<android.media.AudioAttributes>);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setUnfadeableContentTypes(@NonNull java.util.List<java.lang.Integer>);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setUnfadeableUids(@NonNull java.util.List<java.lang.Integer>);
+ }
+
public class HwAudioSource {
method public boolean isPlaying();
method public void start();
@@ -6895,15 +6953,18 @@
public class AudioPolicy {
method public int attachMixes(@NonNull java.util.List<android.media.audiopolicy.AudioMix>);
+ method @FlaggedApi("android.media.audiopolicy.enable_fade_manager_configuration") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public int clearFadeManagerConfigurationForFocusLoss();
method public android.media.AudioRecord createAudioRecordSink(android.media.audiopolicy.AudioMix) throws java.lang.IllegalArgumentException;
method public android.media.AudioTrack createAudioTrackSource(android.media.audiopolicy.AudioMix) throws java.lang.IllegalArgumentException;
method public int detachMixes(@NonNull java.util.List<android.media.audiopolicy.AudioMix>);
+ method @FlaggedApi("android.media.audiopolicy.enable_fade_manager_configuration") @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public android.media.FadeManagerConfiguration getFadeManagerConfigurationForFocusLoss();
method public int getFocusDuckingBehavior();
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioFocusInfo> getFocusStack();
method public int getStatus();
method public boolean removeUidDeviceAffinity(int);
method public boolean removeUserIdDeviceAffinity(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean sendFocusLoss(@NonNull android.media.AudioFocusInfo) throws java.lang.IllegalStateException;
+ method @FlaggedApi("android.media.audiopolicy.enable_fade_manager_configuration") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public int setFadeManagerConfigurationForFocusLoss(@NonNull android.media.FadeManagerConfiguration);
method public int setFocusDuckingBehavior(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException;
method public void setRegistration(String);
method public boolean setUidDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 42daea2..aaeba66 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -369,11 +369,14 @@
}
public class NotificationManager {
+ method @FlaggedApi("android.app.modes_api") @NonNull public String addAutomaticZenRule(@NonNull android.app.AutomaticZenRule, boolean);
method public void cleanUpCallersAfter(long);
method public android.content.ComponentName getEffectsSuppressor();
method public boolean isNotificationPolicyAccessGrantedForPackage(@NonNull String);
+ method @FlaggedApi("android.app.modes_api") public boolean removeAutomaticZenRule(@NonNull String, boolean);
method @RequiresPermission(android.Manifest.permission.MANAGE_NOTIFICATION_LISTENERS) public void setNotificationListenerAccessGranted(@NonNull android.content.ComponentName, boolean, boolean);
method @RequiresPermission(android.Manifest.permission.MANAGE_TOAST_RATE_LIMITING) public void setToastRateLimitingEnabled(boolean);
+ method @FlaggedApi("android.app.modes_api") public boolean updateAutomaticZenRule(@NonNull String, @NonNull android.app.AutomaticZenRule, boolean);
method public void updateNotificationChannel(@NonNull String, int, @NonNull android.app.NotificationChannel);
}
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index ea9bb39..37692d3 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -537,8 +537,8 @@
/**
* Returns whether the given user requires credential entry at this time. This is used to
- * intercept activity launches for locked work apps due to work challenge being triggered or
- * when the profile user is yet to be unlocked.
+ * intercept activity launches for apps corresponding to locked profiles due to separate
+ * challenge being triggered or when the profile user is yet to be unlocked.
*/
public abstract boolean shouldConfirmCredentials(@UserIdInt int userId);
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 9438571..b7db5f5 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -167,7 +167,7 @@
void requestInterruptionFilterFromListener(in INotificationListener token, int interruptionFilter);
int getInterruptionFilterFromListener(in INotificationListener token);
void setOnNotificationPostedTrimFromListener(in INotificationListener token, int trim);
- void setInterruptionFilter(String pkg, int interruptionFilter);
+ void setInterruptionFilter(String pkg, int interruptionFilter, boolean fromUser);
void updateNotificationChannelGroupFromPrivilegedListener(in INotificationListener token, String pkg, in UserHandle user, in NotificationChannelGroup group);
void updateNotificationChannelFromPrivilegedListener(in INotificationListener token, String pkg, in UserHandle user, in NotificationChannel channel);
@@ -205,11 +205,11 @@
@UnsupportedAppUsage
ZenModeConfig getZenModeConfig();
NotificationManager.Policy getConsolidatedNotificationPolicy();
- oneway void setZenMode(int mode, in Uri conditionId, String reason);
+ oneway void setZenMode(int mode, in Uri conditionId, String reason, boolean fromUser);
oneway void notifyConditions(String pkg, in IConditionProvider provider, in Condition[] conditions);
boolean isNotificationPolicyAccessGranted(String pkg);
NotificationManager.Policy getNotificationPolicy(String pkg);
- void setNotificationPolicy(String pkg, in NotificationManager.Policy policy);
+ void setNotificationPolicy(String pkg, in NotificationManager.Policy policy, boolean fromUser);
boolean isNotificationPolicyAccessGrantedForPackage(String pkg);
void setNotificationPolicyAccessGranted(String pkg, boolean granted);
void setNotificationPolicyAccessGrantedForUser(String pkg, int userId, boolean granted);
@@ -217,12 +217,12 @@
Map<String, AutomaticZenRule> getAutomaticZenRules();
// TODO: b/310620812 - Remove getZenRules() when MODES_API is inlined.
List<ZenModeConfig.ZenRule> getZenRules();
- String addAutomaticZenRule(in AutomaticZenRule automaticZenRule, String pkg);
- boolean updateAutomaticZenRule(String id, in AutomaticZenRule automaticZenRule);
- boolean removeAutomaticZenRule(String id);
- boolean removeAutomaticZenRules(String packageName);
+ String addAutomaticZenRule(in AutomaticZenRule automaticZenRule, String pkg, boolean fromUser);
+ boolean updateAutomaticZenRule(String id, in AutomaticZenRule automaticZenRule, boolean fromUser);
+ boolean removeAutomaticZenRule(String id, boolean fromUser);
+ boolean removeAutomaticZenRules(String packageName, boolean fromUser);
int getRuleInstanceCount(in ComponentName owner);
- void setAutomaticZenRuleState(String id, in Condition condition);
+ void setAutomaticZenRuleState(String id, in Condition condition, boolean fromUser);
byte[] getBackupPayload(int user);
void applyRestore(in byte[] payload, int user);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index a510c77..476232c 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -7733,11 +7733,12 @@
} else if (mPictureIcon.getType() == Icon.TYPE_BITMAP) {
// If the icon contains a bitmap, use the old extra so that listeners which look
// for that extra can still find the picture. Don't include the new extra in
- // that case, to avoid duplicating data.
+ // that case, to avoid duplicating data. Leave the unused extra set to null to avoid
+ // crashing apps that came to expect it to be present but null.
extras.putParcelable(EXTRA_PICTURE, mPictureIcon.getBitmap());
- extras.remove(EXTRA_PICTURE_ICON);
+ extras.putParcelable(EXTRA_PICTURE_ICON, null);
} else {
- extras.remove(EXTRA_PICTURE);
+ extras.putParcelable(EXTRA_PICTURE, null);
extras.putParcelable(EXTRA_PICTURE_ICON, mPictureIcon);
}
}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index d23b16d..f76a45b 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1184,14 +1184,20 @@
*/
@UnsupportedAppUsage
public void setZenMode(int mode, Uri conditionId, String reason) {
+ setZenMode(mode, conditionId, reason, /* fromUser= */ false);
+ }
+
+ /** @hide */
+ public void setZenMode(int mode, Uri conditionId, String reason, boolean fromUser) {
INotificationManager service = getService();
try {
- service.setZenMode(mode, conditionId, reason);
+ service.setZenMode(mode, conditionId, reason, fromUser);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
+
/**
* @hide
*/
@@ -1325,9 +1331,19 @@
* @return The id of the newly created rule; null if the rule could not be created.
*/
public String addAutomaticZenRule(AutomaticZenRule automaticZenRule) {
+ return addAutomaticZenRule(automaticZenRule, /* fromUser= */ false);
+ }
+
+ /** @hide */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ @NonNull
+ public String addAutomaticZenRule(@NonNull AutomaticZenRule automaticZenRule,
+ boolean fromUser) {
INotificationManager service = getService();
try {
- return service.addAutomaticZenRule(automaticZenRule, mContext.getPackageName());
+ return service.addAutomaticZenRule(automaticZenRule,
+ mContext.getPackageName(), fromUser);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1347,9 +1363,17 @@
* @return Whether the rule was successfully updated.
*/
public boolean updateAutomaticZenRule(String id, AutomaticZenRule automaticZenRule) {
+ return updateAutomaticZenRule(id, automaticZenRule, /* fromUser= */ false);
+ }
+
+ /** @hide */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public boolean updateAutomaticZenRule(@NonNull String id,
+ @NonNull AutomaticZenRule automaticZenRule, boolean fromUser) {
INotificationManager service = getService();
try {
- return service.updateAutomaticZenRule(id, automaticZenRule);
+ return service.updateAutomaticZenRule(id, automaticZenRule, fromUser);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1367,9 +1391,20 @@
* @param condition The new state of this rule
*/
public void setAutomaticZenRuleState(@NonNull String id, @NonNull Condition condition) {
+ if (Flags.modesApi()) {
+ setAutomaticZenRuleState(id, condition,
+ /* fromUser= */ condition.source == Condition.SOURCE_USER_ACTION);
+ } else {
+ setAutomaticZenRuleState(id, condition, /* fromUser= */ false);
+ }
+ }
+
+ /** @hide */
+ public void setAutomaticZenRuleState(@NonNull String id, @NonNull Condition condition,
+ boolean fromUser) {
INotificationManager service = getService();
try {
- service.setAutomaticZenRuleState(id, condition);
+ service.setAutomaticZenRuleState(id, condition, fromUser);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1388,9 +1423,16 @@
* @return Whether the rule was successfully deleted.
*/
public boolean removeAutomaticZenRule(String id) {
+ return removeAutomaticZenRule(id, /* fromUser= */ false);
+ }
+
+ /** @hide */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public boolean removeAutomaticZenRule(@NonNull String id, boolean fromUser) {
INotificationManager service = getService();
try {
- return service.removeAutomaticZenRule(id);
+ return service.removeAutomaticZenRule(id, fromUser);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1402,9 +1444,14 @@
* @hide
*/
public boolean removeAutomaticZenRules(String packageName) {
+ return removeAutomaticZenRules(packageName, /* fromUser= */ false);
+ }
+
+ /** @hide */
+ public boolean removeAutomaticZenRules(String packageName, boolean fromUser) {
INotificationManager service = getService();
try {
- return service.removeAutomaticZenRules(packageName);
+ return service.removeAutomaticZenRules(packageName, fromUser);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1685,10 +1732,15 @@
*/
// TODO(b/309457271): Update documentation with VANILLA_ICE_CREAM behavior.
public void setNotificationPolicy(@NonNull Policy policy) {
+ setNotificationPolicy(policy, /* fromUser= */ false);
+ }
+
+ /** @hide */
+ public void setNotificationPolicy(@NonNull Policy policy, boolean fromUser) {
checkRequired("policy", policy);
INotificationManager service = getService();
try {
- service.setNotificationPolicy(mContext.getOpPackageName(), policy);
+ service.setNotificationPolicy(mContext.getOpPackageName(), policy, fromUser);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2685,9 +2737,16 @@
*/
// TODO(b/309457271): Update documentation with VANILLA_ICE_CREAM behavior.
public final void setInterruptionFilter(@InterruptionFilter int interruptionFilter) {
+ setInterruptionFilter(interruptionFilter, /* fromUser= */ false);
+ }
+
+ /** @hide */
+ public final void setInterruptionFilter(@InterruptionFilter int interruptionFilter,
+ boolean fromUser) {
final INotificationManager service = getService();
try {
- service.setInterruptionFilter(mContext.getOpPackageName(), interruptionFilter);
+ service.setInterruptionFilter(mContext.getOpPackageName(), interruptionFilter,
+ fromUser);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index 772b0b4..47d19ed 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -88,6 +88,10 @@
# Pinner
per-file pinner-client.aconfig = file:/core/java/android/app/pinner/OWNERS
+# BackgroundInstallControlManager
+per-file BackgroundInstallControlManager.java = file:/services/core/java/com/android/server/pm/OWNERS
+per-file background_install_control_manager.aconfig = file:/services/core/java/com/android/server/pm/OWNERS
+
# ResourcesManager
per-file ResourcesManager.java = file:RESOURCES_OWNERS
diff --git a/core/java/android/app/usage/StorageStats.java b/core/java/android/app/usage/StorageStats.java
index 8d25d7b..87d97d5 100644
--- a/core/java/android/app/usage/StorageStats.java
+++ b/core/java/android/app/usage/StorageStats.java
@@ -17,11 +17,16 @@
package android.app.usage;
import android.annotation.BytesLong;
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Storage statistics for a UID, package, or {@link UserHandle} on a single
* storage volume.
@@ -29,10 +34,47 @@
* @see StorageStatsManager
*/
public final class StorageStats implements Parcelable {
- /** {@hide} */ public long codeBytes;
- /** {@hide} */ public long dataBytes;
- /** {@hide} */ public long cacheBytes;
- /** {@hide} */ public long externalCacheBytes;
+ /** @hide */ public long codeBytes;
+ /** @hide */ public long dataBytes;
+ /** @hide */ public long cacheBytes;
+ /** @hide */ public long apkBytes;
+ /** @hide */ public long libBytes;
+ /** @hide */ public long dmBytes;
+ /** @hide */ public long externalCacheBytes;
+
+ /** Represents all .apk files in application code path.
+ * Can be used as an input to {@link #getAppBytesByDataType(int)}
+ * to get the sum of sizes for files of this type.
+ */
+ @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
+ public static final int APP_DATA_TYPE_FILE_TYPE_APK = 0;
+
+ /** Represents all .dm files in application code path.
+ * Can be used as an input to {@link #getAppBytesByDataType(int)}
+ * to get the sum of sizes for files of this type.
+ */
+ @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
+ public static final int APP_DATA_TYPE_FILE_TYPE_DM = 1;
+
+ /** Represents lib/ in application code path.
+ * Can be used as an input to {@link #getAppBytesByDataType(int)}
+ * to get the size of lib/ directory.
+ */
+ @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
+ public static final int APP_DATA_TYPE_LIB = 2;
+
+ /**
+ * Keep in sync with the file types defined above.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
+ @IntDef(flag = false, value = {
+ APP_DATA_TYPE_FILE_TYPE_APK,
+ APP_DATA_TYPE_FILE_TYPE_DM,
+ APP_DATA_TYPE_LIB,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AppDataType {}
/**
* Return the size of app. This includes {@code APK} files, optimized
@@ -48,6 +90,27 @@
}
/**
+ * Return the size of the specified data type. This includes files stored under
+ * application code path.
+ * <p>
+ * If there is more than one package inside a uid, the return represents the aggregated
+ * stats when query StorageStat for package or uid.
+ * The data is not collected and the return defaults to 0 when query StorageStats for user.
+ *
+ * <p>
+ * Data is isolated for each user on a multiuser device.
+ */
+ @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
+ public long getAppBytesByDataType(@AppDataType int dataType) {
+ switch (dataType) {
+ case APP_DATA_TYPE_FILE_TYPE_APK: return apkBytes;
+ case APP_DATA_TYPE_LIB: return libBytes;
+ case APP_DATA_TYPE_FILE_TYPE_DM: return dmBytes;
+ default: return 0;
+ }
+ }
+
+ /**
* Return the size of all data. This includes files stored under
* {@link Context#getDataDir()}, {@link Context#getCacheDir()},
* {@link Context#getCodeCacheDir()}.
@@ -98,6 +161,9 @@
this.codeBytes = in.readLong();
this.dataBytes = in.readLong();
this.cacheBytes = in.readLong();
+ this.apkBytes = in.readLong();
+ this.libBytes = in.readLong();
+ this.dmBytes = in.readLong();
this.externalCacheBytes = in.readLong();
}
@@ -111,6 +177,9 @@
dest.writeLong(codeBytes);
dest.writeLong(dataBytes);
dest.writeLong(cacheBytes);
+ dest.writeLong(apkBytes);
+ dest.writeLong(libBytes);
+ dest.writeLong(dmBytes);
dest.writeLong(externalCacheBytes);
}
diff --git a/core/java/android/app/usage/UsageEventsQuery.java b/core/java/android/app/usage/UsageEventsQuery.java
index 8c63d18..3cd2923 100644
--- a/core/java/android/app/usage/UsageEventsQuery.java
+++ b/core/java/android/app/usage/UsageEventsQuery.java
@@ -19,9 +19,11 @@
import android.annotation.CurrentTimeMillisLong;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.annotation.UserIdInt;
import android.app.usage.UsageEvents.Event;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
import android.util.ArraySet;
import com.android.internal.util.ArrayUtils;
@@ -40,11 +42,13 @@
private final @CurrentTimeMillisLong long mBeginTimeMillis;
private final @CurrentTimeMillisLong long mEndTimeMillis;
private final @Event.EventType int[] mEventTypes;
+ private final @UserIdInt int mUserId;
private UsageEventsQuery(@NonNull Builder builder) {
mBeginTimeMillis = builder.mBeginTimeMillis;
mEndTimeMillis = builder.mEndTimeMillis;
mEventTypes = ArrayUtils.convertToIntArray(builder.mEventTypes);
+ mUserId = builder.mUserId;
}
private UsageEventsQuery(Parcel in) {
@@ -53,6 +57,7 @@
int eventTypesLength = in.readInt();
mEventTypes = new int[eventTypesLength];
in.readIntArray(mEventTypes);
+ mUserId = in.readInt();
}
/**
@@ -87,6 +92,11 @@
return eventTypeSet;
}
+ /** @hide */
+ public @UserIdInt int getUserId() {
+ return mUserId;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -98,6 +108,7 @@
dest.writeLong(mEndTimeMillis);
dest.writeInt(mEventTypes.length);
dest.writeIntArray(mEventTypes);
+ dest.writeInt(mUserId);
}
@NonNull
@@ -126,6 +137,7 @@
private final @CurrentTimeMillisLong long mBeginTimeMillis;
private final @CurrentTimeMillisLong long mEndTimeMillis;
private final ArraySet<Integer> mEventTypes = new ArraySet<>();
+ private @UserIdInt int mUserId = UserHandle.USER_NULL;
/**
* Constructor that specifies the period for which to return events.
@@ -169,5 +181,15 @@
}
return this;
}
+
+ /**
+ * Specifices the user id for the query.
+ * @param userId for whom the query should be performed.
+ * @hide
+ */
+ public @NonNull Builder setUserId(@UserIdInt int userId) {
+ mUserId = userId;
+ return this;
+ }
}
}
diff --git a/core/java/android/app/usage/flags.aconfig b/core/java/android/app/usage/flags.aconfig
index a611255..4d9d911 100644
--- a/core/java/android/app/usage/flags.aconfig
+++ b/core/java/android/app/usage/flags.aconfig
@@ -35,3 +35,10 @@
description: " Feature flag to support filter based event query API"
bug: "194321117"
}
+
+flag {
+ name: "get_app_bytes_by_data_type_api"
+ namespace: "system_performance"
+ description: "Feature flag for collecting app data size by file type API"
+ bug: "294088945"
+}
diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig
index c95b864..ec2e5fe 100644
--- a/core/java/android/appwidget/flags.aconfig
+++ b/core/java/android/appwidget/flags.aconfig
@@ -12,4 +12,11 @@
namespace: "app_widgets"
description: "Enable adapter conversion to RemoteCollectionItemsAdapter"
bug: "245950570"
-}
\ No newline at end of file
+}
+
+flag {
+ name: "remove_app_widget_service_io_from_critical_path"
+ namespace: "app_widgets"
+ description: "Move state file IO to non-critical path"
+ bug: "312949280"
+}
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index b11840e..879656a 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -220,6 +220,12 @@
*/
public static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN
/**
+ * Test message type without a response.
+ *
+ * @hide
+ */
+ public static final int MESSAGE_ONEWAY_PING = 0x43807378; // +PIN
+ /**
* Message header assigned to the remote authentication handshakes.
*
* @hide
@@ -237,6 +243,18 @@
* @hide
*/
public static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES
+ /**
+ * Message header assigned to the one-way message sent from the wearable device.
+ *
+ * @hide
+ */
+ public static final int MESSAGE_ONEWAY_FROM_WEARABLE = 0x43708287; // +FRW
+ /**
+ * Message header assigned to the one-way message sent to the wearable device.
+ *
+ * @hide
+ */
+ public static final int MESSAGE_ONEWAY_TO_WEARABLE = 0x43847987; // +TOW
/**
* The length limit of Association tag.
diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl b/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl
index fac44b5..44942d6 100644
--- a/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl
+++ b/core/java/android/companion/virtual/camera/IVirtualCameraCallback.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.
@@ -16,11 +16,11 @@
package android.companion.virtual.camera;
import android.companion.virtual.camera.VirtualCameraStreamConfig;
-import android.companion.virtual.camera.VirtualCameraMetadata;
import android.view.Surface;
/**
- * Interface for the virtual camera service and system server to talk back to the virtual camera owner.
+ * Interface for the virtual camera service and system server to talk back to the virtual camera
+ * owner.
*
* @hide
*/
@@ -40,8 +40,7 @@
in VirtualCameraStreamConfig streamConfig);
/**
- * The client application is requesting a camera frame for the given streamId with the provided
- * metadata.
+ * 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,
@@ -52,16 +51,14 @@
* VirtualCameraStreamConfig)}
* @param frameId The frameId that is being requested. Each request will have a different
* frameId, that will be increasing for each call with a particular streamId.
- * @param metadata The metadata requested for the frame. The virtual camera should do its best
- * to honor the requested metadata.
*/
- oneway void onProcessCaptureRequest(
- int streamId, long frameId, in VirtualCameraMetadata metadata);
+ 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
+ * 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.
*/
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraCallback.java b/core/java/android/companion/virtual/camera/VirtualCameraCallback.java
index a18ae03..5b658b8 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraCallback.java
+++ b/core/java/android/companion/virtual/camera/VirtualCameraCallback.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.
@@ -18,7 +18,6 @@
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.companion.virtual.flags.Flags;
import android.view.Surface;
@@ -50,8 +49,7 @@
@NonNull VirtualCameraStreamConfig streamConfig);
/**
- * The client application is requesting a camera frame for the given streamId with the provided
- * metadata.
+ * 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,
@@ -62,12 +60,8 @@
* VirtualCameraStreamConfig)}
* @param frameId The frameId that is being requested. Each request will have a different
* frameId, that will be increasing for each call with a particular streamId.
- * @param metadata The metadata requested for the frame. The virtual camera should do its best
- * to honor the requested metadata but the consumer won't be informed about the metadata set
- * for a particular frame. If null, the requested frame can be anything the producer sends.
*/
- void onProcessCaptureRequest(
- int streamId, long frameId, @Nullable VirtualCameraMetadata metadata);
+ default void onProcessCaptureRequest(int streamId, long frameId) {}
/**
* The stream previously configured when {@link #onStreamConfigured(int, Surface,
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
index f1eb240..a939251 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
+++ b/core/java/android/companion/virtual/camera/VirtualCameraConfig.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.
@@ -224,9 +224,8 @@
}
@Override
- public void onProcessCaptureRequest(
- int streamId, long frameId, VirtualCameraMetadata metadata) {
- mExecutor.execute(() -> mCallback.onProcessCaptureRequest(streamId, frameId, metadata));
+ public void onProcessCaptureRequest(int streamId, long frameId) {
+ mExecutor.execute(() -> mCallback.onProcessCaptureRequest(streamId, frameId));
}
@Override
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.java b/core/java/android/companion/virtual/camera/VirtualCameraMetadata.java
deleted file mode 100644
index 1ba36d0..0000000
--- a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.companion.virtual.camera;
-
-import android.annotation.FlaggedApi;
-import android.annotation.NonNull;
-import android.annotation.SystemApi;
-import android.companion.virtual.flags.Flags;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Data structure used to store camera metadata compatible with VirtualCamera.
- *
- * @hide
- */
-@SystemApi
-@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
-public final class VirtualCameraMetadata implements Parcelable {
-
- /** @hide */
- public VirtualCameraMetadata(@NonNull Parcel in) {}
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {}
-
- @NonNull
- public static final Creator<VirtualCameraMetadata> CREATOR =
- new Creator<>() {
- @Override
- @NonNull
- public VirtualCameraMetadata createFromParcel(Parcel in) {
- return new VirtualCameraMetadata(in);
- }
-
- @Override
- @NonNull
- public VirtualCameraMetadata[] newArray(int size) {
- return new VirtualCameraMetadata[size];
- }
- };
-}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.java b/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.java
index e198821..1272f16 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.java
+++ b/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.java
@@ -24,6 +24,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.Objects;
+
/**
* The configuration of a single virtual camera stream.
*
@@ -98,6 +100,19 @@
return mHeight;
}
+ @Override
+ public boolean equals(Object o) {
+ 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;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mWidth, mHeight, mFormat);
+ }
+
/** Returns the {@link ImageFormat} of this stream. */
@ImageFormat.Format
public int getFormat() {
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index d13d962..12da665 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1393,6 +1393,18 @@
public static final long OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION = 255940284L;
/**
+ * Enables {@link #SCREEN_ORIENTATION_USER} which overrides any orientation requested
+ * by the activity. Fixed orientation apps can be overridden to fullscreen on large
+ * screen devices with ignoreOrientationRequest enabled with this override.
+ *
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ public static final long OVERRIDE_ANY_ORIENTATION_TO_USER = 310816437L;
+
+ /**
* Compares activity window layout min width/height with require space for multi window to
* determine if it can be put into multi window mode.
*/
diff --git a/core/java/android/content/pm/PackageStats.java b/core/java/android/content/pm/PackageStats.java
index 81cfc07..b919c4b 100644
--- a/core/java/android/content/pm/PackageStats.java
+++ b/core/java/android/content/pm/PackageStats.java
@@ -55,6 +55,18 @@
/** Size of cache used by the application. (e.g., /data/data/<app>/cache) */
public long cacheSize;
+ /** Size of .apk files of the application. */
+ /** @hide */
+ public long apkSize;
+
+ /** Size of the libraries of the application. */
+ /** @hide */
+ public long libSize;
+
+ /** Size of the .dm files of the application. */
+ /** @hide */
+ public long dmSize;
+
/**
* Size of the secure container on external storage holding the
* application's code.
@@ -108,6 +120,18 @@
sb.append(" cache=");
sb.append(cacheSize);
}
+ if (apkSize != 0) {
+ sb.append(" apk=");
+ sb.append(apkSize);
+ }
+ if (libSize != 0) {
+ sb.append(" lib=");
+ sb.append(libSize);
+ }
+ if (dmSize != 0) {
+ sb.append(" dm=");
+ sb.append(dmSize);
+ }
if (externalCodeSize != 0) {
sb.append(" extCode=");
sb.append(externalCodeSize);
@@ -149,6 +173,9 @@
codeSize = source.readLong();
dataSize = source.readLong();
cacheSize = source.readLong();
+ apkSize = source.readLong();
+ libSize = source.readLong();
+ dmSize = source.readLong();
externalCodeSize = source.readLong();
externalDataSize = source.readLong();
externalCacheSize = source.readLong();
@@ -162,6 +189,9 @@
codeSize = pStats.codeSize;
dataSize = pStats.dataSize;
cacheSize = pStats.cacheSize;
+ apkSize = pStats.apkSize;
+ libSize = pStats.libSize;
+ dmSize = pStats.dmSize;
externalCodeSize = pStats.externalCodeSize;
externalDataSize = pStats.externalDataSize;
externalCacheSize = pStats.externalCacheSize;
@@ -179,6 +209,9 @@
dest.writeLong(codeSize);
dest.writeLong(dataSize);
dest.writeLong(cacheSize);
+ dest.writeLong(apkSize);
+ dest.writeLong(libSize);
+ dest.writeLong(dmSize);
dest.writeLong(externalCodeSize);
dest.writeLong(externalDataSize);
dest.writeLong(externalCacheSize);
@@ -198,6 +231,9 @@
&& codeSize == otherStats.codeSize
&& dataSize == otherStats.dataSize
&& cacheSize == otherStats.cacheSize
+ && apkSize == otherStats.apkSize
+ && libSize == otherStats.libSize
+ && dmSize == otherStats.dmSize
&& externalCodeSize == otherStats.externalCodeSize
&& externalDataSize == otherStats.externalDataSize
&& externalCacheSize == otherStats.externalCacheSize
@@ -208,7 +244,8 @@
@Override
public int hashCode() {
return Objects.hash(packageName, userHandle, codeSize, dataSize,
- cacheSize, externalCodeSize, externalDataSize, externalCacheSize, externalMediaSize,
+ apkSize, libSize, dmSize, cacheSize, externalCodeSize,
+ externalDataSize, externalCacheSize, externalMediaSize,
externalObbSize);
}
diff --git a/core/java/android/content/pm/SigningDetails.java b/core/java/android/content/pm/SigningDetails.java
index 8c21974..bb09ad2 100644
--- a/core/java/android/content/pm/SigningDetails.java
+++ b/core/java/android/content/pm/SigningDetails.java
@@ -31,6 +31,8 @@
import libcore.util.HexEncoding;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.util.ArrayList;
@@ -49,6 +51,7 @@
private static final String TAG = "SigningDetails";
+ @Retention(RetentionPolicy.SOURCE)
@IntDef({SignatureSchemeVersion.UNKNOWN,
SignatureSchemeVersion.JAR,
SignatureSchemeVersion.SIGNING_BLOCK_V2,
diff --git a/core/java/android/content/pm/SigningInfo.java b/core/java/android/content/pm/SigningInfo.java
index a407704..23daaf2 100644
--- a/core/java/android/content/pm/SigningInfo.java
+++ b/core/java/android/content/pm/SigningInfo.java
@@ -17,9 +17,9 @@
package android.content.pm;
import android.annotation.FlaggedApi;
-import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.pm.SigningDetails.SignatureSchemeVersion;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArraySet;
@@ -53,7 +53,7 @@
* schemas</a>
*/
@FlaggedApi(Flags.FLAG_ARCHIVING)
- public SigningInfo(@IntRange(from = 0) int schemeVersion,
+ public SigningInfo(@SignatureSchemeVersion int schemeVersion,
@Nullable Collection<Signature> apkContentsSigners,
@Nullable Collection<PublicKey> publicKeys,
@Nullable Collection<Signature> signingCertificateHistory) {
@@ -168,7 +168,7 @@
* schemas</a>
*/
@FlaggedApi(Flags.FLAG_ARCHIVING)
- public @IntRange(from = 0) int getSchemeVersion() {
+ public @SignatureSchemeVersion int getSchemeVersion() {
return mSigningDetails.getSignatureSchemeVersion();
}
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index b04b7ba..60d5c14 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -115,3 +115,11 @@
description: "Feature flag to fix duplicated PackageManager flag values"
bug: "314815969"
}
+
+flag {
+ name: "provide_info_of_apk_in_apex"
+ namespace: "package_manager_service"
+ description: "Feature flag to provide the information of APK-in-APEX"
+ bug: "306329516"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 57025c2..9a1796f 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -56,3 +56,11 @@
description: "Add support to lock private space automatically after a time period"
bug: "303201022"
}
+
+flag {
+ name: "avatar_sync"
+ namespace: "multiuser"
+ description: "Implement new Avatar Picker outside of SetttingsLib with ability to select avatars from Google Account and synchronise to any changes."
+ bug: "296829976"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index f71e853..ffd7212 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -715,6 +715,14 @@
boolean startOffload();
void stopOffload();
+
+ /**
+ * Called when {@link DisplayOffloadSession} tries to block screen turning on.
+ *
+ * @param unblocker a {@link Runnable} executed upon all work required before screen turning
+ * on is done.
+ */
+ void onBlockingScreenOn(Runnable unblocker);
}
/** A session token that associates a internal display with a {@link DisplayOffloader}. */
@@ -734,6 +742,15 @@
*/
void updateBrightness(float brightness);
+ /**
+ * Called while display is turning to state ON to leave a small period for displayoffload
+ * session to finish some work.
+ *
+ * @param unblocker a {@link Runnable} used by displayoffload session to notify
+ * {@link DisplayManager} that it can continue turning screen on.
+ */
+ boolean blockScreenOn(Runnable unblocker);
+
/** Returns whether displayoffload supports the given display state. */
static boolean isSupportedOffloadState(int displayState) {
return Display.isSuspendedState(displayState);
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl b/core/java/android/hardware/face/FaceSensorConfigurations.aidl
similarity index 74%
rename from core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl
rename to core/java/android/hardware/face/FaceSensorConfigurations.aidl
index 6c1f0fc..26367b3 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl
+++ b/core/java/android/hardware/face/FaceSensorConfigurations.aidl
@@ -13,12 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package android.hardware.face;
-package android.companion.virtual.camera;
-
-/**
- * Data structure used to store {@link android.hardware.camera2.CameraMetadata} compatible with
- * VirtualCamera.
- * @hide
- */
-parcelable VirtualCameraMetadata;
+parcelable FaceSensorConfigurations;
\ No newline at end of file
diff --git a/core/java/android/hardware/face/FaceSensorConfigurations.java b/core/java/android/hardware/face/FaceSensorConfigurations.java
new file mode 100644
index 0000000..6ef692f
--- /dev/null
+++ b/core/java/android/hardware/face/FaceSensorConfigurations.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.face;
+
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.face.SensorProps;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Slog;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+
+/**
+ * Provides the sensor props for face sensor, if available.
+ * @hide
+ */
+public class FaceSensorConfigurations implements Parcelable {
+ private static final String TAG = "FaceSensorConfigurations";
+
+ private final boolean mResetLockoutRequiresChallenge;
+ private final Map<String, SensorProps[]> mSensorPropsMap;
+
+ public static final Creator<FaceSensorConfigurations> CREATOR =
+ new Creator<FaceSensorConfigurations>() {
+ @Override
+ public FaceSensorConfigurations createFromParcel(Parcel in) {
+ return new FaceSensorConfigurations(in);
+ }
+
+ @Override
+ public FaceSensorConfigurations[] newArray(int size) {
+ return new FaceSensorConfigurations[size];
+ }
+ };
+
+ public FaceSensorConfigurations(boolean resetLockoutRequiresChallenge) {
+ mResetLockoutRequiresChallenge = resetLockoutRequiresChallenge;
+ mSensorPropsMap = new HashMap<>();
+ }
+
+ protected FaceSensorConfigurations(Parcel in) {
+ mResetLockoutRequiresChallenge = in.readByte() != 0;
+ mSensorPropsMap = in.readHashMap(null, String.class, SensorProps[].class);
+ }
+
+ /**
+ * Process AIDL instances to extract sensor props and add it to the sensor map.
+ * @param aidlInstances available face AIDL instances
+ * @param getIFace function that provides the daemon for the specific instance
+ */
+ public void addAidlConfigs(@NonNull String[] aidlInstances,
+ @NonNull Function<String, IFace> getIFace) {
+ for (String aidlInstance : aidlInstances) {
+ final String fqName = IFace.DESCRIPTOR + "/" + aidlInstance;
+ IFace face = getIFace.apply(fqName);
+ try {
+ if (face != null) {
+ mSensorPropsMap.put(aidlInstance, face.getSensorProps());
+ } else {
+ Slog.e(TAG, "Unable to get declared service: " + fqName);
+ }
+ } catch (RemoteException e) {
+ Log.d(TAG, "Unable to get sensor properties!");
+ }
+ }
+ }
+
+ /**
+ * Parse through HIDL configuration and add it to the sensor map.
+ */
+ public void addHidlConfigs(@NonNull String[] hidlConfigStrings,
+ @NonNull Context context) {
+ final List<HidlFaceSensorConfig> hidlFaceSensorConfigs = new ArrayList<>();
+ for (String hidlConfig: hidlConfigStrings) {
+ final HidlFaceSensorConfig hidlFaceSensorConfig = new HidlFaceSensorConfig();
+ try {
+ hidlFaceSensorConfig.parse(hidlConfig, context);
+ } catch (Exception e) {
+ Log.e(TAG, "HIDL sensor configuration format is incorrect.");
+ continue;
+ }
+ if (hidlFaceSensorConfig.getModality() == TYPE_FACE) {
+ hidlFaceSensorConfigs.add(hidlFaceSensorConfig);
+ }
+ }
+ final String hidlHalInstanceName = "defaultHIDL";
+ mSensorPropsMap.put(hidlHalInstanceName, hidlFaceSensorConfigs.toArray(
+ new SensorProps[hidlFaceSensorConfigs.size()]));
+ }
+
+ /**
+ * Returns true if any face sensors have been added.
+ */
+ public boolean hasSensorConfigurations() {
+ return mSensorPropsMap.size() > 0;
+ }
+
+ /**
+ * Returns true if there is only a single face sensor configuration available.
+ */
+ public boolean isSingleSensorConfigurationPresent() {
+ return mSensorPropsMap.size() == 1;
+ }
+
+ /**
+ * Return sensor props for the given instance. If instance is not available,
+ * then null is returned.
+ */
+ @Nullable
+ public Pair<String, SensorProps[]> getSensorPairForInstance(String instance) {
+ if (mSensorPropsMap.containsKey(instance)) {
+ return new Pair<>(instance, mSensorPropsMap.get(instance));
+ }
+
+ return null;
+ }
+
+ /**
+ * Return the first pair of instance and sensor props, which does not correspond to the given
+ * If instance is not available, then null is returned.
+ */
+ @Nullable
+ public Pair<String, SensorProps[]> getSensorPairNotForInstance(String instance) {
+ Optional<String> notAVirtualInstance = mSensorPropsMap.keySet().stream().filter(
+ (instanceName) -> !instanceName.equals(instance)).findFirst();
+ return notAVirtualInstance.map(this::getSensorPairForInstance).orElseGet(
+ this::getSensorPair);
+ }
+
+ /**
+ * Returns the first pair of instance and sensor props that has been added to the map.
+ */
+ @Nullable
+ public Pair<String, SensorProps[]> getSensorPair() {
+ Optional<String> optionalInstance = mSensorPropsMap.keySet().stream().findFirst();
+ return optionalInstance.map(this::getSensorPairForInstance).orElse(null);
+
+ }
+
+ public boolean getResetLockoutRequiresChallenge() {
+ return mResetLockoutRequiresChallenge;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeByte((byte) (mResetLockoutRequiresChallenge ? 1 : 0));
+ dest.writeMap(mSensorPropsMap);
+ }
+}
diff --git a/core/java/android/hardware/face/HidlFaceSensorConfig.java b/core/java/android/hardware/face/HidlFaceSensorConfig.java
new file mode 100644
index 0000000..cab146d
--- /dev/null
+++ b/core/java/android/hardware/face/HidlFaceSensorConfig.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.face;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.common.CommonProps;
+import android.hardware.biometrics.common.SensorStrength;
+import android.hardware.biometrics.face.SensorProps;
+
+import com.android.internal.R;
+
+/**
+ * Parse HIDL face sensor config and map it to SensorProps.aidl to match AIDL.
+ * See core/res/res/values/config.xml config_biometric_sensors
+ * @hide
+ */
+public final class HidlFaceSensorConfig extends SensorProps {
+ private int mSensorId;
+ private int mModality;
+ private int mStrength;
+
+ /**
+ * Parse through the config string and map it to SensorProps.aidl.
+ * @throws IllegalArgumentException when config string has unexpected format
+ */
+ public void parse(@NonNull String config, @NonNull Context context)
+ throws IllegalArgumentException {
+ final String[] elems = config.split(":");
+ if (elems.length < 3) {
+ throw new IllegalArgumentException();
+ }
+ mSensorId = Integer.parseInt(elems[0]);
+ mModality = Integer.parseInt(elems[1]);
+ mStrength = Integer.parseInt(elems[2]);
+ mapHidlToAidlFaceSensorConfigurations(context);
+ }
+
+ @BiometricAuthenticator.Modality
+ public int getModality() {
+ return mModality;
+ }
+
+ private void mapHidlToAidlFaceSensorConfigurations(@NonNull Context context) {
+ commonProps = new CommonProps();
+ commonProps.sensorId = mSensorId;
+ commonProps.sensorStrength = authenticatorStrengthToPropertyStrength(mStrength);
+ halControlsPreview = context.getResources().getBoolean(
+ R.bool.config_faceAuthSupportsSelfIllumination);
+ commonProps.maxEnrollmentsPerUser = context.getResources().getInteger(
+ R.integer.config_faceMaxTemplatesPerUser);
+ commonProps.componentInfo = null;
+ supportsDetectInteraction = false;
+ }
+
+ private byte authenticatorStrengthToPropertyStrength(
+ @BiometricManager.Authenticators.Types int strength) {
+ switch (strength) {
+ case BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE:
+ return SensorStrength.CONVENIENCE;
+ case BiometricManager.Authenticators.BIOMETRIC_WEAK:
+ return SensorStrength.WEAK;
+ case BiometricManager.Authenticators.BIOMETRIC_STRONG:
+ return SensorStrength.STRONG;
+ default:
+ throw new IllegalArgumentException("Unknown strength: " + strength);
+ }
+ }
+}
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 7080133..0096877 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -26,6 +26,7 @@
import android.hardware.face.Face;
import android.hardware.face.FaceAuthenticateOptions;
import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.FaceSensorConfigurations;
import android.view.Surface;
/**
@@ -167,6 +168,10 @@
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
void registerAuthenticators(in List<FaceSensorPropertiesInternal> hidlSensors);
+ //Register all available face sensors.
+ @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+ void registerAuthenticatorsLegacy(in FaceSensorConfigurations faceSensorConfigurations);
+
// Adds a callback which gets called when the service registers all of the face
// authenticators. The callback is automatically removed after it's invoked.
void addAuthenticatorsRegisteredCallback(IFaceAuthenticatorsRegisteredCallback callback);
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl b/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.aidl
similarity index 74%
copy from core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl
copy to core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.aidl
index 6c1f0fc..ebb05dc 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl
+++ b/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.aidl
@@ -13,12 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package android.hardware.fingerprint;
-package android.companion.virtual.camera;
-
-/**
- * Data structure used to store {@link android.hardware.camera2.CameraMetadata} compatible with
- * VirtualCamera.
- * @hide
- */
-parcelable VirtualCameraMetadata;
+parcelable FingerprintSensorConfigurations;
\ No newline at end of file
diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java b/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java
new file mode 100644
index 0000000..f214494a
--- /dev/null
+++ b/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.fingerprint;
+
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.SensorProps;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+
+/**
+ * Provides the sensor props for fingerprint sensor, if available.
+ * @hide
+ */
+
+public class FingerprintSensorConfigurations implements Parcelable {
+ private static final String TAG = "FingerprintSensorConfigurations";
+
+ private final Map<String, SensorProps[]> mSensorPropsMap;
+ private final boolean mResetLockoutRequiresHardwareAuthToken;
+
+ public static final Creator<FingerprintSensorConfigurations> CREATOR =
+ new Creator<>() {
+ @Override
+ public FingerprintSensorConfigurations createFromParcel(Parcel in) {
+ return new FingerprintSensorConfigurations(in);
+ }
+
+ @Override
+ public FingerprintSensorConfigurations[] newArray(int size) {
+ return new FingerprintSensorConfigurations[size];
+ }
+ };
+
+ public FingerprintSensorConfigurations(boolean resetLockoutRequiresHardwareAuthToken) {
+ mResetLockoutRequiresHardwareAuthToken = resetLockoutRequiresHardwareAuthToken;
+ mSensorPropsMap = new HashMap<>();
+ }
+
+ /**
+ * Process AIDL instances to extract sensor props and add it to the sensor map.
+ * @param aidlInstances available face AIDL instances
+ * @param getIFingerprint function that provides the daemon for the specific instance
+ */
+ public void addAidlSensors(@NonNull String[] aidlInstances,
+ @NonNull Function<String, IFingerprint> getIFingerprint) {
+ for (String aidlInstance : aidlInstances) {
+ try {
+ final String fqName = IFingerprint.DESCRIPTOR + "/" + aidlInstance;
+ final IFingerprint fp = getIFingerprint.apply(fqName);
+ if (fp != null) {
+ SensorProps[] props = fp.getSensorProps();
+ mSensorPropsMap.put(aidlInstance, props);
+ } else {
+ Log.d(TAG, "IFingerprint null for instance " + aidlInstance);
+ }
+ } catch (RemoteException e) {
+ Log.d(TAG, "Unable to get sensor properties!");
+ }
+ }
+ }
+
+ /**
+ * Parse through HIDL configuration and add it to the sensor map.
+ */
+ public void addHidlSensors(@NonNull String[] hidlConfigStrings,
+ @NonNull Context context) {
+ final List<HidlFingerprintSensorConfig> hidlFingerprintSensorConfigs = new ArrayList<>();
+ for (String hidlConfigString : hidlConfigStrings) {
+ final HidlFingerprintSensorConfig hidlFingerprintSensorConfig =
+ new HidlFingerprintSensorConfig();
+ try {
+ hidlFingerprintSensorConfig.parse(hidlConfigString, context);
+ } catch (Exception e) {
+ Log.e(TAG, "HIDL sensor configuration format is incorrect.");
+ continue;
+ }
+ if (hidlFingerprintSensorConfig.getModality() == TYPE_FINGERPRINT) {
+ hidlFingerprintSensorConfigs.add(hidlFingerprintSensorConfig);
+ }
+ }
+ final String hidlHalInstanceName = "defaultHIDL";
+ mSensorPropsMap.put(hidlHalInstanceName,
+ hidlFingerprintSensorConfigs.toArray(
+ new HidlFingerprintSensorConfig[hidlFingerprintSensorConfigs.size()]));
+ }
+
+ protected FingerprintSensorConfigurations(Parcel in) {
+ mResetLockoutRequiresHardwareAuthToken = in.readByte() != 0;
+ mSensorPropsMap = in.readHashMap(null /* loader */, String.class, SensorProps[].class);
+ }
+
+ /**
+ * Returns true if any fingerprint sensors have been added.
+ */
+ public boolean hasSensorConfigurations() {
+ return mSensorPropsMap.size() > 0;
+ }
+
+ /**
+ * Returns true if there is only a single fingerprint sensor configuration available.
+ */
+ public boolean isSingleSensorConfigurationPresent() {
+ return mSensorPropsMap.size() == 1;
+ }
+
+ /**
+ * Return sensor props for the given instance. If instance is not available,
+ * then null is returned.
+ */
+ @Nullable
+ public Pair<String, SensorProps[]> getSensorPairForInstance(String instance) {
+ if (mSensorPropsMap.containsKey(instance)) {
+ return new Pair<>(instance, mSensorPropsMap.get(instance));
+ }
+
+ return null;
+ }
+
+ /**
+ * Return the first pair of instance and sensor props, which does not correspond to the given
+ * If instance is not available, then null is returned.
+ */
+ @Nullable
+ public Pair<String, SensorProps[]> getSensorPairNotForInstance(String instance) {
+ Optional<String> notAVirtualInstance = mSensorPropsMap.keySet().stream().filter(
+ (instanceName) -> !instanceName.equals(instance)).findFirst();
+ return notAVirtualInstance.map(this::getSensorPairForInstance).orElseGet(
+ this::getSensorPair);
+ }
+
+ /**
+ * Returns the first pair of instance and sensor props that has been added to the map.
+ */
+ @Nullable
+ public Pair<String, SensorProps[]> getSensorPair() {
+ Optional<String> optionalInstance = mSensorPropsMap.keySet().stream().findFirst();
+ return optionalInstance.map(this::getSensorPairForInstance).orElse(null);
+
+ }
+
+ public boolean getResetLockoutRequiresHardwareAuthToken() {
+ return mResetLockoutRequiresHardwareAuthToken;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeByte((byte) (mResetLockoutRequiresHardwareAuthToken ? 1 : 0));
+ dest.writeMap(mSensorPropsMap);
+ }
+}
diff --git a/core/java/android/hardware/fingerprint/HidlFingerprintSensorConfig.java b/core/java/android/hardware/fingerprint/HidlFingerprintSensorConfig.java
new file mode 100644
index 0000000..d481153
--- /dev/null
+++ b/core/java/android/hardware/fingerprint/HidlFingerprintSensorConfig.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.fingerprint;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.common.CommonProps;
+import android.hardware.biometrics.common.SensorStrength;
+import android.hardware.biometrics.fingerprint.FingerprintSensorType;
+import android.hardware.biometrics.fingerprint.SensorLocation;
+import android.hardware.biometrics.fingerprint.SensorProps;
+
+import com.android.internal.R;
+import com.android.internal.util.ArrayUtils;
+
+/**
+ * Parse HIDL fingerprint sensor config and map it to SensorProps.aidl to match AIDL.
+ * See core/res/res/values/config.xml config_biometric_sensors
+ * @hide
+ */
+public final class HidlFingerprintSensorConfig extends SensorProps {
+ private int mSensorId;
+ private int mModality;
+ private int mStrength;
+
+ /**
+ * Parse through the config string and map it to SensorProps.aidl.
+ * @throws IllegalArgumentException when config string has unexpected format
+ */
+ public void parse(@NonNull String config, @NonNull Context context)
+ throws IllegalArgumentException {
+ final String[] elems = config.split(":");
+ if (elems.length < 3) {
+ throw new IllegalArgumentException();
+ }
+ mSensorId = Integer.parseInt(elems[0]);
+ mModality = Integer.parseInt(elems[1]);
+ mStrength = Integer.parseInt(elems[2]);
+ mapHidlToAidlSensorConfiguration(context);
+ }
+
+ @BiometricAuthenticator.Modality
+ public int getModality() {
+ return mModality;
+ }
+
+ private void mapHidlToAidlSensorConfiguration(@NonNull Context context) {
+ commonProps = new CommonProps();
+ commonProps.componentInfo = null;
+ commonProps.sensorId = mSensorId;
+ commonProps.sensorStrength = authenticatorStrengthToPropertyStrength(mStrength);
+ commonProps.maxEnrollmentsPerUser = context.getResources().getInteger(
+ R.integer.config_fingerprintMaxTemplatesPerUser);
+ halControlsIllumination = false;
+ sensorLocations = new SensorLocation[1];
+
+ final int[] udfpsProps = context.getResources().getIntArray(
+ com.android.internal.R.array.config_udfps_sensor_props);
+ final boolean isUdfps = !ArrayUtils.isEmpty(udfpsProps);
+ // config_is_powerbutton_fps indicates whether device has a power button fingerprint sensor.
+ final boolean isPowerbuttonFps = context.getResources().getBoolean(
+ R.bool.config_is_powerbutton_fps);
+
+ if (isUdfps) {
+ sensorType = FingerprintSensorType.UNKNOWN;
+ } else if (isPowerbuttonFps) {
+ sensorType = FingerprintSensorType.POWER_BUTTON;
+ } else {
+ sensorType = FingerprintSensorType.REAR;
+ }
+
+ if (isUdfps && udfpsProps.length == 3) {
+ setSensorLocation(udfpsProps[0], udfpsProps[1], udfpsProps[2]);
+ } else {
+ setSensorLocation(540 /* sensorLocationX */, 1636 /* sensorLocationY */,
+ 130 /* sensorRadius */);
+ }
+
+ }
+
+ private void setSensorLocation(int sensorLocationX,
+ int sensorLocationY, int sensorRadius) {
+ sensorLocations[0] = new SensorLocation();
+ sensorLocations[0].display = "";
+ sensorLocations[0].sensorLocationX = sensorLocationX;
+ sensorLocations[0].sensorLocationY = sensorLocationY;
+ sensorLocations[0].sensorRadius = sensorRadius;
+ }
+
+ private byte authenticatorStrengthToPropertyStrength(
+ @BiometricManager.Authenticators.Types int strength) {
+ switch (strength) {
+ case BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE:
+ return SensorStrength.CONVENIENCE;
+ case BiometricManager.Authenticators.BIOMETRIC_WEAK:
+ return SensorStrength.WEAK;
+ case BiometricManager.Authenticators.BIOMETRIC_STRONG:
+ return SensorStrength.STRONG;
+ default:
+ throw new IllegalArgumentException("Unknown strength: " + strength);
+ }
+ }
+}
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index f594c00..606b171 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -31,6 +31,7 @@
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.FingerprintSensorConfigurations;
import java.util.List;
/**
@@ -173,6 +174,10 @@
@EnforcePermission("MANAGE_FINGERPRINT")
void removeClientActiveCallback(IFingerprintClientActiveCallback callback);
+ //Register all available fingerprint sensors.
+ @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+ void registerAuthenticatorsLegacy(in FingerprintSensorConfigurations fingerprintSensorConfigurations);
+
// Registers all HIDL and AIDL sensors. Only HIDL sensor properties need to be provided, because
// AIDL sensor properties are retrieved directly from the available HALs. If no HIDL HALs exist,
// hidlSensors must be non-null and empty. See AuthService.java
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 209a595..d3f2c7a 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -92,3 +92,6 @@
per-file IThermal* = file:/THERMAL_OWNERS
per-file CoolingDevice.java = file:/THERMAL_OWNERS
per-file Temperature.java = file:/THERMAL_OWNERS
+
+# SecurityStateManager
+per-file *SecurityStateManager* = file:/SECURITY_STATE_OWNERS
\ No newline at end of file
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 4bfff16..7d00b80 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -6911,7 +6911,11 @@
* <td></td>
* </tr>
* </table>
+ *
+ * @deprecated This field may not be well supported by some contacts apps and is discouraged
+ * to use.
*/
+ @Deprecated
public static final class Im implements DataColumnsWithJoins, CommonColumns, ContactCounts {
/**
* This utility class cannot be instantiated
@@ -7721,7 +7725,11 @@
* <td></td>
* </tr>
* </table>
+ *
+ * @deprecated This field may not be well supported by some contacts apps and is discouraged
+ * to use.
*/
+ @Deprecated
public static final class SipAddress implements DataColumnsWithJoins, CommonColumns,
ContactCounts {
/**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 4af657d..942ce971 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -14339,6 +14339,14 @@
public static final String MODE_RINGER = "mode_ringer";
/**
+ * Whether or not Alarm stream should always be muted with Ringer.
+ *
+ * @hide
+ */
+ public static final String MUTE_ALARM_STREAM_WITH_RINGER_MODE =
+ "mute_alarm_stream_with_ringer_mode";
+
+ /**
* Overlay display devices setting.
* The associated value is a specially formatted string that describes the
* size and density of simulated secondary display devices.
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 72c436e..e4af2da 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -4940,6 +4940,13 @@
*/
public static final String COLUMN_IS_NTN = "is_ntn";
+ /**
+ * TelephonyProvider column name to indicate the service capability bitmasks.
+ *
+ * @hide
+ */
+ public static final String COLUMN_SERVICE_CAPABILITIES = "service_capabilities";
+
/** All columns in {@link SimInfo} table. */
private static final List<String> ALL_COLUMNS = List.of(
COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID,
@@ -5011,7 +5018,8 @@
COLUMN_USER_HANDLE,
COLUMN_SATELLITE_ENABLED,
COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER,
- COLUMN_IS_NTN
+ COLUMN_IS_NTN,
+ COLUMN_SERVICE_CAPABILITIES
);
/**
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 875031f..23c8393 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -960,8 +960,8 @@
mKeyphraseMetadata = new KeyphraseMetadata(1, mText, fakeSupportedLocales,
AlwaysOnHotwordDetector.RECOGNITION_MODE_VOICE_TRIGGER);
}
+ notifyStateChangedLocked();
}
- notifyStateChanged(availability);
}
/**
@@ -1371,8 +1371,8 @@
mAvailability = STATE_INVALID;
mIsAvailabilityOverriddenByTestApi = false;
+ notifyStateChangedLocked();
}
- notifyStateChanged(STATE_INVALID);
super.destroy();
}
@@ -1402,8 +1402,6 @@
*/
// TODO(b/281608561): remove the enrollment flow from AlwaysOnHotwordDetector
void onSoundModelsChanged() {
- boolean notifyError = false;
-
synchronized (mLock) {
if (mAvailability == STATE_INVALID
|| mAvailability == STATE_HARDWARE_UNAVAILABLE
@@ -1444,9 +1442,6 @@
// calling stopRecognition where there is no started session.
Log.w(TAG, "Failed to stop recognition after enrollment update: code="
+ result);
-
- // Execute a refresh availability task - which should then notify of a change.
- new RefreshAvailabilityTask().execute();
} catch (Exception e) {
Slog.w(TAG, "Failed to stop recognition after enrollment update", e);
if (CompatChanges.isChangeEnabled(SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS)) {
@@ -1455,14 +1450,14 @@
+ Log.getStackTraceString(e),
FailureSuggestedAction.RECREATE_DETECTOR));
} else {
- notifyError = true;
+ updateAndNotifyStateChangedLocked(STATE_ERROR);
}
+ return;
}
}
- }
- if (notifyError) {
- updateAndNotifyStateChanged(STATE_ERROR);
+ // Execute a refresh availability task - which should then notify of a change.
+ new RefreshAvailabilityTask().execute();
}
}
@@ -1578,11 +1573,10 @@
}
}
- private void updateAndNotifyStateChanged(int availability) {
- synchronized (mLock) {
- updateAvailabilityLocked(availability);
- }
- notifyStateChanged(availability);
+ @GuardedBy("mLock")
+ private void updateAndNotifyStateChangedLocked(int availability) {
+ updateAvailabilityLocked(availability);
+ notifyStateChangedLocked();
}
@GuardedBy("mLock")
@@ -1596,17 +1590,17 @@
}
}
- private void notifyStateChanged(int newAvailability) {
+ @GuardedBy("mLock")
+ private void notifyStateChangedLocked() {
Message message = Message.obtain(mHandler, MSG_AVAILABILITY_CHANGED);
- message.arg1 = newAvailability;
+ message.arg1 = mAvailability;
message.sendToTarget();
}
+ @GuardedBy("mLock")
private void sendUnknownFailure(String failureMessage) {
- synchronized (mLock) {
- // update but do not call onAvailabilityChanged callback for STATE_ERROR
- updateAvailabilityLocked(STATE_ERROR);
- }
+ // update but do not call onAvailabilityChanged callback for STATE_ERROR
+ updateAvailabilityLocked(STATE_ERROR);
Message.obtain(mHandler, MSG_DETECTION_UNKNOWN_FAILURE, failureMessage).sendToTarget();
}
@@ -1822,17 +1816,19 @@
availability = STATE_KEYPHRASE_UNENROLLED;
}
}
+ updateAndNotifyStateChangedLocked(availability);
}
- updateAndNotifyStateChanged(availability);
} catch (Exception e) {
// Any exception here not caught will crash the process because AsyncTask does not
// bubble up the exceptions to the client app, so we must propagate it to the app.
Slog.w(TAG, "Failed to refresh availability", e);
- if (CompatChanges.isChangeEnabled(SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS)) {
- sendUnknownFailure(
- "Failed to refresh availability: " + Log.getStackTraceString(e));
- } else {
- updateAndNotifyStateChanged(STATE_ERROR);
+ synchronized (mLock) {
+ if (CompatChanges.isChangeEnabled(SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS)) {
+ sendUnknownFailure(
+ "Failed to refresh availability: " + Log.getStackTraceString(e));
+ } else {
+ updateAndNotifyStateChangedLocked(STATE_ERROR);
+ }
}
}
diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java
index b7d9705..adc54f5 100644
--- a/core/java/android/service/voice/VisualQueryDetector.java
+++ b/core/java/android/service/voice/VisualQueryDetector.java
@@ -94,7 +94,9 @@
*/
public void updateState(@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory) {
- mInitializationDelegate.updateState(options, sharedMemory);
+ synchronized (mInitializationDelegate.getLock()) {
+ mInitializationDelegate.updateState(options, sharedMemory);
+ }
}
@@ -116,18 +118,21 @@
if (DEBUG) {
Slog.i(TAG, "#startRecognition");
}
- // check if the detector is active with the initialization delegate
- mInitializationDelegate.startRecognition();
+ synchronized (mInitializationDelegate.getLock()) {
+ // check if the detector is active with the initialization delegate
+ mInitializationDelegate.startRecognition();
- try {
- mManagerService.startPerceiving(new BinderCallback(mExecutor, mCallback));
- } catch (SecurityException e) {
- Slog.e(TAG, "startRecognition failed: " + e);
- return false;
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ try {
+ mManagerService.startPerceiving(new BinderCallback(
+ mExecutor, mCallback, mInitializationDelegate.getLock()));
+ } catch (SecurityException e) {
+ Slog.e(TAG, "startRecognition failed: " + e);
+ return false;
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return true;
}
- return true;
}
/**
@@ -140,15 +145,17 @@
if (DEBUG) {
Slog.i(TAG, "#stopRecognition");
}
- // check if the detector is active with the initialization delegate
- mInitializationDelegate.startRecognition();
+ synchronized (mInitializationDelegate.getLock()) {
+ // check if the detector is active with the initialization delegate
+ mInitializationDelegate.stopRecognition();
- try {
- mManagerService.stopPerceiving();
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ try {
+ mManagerService.stopPerceiving();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return true;
}
- return true;
}
/**
@@ -160,12 +167,16 @@
if (DEBUG) {
Slog.i(TAG, "#destroy");
}
- mInitializationDelegate.destroy();
+ synchronized (mInitializationDelegate.getLock()) {
+ mInitializationDelegate.destroy();
+ }
}
/** @hide */
public void dump(String prefix, PrintWriter pw) {
- // TODO: implement this
+ synchronized (mInitializationDelegate.getLock()) {
+ mInitializationDelegate.dump(prefix, pw);
+ }
}
/** @hide */
@@ -175,7 +186,9 @@
/** @hide */
void registerOnDestroyListener(Consumer<AbstractDetector> onDestroyListener) {
- mInitializationDelegate.registerOnDestroyListener(onDestroyListener);
+ synchronized (mInitializationDelegate.getLock()) {
+ mInitializationDelegate.registerOnDestroyListener(onDestroyListener);
+ }
}
/**
@@ -282,6 +295,15 @@
public boolean isUsingSandboxedDetectionService() {
return true;
}
+
+ @Override
+ public void dump(String prefix, PrintWriter pw) {
+ // No-op
+ }
+
+ private Object getLock() {
+ return mLock;
+ }
}
private static class BinderCallback
@@ -289,31 +311,43 @@
private final Executor mExecutor;
private final VisualQueryDetector.Callback mCallback;
- BinderCallback(Executor executor, VisualQueryDetector.Callback callback) {
+ private final Object mLock;
+
+ BinderCallback(Executor executor, VisualQueryDetector.Callback callback, Object lock) {
this.mExecutor = executor;
this.mCallback = callback;
+ this.mLock = lock;
}
/** Called when the detected result is valid. */
@Override
public void onQueryDetected(@NonNull String partialQuery) {
Slog.v(TAG, "BinderCallback#onQueryDetected");
- Binder.withCleanCallingIdentity(() -> mExecutor.execute(
- () -> mCallback.onQueryDetected(partialQuery)));
+ Binder.withCleanCallingIdentity(() -> {
+ synchronized (mLock) {
+ mCallback.onQueryDetected(partialQuery);
+ }
+ });
}
@Override
public void onQueryFinished() {
Slog.v(TAG, "BinderCallback#onQueryFinished");
- Binder.withCleanCallingIdentity(() -> mExecutor.execute(
- () -> mCallback.onQueryFinished()));
+ Binder.withCleanCallingIdentity(() -> {
+ synchronized (mLock) {
+ mCallback.onQueryFinished();
+ }
+ });
}
@Override
public void onQueryRejected() {
Slog.v(TAG, "BinderCallback#onQueryRejected");
- Binder.withCleanCallingIdentity(() -> mExecutor.execute(
- () -> mCallback.onQueryRejected()));
+ Binder.withCleanCallingIdentity(() -> {
+ synchronized (mLock) {
+ mCallback.onQueryRejected();
+ }
+ });
}
/** Called when the detection fails due to an error. */
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index 31d759e..18080e4 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -287,6 +287,16 @@
}
/**
+ * Called when a display hdcp levels change event is received.
+ *
+ * @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
+ * @param connectedLevel the new connected HDCP level
+ * @param maxLevel the maximum HDCP level
+ */
+ public void onHdcpLevelsChanged(long physicalDisplayId, int connectedLevel, int maxLevel) {
+ }
+
+ /**
* Represents a mapping between a UID and an override frame rate
*/
public static class FrameRateOverride {
@@ -374,4 +384,11 @@
onFrameRateOverridesChanged(timestampNanos, physicalDisplayId, overrides);
}
+ // Called from native code.
+ @SuppressWarnings("unused")
+ private void dispatchHdcpLevelsChanged(long physicalDisplayId, int connectedLevel,
+ int maxLevel) {
+ onHdcpLevelsChanged(physicalDisplayId, connectedLevel, maxLevel);
+ }
+
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index cbbe785..b957b31 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -22,7 +22,6 @@
import static android.graphics.Matrix.MSKEW_Y;
import static android.graphics.Matrix.MTRANS_X;
import static android.graphics.Matrix.MTRANS_Y;
-import static android.view.Display.INVALID_DISPLAY;
import static android.view.SurfaceControlProto.HASH_CODE;
import static android.view.SurfaceControlProto.LAYER_ID;
import static android.view.SurfaceControlProto.NAME;
@@ -38,7 +37,6 @@
import android.annotation.Size;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Context;
import android.graphics.ColorSpace;
import android.graphics.GraphicBuffer;
import android.graphics.Matrix;
@@ -53,13 +51,8 @@
import android.hardware.OverlayProperties;
import android.hardware.SyncFence;
import android.hardware.display.DeviceProductInfo;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.DisplayManagerGlobal;
import android.hardware.display.DisplayedContentSample;
import android.hardware.display.DisplayedContentSamplingAttributes;
-import android.hardware.display.IDisplayManager;
-import android.hardware.display.IVirtualDisplayCallback;
-import android.hardware.display.VirtualDisplay;
import android.hardware.graphics.common.DisplayDecorationSupport;
import android.opengl.EGLDisplay;
import android.opengl.EGLSync;
@@ -68,8 +61,6 @@
import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
-import android.os.RemoteException;
-import android.os.ServiceManager;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
@@ -2355,92 +2346,6 @@
}
/**
- * Because this API is now going through {@link DisplayManager}, orientation and displayRect
- * will automatically be computed based on configuration changes. Because of this, the params
- * orientation and displayRect are ignored
- *
- * @hide
- */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
- publicAlternatives = "Use {@code VirtualDisplay#resize(int, int, int)} instead.",
- trackingBug = 247078497)
- public static void setDisplayProjection(IBinder displayToken, int orientation,
- Rect layerStackRect, Rect displayRect) {
- DisplayManagerGlobal.getInstance().resizeVirtualDisplay(
- IVirtualDisplayCallback.Stub.asInterface(displayToken), layerStackRect.width(),
- layerStackRect.height(), 1);
- }
-
- /**
- * @hide
- */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
- publicAlternatives = "Use {@code MediaProjection#createVirtualDisplay()} with flag "
- + " {@code VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR} for mirroring instead.",
- trackingBug = 247078497)
- public static void setDisplayLayerStack(IBinder displayToken, int layerStack) {
- IBinder b = ServiceManager.getService(Context.DISPLAY_SERVICE);
- if (b == null) {
- throw new UnsupportedOperationException();
- }
-
- IDisplayManager dm = IDisplayManager.Stub.asInterface(b);
- try {
- dm.setDisplayIdToMirror(displayToken, layerStack);
- } catch (RemoteException e) {
- throw new UnsupportedOperationException(e);
- }
- }
-
- /**
- * @hide
- */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
- publicAlternatives = "Use {@code VirtualDisplay#setSurface(Surface)} instead.",
- trackingBug = 247078497)
- public static void setDisplaySurface(IBinder displayToken, Surface surface) {
- IVirtualDisplayCallback virtualDisplayCallback =
- IVirtualDisplayCallback.Stub.asInterface(displayToken);
- DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
- dm.setVirtualDisplaySurface(virtualDisplayCallback, surface);
- }
-
- /**
- * Secure is no longer supported because this is only called from outside system which cannot
- * create secure displays.
- * @hide
- */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
- publicAlternatives = "Use {@code MediaProjection#createVirtualDisplay()} or "
- + "{@code DisplayManager#createVirtualDisplay()} instead.",
- trackingBug = 247078497)
- public static IBinder createDisplay(String name, boolean secure) {
- if (name == null) {
- throw new IllegalArgumentException("name must not be null");
- }
-
- // We don't have a size yet so pass in 1 for width and height since 0 is invalid
- VirtualDisplay vd = DisplayManager.createVirtualDisplay(name, 1 /* width */, 1 /* height */,
- INVALID_DISPLAY, null /* Surface */);
- return vd == null ? null : vd.getToken().asBinder();
- }
-
- /**
- * @hide
- */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
- publicAlternatives = "Use {@code VirtualDisplay#release()} instead.",
- trackingBug = 247078497)
- public static void destroyDisplay(IBinder displayToken) {
- if (displayToken == null) {
- throw new IllegalArgumentException("displayToken must not be null");
- }
-
- DisplayManagerGlobal.getInstance().releaseVirtualDisplay(
- IVirtualDisplayCallback.Stub.asInterface(displayToken));
- }
-
- /**
* Returns whether protected content is supported in GPU composition.
* @hide
*/
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index cbafd1c..ec99459 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -32,6 +32,7 @@
import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH_ERROR_CODE;
import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
+import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision;
import static android.view.flags.Flags.toolkitSetFrameRateReadOnly;
import static android.view.flags.Flags.viewVelocityApi;
import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR;
@@ -2309,6 +2310,7 @@
protected static final int[] PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
private static boolean sToolkitSetFrameRateReadOnlyFlagValue;
+ private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
static {
EMPTY_STATE_SET = StateSet.get(0);
@@ -2393,6 +2395,7 @@
| StateSet.VIEW_STATE_PRESSED);
sToolkitSetFrameRateReadOnlyFlagValue = toolkitSetFrameRateReadOnly();
+ sToolkitMetricsForFrameRateDecisionFlagValue = toolkitMetricsForFrameRateDecision();
}
/**
@@ -33056,7 +33059,10 @@
}
private float getSizePercentage() {
- if (mResources == null || getVisibility() != VISIBLE) {
+ float alpha = mTransformationInfo != null ? mTransformationInfo.mAlpha : 1;
+ int visibility = mViewFlags & VISIBILITY_MASK;
+
+ if (mResources == null || alpha == 0 || visibility != VISIBLE) {
return 0;
}
@@ -33084,22 +33090,26 @@
ViewRootImpl viewRootImpl = getViewRootImpl();
float sizePercentage = getSizePercentage();
int frameRateCateogry = calculateFrameRateCategory(sizePercentage);
- if (sToolkitSetFrameRateReadOnlyFlagValue && viewRootImpl != null
- && sizePercentage > 0) {
- if (mPreferredFrameRate < 0) {
- if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
- frameRateCateogry = FRAME_RATE_CATEGORY_NO_PREFERENCE;
- } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) {
- frameRateCateogry = FRAME_RATE_CATEGORY_LOW;
- } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) {
- frameRateCateogry = FRAME_RATE_CATEGORY_NORMAL;
- } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) {
- frameRateCateogry = FRAME_RATE_CATEGORY_HIGH;
+ if (viewRootImpl != null && sizePercentage > 0) {
+ if (sToolkitSetFrameRateReadOnlyFlagValue) {
+ if (mPreferredFrameRate < 0) {
+ if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
+ frameRateCateogry = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+ } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) {
+ frameRateCateogry = FRAME_RATE_CATEGORY_LOW;
+ } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) {
+ frameRateCateogry = FRAME_RATE_CATEGORY_NORMAL;
+ } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) {
+ frameRateCateogry = FRAME_RATE_CATEGORY_HIGH;
+ }
+ } else {
+ viewRootImpl.votePreferredFrameRate(mPreferredFrameRate);
}
- } else {
- viewRootImpl.votePreferredFrameRate(mPreferredFrameRate);
+ viewRootImpl.votePreferredFrameRateCategory(frameRateCateogry);
}
- viewRootImpl.votePreferredFrameRateCategory(frameRateCateogry);
+ if (sToolkitMetricsForFrameRateDecisionFlagValue) {
+ viewRootImpl.recordViewPercentage(sizePercentage);
+ }
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e83488e..1f81a64 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -235,6 +235,7 @@
import com.android.internal.view.BaseSurfaceHolder;
import com.android.internal.view.RootViewSurfaceTaker;
import com.android.internal.view.SurfaceCallbackHelper;
+import com.android.modules.expresslog.Counter;
import com.android.window.flags.Flags;
import java.io.IOException;
@@ -252,7 +253,6 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.function.Predicate;
-
/**
* The top of a view hierarchy, implementing the needed protocol between View
* and the WindowManager. This is for the most part an internal implementation
@@ -528,8 +528,6 @@
// Set to true to stop input during an Activity Transition.
boolean mPausedForTransition = false;
- boolean mLastInCompatMode = false;
-
SurfaceHolder.Callback2 mSurfaceHolderCallback;
BaseSurfaceHolder mSurfaceHolder;
boolean mIsCreating;
@@ -830,6 +828,8 @@
private boolean mInsetsAnimationRunning;
private long mPreviousFrameDrawnTime = -1;
+ // The largest view size percentage to the display size. Used on trace to collect metric.
+ private float mLargestChildPercentage = 0.0f;
/**
* The resolved pointer icon type requested by this window.
@@ -1069,6 +1069,7 @@
private String mTag = TAG;
private String mFpsTraceName;
+ private String mLargestViewTraceName;
private static boolean sToolkitSetFrameRateReadOnlyFlagValue;
private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
@@ -1320,6 +1321,7 @@
attrs = mWindowAttributes;
setTag();
mFpsTraceName = "FPS of " + getTitle();
+ mLargestViewTraceName = "Largest view percentage(per hundred) of " + getTitle();
if (DEBUG_KEEP_SCREEN_ON && (mClientWindowLayoutFlags
& WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) != 0
@@ -1375,11 +1377,6 @@
}
if (DEBUG_LAYOUT) Log.d(mTag, "WindowLayout in setView:" + attrs);
- if (!compatibilityInfo.supportsScreen()) {
- attrs.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
- mLastInCompatMode = true;
- }
-
mSoftInputMode = attrs.softInputMode;
mWindowAttributesChanged = true;
mAttachInfo.mRootView = view;
@@ -1913,10 +1910,6 @@
// Keep track of the actual window flags supplied by the client.
mClientWindowLayoutFlags = attrs.flags;
- // Preserve compatible window flag if exists.
- final int compatibleWindowFlag = mWindowAttributes.privateFlags
- & WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
-
// Preserve system UI visibility.
final int systemUiVisibility = mWindowAttributes.systemUiVisibility;
final int subtreeSystemUiVisibility = mWindowAttributes.subtreeSystemUiVisibility;
@@ -1948,8 +1941,7 @@
mWindowAttributes.subtreeSystemUiVisibility = subtreeSystemUiVisibility;
mWindowAttributes.insetsFlags.appearance = appearance;
mWindowAttributes.insetsFlags.behavior = behavior;
- mWindowAttributes.privateFlags |= compatibleWindowFlag
- | appearanceAndBehaviorPrivateFlags;
+ mWindowAttributes.privateFlags |= appearanceAndBehaviorPrivateFlags;
if (mWindowAttributes.preservePreviousSurfaceInsets) {
// Restore old surface insets.
@@ -3150,21 +3142,6 @@
final boolean shouldOptimizeMeasure = shouldOptimizeMeasure(lp);
WindowManager.LayoutParams params = null;
- CompatibilityInfo compatibilityInfo =
- mDisplay.getDisplayAdjustments().getCompatibilityInfo();
- if (compatibilityInfo.supportsScreen() == mLastInCompatMode) {
- params = lp;
- mFullRedrawNeeded = true;
- mLayoutRequested = true;
- if (mLastInCompatMode) {
- params.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
- mLastInCompatMode = false;
- } else {
- params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
- mLastInCompatMode = true;
- }
- }
-
Rect frame = mWinFrame;
if (mFirst) {
mFullRedrawNeeded = true;
@@ -4766,6 +4743,10 @@
long fps = NANOS_PER_SEC / timeDiff;
Trace.setCounter(mFpsTraceName, fps);
mPreviousFrameDrawnTime = expectedDrawnTime;
+
+ long percentage = (long) (mLargestChildPercentage * 100);
+ Trace.setCounter(mLargestViewTraceName, percentage);
+ mLargestChildPercentage = 0.0f;
}
private void reportDrawFinished(@Nullable Transaction t, int seqId) {
@@ -5086,6 +5067,7 @@
if (DEBUG_FPS) {
trackFPS();
}
+
if (sToolkitMetricsForFrameRateDecisionFlagValue) {
collectFrameRateDecisionMetrics();
}
@@ -9723,6 +9705,9 @@
} else {
q.mReceiver.finishInputEvent(q.mEvent, handled);
}
+ if (q.mEvent instanceof KeyEvent) {
+ logHandledSystemKey((KeyEvent) q.mEvent, handled);
+ }
} else {
q.mEvent.recycleIfNeededAfterDispatch();
}
@@ -9730,6 +9715,19 @@
recycleQueuedInputEvent(q);
}
+ private void logHandledSystemKey(KeyEvent event, boolean handled) {
+ final int keyCode = event.getKeyCode();
+ if (keyCode != KeyEvent.KEYCODE_STEM_PRIMARY) {
+ return;
+ }
+ if (event.isDown() && event.getRepeatCount() == 0 && handled) {
+ // Initial DOWN event is handled. Log the stem primary key press.
+ Counter.logIncrementWithUid(
+ "input.value_app_handled_stem_primary_key_gestures_count",
+ Process.myUid());
+ }
+ }
+
static boolean isTerminalInputEvent(InputEvent event) {
if (event instanceof KeyEvent) {
final KeyEvent keyEvent = (KeyEvent)event;
@@ -12175,7 +12173,8 @@
|| motionEventAction == MotionEvent.ACTION_UP;
boolean undesiredType = windowType == TYPE_INPUT_METHOD;
// use toolkitSetFrameRate flag to gate the change
- return desiredAction && !undesiredType && sToolkitSetFrameRateReadOnlyFlagValue;
+ return desiredAction && !undesiredType && sToolkitSetFrameRateReadOnlyFlagValue
+ && getFrameRateBoostOnTouchEnabled();
}
/**
@@ -12250,6 +12249,15 @@
return mIsFrameRateBoosting;
}
+ /**
+ * Get the value of mFrameRateBoostOnTouchEnabled
+ * Can be used to checked if touch boost is enabled. The default value is true.
+ */
+ @VisibleForTesting
+ public boolean getFrameRateBoostOnTouchEnabled() {
+ return mWindowAttributes.getFrameRateBoostOnTouchEnabled();
+ }
+
private void boostFrameRate(int boostTimeOut) {
mIsFrameRateBoosting = true;
setPreferredFrameRateCategory(mPreferredFrameRateCategory);
@@ -12279,4 +12287,10 @@
void setBackKeyCallbackForWindowlessWindow(@NonNull Predicate<KeyEvent> callback) {
mWindowlessBackKeyCallback = callback;
}
+
+ void recordViewPercentage(float percentage) {
+ if (!Trace.isEnabled()) return;
+ // Record the largest view of percentage to the display size.
+ mLargestChildPercentage = Math.max(percentage, mLargestChildPercentage);
+ }
}
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 87537fbc..7bae7ec 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -334,6 +334,9 @@
private boolean mOverlayWithDecorCaptionEnabled = true;
private boolean mCloseOnSwipeEnabled = false;
+ private static boolean sToolkitSetFrameRateReadOnlyFlagValue =
+ android.view.flags.Flags.toolkitSetFrameRateReadOnly();
+
// The current window attributes.
@UnsupportedAppUsage
private final WindowManager.LayoutParams mWindowAttributes =
@@ -1373,6 +1376,39 @@
}
/**
+ * Sets whether the frame rate touch boost is enabled for this Window.
+ * When enabled, the frame rate will be boosted when a user touches the Window.
+ *
+ * @param enabled whether the frame rate touch boost is enabled.
+ * @see #getFrameRateBoostOnTouchEnabled()
+ * @see WindowManager.LayoutParams#setFrameRateBoostOnTouchEnabled(boolean)
+ */
+ @FlaggedApi(android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+ public void setFrameRateBoostOnTouchEnabled(boolean enabled) {
+ if (sToolkitSetFrameRateReadOnlyFlagValue) {
+ final WindowManager.LayoutParams attrs = getAttributes();
+ attrs.setFrameRateBoostOnTouchEnabled(enabled);
+ dispatchWindowAttributesChanged(attrs);
+ }
+ }
+
+ /**
+ * Get whether frame rate touch boost is enabled
+ * {@link #setFrameRateBoostOnTouchEnabled(boolean)}
+ *
+ * @return whether the frame rate touch boost is enabled.
+ * @see #setFrameRateBoostOnTouchEnabled(boolean)
+ * @see WindowManager.LayoutParams#getFrameRateBoostOnTouchEnabled()
+ */
+ @FlaggedApi(android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+ public boolean getFrameRateBoostOnTouchEnabled() {
+ if (sToolkitSetFrameRateReadOnlyFlagValue) {
+ return getAttributes().getFrameRateBoostOnTouchEnabled();
+ }
+ return true;
+ }
+
+ /**
* If {@code isPreferred} is true, this method requests that the connected display does minimal
* post processing when this window is visible on the screen. Otherwise, it requests that the
* display switches back to standard image processing.
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 1712fd3..f76822f 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -3143,13 +3143,6 @@
@UnsupportedAppUsage
public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 1 << 6;
- /** Window flag: special flag to limit the size of the window to be
- * original size ([320x480] x density). Used to create window for applications
- * running under compatibility mode.
- *
- * {@hide} */
- public static final int PRIVATE_FLAG_COMPATIBLE_WINDOW = 1 << 7;
-
/** Window flag: a special option intended for system dialogs. When
* this flag is set, the window will demand focus unconditionally when
* it is created.
@@ -3345,7 +3338,6 @@
SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION,
PRIVATE_FLAG_NO_MOVE_ANIMATION,
- PRIVATE_FLAG_COMPATIBLE_WINDOW,
PRIVATE_FLAG_SYSTEM_ERROR,
PRIVATE_FLAG_OPTIMIZE_MEASURE,
PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
@@ -3399,10 +3391,6 @@
equals = PRIVATE_FLAG_NO_MOVE_ANIMATION,
name = "NO_MOVE_ANIMATION"),
@ViewDebug.FlagToString(
- mask = PRIVATE_FLAG_COMPATIBLE_WINDOW,
- equals = PRIVATE_FLAG_COMPATIBLE_WINDOW,
- name = "COMPATIBLE_WINDOW"),
- @ViewDebug.FlagToString(
mask = PRIVATE_FLAG_SYSTEM_ERROR,
equals = PRIVATE_FLAG_SYSTEM_ERROR,
name = "SYSTEM_ERROR"),
@@ -4344,6 +4332,13 @@
private float mDesiredHdrHeadroom = 0;
/**
+ * For variable refresh rate project.
+ */
+ private boolean mFrameRateBoostOnTouch = true;
+ private static boolean sToolkitSetFrameRateReadOnlyFlagValue =
+ android.view.flags.Flags.toolkitSetFrameRateReadOnly();
+
+ /**
* Carries the requests about {@link WindowInsetsController.Appearance} and
* {@link WindowInsetsController.Behavior} to the system windows which can produce insets.
*
@@ -4778,6 +4773,32 @@
}
/**
+ * Set the value whether we should enable Touch Boost
+ *
+ * @param enabled Whether we should enable Touch Boost
+ */
+ @FlaggedApi(android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+ public void setFrameRateBoostOnTouchEnabled(boolean enabled) {
+ if (sToolkitSetFrameRateReadOnlyFlagValue) {
+ mFrameRateBoostOnTouch = enabled;
+ }
+ }
+
+ /**
+ * Get the value whether we should enable touch boost as set
+ * by {@link #setFrameRateBoostOnTouchEnabled(boolean)}
+ *
+ * @return A boolean value to indicate whether we should enable touch boost
+ */
+ @FlaggedApi(android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+ public boolean getFrameRateBoostOnTouchEnabled() {
+ if (sToolkitSetFrameRateReadOnlyFlagValue) {
+ return mFrameRateBoostOnTouch;
+ }
+ return true;
+ }
+
+ /**
* <p>
* Blurs the screen behind the window. The effect is similar to that of {@link #dimAmount},
* but instead of dimmed, the content behind the window will be blurred (or combined with
@@ -4928,6 +4949,9 @@
out.writeTypedArray(paramsForRotation, 0 /* parcelableFlags */);
out.writeInt(mDisplayFlags);
out.writeFloat(mDesiredHdrHeadroom);
+ if (sToolkitSetFrameRateReadOnlyFlagValue) {
+ out.writeBoolean(mFrameRateBoostOnTouch);
+ }
}
public static final @android.annotation.NonNull Parcelable.Creator<LayoutParams> CREATOR
@@ -5000,6 +5024,9 @@
paramsForRotation = in.createTypedArray(LayoutParams.CREATOR);
mDisplayFlags = in.readInt();
mDesiredHdrHeadroom = in.readFloat();
+ if (sToolkitSetFrameRateReadOnlyFlagValue) {
+ mFrameRateBoostOnTouch = in.readBoolean();
+ }
}
@SuppressWarnings({"PointlessBitwiseExpression"})
@@ -5336,6 +5363,12 @@
changes |= LAYOUT_CHANGED;
}
+ if (sToolkitSetFrameRateReadOnlyFlagValue
+ && mFrameRateBoostOnTouch != o.mFrameRateBoostOnTouch) {
+ mFrameRateBoostOnTouch = o.mFrameRateBoostOnTouch;
+ changes |= LAYOUT_CHANGED;
+ }
+
return changes;
}
@@ -5558,6 +5591,11 @@
sb.append(prefix).append(" forciblyShownTypes=").append(
WindowInsets.Type.toString(forciblyShownTypes));
}
+ if (sToolkitSetFrameRateReadOnlyFlagValue && mFrameRateBoostOnTouch) {
+ sb.append(System.lineSeparator());
+ sb.append(prefix).append(" frameRateBoostOnTouch=");
+ sb.append(mFrameRateBoostOnTouch);
+ }
if (paramsForRotation != null && paramsForRotation.length != 0) {
sb.append(System.lineSeparator());
sb.append(prefix).append(" paramsForRotation:");
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 53aed49..49a2843 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -5400,13 +5400,10 @@
public static final AccessibilityAction ACTION_PREVIOUS_HTML_ELEMENT =
new AccessibilityAction(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT);
+ // TODO(316638728): restore ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT in javadoc
/**
* Action to scroll the node content forward.
*
- * <p>
- * <strong>Arguments:</strong>
- * {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT}. This is an optional argument.
- * </p>
* <p>The UI element that implements this should send a
* {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event. Depending on the orientation,
* this element should also add the relevant directional scroll actions of
@@ -5447,12 +5444,10 @@
public static final AccessibilityAction ACTION_SCROLL_FORWARD =
new AccessibilityAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
+ // TODO(316638728): restore ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT in javadoc
/**
* Action to scroll the node content backward.
- * <p>
- * <strong>Arguments:</strong>
- * {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT}. This is an optional argument.
- * </p>
+ *
* <p>The UI element that implements this should send a
* {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event. Depending on the orientation,
* this element should also add the relevant directional scroll actions of
@@ -5647,48 +5642,40 @@
@NonNull public static final AccessibilityAction ACTION_SCROLL_IN_DIRECTION =
new AccessibilityAction(R.id.accessibilityActionScrollInDirection);
+ // TODO(316638728): restore ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT in javadoc
/**
* Action to scroll the node content up.
- * <p>
- * <strong>Arguments:</strong>
- * {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT}. This is an optional argument.
- * </p>
+ *
* <p>The UI element that implements this should send a
* {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
*/
public static final AccessibilityAction ACTION_SCROLL_UP =
new AccessibilityAction(R.id.accessibilityActionScrollUp);
+ // TODO(316638728): restore ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT in javadoc
/**
* Action to scroll the node content left.
- * <p>
- * <strong>Arguments:</strong>
- * {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT}. This is an optional argument.
- * </p>
+ *
* <p>The UI element that implements this should send a
* {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
*/
public static final AccessibilityAction ACTION_SCROLL_LEFT =
new AccessibilityAction(R.id.accessibilityActionScrollLeft);
+ // TODO(316638728): restore ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT in javadoc
/**
* Action to scroll the node content down.
- * <p>
- * <strong>Arguments:</strong>
- * {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT}. This is an optional argument.
- * </p>
+ *
* <p>The UI element that implements this should send a
* {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
*/
public static final AccessibilityAction ACTION_SCROLL_DOWN =
new AccessibilityAction(R.id.accessibilityActionScrollDown);
+ // TODO(316638728): restore ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT in javadoc
/**
* Action to scroll the node content right.
- * <p>
- * <strong>Arguments:</strong>
- * {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT}. This is an optional argument.
- * </p>
+ *
* <p>The UI element that implements this should send a
* {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
*/
diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
index b3359b7..70d8abe 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -23,9 +23,6 @@
import android.annotation.FlaggedApi;
import android.annotation.InterpolatorRes;
import android.annotation.TestApi;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledSince;
-import android.compat.annotation.Overridable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Resources;
@@ -55,18 +52,6 @@
private static final int TOGETHER = 0;
private static final int SEQUENTIALLY = 1;
- /**
- * For apps targeting {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} and above,
- * this change ID enables to use expectedPresentationTime instead of the frameTime
- * for the frame start time .
- *
- * @hide
- */
- @ChangeId
- @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM)
- @Overridable
- public static final long OVERRIDE_ENABLE_EXPECTED_PRSENTATION_TIME = 278730197L;
-
private static boolean sExpectedPresentationTimeFlagValue;
static {
sExpectedPresentationTimeFlagValue = expectedPresentationTimeReadOnly();
diff --git a/core/java/android/window/BackMotionEvent.java b/core/java/android/window/BackMotionEvent.java
index c475723..7cda3a3 100644
--- a/core/java/android/window/BackMotionEvent.java
+++ b/core/java/android/window/BackMotionEvent.java
@@ -36,6 +36,7 @@
private final float mProgress;
private final float mVelocityX;
private final float mVelocityY;
+ private final boolean mTriggerBack;
@BackEvent.SwipeEdge
private final int mSwipeEdge;
@@ -54,6 +55,7 @@
* Value in pixels/second. {@link Float#NaN} if was not computed.
* @param velocityY Y velocity computed from the touch point of this event.
* Value in pixels/second. {@link Float#NaN} if was not computed.
+ * @param triggerBack Indicates whether the back arrow is in the triggered state or not
* @param swipeEdge Indicates which edge the swipe starts from.
* @param departingAnimationTarget The remote animation target of the departing
* application window.
@@ -64,6 +66,7 @@
float progress,
float velocityX,
float velocityY,
+ boolean triggerBack,
@BackEvent.SwipeEdge int swipeEdge,
@Nullable RemoteAnimationTarget departingAnimationTarget) {
mTouchX = touchX;
@@ -71,6 +74,7 @@
mProgress = progress;
mVelocityX = velocityX;
mVelocityY = velocityY;
+ mTriggerBack = triggerBack;
mSwipeEdge = swipeEdge;
mDepartingAnimationTarget = departingAnimationTarget;
}
@@ -81,6 +85,7 @@
mProgress = in.readFloat();
mVelocityX = in.readFloat();
mVelocityY = in.readFloat();
+ mTriggerBack = in.readBoolean();
mSwipeEdge = in.readInt();
mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR);
}
@@ -110,6 +115,7 @@
dest.writeFloat(mProgress);
dest.writeFloat(mVelocityX);
dest.writeFloat(mVelocityY);
+ dest.writeBoolean(mTriggerBack);
dest.writeInt(mSwipeEdge);
dest.writeTypedObject(mDepartingAnimationTarget, flags);
}
@@ -157,6 +163,15 @@
}
/**
+ * Returns whether the back arrow is in the triggered state or not
+ *
+ * @return boolean indicating whether the back arrow is in the triggered state or not
+ */
+ public boolean getTriggerBack() {
+ return mTriggerBack;
+ }
+
+ /**
* Returns the screen edge that the swipe starts from.
*/
@BackEvent.SwipeEdge
@@ -182,6 +197,7 @@
+ ", mProgress=" + mProgress
+ ", mVelocityX=" + mVelocityX
+ ", mVelocityY=" + mVelocityY
+ + ", mTriggerBack=" + mTriggerBack
+ ", mSwipeEdge" + mSwipeEdge
+ ", mDepartingAnimationTarget" + mDepartingAnimationTarget
+ "}";
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 5bfa3d7..7c9340e 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -165,6 +165,9 @@
public static final int FLAGS_IS_NON_APP_WINDOW =
FLAG_IS_WALLPAPER | FLAG_IS_INPUT_METHOD | FLAG_IS_SYSTEM_WINDOW;
+ /** The change will not participate in the animation. */
+ public static final int FLAGS_IS_OCCLUDED_NO_ANIMATION = FLAG_IS_OCCLUDED | FLAG_NO_ANIMATION;
+
/** @hide */
@IntDef(prefix = { "FLAG_" }, value = {
FLAG_NONE,
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 0077dab..4b5595f 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
@@ -36,3 +36,10 @@
bug: "316139088"
is_fixed_read_only: true
}
+
+flag {
+ name: "user_min_aspect_ratio_app_default"
+ namespace: "large_screen_experiences_app_compat"
+ description: "Whether the API PackageManager.USER_MIN_ASPECT_RATIO_APP_DEFAULT is available"
+ bug: "310816437"
+}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 52ad49a..216acdc 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -66,4 +66,12 @@
description: "Predictive back for system animations"
bug: "309545085"
is_fixed_read_only: true
+}
+
+flag {
+ name: "activity_snapshot_by_default"
+ namespace: "systemui"
+ description: "Enable record activity snapshot by default"
+ bug: "259497289"
+ is_fixed_read_only: true
}
\ No newline at end of file
diff --git a/core/java/com/android/internal/pm/parsing/IPackageCacher.java b/core/java/com/android/internal/pm/parsing/IPackageCacher.java
new file mode 100644
index 0000000..3e01730
--- /dev/null
+++ b/core/java/com/android/internal/pm/parsing/IPackageCacher.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.pm.parsing;
+
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
+
+import java.io.File;
+
+/** @hide */
+public interface IPackageCacher {
+
+ /**
+ * Returns the cached parse result for {@code packageFile} for parse flags {@code flags},
+ * or {@code null} if no cached result exists.
+ */
+ ParsedPackage getCachedResult(File packageFile, int flags);
+
+ /**
+ * Caches the parse result for {@code packageFile} with flags {@code flags}.
+ */
+ void cacheResult(File packageFile, int flags, ParsedPackage parsed);
+}
diff --git a/services/core/java/com/android/server/pm/parsing/PackageParser2.java b/core/java/com/android/internal/pm/parsing/PackageParser2.java
similarity index 69%
rename from services/core/java/com/android/server/pm/parsing/PackageParser2.java
rename to core/java/com/android/internal/pm/parsing/PackageParser2.java
index b6a08a5..e413293 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageParser2.java
+++ b/core/java/com/android/internal/pm/parsing/PackageParser2.java
@@ -14,13 +14,12 @@
* limitations under the License.
*/
-package com.android.server.pm.parsing;
+package com.android.internal.pm.parsing;
import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread;
-import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.parsing.PackageLite;
import android.content.pm.parsing.result.ParseInput;
@@ -28,26 +27,20 @@
import android.content.pm.parsing.result.ParseTypeImpl;
import android.content.res.TypedArray;
import android.os.Build;
-import android.os.ServiceManager;
import android.os.SystemClock;
import android.permission.PermissionManager;
import android.util.DisplayMetrics;
import android.util.Slog;
-import com.android.internal.compat.IPlatformCompat;
import com.android.internal.pm.parsing.pkg.PackageImpl;
import com.android.internal.pm.parsing.pkg.ParsedPackage;
import com.android.internal.pm.pkg.parsing.ParsingPackage;
import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import com.android.internal.pm.pkg.parsing.ParsingUtils;
import com.android.internal.util.ArrayUtils;
-import com.android.server.SystemConfig;
-import com.android.server.pm.PackageManagerException;
-import com.android.server.pm.PackageManagerService;
import java.io.File;
import java.util.List;
-import java.util.Set;
/**
* The v2 of package parsing for use when parsing is initiated in the server and must
@@ -59,50 +52,6 @@
*/
public class PackageParser2 implements AutoCloseable {
- /**
- * For parsing inside the system server but outside of {@link PackageManagerService}.
- * Generally used for parsing information in an APK that hasn't been installed yet.
- *
- * This must be called inside the system process as it relies on {@link ServiceManager}.
- */
- @NonNull
- public static PackageParser2 forParsingFileWithDefaults() {
- IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
- ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
- return new PackageParser2(null /* separateProcesses */, null /* displayMetrics */,
- null /* cacheDir */, new Callback() {
- @Override
- public boolean isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo) {
- try {
- return platformCompat.isChangeEnabled(changeId, appInfo);
- } catch (Exception e) {
- // This shouldn't happen, but assume enforcement if it does
- Slog.wtf(TAG, "IPlatformCompat query failed", e);
- return true;
- }
- }
-
- @Override
- public boolean hasFeature(String feature) {
- // Assume the device doesn't support anything. This will affect permission parsing
- // and will force <uses-permission/> declarations to include all requiredNotFeature
- // permissions and exclude all requiredFeature permissions. This mirrors the old
- // behavior.
- return false;
- }
-
- @Override
- public Set<String> getHiddenApiWhitelistedApps() {
- return SystemConfig.getInstance().getHiddenApiWhitelistedApps();
- }
-
- @Override
- public Set<String> getInstallConstraintsAllowlist() {
- return SystemConfig.getInstance().getInstallConstraintsAllowlist();
- }
- });
- }
-
private static final String TAG = ParsingUtils.TAG;
private static final boolean LOG_PARSE_TIMINGS = Build.IS_DEBUGGABLE;
@@ -118,12 +67,12 @@
private final ThreadLocal<ParseTypeImpl> mSharedResult;
@Nullable
- protected PackageCacher mCacher;
+ protected IPackageCacher mCacher;
- private final ParsingPackageUtils parsingUtils;
+ private final ParsingPackageUtils mParsingUtils;
public PackageParser2(String[] separateProcesses, DisplayMetrics displayMetrics,
- @Nullable File cacheDir, @NonNull Callback callback) {
+ @Nullable IPackageCacher cacher, @NonNull Callback callback) {
if (displayMetrics == null) {
displayMetrics = new DisplayMetrics();
displayMetrics.setToDefaults();
@@ -134,9 +83,9 @@
List<PermissionManager.SplitPermissionInfo> splitPermissions = permissionManager
.getSplitPermissions();
- mCacher = cacheDir == null ? null : new PackageCacher(cacheDir);
+ mCacher = cacher;
- parsingUtils = new ParsingPackageUtils(separateProcesses, displayMetrics, splitPermissions,
+ mParsingUtils = new ParsingPackageUtils(separateProcesses, displayMetrics, splitPermissions,
callback);
ParseInput.Callback enforcementCallback = (changeId, packageName, targetSdkVersion) -> {
@@ -155,7 +104,7 @@
*/
@AnyThread
public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches)
- throws PackageManagerException {
+ throws PackageParserException {
var files = packageFile.listFiles();
// Apk directory is directly nested under the current directory
if (ArrayUtils.size(files) == 1 && files[0].isDirectory()) {
@@ -171,9 +120,9 @@
long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
ParseInput input = mSharedResult.get().reset();
- ParseResult<ParsingPackage> result = parsingUtils.parsePackage(input, packageFile, flags);
+ ParseResult<ParsingPackage> result = mParsingUtils.parsePackage(input, packageFile, flags);
if (result.isError()) {
- throw new PackageManagerException(result.getErrorCode(), result.getErrorMessage(),
+ throw new PackageParserException(result.getErrorCode(), result.getErrorMessage(),
result.getException());
}
@@ -201,12 +150,12 @@
*/
@AnyThread
public ParsedPackage parsePackageFromPackageLite(PackageLite packageLite, int flags)
- throws PackageManagerException {
+ throws PackageParserException {
ParseInput input = mSharedResult.get().reset();
- ParseResult<ParsingPackage> result = parsingUtils.parsePackageFromPackageLite(input,
+ ParseResult<ParsingPackage> result = mParsingUtils.parsePackageFromPackageLite(input,
packageLite, flags);
if (result.isError()) {
- throw new PackageManagerException(result.getErrorCode(), result.getErrorMessage(),
+ throw new PackageParserException(result.getErrorCode(), result.getErrorMessage(),
result.getException());
}
return result.getResult().hideAsParsed();
@@ -226,7 +175,7 @@
mSharedAppInfo.remove();
}
- public static abstract class Callback implements ParsingPackageUtils.Callback {
+ public abstract static class Callback implements ParsingPackageUtils.Callback {
@Override
public final ParsingPackage startParsingPackage(@NonNull String packageName,
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index c24d21d..17aad43 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -653,6 +653,8 @@
sizeof("-Xps-save-resolved-classes-delay-ms:")-1 + PROPERTY_VALUE_MAX];
char profileMinSavePeriodOptsBuf[sizeof("-Xps-min-save-period-ms:")-1 + PROPERTY_VALUE_MAX];
char profileMinFirstSaveOptsBuf[sizeof("-Xps-min-first-save-ms:") - 1 + PROPERTY_VALUE_MAX];
+ char profileInlineCacheThresholdOptsBuf[
+ sizeof("-Xps-inline-cache-threshold:") - 1 + PROPERTY_VALUE_MAX];
char madviseWillNeedFileSizeVdex[
sizeof("-XMadviseWillNeedVdexFileSize:")-1 + PROPERTY_VALUE_MAX];
char madviseWillNeedFileSizeOdex[
@@ -898,6 +900,9 @@
parseRuntimeOption("dalvik.vm.ps-min-first-save-ms", profileMinFirstSaveOptsBuf,
"-Xps-min-first-save-ms:");
+ parseRuntimeOption("dalvik.vm.ps-inline-cache-threshold", profileInlineCacheThresholdOptsBuf,
+ "-Xps-inline-cache-threshold:");
+
property_get("ro.config.low_ram", propBuf, "");
if (strcmp(propBuf, "true") == 0) {
addOption("-XX:LowMemoryMode");
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index fef8ad7..f007cc5 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -41,6 +41,7 @@
jmethodID dispatchHotplugConnectionError;
jmethodID dispatchModeChanged;
jmethodID dispatchFrameRateOverrides;
+ jmethodID dispatchHdcpLevelsChanged;
struct {
jclass clazz;
@@ -96,6 +97,8 @@
void dispatchFrameRateOverrides(nsecs_t timestamp, PhysicalDisplayId displayId,
std::vector<FrameRateOverride> overrides) override;
void dispatchNullEvent(nsecs_t timestamp, PhysicalDisplayId displayId) override {}
+ void dispatchHdcpLevelsChanged(PhysicalDisplayId displayId, int connectedLevel,
+ int maxLevel) override;
};
NativeDisplayEventReceiver::NativeDisplayEventReceiver(JNIEnv* env, jobject receiverWeak,
@@ -294,6 +297,22 @@
mMessageQueue->raiseAndClearException(env, "dispatchModeChanged");
}
+void NativeDisplayEventReceiver::dispatchHdcpLevelsChanged(PhysicalDisplayId displayId,
+ int connectedLevel, int maxLevel) {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+
+ ScopedLocalRef<jobject> receiverObj(env, GetReferent(env, mReceiverWeakGlobal));
+ if (receiverObj.get()) {
+ ALOGV("receiver %p ~ Invoking hdcp levels changed handler.", this);
+ env->CallVoidMethod(receiverObj.get(),
+ gDisplayEventReceiverClassInfo.dispatchHdcpLevelsChanged,
+ displayId.value, connectedLevel, maxLevel);
+ ALOGV("receiver %p ~ Returned from hdcp levels changed handler.", this);
+ }
+
+ mMessageQueue->raiseAndClearException(env, "dispatchHdcpLevelsChanged");
+}
+
static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, jobject vsyncEventDataWeak,
jobject messageQueueObj, jint vsyncSource, jint eventRegistration,
jlong layerHandle) {
@@ -385,6 +404,9 @@
GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz,
"dispatchFrameRateOverrides",
"(JJ[Landroid/view/DisplayEventReceiver$FrameRateOverride;)V");
+ gDisplayEventReceiverClassInfo.dispatchHdcpLevelsChanged =
+ GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchHdcpLevelsChanged",
+ "(JII)V");
jclass frameRateOverrideClazz =
FindClassOrDie(env, "android/view/DisplayEventReceiver$FrameRateOverride");
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index 052e2f2..d3f3af7 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -610,6 +610,9 @@
// ringer mode.
optional SettingProto mode_ringer = 75 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ // This is an optional feature where ringer mode affects alarm stream as well
+ optional SettingProto mute_alarm_stream_with_ringer_mode = 160 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
reserved 147; // Used to be apply_ramping_ringer
message MultiSim {
@@ -1086,5 +1089,5 @@
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 160;
+ // Next tag = 161;
}
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index c5889ba..75cfba0 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -429,6 +429,7 @@
optional string base_dir = 1;
optional string res_dir = 2;
optional string data_dir = 3;
+ optional int32 targetSdkVersion = 4;
}
optional AppInfo appinfo = 8;
optional ProcessRecordProto app = 9;
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index d0de5f0..804e9ef 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2262,6 +2262,9 @@
<!-- The default min volume for the alarm stream -->
<integer name="config_audio_alarm_min_vol">1</integer>
+ <!-- Flag indicating if ringer mode affects alarm stream -->
+ <bool name="config_audio_ringer_mode_affects_alarm_stream">false</bool>
+
<!-- The default value for whether head tracking for
spatial audio is enabled for a newly connected audio device -->
<bool name="config_spatial_audio_head_tracking_enabled_default">false</bool>
@@ -6862,4 +6865,8 @@
<!-- Whether the media player is shown on the quick settings -->
<bool name="config_quickSettingsShowMediaPlayer">true</bool>
+
+ <!-- Defines suitability of the built-in speaker route.
+ Refer to {@link MediaRoute2Info} to see supported values. -->
+ <integer name="config_mediaRouter_builtInSpeakerSuitability">0</integer>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 3894330..9589fb0 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -288,6 +288,7 @@
<java-symbol type="integer" name="config_audio_ring_vol_default" />
<java-symbol type="integer" name="config_audio_ring_vol_steps" />
<java-symbol type="integer" name="config_audio_alarm_min_vol" />
+ <java-symbol type="bool" name="config_audio_ringer_mode_affects_alarm_stream" />
<java-symbol type="bool" name="config_spatial_audio_head_tracking_enabled_default" />
<java-symbol type="bool" name="config_avoidGfxAccel" />
<java-symbol type="bool" name="config_bluetooth_address_validation" />
@@ -5306,4 +5307,7 @@
<java-symbol type="bool" name="config_viewBasedRotaryEncoderHapticsEnabled" />
<java-symbol type="bool" name="config_quickSettingsShowMediaPlayer" />
+
+ <!-- Android MediaRouter framework configs. -->
+ <java-symbol type="integer" name="config_mediaRouter_builtInSpeakerSuitability" />
</resources>
diff --git a/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java b/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
index 2b4123a..73d7fe9 100644
--- a/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
+++ b/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
@@ -16,6 +16,9 @@
package android.companion;
+import static android.companion.CompanionDeviceManager.MESSAGE_ONEWAY_PING;
+import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_PING;
+
import android.content.Context;
import android.os.SystemClock;
import android.test.InstrumentationTestCase;
@@ -36,16 +39,22 @@
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+/**
+ * Tests that CDM can intake incoming messages in the system data transport and output results.
+ *
+ * Build/Install/Run: atest CompanionTests:SystemDataTransportTest
+ */
public class SystemDataTransportTest extends InstrumentationTestCase {
private static final String TAG = "SystemDataTransportTest";
private static final int MESSAGE_INVALID = 0xF00DCAFE;
-
- private static final int MESSAGE_REQUEST_INVALID = 0x63636363; // ????
- private static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN
-
+ private static final int MESSAGE_ONEWAY_INVALID = 0x43434343; // ++++
private static final int MESSAGE_RESPONSE_INVALID = 0x33333333; // !!!!
+ private static final int MESSAGE_REQUEST_INVALID = 0x63636363; // ????
+
private static final int MESSAGE_RESPONSE_SUCCESS = 0x33838567; // !SUC
private static final int MESSAGE_RESPONSE_FAILURE = 0x33706573; // !FAI
@@ -122,8 +131,6 @@
new Random().nextBytes(blob);
final byte[] input = generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 1, blob);
- final byte[] expected = generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 1, blob);
- assertTransportBehavior(input, expected);
}
public void testMultiplePingPing() {
@@ -176,6 +183,43 @@
testPingHandRolled();
}
+ public void testInvalidOnewayMessages() throws InterruptedException {
+ // Add a callback
+ final CountDownLatch received = new CountDownLatch(1);
+ mCdm.addOnMessageReceivedListener(Runnable::run, MESSAGE_ONEWAY_INVALID,
+ (id, data) -> received.countDown());
+
+ final byte[] input = generatePacket(MESSAGE_ONEWAY_INVALID, /* sequence */ 1);
+ final ByteArrayInputStream in = new ByteArrayInputStream(input);
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ mCdm.attachSystemDataTransport(mAssociationId, in, out);
+
+ // Assert that a one-way message was ignored (does not trigger a callback)
+ assertFalse(received.await(5, TimeUnit.SECONDS));
+
+ // There should not be a response to one-way messages
+ assertEquals(0, out.toByteArray().length);
+ }
+
+
+ public void testOnewayMessages() throws InterruptedException {
+ // Add a callback
+ final CountDownLatch received = new CountDownLatch(1);
+ mCdm.addOnMessageReceivedListener(Runnable::run, MESSAGE_ONEWAY_PING,
+ (id, data) -> received.countDown());
+
+ final byte[] input = generatePacket(MESSAGE_ONEWAY_PING, /* sequence */ 1);
+ final ByteArrayInputStream in = new ByteArrayInputStream(input);
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ mCdm.attachSystemDataTransport(mAssociationId, in, out);
+
+ // Assert that a one-way message was received
+ assertTrue(received.await(1, TimeUnit.SECONDS));
+
+ // There should not be a response to one-way messages
+ assertEquals(0, out.toByteArray().length);
+ }
+
public static byte[] concat(byte[]... blobs) {
int length = 0;
for (byte[] blob : blobs) {
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 1577d9c1c1..5b0502d 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -1244,29 +1244,33 @@
}
@Test
- public void testBigPictureStyle_setExtras_pictureIconNull_noPictureIconKey() {
+ public void testBigPictureStyle_setExtras_pictureIconNull_pictureIconKeyNull() {
Notification.BigPictureStyle bpStyle = new Notification.BigPictureStyle();
bpStyle.bigPicture((Bitmap) null);
Bundle extras = new Bundle();
bpStyle.addExtras(extras);
- assertThat(extras.containsKey(EXTRA_PICTURE_ICON)).isFalse();
+ assertThat(extras.containsKey(EXTRA_PICTURE_ICON)).isTrue();
+ final Parcelable pictureIcon = extras.getParcelable(EXTRA_PICTURE_ICON);
+ assertThat(pictureIcon).isNull();
}
@Test
- public void testBigPictureStyle_setExtras_pictureIconNull_noPictureKey() {
+ public void testBigPictureStyle_setExtras_pictureIconNull_pictureKeyNull() {
Notification.BigPictureStyle bpStyle = new Notification.BigPictureStyle();
bpStyle.bigPicture((Bitmap) null);
Bundle extras = new Bundle();
bpStyle.addExtras(extras);
- assertThat(extras.containsKey(EXTRA_PICTURE)).isFalse();
+ assertThat(extras.containsKey(EXTRA_PICTURE)).isTrue();
+ final Parcelable picture = extras.getParcelable(EXTRA_PICTURE);
+ assertThat(picture).isNull();
}
@Test
- public void testBigPictureStyle_setExtras_pictureIconTypeBitmap_noPictureIconKey() {
+ public void testBigPictureStyle_setExtras_pictureIconTypeBitmap_pictureIconKeyNull() {
Bitmap bitmap = Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888);
Notification.BigPictureStyle bpStyle = new Notification.BigPictureStyle();
bpStyle.bigPicture(bitmap);
@@ -1274,11 +1278,13 @@
Bundle extras = new Bundle();
bpStyle.addExtras(extras);
- assertThat(extras.containsKey(EXTRA_PICTURE_ICON)).isFalse();
+ assertThat(extras.containsKey(EXTRA_PICTURE_ICON)).isTrue();
+ final Parcelable pictureIcon = extras.getParcelable(EXTRA_PICTURE_ICON);
+ assertThat(pictureIcon).isNull();
}
@Test
- public void testBigPictureStyle_setExtras_pictureIconTypeIcon_noPictureKey() {
+ public void testBigPictureStyle_setExtras_pictureIconTypeIcon_pictureKeyNull() {
Icon icon = Icon.createWithResource(mContext, R.drawable.btn_plus);
Notification.BigPictureStyle bpStyle = new Notification.BigPictureStyle();
bpStyle.bigPicture(icon);
@@ -1286,7 +1292,9 @@
Bundle extras = new Bundle();
bpStyle.addExtras(extras);
- assertThat(extras.containsKey(EXTRA_PICTURE)).isFalse();
+ assertThat(extras.containsKey(EXTRA_PICTURE)).isTrue();
+ final Parcelable picture = extras.getParcelable(EXTRA_PICTURE);
+ assertThat(picture).isNull();
}
@Test
diff --git a/core/tests/coretests/src/android/hardware/face/FaceSensorConfigurationsTest.java b/core/tests/coretests/src/android/hardware/face/FaceSensorConfigurationsTest.java
new file mode 100644
index 0000000..da3a465
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/face/FaceSensorConfigurationsTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.face;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.face.SensorProps;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.function.Function;
+
+@Presubmit
+@SmallTest
+public class FaceSensorConfigurationsTest {
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+ @Mock
+ private Context mContext;
+ @Mock
+ private Resources mResources;
+ @Mock
+ private IFace mFace;
+ @Mock
+ private Function<String, IFace> mGetIFace;
+
+ private final String[] mAidlInstances = new String[]{"default", "virtual"};
+ private String[] mHidlConfigStrings = new String[]{"0:2:15", "0:8:15"};
+ private FaceSensorConfigurations mFaceSensorConfigurations;
+
+ @Before
+ public void setUp() throws RemoteException {
+ when(mGetIFace.apply(anyString())).thenReturn(mFace);
+ when(mFace.getSensorProps()).thenReturn(new SensorProps[]{});
+ when(mContext.getResources()).thenReturn(mResources);
+ }
+
+ @Test
+ public void testAidlInstanceSensorProps() {
+ mFaceSensorConfigurations = new FaceSensorConfigurations(false);
+ mFaceSensorConfigurations.addAidlConfigs(mAidlInstances, mGetIFace);
+
+ assertThat(mFaceSensorConfigurations.hasSensorConfigurations()).isTrue();
+ assertThat(!mFaceSensorConfigurations.isSingleSensorConfigurationPresent()).isTrue();
+ assertThat(mFaceSensorConfigurations.getResetLockoutRequiresChallenge())
+ .isFalse();
+ }
+
+ @Test
+ public void testHidlConfigStrings() {
+ mFaceSensorConfigurations = new FaceSensorConfigurations(true);
+ mFaceSensorConfigurations.addHidlConfigs(mHidlConfigStrings, mContext);
+
+ assertThat(mFaceSensorConfigurations.isSingleSensorConfigurationPresent()).isTrue();
+ assertThat(mFaceSensorConfigurations.getResetLockoutRequiresChallenge())
+ .isTrue();
+ }
+
+ @Test
+ public void testHidlConfigStrings_incorrectFormat() {
+ mHidlConfigStrings = new String[]{"0:8:15", "0:2", "0:face:15"};
+ mFaceSensorConfigurations = new FaceSensorConfigurations(true);
+ mFaceSensorConfigurations.addHidlConfigs(mHidlConfigStrings, mContext);
+
+ assertThat(mFaceSensorConfigurations.isSingleSensorConfigurationPresent()).isTrue();
+ assertThat(mFaceSensorConfigurations.getResetLockoutRequiresChallenge())
+ .isTrue();
+ }
+}
diff --git a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintSensorConfigurationsTest.java b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintSensorConfigurationsTest.java
new file mode 100644
index 0000000..613089c
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintSensorConfigurationsTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.fingerprint;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.SensorProps;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.function.Function;
+
+
+@Presubmit
+@SmallTest
+public class FingerprintSensorConfigurationsTest {
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+ @Mock
+ private Context mContext;
+ @Mock
+ private Resources mResources;
+ @Mock
+ private IFingerprint mFingerprint;
+ @Mock
+ private Function<String, IFingerprint> mGetIFingerprint;
+
+ private final String[] mAidlInstances = new String[]{"default", "virtual"};
+ private String[] mHidlConfigStrings = new String[]{"0:2:15", "0:8:15"};
+ private FingerprintSensorConfigurations mFingerprintSensorConfigurations;
+
+ @Before
+ public void setUp() throws RemoteException {
+ when(mGetIFingerprint.apply(anyString())).thenReturn(mFingerprint);
+ when(mFingerprint.getSensorProps()).thenReturn(new SensorProps[]{});
+ when(mContext.getResources()).thenReturn(mResources);
+ }
+
+ @Test
+ public void testAidlInstanceSensorProps() {
+ mFingerprintSensorConfigurations = new FingerprintSensorConfigurations(
+ true /* resetLockoutRequiresHardwareAuthToken */);
+ mFingerprintSensorConfigurations.addAidlSensors(mAidlInstances, mGetIFingerprint);
+
+ assertThat(mFingerprintSensorConfigurations.hasSensorConfigurations()).isTrue();
+ assertThat(!mFingerprintSensorConfigurations.isSingleSensorConfigurationPresent())
+ .isTrue();
+ assertThat(mFingerprintSensorConfigurations.getResetLockoutRequiresHardwareAuthToken())
+ .isTrue();
+ }
+
+ @Test
+ public void testHidlConfigStrings() {
+ mFingerprintSensorConfigurations = new FingerprintSensorConfigurations(
+ false /* resetLockoutRequiresHardwareAuthToken */);
+ mFingerprintSensorConfigurations.addHidlSensors(mHidlConfigStrings, mContext);
+
+ assertThat(mFingerprintSensorConfigurations.isSingleSensorConfigurationPresent()).isTrue();
+ assertThat(mFingerprintSensorConfigurations.getResetLockoutRequiresHardwareAuthToken())
+ .isFalse();
+ }
+
+ @Test
+ public void testHidlConfigStrings_incorrectFormat() {
+ mHidlConfigStrings = new String[]{"0:8:15", "0:2", "0:fingerprint:15"};
+ mFingerprintSensorConfigurations = new FingerprintSensorConfigurations(
+ false /* resetLockoutRequiresHardwareAuthToken */);
+ mFingerprintSensorConfigurations.addHidlSensors(mHidlConfigStrings, mContext);
+
+ assertThat(mFingerprintSensorConfigurations.isSingleSensorConfigurationPresent()).isTrue();
+ assertThat(mFingerprintSensorConfigurations.getResetLockoutRequiresHardwareAuthToken())
+ .isFalse();
+ }
+}
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index b30a0c8..cf3eb12 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -659,8 +659,6 @@
ViewRootImpl viewRootImpl = view.getViewRootImpl();
sInstrumentation.runOnMainSync(() -> {
view.invalidate();
- assertEquals(viewRootImpl.getLastPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_NORMAL);
viewRootImpl.notifyInsetsAnimationRunningStateChanged(true);
view.invalidate();
});
@@ -672,6 +670,37 @@
});
}
+
+ /**
+ * Test FrameRateBoostOnTouchEnabled API
+ */
+ @Test
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+ public void votePreferredFrameRate_frameRateBoostOnTouch() {
+ View view = new View(sContext);
+ attachViewToWindow(view);
+ sInstrumentation.waitForIdleSync();
+
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
+ final WindowManager.LayoutParams attrs = viewRootImpl.mWindowAttributes;
+ assertEquals(attrs.getFrameRateBoostOnTouchEnabled(), true);
+ assertEquals(viewRootImpl.getFrameRateBoostOnTouchEnabled(),
+ attrs.getFrameRateBoostOnTouchEnabled());
+
+ sInstrumentation.runOnMainSync(() -> {
+ attrs.setFrameRateBoostOnTouchEnabled(false);
+ viewRootImpl.setLayoutParams(attrs, false);
+ });
+ sInstrumentation.waitForIdleSync();
+
+ sInstrumentation.runOnMainSync(() -> {
+ final WindowManager.LayoutParams newAttrs = viewRootImpl.mWindowAttributes;
+ assertEquals(newAttrs.getFrameRateBoostOnTouchEnabled(), false);
+ assertEquals(viewRootImpl.getFrameRateBoostOnTouchEnabled(),
+ newAttrs.getFrameRateBoostOnTouchEnabled());
+ });
+ }
+
@Test
public void forceInvertOffDarkThemeOff_forceDarkModeDisabled() {
mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index 68c0693..a709d7b 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -84,6 +84,7 @@
/* progress = */ 0,
/* velocityX = */ 0,
/* velocityY = */ 0,
+ /* triggerBack = */ false,
/* swipeEdge = */ BackEvent.EDGE_LEFT,
/* departingAnimationTarget = */ null);
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 69a6e6d..c6f920f 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -518,6 +518,8 @@
<permission name="android.permission.ACCESS_BROADCAST_RADIO"/>
<!-- Permission required for CTS test - CtsAmbientContextServiceTestCases -->
<permission name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT"/>
+ <!-- Permission required for CTS test - CtsWearableSensingServiceTestCases -->
+ <permission name="android.permission.MANAGE_WEARABLE_SENSING_SERVICE"/>
<!-- Permission required for CTS test - CtsTelephonyProviderTestCases -->
<permission name="android.permission.WRITE_APN_SETTINGS"/>
<!-- Permission required for GTS test - GtsStatsdHostTestCases -->
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java
deleted file mode 100644
index ff49cdc..0000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java
+++ /dev/null
@@ -1,119 +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 androidx.window.extensions.embedding;
-
-import static java.util.Objects.requireNonNull;
-
-import android.graphics.Rect;
-import android.os.Bundle;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
-/**
- * The parameter to create an overlay container that retrieved from
- * {@link android.app.ActivityOptions} bundle.
- */
-class OverlayCreateParams {
-
- // TODO(b/295803704): Move them to WM Extensions so that we can reuse in WM Jetpack.
- @VisibleForTesting
- static final String KEY_OVERLAY_CREATE_PARAMS =
- "androidx.window.extensions.OverlayCreateParams";
-
- @VisibleForTesting
- static final String KEY_OVERLAY_CREATE_PARAMS_TASK_ID =
- "androidx.window.extensions.OverlayCreateParams.taskId";
-
- @VisibleForTesting
- static final String KEY_OVERLAY_CREATE_PARAMS_TAG =
- "androidx.window.extensions.OverlayCreateParams.tag";
-
- @VisibleForTesting
- static final String KEY_OVERLAY_CREATE_PARAMS_BOUNDS =
- "androidx.window.extensions.OverlayCreateParams.bounds";
-
- private final int mTaskId;
-
- @NonNull
- private final String mTag;
-
- @NonNull
- private final Rect mBounds;
-
- OverlayCreateParams(int taskId, @NonNull String tag, @NonNull Rect bounds) {
- mTaskId = taskId;
- mTag = requireNonNull(tag);
- mBounds = requireNonNull(bounds);
- }
-
- int getTaskId() {
- return mTaskId;
- }
-
- @NonNull
- String getTag() {
- return mTag;
- }
-
- @NonNull
- Rect getBounds() {
- return mBounds;
- }
-
- @Override
- public int hashCode() {
- int result = mTaskId;
- result = 31 * result + mTag.hashCode();
- result = 31 * result + mBounds.hashCode();
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj == this) return true;
- if (!(obj instanceof OverlayCreateParams thatParams)) return false;
- return mTaskId == thatParams.mTaskId
- && mTag.equals(thatParams.mTag)
- && mBounds.equals(thatParams.mBounds);
- }
-
- @Override
- public String toString() {
- return OverlayCreateParams.class.getSimpleName() + ": {"
- + "taskId=" + mTaskId
- + ", tag=" + mTag
- + ", bounds=" + mBounds
- + "}";
- }
-
- /** Retrieves the {@link OverlayCreateParams} from {@link android.app.ActivityOptions} bundle */
- @Nullable
- static OverlayCreateParams fromBundle(@NonNull Bundle bundle) {
- final Bundle paramsBundle = bundle.getBundle(KEY_OVERLAY_CREATE_PARAMS);
- if (paramsBundle == null) {
- return null;
- }
- final int taskId = paramsBundle.getInt(KEY_OVERLAY_CREATE_PARAMS_TASK_ID);
- final String tag = requireNonNull(paramsBundle.getString(KEY_OVERLAY_CREATE_PARAMS_TAG));
- final Rect bounds = requireNonNull(paramsBundle.getParcelable(
- KEY_OVERLAY_CREATE_PARAMS_BOUNDS, Rect.class));
-
- return new OverlayCreateParams(taskId, tag, bounds);
- }
-}
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 4973a4d..15ee4e1 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -34,6 +34,7 @@
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED;
+import static androidx.window.extensions.embedding.ActivityEmbeddingOptionsProperties.KEY_OVERLAY_TAG;
import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior;
import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior;
import static androidx.window.extensions.embedding.SplitContainer.isStickyPlaceholderRule;
@@ -136,6 +137,15 @@
private Function<SplitAttributesCalculatorParams, SplitAttributes> mSplitAttributesCalculator;
/**
+ * A calculator function to compute {@link ActivityStack} attributes in a task, which is called
+ * when there's {@link #onTaskFragmentParentInfoChanged} or folding state changed.
+ */
+ @GuardedBy("mLock")
+ @Nullable
+ private Function<ActivityStackAttributesCalculatorParams, ActivityStackAttributes>
+ mActivityStackAttributesCalculator;
+
+ /**
* Map from Task id to {@link TaskContainer} which contains all TaskFragment and split pair info
* below it.
* When the app is host of multiple Tasks, there can be multiple splits controlled by the same
@@ -319,6 +329,22 @@
}
}
+ @Override
+ public void setActivityStackAttributesCalculator(
+ @NonNull Function<ActivityStackAttributesCalculatorParams, ActivityStackAttributes>
+ calculator) {
+ synchronized (mLock) {
+ mActivityStackAttributesCalculator = calculator;
+ }
+ }
+
+ @Override
+ public void clearActivityStackAttributesCalculator() {
+ synchronized (mLock) {
+ mActivityStackAttributesCalculator = null;
+ }
+ }
+
@GuardedBy("mLock")
@Nullable
Function<SplitAttributesCalculatorParams, SplitAttributes> getSplitAttributesCalculator() {
@@ -1412,7 +1438,7 @@
@NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
@Nullable Activity launchingActivity) {
return createEmptyContainer(wct, intent, taskId, new Rect(), launchingActivity,
- null /* overlayTag */);
+ null /* overlayTag */, null /* launchOptions */);
}
/**
@@ -1426,7 +1452,7 @@
TaskFragmentContainer createEmptyContainer(
@NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
@NonNull Rect bounds, @Nullable Activity launchingActivity,
- @Nullable String overlayTag) {
+ @Nullable String overlayTag, @Nullable Bundle launchOptions) {
// We need an activity in the organizer process in the same Task to use as the owner
// activity, as well as to get the Task window info.
final Activity activityInTask;
@@ -1443,7 +1469,8 @@
return null;
}
final TaskFragmentContainer container = newContainer(null /* pendingAppearedActivity */,
- intent, activityInTask, taskId, null /* pairedPrimaryContainer*/, overlayTag);
+ intent, activityInTask, taskId, null /* pairedPrimaryContainer*/, overlayTag,
+ launchOptions);
final IBinder taskFragmentToken = container.getTaskFragmentToken();
// Note that taskContainer will not exist before calling #newContainer if the container
// is the first embedded TF in the task.
@@ -1570,14 +1597,16 @@
TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity,
@NonNull Activity activityInTask, int taskId) {
return newContainer(pendingAppearedActivity, null /* pendingAppearedIntent */,
- activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */);
+ activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */,
+ null /* launchOptions */);
}
@GuardedBy("mLock")
TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent,
@NonNull Activity activityInTask, int taskId) {
return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
- activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */);
+ activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */,
+ null /* launchOptions */);
}
@GuardedBy("mLock")
@@ -1585,7 +1614,8 @@
@NonNull Activity activityInTask, int taskId,
@NonNull TaskFragmentContainer pairedPrimaryContainer) {
return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
- activityInTask, taskId, pairedPrimaryContainer, null /* tag */);
+ activityInTask, taskId, pairedPrimaryContainer, null /* tag */,
+ null /* launchOptions */);
}
/**
@@ -1602,11 +1632,14 @@
* @param overlayTag The tag for the new created overlay container. It must be
* needed if {@code isOverlay} is {@code true}. Otherwise,
* it should be {@code null}.
+ * @param launchOptions The launch options bundle to create a container. Must be
+ * specified for overlay container.
*/
@GuardedBy("mLock")
TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity,
@Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId,
- @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag) {
+ @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag,
+ @Nullable Bundle launchOptions) {
if (activityInTask == null) {
throw new IllegalArgumentException("activityInTask must not be null,");
}
@@ -1615,7 +1648,8 @@
}
final TaskContainer taskContainer = mTaskContainers.get(taskId);
final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity,
- pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer, overlayTag);
+ pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer, overlayTag,
+ launchOptions);
return container;
}
@@ -2345,28 +2379,28 @@
@GuardedBy("mLock")
@Nullable
TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(
- @NonNull WindowContainerTransaction wct,
- @NonNull OverlayCreateParams overlayCreateParams, int launchTaskId,
+ @NonNull WindowContainerTransaction wct, @NonNull Bundle options,
@NonNull Intent intent, @NonNull Activity launchActivity) {
- final int taskId = overlayCreateParams.getTaskId();
- if (taskId != launchTaskId) {
- // The task ID doesn't match the launch activity's. Cannot determine the host task
- // to launch the overlay.
- throw new IllegalArgumentException("The task ID of "
- + "OverlayCreateParams#launchingActivity must match the task ID of "
- + "the activity to #startActivity with the activity options that takes "
- + "OverlayCreateParams.");
- }
final List<TaskFragmentContainer> overlayContainers =
getAllOverlayTaskFragmentContainers();
- final String overlayTag = overlayCreateParams.getTag();
+ final String overlayTag = Objects.requireNonNull(options.getString(KEY_OVERLAY_TAG));
// If the requested bounds of OverlayCreateParams are smaller than minimum dimensions
// specified by Intent, expand the overlay container to fill the parent task instead.
- final Rect bounds = overlayCreateParams.getBounds();
- final Size minDimensions = getMinDimensions(intent);
- final boolean shouldExpandContainer = boundsSmallerThanMinDimensions(bounds,
- minDimensions);
+ final ActivityStackAttributesCalculatorParams params =
+ new ActivityStackAttributesCalculatorParams(mPresenter.toParentContainerInfo(
+ mPresenter.getTaskProperties(launchActivity)), overlayTag, options);
+ // Fallback to expand the bounds if there's no activityStackAttributes calculator.
+ final Rect relativeBounds = mActivityStackAttributesCalculator != null
+ ? new Rect(mActivityStackAttributesCalculator.apply(params).getRelativeBounds())
+ : new Rect();
+ final boolean shouldExpandContainer = boundsSmallerThanMinDimensions(relativeBounds,
+ getMinDimensions(intent));
+ // Expand the bounds if the requested bounds are smaller than minimum dimensions.
+ if (shouldExpandContainer) {
+ relativeBounds.setEmpty();
+ }
+ final int taskId = getTaskId(launchActivity);
if (!overlayContainers.isEmpty()) {
for (final TaskFragmentContainer overlayContainer : overlayContainers) {
if (!overlayTag.equals(overlayContainer.getOverlayTag())
@@ -2390,7 +2424,7 @@
final Rect taskBounds = overlayContainer.getTaskContainer().getTaskProperties()
.getTaskMetrics().getBounds();
final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
- final Rect sanitizedBounds = sanitizeBounds(bounds, intent, taskBounds);
+ final Rect sanitizedBounds = sanitizeBounds(relativeBounds, intent, taskBounds);
mPresenter.resizeTaskFragment(wct, overlayToken, sanitizedBounds);
mPresenter.setTaskFragmentIsolatedNavigation(wct, overlayToken,
!sanitizedBounds.isEmpty());
@@ -2402,8 +2436,9 @@
}
}
}
- return createEmptyContainer(wct, intent, taskId,
- (shouldExpandContainer ? new Rect() : bounds), launchActivity, overlayTag);
+ // Launch the overlay container to the task with taskId.
+ return createEmptyContainer(wct, intent, taskId, relativeBounds, launchActivity, overlayTag,
+ options);
}
private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter {
@@ -2568,12 +2603,11 @@
final TaskFragmentContainer launchedInTaskFragment;
if (launchingActivity != null) {
final int taskId = getTaskId(launchingActivity);
- final OverlayCreateParams overlayCreateParams =
- OverlayCreateParams.fromBundle(options);
+ final String overlayTag = options.getString(KEY_OVERLAY_TAG);
if (Flags.activityEmbeddingOverlayPresentationFlag()
- && overlayCreateParams != null) {
+ && overlayTag != null) {
launchedInTaskFragment = createOrUpdateOverlayTaskFragmentIfNeeded(wct,
- overlayCreateParams, taskId, intent, launchingActivity);
+ options, intent, launchingActivity);
} else {
launchedInTaskFragment = resolveStartActivityIntent(wct, taskId, intent,
launchingActivity);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index b5c32bb..acfd8e4 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -1084,4 +1084,14 @@
WindowMetrics getTaskWindowMetrics(@NonNull Activity activity) {
return getTaskProperties(activity).getTaskMetrics();
}
+
+ @NonNull
+ ParentContainerInfo toParentContainerInfo(@NonNull TaskProperties taskProperties) {
+ final Configuration configuration = taskProperties.getConfiguration();
+ final WindowLayoutInfo windowLayoutInfo = mWindowLayoutComponent
+ .getCurrentWindowLayoutInfo(taskProperties.getDisplayId(),
+ configuration.windowConfiguration);
+ return new ParentContainerInfo(taskProperties.getTaskMetrics(), configuration,
+ windowLayoutInfo);
+ }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 26b8d9c..afd554b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -24,6 +24,7 @@
import android.content.Intent;
import android.graphics.Rect;
import android.os.Binder;
+import android.os.Bundle;
import android.os.IBinder;
import android.util.Size;
import android.window.TaskFragmentAnimationParams;
@@ -105,6 +106,13 @@
@Nullable
private final String mOverlayTag;
+ /**
+ * The launch options that was used to create this container. Must not be {@code null} for
+ * {@link #isOverlay()} container.
+ */
+ @Nullable
+ private final Bundle mLaunchOptions;
+
/** Indicates whether the container was cleaned up after the last activity was removed. */
private boolean mIsFinished;
@@ -165,7 +173,7 @@
/**
* @see #TaskFragmentContainer(Activity, Intent, TaskContainer, SplitController,
- * TaskFragmentContainer, String)
+ * TaskFragmentContainer, String, Bundle)
*/
TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
@Nullable Intent pendingAppearedIntent,
@@ -173,7 +181,8 @@
@NonNull SplitController controller,
@Nullable TaskFragmentContainer pairedPrimaryContainer) {
this(pendingAppearedActivity, pendingAppearedIntent, taskContainer,
- controller, pairedPrimaryContainer, null /* overlayTag */);
+ controller, pairedPrimaryContainer, null /* overlayTag */,
+ null /* launchOptions */);
}
/**
@@ -181,11 +190,14 @@
* container transaction.
* @param pairedPrimaryContainer when it is set, the new container will be add right above it
* @param overlayTag Sets to indicate this taskFragment is an overlay container
+ * @param launchOptions The launch options to create this container. Must not be
+ * {@code null} for an overlay container
*/
TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
@Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer,
@NonNull SplitController controller,
- @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag) {
+ @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag,
+ @Nullable Bundle launchOptions) {
if ((pendingAppearedActivity == null && pendingAppearedIntent == null)
|| (pendingAppearedActivity != null && pendingAppearedIntent != null)) {
throw new IllegalArgumentException(
@@ -195,6 +207,10 @@
mToken = new Binder("TaskFragmentContainer");
mTaskContainer = taskContainer;
mOverlayTag = overlayTag;
+ if (overlayTag != null) {
+ Objects.requireNonNull(launchOptions);
+ }
+ mLaunchOptions = launchOptions;
if (pairedPrimaryContainer != null) {
// The TaskFragment will be positioned right above the paired container.
@@ -901,8 +917,8 @@
}
/**
- * Returns the tag specified in {@link OverlayCreateParams#getTag()}. {@code null} if this
- * taskFragment container is not an overlay container.
+ * Returns the tag specified in launch options. {@code null} if this taskFragment container is
+ * not an overlay container.
*/
@Nullable
String getOverlayTag() {
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 4c2433f..678bdef 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
@@ -18,14 +18,11 @@
import static android.view.Display.DEFAULT_DISPLAY;
+import static androidx.window.extensions.embedding.ActivityEmbeddingOptionsProperties.KEY_OVERLAY_TAG;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPairRuleBuilder;
-import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS;
-import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_BOUNDS;
-import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_TAG;
-import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_TASK_ID;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -45,6 +42,7 @@
import static org.mockito.Mockito.never;
import android.app.Activity;
+import android.app.ActivityOptions;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -98,9 +96,6 @@
@Rule
public final SetFlagsRule mSetFlagRule = new SetFlagsRule();
- private static final OverlayCreateParams TEST_OVERLAY_CREATE_PARAMS =
- new OverlayCreateParams(TASK_ID, "test,", new Rect(0, 0, 200, 200));
-
private SplitController.ActivityStartMonitor mMonitor;
private Intent mIntent;
@@ -165,37 +160,15 @@
}
@Test
- public void testOverlayCreateParamsFromBundle() {
- assertThat(OverlayCreateParams.fromBundle(new Bundle())).isNull();
-
- assertThat(OverlayCreateParams.fromBundle(createOverlayCreateParamsTestBundle()))
- .isEqualTo(TEST_OVERLAY_CREATE_PARAMS);
- }
-
- @Test
public void testStartActivity_overlayFeatureDisabled_notInvokeCreateOverlayContainer() {
mSetFlagRule.disableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG);
- mMonitor.onStartActivity(mActivity, mIntent, createOverlayCreateParamsTestBundle());
+ final Bundle optionsBundle = ActivityOptions.makeBasic().toBundle();
+ optionsBundle.putString(KEY_OVERLAY_TAG, "test");
+ mMonitor.onStartActivity(mActivity, mIntent, optionsBundle);
verify(mSplitController, never()).createOrUpdateOverlayTaskFragmentIfNeeded(any(), any(),
- anyInt(), any(), any());
- }
-
- @NonNull
- private static Bundle createOverlayCreateParamsTestBundle() {
- final Bundle bundle = new Bundle();
-
- final Bundle paramsBundle = new Bundle();
- paramsBundle.putInt(KEY_OVERLAY_CREATE_PARAMS_TASK_ID,
- TEST_OVERLAY_CREATE_PARAMS.getTaskId());
- paramsBundle.putString(KEY_OVERLAY_CREATE_PARAMS_TAG, TEST_OVERLAY_CREATE_PARAMS.getTag());
- paramsBundle.putObject(KEY_OVERLAY_CREATE_PARAMS_BOUNDS,
- TEST_OVERLAY_CREATE_PARAMS.getBounds());
-
- bundle.putBundle(KEY_OVERLAY_CREATE_PARAMS, paramsBundle);
-
- return bundle;
+ any(), any());
}
@Test
@@ -221,19 +194,11 @@
}
@Test
- public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_taskIdNotMatch_throwException() {
- assertThrows("The method must return null due to task mismatch between"
- + " launchingActivity and OverlayCreateParams", IllegalArgumentException.class,
- () -> createOrUpdateOverlayTaskFragmentIfNeeded(
- TEST_OVERLAY_CREATE_PARAMS, TASK_ID + 1));
- }
-
- @Test
public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_anotherTagInTask_dismissOverlay() {
createExistingOverlayContainers();
- final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
- new OverlayCreateParams(TASK_ID, "test3", new Rect(0, 0, 100, 100)), TASK_ID);
+ final TaskFragmentContainer overlayContainer =
+ createOrUpdateOverlayTaskFragmentIfNeeded("test3");
assertWithMessage("overlayContainer1 must be dismissed since the new overlay container"
+ " is launched to the same task")
@@ -245,9 +210,9 @@
public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_sameTagAnotherTask_dismissOverlay() {
createExistingOverlayContainers();
- final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
- new OverlayCreateParams(TASK_ID + 2, "test1", new Rect(0, 0, 100, 100)),
- TASK_ID + 2);
+ doReturn(TASK_ID + 2).when(mActivity).getTaskId();
+ final TaskFragmentContainer overlayContainer =
+ createOrUpdateOverlayTaskFragmentIfNeeded("test1");
assertWithMessage("overlayContainer1 must be dismissed since the new overlay container"
+ " is launched with the same tag as an existing overlay container in a different "
@@ -261,9 +226,10 @@
createExistingOverlayContainers();
final Rect bounds = new Rect(0, 0, 100, 100);
+ mSplitController.setActivityStackAttributesCalculator(params ->
+ new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
- new OverlayCreateParams(TASK_ID, "test1", bounds),
- TASK_ID);
+ "test1");
assertWithMessage("overlayContainer1 must be updated since the new overlay container"
+ " is launched with the same tag and task")
@@ -279,9 +245,8 @@
public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_dismissMultipleOverlays() {
createExistingOverlayContainers();
- final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
- new OverlayCreateParams(TASK_ID, "test2", new Rect(0, 0, 100, 100)),
- TASK_ID);
+ final TaskFragmentContainer overlayContainer =
+ createOrUpdateOverlayTaskFragmentIfNeeded("test2");
// OverlayContainer1 is dismissed since new container is launched in the same task with
// different tag. OverlayContainer2 is dismissed since new container is launched with the
@@ -304,8 +269,11 @@
mIntent.setComponent(new ComponentName(ApplicationProvider.getApplicationContext(),
MinimumDimensionActivity.class));
- final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
- TEST_OVERLAY_CREATE_PARAMS, TASK_ID);
+ final Rect bounds = new Rect(0, 0, 100, 100);
+ mSplitController.setActivityStackAttributesCalculator(params ->
+ new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
+ final TaskFragmentContainer overlayContainer =
+ createOrUpdateOverlayTaskFragmentIfNeeded("test");
final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
@@ -316,7 +284,7 @@
// Call createOrUpdateOverlayTaskFragmentIfNeeded again to check the update case.
clearInvocations(mSplitPresenter);
- createOrUpdateOverlayTaskFragmentIfNeeded(TEST_OVERLAY_CREATE_PARAMS, TASK_ID);
+ createOrUpdateOverlayTaskFragmentIfNeeded("test");
verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
@@ -329,11 +297,11 @@
public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_notInTaskBounds_expandOverlay() {
final Rect bounds = new Rect(TASK_BOUNDS);
bounds.offset(10, 10);
- final OverlayCreateParams paramsOutsideTaskBounds = new OverlayCreateParams(TASK_ID,
- "test", bounds);
+ mSplitController.setActivityStackAttributesCalculator(params ->
+ new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
- final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
- paramsOutsideTaskBounds, TASK_ID);
+ final TaskFragmentContainer overlayContainer =
+ createOrUpdateOverlayTaskFragmentIfNeeded("test");
final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
@@ -344,7 +312,7 @@
// Call createOrUpdateOverlayTaskFragmentIfNeeded again to check the update case.
clearInvocations(mSplitPresenter);
- createOrUpdateOverlayTaskFragmentIfNeeded(paramsOutsideTaskBounds, TASK_ID);
+ createOrUpdateOverlayTaskFragmentIfNeeded("test");
verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
@@ -355,15 +323,17 @@
@Test
public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_createOverlay() {
- final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
- TEST_OVERLAY_CREATE_PARAMS, TASK_ID);
+ final Rect bounds = new Rect(0, 0, 100, 100);
+ mSplitController.setActivityStackAttributesCalculator(params ->
+ new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
+ final TaskFragmentContainer overlayContainer =
+ createOrUpdateOverlayTaskFragmentIfNeeded("test");
assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
.containsExactly(overlayContainer);
assertThat(overlayContainer.getTaskId()).isEqualTo(TASK_ID);
- assertThat(overlayContainer
- .areLastRequestedBoundsEqual(TEST_OVERLAY_CREATE_PARAMS.getBounds())).isTrue();
- assertThat(overlayContainer.getOverlayTag()).isEqualTo(TEST_OVERLAY_CREATE_PARAMS.getTag());
+ assertThat(overlayContainer.areLastRequestedBoundsEqual(bounds)).isTrue();
+ assertThat(overlayContainer.getOverlayTag()).isEqualTo("test");
}
@Test
@@ -416,12 +386,14 @@
@Test
public void testGetTopNonFinishingActivityWithOverlay() {
- createTestOverlayContainer(TASK_ID, "test1");
+ TaskFragmentContainer overlayContainer = createTestOverlayContainer(TASK_ID, "test1");
+
final Activity activity = createMockActivity();
final TaskFragmentContainer container = createMockTaskFragmentContainer(activity);
final TaskContainer task = container.getTaskContainer();
- assertThat(task.getTopNonFinishingActivity(true /* includeOverlay */)).isEqualTo(mActivity);
+ assertThat(task.getTopNonFinishingActivity(true /* includeOverlay */))
+ .isEqualTo(overlayContainer.getTopNonFinishingActivity());
assertThat(task.getTopNonFinishingActivity(false /* includeOverlay */)).isEqualTo(activity);
}
@@ -458,10 +430,11 @@
* #createOrUpdateOverlayTaskFragmentIfNeeded}
*/
@Nullable
- private TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(
- @NonNull OverlayCreateParams params, int taskId) {
- return mSplitController.createOrUpdateOverlayTaskFragmentIfNeeded(mTransaction, params,
- taskId, mIntent, mActivity);
+ private TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(@NonNull String tag) {
+ final Bundle launchOptions = new Bundle();
+ launchOptions.putString(KEY_OVERLAY_TAG, tag);
+ return mSplitController.createOrUpdateOverlayTaskFragmentIfNeeded(mTransaction,
+ launchOptions, mIntent, mActivity);
}
/** Creates a mock TaskFragment that has been registered and appeared in the organizer. */
@@ -475,10 +448,11 @@
@NonNull
private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag) {
+ Activity activity = createMockActivity();
TaskFragmentContainer overlayContainer = mSplitController.newContainer(
- null /* pendingAppearedActivity */, mIntent, mActivity, taskId,
- null /* pairedPrimaryContainer */, tag);
- setupTaskFragmentInfo(overlayContainer, mActivity);
+ null /* pendingAppearedActivity */, mIntent, activity, taskId,
+ null /* pairedPrimaryContainer */, tag, Bundle.EMPTY);
+ setupTaskFragmentInfo(overlayContainer, activity);
return overlayContainer;
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 8c274a2..bab4e91 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -590,7 +590,7 @@
assertFalse(result);
verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
- anyString());
+ anyString(), any());
}
@Test
@@ -753,7 +753,7 @@
assertTrue(result);
verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
- anyString());
+ anyString(), any());
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
}
@@ -796,7 +796,7 @@
assertTrue(result);
verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
- anyString());
+ anyString(), any());
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
}
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 4511f3b..901d5fa 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -57,3 +57,10 @@
description: "Enables left/right split in portrait"
bug: "291018646"
}
+
+flag {
+ name: "enable_new_bubble_animations"
+ namespace: "multitasking"
+ description: "Enables new animations for expand and collapse for bubbles"
+ bug: "311450609"
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationConstants.java
index e06d3ef..5b0de50 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationConstants.java
@@ -21,5 +21,4 @@
*/
class BackAnimationConstants {
static final float UPDATE_SYSUI_FLAGS_THRESHOLD = 0.20f;
- static final float PROGRESS_COMMIT_THRESHOLD = 0.1f;
}
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 d8c691b..a498236 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
@@ -70,9 +70,11 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import java.io.PrintWriter;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -124,6 +126,7 @@
private final Context mContext;
private final ContentResolver mContentResolver;
private final ShellController mShellController;
+ private final ShellCommandHandler mShellCommandHandler;
private final ShellExecutor mShellExecutor;
private final Handler mBgHandler;
@@ -180,7 +183,8 @@
@NonNull @ShellBackgroundThread Handler backgroundHandler,
Context context,
@NonNull BackAnimationBackground backAnimationBackground,
- ShellBackAnimationRegistry shellBackAnimationRegistry) {
+ ShellBackAnimationRegistry shellBackAnimationRegistry,
+ ShellCommandHandler shellCommandHandler) {
this(
shellInit,
shellController,
@@ -190,7 +194,8 @@
context,
context.getContentResolver(),
backAnimationBackground,
- shellBackAnimationRegistry);
+ shellBackAnimationRegistry,
+ shellCommandHandler);
}
@VisibleForTesting
@@ -203,7 +208,8 @@
Context context,
ContentResolver contentResolver,
@NonNull BackAnimationBackground backAnimationBackground,
- ShellBackAnimationRegistry shellBackAnimationRegistry) {
+ ShellBackAnimationRegistry shellBackAnimationRegistry,
+ ShellCommandHandler shellCommandHandler) {
mShellController = shellController;
mShellExecutor = shellExecutor;
mActivityTaskManager = activityTaskManager;
@@ -219,6 +225,7 @@
.build();
mShellBackAnimationRegistry = shellBackAnimationRegistry;
mLatencyTracker = LatencyTracker.getInstance(mContext);
+ mShellCommandHandler = shellCommandHandler;
}
private void onInit() {
@@ -227,6 +234,7 @@
createAdapter();
mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
this::createExternalInterface, this);
+ mShellCommandHandler.addDumpCallback(this::dump, this);
}
private void setupAnimationDeveloperSettingsObserver(
@@ -968,4 +976,20 @@
};
mBackAnimationAdapter = new BackAnimationAdapter(runner);
}
+
+ /**
+ * Description of current BackAnimationController state.
+ */
+ private void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "BackAnimationController state:");
+ pw.println(prefix + " mEnableAnimations=" + mEnableAnimations.get());
+ pw.println(prefix + " mBackGestureStarted=" + mBackGestureStarted);
+ pw.println(prefix + " mPostCommitAnimationInProgress=" + mPostCommitAnimationInProgress);
+ pw.println(prefix + " mShouldStartOnNextMoveEvent=" + mShouldStartOnNextMoveEvent);
+ pw.println(prefix + " mCurrentTracker state:");
+ mCurrentTracker.dump(pw, prefix + " ");
+ pw.println(prefix + " mQueuedTracker state:");
+ mQueuedTracker.dump(pw, prefix + " ");
+ }
+
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
index 215a6cc..30d5edb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
@@ -18,9 +18,9 @@
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static android.window.BackEvent.EDGE_RIGHT;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY;
-import static com.android.wm.shell.back.BackAnimationConstants.PROGRESS_COMMIT_THRESHOLD;
import static com.android.wm.shell.back.BackAnimationConstants.UPDATE_SYSUI_FLAGS_THRESHOLD;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
@@ -91,7 +91,7 @@
}
};
private static final float MIN_WINDOW_ALPHA = 0.01f;
- private static final float WINDOW_X_SHIFT_DP = 96;
+ private static final float WINDOW_X_SHIFT_DP = 48;
private static final int SCALE_FACTOR = 100;
// TODO(b/264710590): Use the progress commit threshold from ViewConfiguration once it exists.
private static final float TARGET_COMMIT_PROGRESS = 0.5f;
@@ -126,6 +126,8 @@
private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
private boolean mBackInProgress = false;
+ private boolean mIsRightEdge;
+ private boolean mTriggerBack = false;
private PointF mTouchPos = new PointF();
private IRemoteAnimationFinishedCallback mFinishCallback;
@@ -209,6 +211,7 @@
private void finishAnimation() {
if (mEnteringTarget != null) {
+ mTransaction.setCornerRadius(mEnteringTarget.leash, 0);
mEnteringTarget.leash.release();
mEnteringTarget = null;
}
@@ -241,14 +244,15 @@
private void onGestureProgress(@NonNull BackEvent backEvent) {
if (!mBackInProgress) {
+ mIsRightEdge = backEvent.getSwipeEdge() == EDGE_RIGHT;
mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
mBackInProgress = true;
}
mTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
float progress = backEvent.getProgress();
- float springProgress = (progress > PROGRESS_COMMIT_THRESHOLD
- ? mapLinear(progress, 0.1f, 1, TARGET_COMMIT_PROGRESS, 1)
+ float springProgress = (mTriggerBack
+ ? mapLinear(progress, 0f, 1, TARGET_COMMIT_PROGRESS, 1)
: mapLinear(progress, 0, 1f, 0, TARGET_COMMIT_PROGRESS)) * SCALE_FACTOR;
mLeavingProgressSpring.animateToFinalPosition(springProgress);
mEnteringProgressSpring.animateToFinalPosition(springProgress);
@@ -312,7 +316,7 @@
transformWithProgress(
mEnteringProgress,
Math.max(
- smoothstep(ENTER_ALPHA_THRESHOLD, 1, mEnteringProgress),
+ smoothstep(ENTER_ALPHA_THRESHOLD, 0.7f, mEnteringProgress),
MIN_WINDOW_ALPHA), /* alpha */
mEnteringTarget.leash,
mEnteringRect,
@@ -337,14 +341,13 @@
mClosingTarget.leash,
mClosingRect,
0,
- mWindowXShift
+ mIsRightEdge ? 0 : mWindowXShift
);
}
}
private void transformWithProgress(float progress, float alpha, SurfaceControl surface,
RectF targetRect, float deltaXMin, float deltaXMax) {
- final float touchY = mTouchPos.y;
final int width = mStartTaskRect.width();
final int height = mStartTaskRect.height();
@@ -376,12 +379,14 @@
private final class Callback extends IOnBackInvokedCallback.Default {
@Override
public void onBackStarted(BackMotionEvent backEvent) {
+ mTriggerBack = backEvent.getTriggerBack();
mProgressAnimator.onBackStarted(backEvent,
CrossActivityBackAnimation.this::onGestureProgress);
}
@Override
public void onBackProgressed(@NonNull BackMotionEvent backEvent) {
+ mTriggerBack = backEvent.getTriggerBack();
mProgressAnimator.onBackProgressed(backEvent);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
index 4bd56d4..6213f62 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
@@ -24,6 +24,8 @@
import android.window.BackEvent;
import android.window.BackMotionEvent;
+import java.io.PrintWriter;
+
/**
* Helper class to record the touch location for gesture and generate back events.
*/
@@ -129,6 +131,7 @@
/* progress = */ 0,
/* velocityX = */ 0,
/* velocityY = */ 0,
+ /* triggerBack = */ mTriggerBack,
/* swipeEdge = */ mSwipeEdge,
/* departingAnimationTarget = */ target);
}
@@ -204,6 +207,7 @@
/* progress = */ progress,
/* velocityX = */ mLatestVelocityX,
/* velocityY = */ mLatestVelocityY,
+ /* triggerBack = */ mTriggerBack,
/* swipeEdge = */ mSwipeEdge,
/* departingAnimationTarget = */ null);
}
@@ -219,6 +223,12 @@
mNonLinearFactor = nonLinearFactor;
}
+ void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "TouchTracker state:");
+ pw.println(prefix + " mState=" + mState);
+ pw.println(prefix + " mTriggerBack=" + mTriggerBack);
+ }
+
enum TouchTrackerState {
INITIAL, ACTIVE, FINISHED
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index b7f749e..470a825 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -1291,7 +1291,7 @@
// We only show user education for conversation bubbles right now
return false;
}
- final boolean seen = getPrefBoolean(ManageEducationViewKt.PREF_MANAGED_EDUCATION);
+ final boolean seen = getPrefBoolean(ManageEducationView.PREF_MANAGED_EDUCATION);
final boolean shouldShow = (!seen || BubbleDebugConfig.forceShowUserEducation(mContext))
&& mExpandedBubble != null && mExpandedBubble.getExpandedView() != null;
if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
@@ -1342,7 +1342,7 @@
// We only show user education for conversation bubbles right now
return false;
}
- final boolean seen = getPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION);
+ final boolean seen = getPrefBoolean(StackEducationView.PREF_STACK_EDUCATION);
final boolean shouldShow = !seen || BubbleDebugConfig.forceShowUserEducation(mContext);
if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
Log.d(TAG, "Show stack edu: " + shouldShow);
@@ -2323,7 +2323,8 @@
updateOverflowVisibility();
updatePointerPosition(false /* forIme */);
mExpandedAnimationController.expandFromStack(() -> {
- if (mIsExpanded && mExpandedBubble.getExpandedView() != null) {
+ if (mIsExpanded && mExpandedBubble != null
+ && mExpandedBubble.getExpandedView() != null) {
maybeShowManageEdu();
}
updateOverflowDotVisibility(true /* expanding */);
@@ -2384,7 +2385,7 @@
}
mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
- if (mExpandedBubble.getExpandedView() != null) {
+ if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
mExpandedBubble.getExpandedView().setContentAlpha(0f);
mExpandedBubble.getExpandedView().setBackgroundAlpha(0f);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
index 61e17c8..da71b1c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
@@ -33,15 +33,16 @@
* User education view to highlight the manage button that allows a user to configure the settings
* for the bubble. Shown only the first time a user expands a bubble.
*/
-class ManageEducationView(context: Context, positioner: BubblePositioner) : LinearLayout(context) {
+class ManageEducationView(
+ context: Context,
+ private val positioner: BubblePositioner
+) : LinearLayout(context) {
- private val TAG =
- if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "ManageEducationView"
- else BubbleDebugConfig.TAG_BUBBLES
+ companion object {
+ const val PREF_MANAGED_EDUCATION: String = "HasSeenBubblesManageOnboarding"
+ private const val ANIMATE_DURATION: Long = 200
+ }
- private val ANIMATE_DURATION: Long = 200
-
- private val positioner: BubblePositioner = positioner
private val manageView by lazy { requireViewById<ViewGroup>(R.id.manage_education_view) }
private val manageButton by lazy { requireViewById<Button>(R.id.manage_button) }
private val gotItButton by lazy { requireViewById<Button>(R.id.got_it) }
@@ -128,7 +129,7 @@
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.alpha(1f)
}
- setShouldShow(false)
+ updateManageEducationSeen()
}
/**
@@ -218,13 +219,11 @@
}
}
- private fun setShouldShow(shouldShow: Boolean) {
+ private fun updateManageEducationSeen() {
context
.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
.edit()
- .putBoolean(PREF_MANAGED_EDUCATION, !shouldShow)
+ .putBoolean(PREF_MANAGED_EDUCATION, true)
.apply()
}
}
-
-const val PREF_MANAGED_EDUCATION: String = "HasSeenBubblesManageOnboarding"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
index 2cabb65..95f1017 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
@@ -34,19 +34,15 @@
*/
class StackEducationView(
context: Context,
- positioner: BubblePositioner,
- controller: BubbleController
+ private val positioner: BubblePositioner,
+ private val controller: BubbleController
) : LinearLayout(context) {
- private val TAG =
- if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "BubbleStackEducationView"
- else BubbleDebugConfig.TAG_BUBBLES
-
- private val ANIMATE_DURATION: Long = 200
- private val ANIMATE_DURATION_SHORT: Long = 40
-
- private val positioner: BubblePositioner = positioner
- private val controller: BubbleController = controller
+ companion object {
+ const val PREF_STACK_EDUCATION: String = "HasSeenBubblesOnboarding"
+ private const val ANIMATE_DURATION: Long = 200
+ private const val ANIMATE_DURATION_SHORT: Long = 40
+ }
private val view by lazy { requireViewById<View>(R.id.stack_education_layout) }
private val titleTextView by lazy { requireViewById<TextView>(R.id.stack_education_title) }
@@ -175,7 +171,7 @@
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.alpha(1f)
}
- setShouldShow(false)
+ updateStackEducationSeen()
return true
}
@@ -196,13 +192,11 @@
.withEndAction { visibility = GONE }
}
- private fun setShouldShow(shouldShow: Boolean) {
+ private fun updateStackEducationSeen() {
context
.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
.edit()
- .putBoolean(PREF_STACK_EDUCATION, !shouldShow)
+ .putBoolean(PREF_STACK_EDUCATION, true)
.apply()
}
}
-
-const val PREF_STACK_EDUCATION: String = "HasSeenBubblesOnboarding"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
index 5b0239f..02af2d0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
@@ -563,7 +563,7 @@
? p.x - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR
: p.x + mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR;
animationForChild(child)
- .translationX(fromX, p.y)
+ .translationX(fromX, p.x)
.start();
} else {
float fromY = p.y - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR;
@@ -634,4 +634,9 @@
.start();
}
}
+
+ /** Returns true if we're in the middle of a collapse or expand animation. */
+ boolean isAnimating() {
+ return mAnimatingCollapse || mAnimatingExpand;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index f794fef..893a87f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -48,12 +48,14 @@
private static final float EXPANDED_VIEW_ANIMATE_OUT_SCALE_AMOUNT = .75f;
private static final int EXPANDED_VIEW_ALPHA_ANIMATION_DURATION = 150;
private static final int EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION = 100;
+ private static final int EXPANDED_VIEW_ANIMATE_POSITION_DURATION = 300;
+ private static final int EXPANDED_VIEW_DISMISS_DURATION = 250;
+ private static final int EXPANDED_VIEW_DRAG_ANIMATION_DURATION = 150;
/**
* Additional scale applied to expanded view when it is positioned inside a magnetic target.
*/
- private static final float EXPANDED_VIEW_IN_TARGET_SCALE = 0.75f;
- private static final int EXPANDED_VIEW_ANIMATE_POSITION_DURATION = 300;
- private static final int EXPANDED_VIEW_DISMISS_DURATION = 250;
+ private static final float EXPANDED_VIEW_IN_TARGET_SCALE = 0.6f;
+ private static final float EXPANDED_VIEW_DRAG_SCALE = 0.5f;
/** Spring config for the expanded view scale-in animation. */
private final PhysicsAnimator.SpringConfig mScaleInSpringConfig =
@@ -72,6 +74,7 @@
private final Context mContext;
private final BubbleBarLayerView mLayerView;
private final BubblePositioner mPositioner;
+ private final int[] mTmpLocation = new int[2];
private BubbleViewProvider mExpandedBubble;
private boolean mIsExpanded = false;
@@ -220,6 +223,25 @@
}
/**
+ * Animate the expanded bubble when it is being dragged
+ */
+ public void animateStartDrag() {
+ final BubbleBarExpandedView bbev = getExpandedView();
+ if (bbev == null) {
+ Log.w(TAG, "Trying to animate start drag without a bubble");
+ return;
+ }
+ bbev.setPivotX(bbev.getWidth() / 2f);
+ bbev.setPivotY(0f);
+ bbev.animate()
+ .scaleX(EXPANDED_VIEW_DRAG_SCALE)
+ .scaleY(EXPANDED_VIEW_DRAG_SCALE)
+ .setInterpolator(Interpolators.EMPHASIZED)
+ .setDuration(EXPANDED_VIEW_DRAG_ANIMATION_DURATION)
+ .start();
+ }
+
+ /**
* Animates dismissal of currently expanded bubble
*
* @param endRunnable a runnable to run at the end of the animation
@@ -261,7 +283,10 @@
.setDuration(EXPANDED_VIEW_ANIMATE_POSITION_DURATION)
.setInterpolator(Interpolators.EMPHASIZED_DECELERATE)
.withStartAction(() -> bbev.setAnimating(true))
- .withEndAction(() -> bbev.setAnimating(false))
+ .withEndAction(() -> {
+ bbev.setAnimating(false);
+ bbev.resetPivot();
+ })
.start();
}
@@ -277,25 +302,52 @@
Log.w(TAG, "Trying to snap the expanded view to target without a bubble");
return;
}
- Point expandedViewCenter = getViewCenterOnScreen(bbev);
-
- // Calculate the difference between the target's center coordinates and the object's.
- // Animating the object's x/y properties by these values will center the object on top
- // of the magnetic target.
- float xDiff = target.getCenterOnScreen().x - expandedViewCenter.x;
- float yDiff = target.getCenterOnScreen().y - expandedViewCenter.y;
// Calculate scale of expanded view so it fits inside the magnetic target
float bbevMaxSide = Math.max(bbev.getWidth(), bbev.getHeight());
- float targetMaxSide = Math.max(target.getTargetView().getWidth(),
- target.getTargetView().getHeight());
- float scale = (targetMaxSide * EXPANDED_VIEW_IN_TARGET_SCALE) / bbevMaxSide;
+ View targetView = target.getTargetView();
+ float targetMaxSide = Math.max(targetView.getWidth(), targetView.getHeight());
+ // Reduce target size to have some padding between the target and expanded view
+ targetMaxSide *= EXPANDED_VIEW_IN_TARGET_SCALE;
+ float scaleInTarget = targetMaxSide / bbevMaxSide;
+
+ // Scale around the top center of the expanded view. Same as when dragging.
+ bbev.setPivotX(bbev.getWidth() / 2f);
+ bbev.setPivotY(0);
+
+ // When the view animates into the target, it is scaled down with the pivot at center top.
+ // Find the point on the view that would be the center of the view at its final scale.
+ // Once we know that, we can calculate x and y distance from the center of the target view
+ // and use that for the translation animation to ensure that the view at final scale is
+ // placed at the center of the target.
+
+ // Set mTmpLocation to the current location of the view on the screen, taking into account
+ // any scale applied.
+ bbev.getLocationOnScreen(mTmpLocation);
+ // Since pivotX is at the center of the x-axis, even at final scale, center of the view on
+ // x-axis will be the same as the center of the view at current size.
+ // Get scaled width of the view and adjust mTmpLocation so that point on x-axis is at the
+ // center of the view at its current size.
+ float currentWidth = bbev.getWidth() * bbev.getScaleX();
+ mTmpLocation[0] += currentWidth / 2;
+ // Since pivotY is at the top of the view, at final scale, top coordinate of the view
+ // remains the same.
+ // Get height of the view at final scale and adjust mTmpLocation so that point on y-axis is
+ // moved down by half of the height at final scale.
+ float targetHeight = bbev.getHeight() * scaleInTarget;
+ mTmpLocation[1] += targetHeight / 2;
+ // mTmpLocation is now set to the point on the view that will be the center of the view once
+ // scale is applied.
+
+ // Calculate the difference between the target's center coordinates and mTmpLocation
+ float xDiff = target.getCenterOnScreen().x - mTmpLocation[0];
+ float yDiff = target.getCenterOnScreen().y - mTmpLocation[1];
bbev.animate()
.translationX(bbev.getTranslationX() + xDiff)
.translationY(bbev.getTranslationY() + yDiff)
- .scaleX(scale)
- .scaleY(scale)
+ .scaleX(scaleInTarget)
+ .scaleY(scaleInTarget)
.setDuration(EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION)
.setInterpolator(Interpolators.EMPHASIZED)
.withStartAction(() -> bbev.setAnimating(true))
@@ -319,8 +371,8 @@
}
expandedView
.animate()
- .scaleX(1f)
- .scaleY(1f)
+ .scaleX(EXPANDED_VIEW_DRAG_SCALE)
+ .scaleY(EXPANDED_VIEW_DRAG_SCALE)
.setDuration(EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION)
.setInterpolator(Interpolators.EMPHASIZED)
.withStartAction(() -> expandedView.setAnimating(true))
@@ -385,12 +437,4 @@
final int height = mPositioner.getExpandedViewHeightForBubbleBar(isOverflowExpanded);
return new Size(width, height);
}
-
- private Point getViewCenterOnScreen(View view) {
- Point center = new Point();
- int[] onScreenLocation = view.getLocationOnScreen();
- center.x = (int) (onScreenLocation[0] + (view.getWidth() / 2f));
- center.y = (int) (onScreenLocation[1] + (view.getHeight() / 2f));
- return center;
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
index d215450..5e634a2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
@@ -74,6 +74,9 @@
}
private inner class HandleDragListener : RelativeTouchListener() {
+
+ private var isMoving = false
+
override fun onDown(v: View, ev: MotionEvent): Boolean {
// While animating, don't allow new touch events
return !expandedView.isAnimating
@@ -87,6 +90,10 @@
dx: Float,
dy: Float
) {
+ if (!isMoving) {
+ isMoving = true
+ animationHelper.animateStartDrag()
+ }
expandedView.translationX = expandedViewInitialTranslationX + dx
expandedView.translationY = expandedViewInitialTranslationY + dy
dismissView.show()
@@ -114,6 +121,7 @@
animationHelper.animateToRestPosition()
dismissView.hide()
}
+ isMoving = false
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 3c6bc17..fc97c798 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -363,7 +363,8 @@
@ShellMainThread ShellExecutor shellExecutor,
@ShellBackgroundThread Handler backgroundHandler,
BackAnimationBackground backAnimationBackground,
- Optional<ShellBackAnimationRegistry> shellBackAnimationRegistry) {
+ Optional<ShellBackAnimationRegistry> shellBackAnimationRegistry,
+ ShellCommandHandler shellCommandHandler) {
if (BackAnimationController.IS_ENABLED) {
return shellBackAnimationRegistry.map(
(animations) ->
@@ -374,7 +375,8 @@
backgroundHandler,
context,
backAnimationBackground,
- animations));
+ animations,
+ shellCommandHandler));
}
return Optional.empty();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index ae21c4b..f58aeac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -277,11 +277,6 @@
params.token = appToken;
params.packageName = activityInfo.packageName;
params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
-
- if (!context.getResources().getCompatibilityInfo().supportsScreen()) {
- params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
- }
-
params.setTitle("Splash Screen " + title);
return params;
}
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 af69b52..b0d8b47 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
@@ -757,6 +757,10 @@
}
if (!change.hasFlags(FLAG_IS_OCCLUDED)) {
allOccluded = false;
+ } else if (change.hasAllFlags(TransitionInfo.FLAGS_IS_OCCLUDED_NO_ANIMATION)) {
+ // Remove the change because it should be invisible in the animation.
+ info.getChanges().remove(i);
+ continue;
}
// The change has already animated by back gesture, don't need to play transition
// animation on it.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 771876f..9ded6ea 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -63,6 +63,7 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
+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.sysui.ShellSharedConstants;
@@ -110,6 +111,8 @@
@Mock
private InputManager mInputManager;
+ @Mock
+ private ShellCommandHandler mShellCommandHandler;
private BackAnimationController mController;
private TestableContentResolver mContentResolver;
@@ -145,7 +148,8 @@
mContext,
mContentResolver,
mAnimationBackground,
- mShellBackAnimationRegistry);
+ mShellBackAnimationRegistry,
+ mShellCommandHandler);
mShellInit.init();
mShellExecutor.flushAll();
}
@@ -298,7 +302,8 @@
mContext,
mContentResolver,
mAnimationBackground,
- mShellBackAnimationRegistry);
+ mShellBackAnimationRegistry,
+ mShellCommandHandler);
shellInit.init();
registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
index 874ef80..91503b1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
@@ -53,6 +53,7 @@
/* progress = */ progress,
/* velocityX = */ 0,
/* velocityY = */ 0,
+ /* triggerBack = */ false,
/* swipeEdge = */ BackEvent.EDGE_LEFT,
/* departingAnimationTarget = */ null);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
index c1ff260..60f1d271 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
@@ -16,52 +16,51 @@
package com.android.wm.shell.bubbles.animation;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.annotation.SuppressLint;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.testing.AndroidTestingRunner;
import android.view.View;
import android.view.WindowManager;
import android.widget.FrameLayout;
import androidx.dynamicanimation.animation.DynamicAnimation;
+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;
import com.android.wm.shell.bubbles.BubbleStackView;
+import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestCase {
- private int mDisplayWidth = 500;
- private int mDisplayHeight = 1000;
-
- private Runnable mOnBubbleAnimatedOutAction = mock(Runnable.class);
+ private final Semaphore mBubbleRemovedSemaphore = new Semaphore(0);
+ private final Runnable mOnBubbleAnimatedOutAction = mBubbleRemovedSemaphore::release;
ExpandedAnimationController mExpandedController;
private int mStackOffset;
private PointF mExpansionPoint;
private BubblePositioner mPositioner;
- private BubbleStackView.StackViewState mStackViewState = new BubbleStackView.StackViewState();
+ private final BubbleStackView.StackViewState mStackViewState =
+ new BubbleStackView.StackViewState();
- @SuppressLint("VisibleForTests")
@Before
public void setUp() throws Exception {
super.setUp();
@@ -70,15 +69,13 @@
getContext().getSystemService(WindowManager.class));
mPositioner.updateInternal(Configuration.ORIENTATION_PORTRAIT,
Insets.of(0, 0, 0, 0),
- new Rect(0, 0, mDisplayWidth, mDisplayHeight));
+ new Rect(0, 0, 500, 1000));
BubbleStackView stackView = mock(BubbleStackView.class);
- when(stackView.getState()).thenReturn(getStackViewState());
mExpandedController = new ExpandedAnimationController(mPositioner,
mOnBubbleAnimatedOutAction,
stackView);
- spyOn(mExpandedController);
addOneMoreThanBubbleLimitBubbles();
mLayout.setActiveController(mExpandedController);
@@ -86,9 +83,18 @@
Resources res = mLayout.getResources();
mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
mExpansionPoint = new PointF(100, 100);
+
+ getStackViewState();
+ when(stackView.getState()).thenAnswer(i -> getStackViewState());
+ waitForMainThread();
}
- public BubbleStackView.StackViewState getStackViewState() {
+ @After
+ public void tearDown() {
+ waitForMainThread();
+ }
+
+ private BubbleStackView.StackViewState getStackViewState() {
mStackViewState.numberOfBubbles = mLayout.getChildCount();
mStackViewState.selectedIndex = 0;
mStackViewState.onLeft = mPositioner.isStackOnLeft(mExpansionPoint);
@@ -96,68 +102,71 @@
}
@Test
- @Ignore
- public void testExpansionAndCollapse() throws InterruptedException {
- Runnable afterExpand = mock(Runnable.class);
- mExpandedController.expandFromStack(afterExpand);
- waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
-
+ public void testExpansionAndCollapse() throws Exception {
+ expand();
testBubblesInCorrectExpandedPositions();
- verify(afterExpand).run();
+ waitForMainThread();
- Runnable afterCollapse = mock(Runnable.class);
+ final Semaphore semaphore = new Semaphore(0);
+ Runnable afterCollapse = semaphore::release;
mExpandedController.collapseBackToStack(mExpansionPoint, false, afterCollapse);
- waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
-
- testStackedAtPosition(mExpansionPoint.x, mExpansionPoint.y, -1);
- verify(afterExpand).run();
+ assertThat(semaphore.tryAcquire(1, 2, TimeUnit.SECONDS)).isTrue();
+ waitForAnimation();
+ testStackedAtPosition(mExpansionPoint.x, mExpansionPoint.y);
}
@Test
- @Ignore
- public void testOnChildAdded() throws InterruptedException {
+ public void testOnChildAdded() throws Exception {
expand();
+ waitForMainThread();
// Add another new view and wait for its animation.
final View newView = new FrameLayout(getContext());
mLayout.addView(newView, 0);
- waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+ waitForAnimation();
testBubblesInCorrectExpandedPositions();
}
@Test
- @Ignore
- public void testOnChildRemoved() throws InterruptedException {
+ public void testOnChildRemoved() throws Exception {
expand();
+ waitForMainThread();
- // Remove some views and see if the remaining child views still pass the expansion test.
+ // Remove some views and verify the remaining child views still pass the expansion test.
mLayout.removeView(mViews.get(0));
mLayout.removeView(mViews.get(3));
- waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+
+ // Removing a view will invoke onBubbleAnimatedOutAction. Block until it gets called twice.
+ assertThat(mBubbleRemovedSemaphore.tryAcquire(2, 2, TimeUnit.SECONDS)).isTrue();
+
+ waitForAnimation();
testBubblesInCorrectExpandedPositions();
}
@Test
- public void testDragBubbleOutDoesntNPE() throws InterruptedException {
+ public void testDragBubbleOutDoesntNPE() {
mExpandedController.onGestureFinished();
mExpandedController.dragBubbleOut(mViews.get(0), 1, 1);
}
/** Expand the stack and wait for animations to finish. */
private void expand() throws InterruptedException {
- mExpandedController.expandFromStack(mock(Runnable.class));
- waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+ final Semaphore semaphore = new Semaphore(0);
+ Runnable afterExpand = semaphore::release;
+
+ mExpandedController.expandFromStack(afterExpand);
+ assertThat(semaphore.tryAcquire(1, TimeUnit.SECONDS)).isTrue();
}
/** Check that children are in the correct positions for being stacked. */
- private void testStackedAtPosition(float x, float y, int offsetMultiplier) {
+ private void testStackedAtPosition(float x, float y) {
// Make sure the rest of the stack moved again, including the first bubble not moving, and
// is stacked to the right now that we're on the right side of the screen.
for (int i = 0; i < mLayout.getChildCount(); i++) {
- assertEquals(x + i * offsetMultiplier * mStackOffset,
- mLayout.getChildAt(i).getTranslationX(), 2f);
- assertEquals(y, mLayout.getChildAt(i).getTranslationY(), 2f);
+ assertEquals(x, mLayout.getChildAt(i).getTranslationX(), 2f);
+ assertEquals(y + Math.min(i, 1) * mStackOffset, mLayout.getChildAt(i).getTranslationY(),
+ 2f);
assertEquals(1f, mLayout.getChildAt(i).getAlpha(), .01f);
}
}
@@ -175,4 +184,22 @@
mLayout.getChildAt(i).getTranslationY(), 2f);
}
}
+
+ private void waitForAnimation() throws Exception {
+ final Semaphore semaphore = new Semaphore(0);
+ boolean[] animating = new boolean[]{ true };
+ for (int i = 0; i < 4; i++) {
+ if (animating[0]) {
+ mMainThreadHandler.post(() -> {
+ if (!mExpandedController.isAnimating()) {
+ animating[0] = false;
+ semaphore.release();
+ }
+ });
+ Thread.sleep(500);
+ }
+ }
+ assertThat(semaphore.tryAcquire(1, 2, TimeUnit.SECONDS)).isTrue();
+ waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayoutTestCase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayoutTestCase.java
index 48ae296..2ed5add 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayoutTestCase.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayoutTestCase.java
@@ -164,11 +164,17 @@
@Override
public void cancelAllAnimations() {
+ if (mLayout.getChildCount() == 0) {
+ return;
+ }
mMainThreadHandler.post(super::cancelAllAnimations);
}
@Override
public void cancelAnimationsOnView(View view) {
+ if (mLayout.getChildCount() == 0) {
+ return;
+ }
mMainThreadHandler.post(() -> super.cancelAnimationsOnView(view));
}
@@ -221,6 +227,9 @@
@Override
protected void startPathAnimation() {
+ if (mLayout.getChildCount() == 0) {
+ return;
+ }
mMainThreadHandler.post(super::startPathAnimation);
}
}
@@ -322,4 +331,9 @@
e.printStackTrace();
}
}
+
+ /** Waits for the main thread to finish processing all pending runnables. */
+ public void waitForMainThread() {
+ runOnMainThreadAndBlock(() -> {});
+ }
}
diff --git a/location/java/android/location/flags/gnss.aconfig b/location/java/android/location/flags/gnss.aconfig
index b6055e8..f4b1056 100644
--- a/location/java/android/location/flags/gnss.aconfig
+++ b/location/java/android/location/flags/gnss.aconfig
@@ -20,3 +20,10 @@
description: "Flag for GnssMeasurementRequest WorkSource API"
bug: "295235160"
}
+
+flag {
+ name: "release_supl_connection_on_timeout"
+ namespace: "location"
+ description: "Flag for releasing SUPL connection on timeout"
+ bug: "315024652"
+}
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index 5a72b0b..1a3d7b7 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -23,6 +23,8 @@
import android.annotation.TestApi;
import android.util.SparseIntArray;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
@@ -402,7 +404,9 @@
private final AudioDevicePort mPort;
- AudioDeviceInfo(AudioDevicePort port) {
+ /** @hide */
+ @VisibleForTesting
+ public AudioDeviceInfo(AudioDevicePort port) {
mPort = port;
}
diff --git a/media/java/android/media/AudioDevicePort.java b/media/java/android/media/AudioDevicePort.java
index 73bc6f9..2de8eef 100644
--- a/media/java/android/media/AudioDevicePort.java
+++ b/media/java/android/media/AudioDevicePort.java
@@ -20,6 +20,8 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
+import com.android.aconfig.annotations.VisibleForTesting;
+
import java.util.Arrays;
import java.util.List;
@@ -38,6 +40,26 @@
public class AudioDevicePort extends AudioPort {
+ /** @hide */
+ // TODO: b/316864909 - Remove this method once there's a way to fake audio device ports further
+ // down the stack.
+ @VisibleForTesting
+ public static AudioDevicePort createForTesting(
+ int type, @NonNull String name, @NonNull String address) {
+ return new AudioDevicePort(
+ new AudioHandle(/* id= */ 0),
+ name,
+ /* samplingRates= */ null,
+ /* channelMasks= */ null,
+ /* channelIndexMasks= */ null,
+ /* formats= */ null,
+ /* gains= */ null,
+ type,
+ address,
+ /* encapsulationModes= */ null,
+ /* encapsulationMetadataTypes= */ null);
+ }
+
private final int mType;
private final String mAddress;
private final int[] mEncapsulationModes;
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 3dfd572..a5a69f9 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -22,6 +22,7 @@
import static android.media.audio.Flags.autoPublicVolumeApiHardening;
import static android.media.audio.Flags.automaticBtDeviceType;
import static android.media.audio.Flags.FLAG_FOCUS_FREEZE_TEST_API;
+import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
import android.Manifest;
import android.annotation.CallbackExecutor;
@@ -5120,6 +5121,71 @@
}
/**
+ * Notifies an application with a focus listener of gain or loss of audio focus
+ *
+ * <p>This is similar to {@link #dispatchAudioFocusChange(AudioFocusInfo, int, AudioPolicy)} but
+ * with additional functionality of fade. The players of the application with audio focus
+ * change, provided they meet the active {@link FadeManagerConfiguration} requirements, are
+ * faded before dispatching the callback to the application. For example, players of the
+ * application losing audio focus will be faded out, whereas players of the application gaining
+ * audio focus will be faded in, if needed.
+ *
+ * <p>The applicability of fade is decided against the supplied active {@link AudioFocusInfo}.
+ * This list cannot be {@code null}. The list can be empty if no other active
+ * {@link AudioFocusInfo} available at the time of the dispatch.
+ *
+ * <p>The {@link FadeManagerConfiguration} supplied here is prioritized over existing fade
+ * configurations. If none supplied, either the {@link FadeManagerConfiguration} set through
+ * {@link AudioPolicy} or the default will be used to determine the fade properties.
+ *
+ * <p>This method can only be used by owners of an {@link AudioPolicy} configured with
+ * {@link AudioPolicy.Builder#setIsAudioFocusPolicy(boolean)} set to true.
+ *
+ * @param afi the recipient of the focus change, that has previously requested audio focus, and
+ * that was received by the {@code AudioPolicy} through
+ * {@link AudioPolicy.AudioPolicyFocusListener#onAudioFocusRequest(AudioFocusInfo, int)}
+ * @param focusChange one of focus gain types ({@link #AUDIOFOCUS_GAIN},
+ * {@link #AUDIOFOCUS_GAIN_TRANSIENT}, {@link #AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK} or
+ * {@link #AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE})
+ * or one of the focus loss types ({@link AudioManager#AUDIOFOCUS_LOSS},
+ * {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT},
+ * or {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}).
+ * <br>For the focus gain, the change type should be the same as the app requested
+ * @param ap a valid registered {@link AudioPolicy} configured as a focus policy.
+ * @param otherActiveAfis active {@link AudioFocusInfo} that are granted audio focus at the time
+ * of dispatch
+ * @param transientFadeMgrConfig {@link FadeManagerConfiguration} that will be used for fading
+ * players resulting from this dispatch. This is a transient configuration that is only
+ * valid for this focus change and shall be discarded after processing this request.
+ * @return {@link #AUDIOFOCUS_REQUEST_FAILED} if the focus client didn't have a listener or if
+ * there was an error sending the request, or {@link #AUDIOFOCUS_REQUEST_GRANTED} if the
+ * dispatch was successfully sent, or {@link #AUDIOFOCUS_REQUEST_DELAYED} if
+ * the request was successful but the dispatch of focus change was delayed due to a fade
+ * operation.
+ * @throws NullPointerException if the {@link AudioFocusInfo} or {@link AudioPolicy} or list of
+ * other active {@link AudioFocusInfo} are {@code null}.
+ * @hide
+ */
+ @FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION)
+ @SystemApi
+ @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public int dispatchAudioFocusChangeWithFade(@NonNull AudioFocusInfo afi, int focusChange,
+ @NonNull AudioPolicy ap, @NonNull List<AudioFocusInfo> otherActiveAfis,
+ @Nullable FadeManagerConfiguration transientFadeMgrConfig) {
+ Objects.requireNonNull(afi, "AudioFocusInfo cannot be null");
+ Objects.requireNonNull(ap, "AudioPolicy cannot be null");
+ Objects.requireNonNull(otherActiveAfis, "Other active AudioFocusInfo list cannot be null");
+
+ IAudioService service = getService();
+ try {
+ return service.dispatchFocusChangeWithFade(afi, focusChange, ap.cb(), otherActiveAfis,
+ transientFadeMgrConfig);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* @hide
* Used internally by telephony package to abandon audio focus, typically after a call or
* when ringing ends and the call is rejected or not answered.
diff --git a/media/java/android/media/FadeManagerConfiguration.java b/media/java/android/media/FadeManagerConfiguration.java
index 337d4b0..40b0e3e 100644
--- a/media/java/android/media/FadeManagerConfiguration.java
+++ b/media/java/android/media/FadeManagerConfiguration.java
@@ -16,12 +16,13 @@
package android.media;
-import static com.android.media.flags.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
+import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArrayMap;
@@ -93,11 +94,9 @@
* Helps with recreating a new instance from another to simply change/add on top of the
* existing ones</li>
* </ul>
- * TODO(b/304835727): Convert into system API so that it can be set through AudioPolicy
- *
* @hide
*/
-
+@SystemApi
@FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION)
public final class FadeManagerConfiguration implements Parcelable {
@@ -523,6 +522,7 @@
*
* @param fadeState one of the fade state in {@link FadeStateEnum}
* @return human-readable string
+ * @hide
*/
@NonNull
public static String fadeStateToString(@FadeStateEnum int fadeState) {
@@ -712,7 +712,8 @@
*
* <p><b>Notes:</b>
* <ul>
- * <li>When fade state is set to enabled, the builder expects at least one valid usage to be
+ * <li>When fade state is set to {@link #FADE_STATE_ENABLED_DEFAULT} or
+ * {@link #FADE_STATE_ENABLED_AUTO}, the builder expects at least one valid usage to be
* set/added. Failure to do so will result in an exception during {@link #build()}</li>
* <li>Every usage added to the fadeable list should have corresponding volume shaper
* configs defined. This can be achieved by setting either the duration or volume shaper
@@ -720,8 +721,8 @@
* {@link #setFadeOutVolumeShaperConfigForUsage(int, VolumeShaper.Configuration)}</li>
* <li> It is recommended to set volume shaper configurations individually for fade out and
* fade in</li>
- * <li>For any incomplete volume shaper configs a volume shaper configuration will be
- * created using either the default fade durations or the ones provided as part of the
+ * <li>For any incomplete volume shaper configurations, a volume shaper configuration will
+ * be created using either the default fade durations or the ones provided as part of the
* {@link #Builder(long, long)}</li>
* <li>Additional volume shaper configs can also configured for a given usage
* with additional attributes like content-type in order to achieve finer fade controls.
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index a52f0b0..5c268d4 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -28,6 +28,7 @@
import android.media.AudioRecordingConfiguration;
import android.media.AudioRoutesInfo;
import android.media.BluetoothProfileConnectionInfo;
+import android.media.FadeManagerConfiguration;
import android.media.IAudioDeviceVolumeDispatcher;
import android.media.IAudioFocusDispatcher;
import android.media.IAudioModeDispatcher;
@@ -398,6 +399,14 @@
int dispatchFocusChange(in AudioFocusInfo afi, in int focusChange,
in IAudioPolicyCallback pcb);
+ @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)")
+ int dispatchFocusChangeWithFade(in AudioFocusInfo afi,
+ in int focusChange,
+ in IAudioPolicyCallback pcb,
+ in List<AudioFocusInfo> otherActiveAfis,
+ in FadeManagerConfiguration transientFadeMgrConfig);
+
oneway void playerHasOpPlayAudio(in int piid, in boolean hasOpPlayAudio);
@EnforcePermission("BLUETOOTH_STACK")
@@ -754,4 +763,16 @@
oneway void removeLoudnessCodecInfo(int piid, in LoudnessCodecInfo codecInfo);
PersistableBundle getLoudnessParams(int piid, in LoudnessCodecInfo codecInfo);
+
+ @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)")
+ int setFadeManagerConfigurationForFocusLoss(in FadeManagerConfiguration fmcForFocusLoss);
+
+ @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)")
+ int clearFadeManagerConfigurationForFocusLoss();
+
+ @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)")
+ FadeManagerConfiguration getFadeManagerConfigurationForFocusLoss();
}
diff --git a/media/java/android/media/IMediaRouter2.aidl b/media/java/android/media/IMediaRouter2.aidl
index 29bfd1a..e2dddad 100644
--- a/media/java/android/media/IMediaRouter2.aidl
+++ b/media/java/android/media/IMediaRouter2.aidl
@@ -19,6 +19,7 @@
import android.media.MediaRoute2Info;
import android.media.RoutingSessionInfo;
import android.os.Bundle;
+import android.os.UserHandle;
/**
* @hide
@@ -35,5 +36,6 @@
* Call MediaRouterService#requestCreateSessionWithRouter2 to pass the result.
*/
void requestCreateSessionByManager(long uniqueRequestId, in RoutingSessionInfo oldSession,
- in MediaRoute2Info route);
+ in MediaRoute2Info route, in UserHandle transferInitiatorUserHandle,
+ in String transferInitiatorPackageName);
}
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index fa4d1a1..04e99ea 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -64,7 +64,8 @@
void requestCreateSessionWithRouter2(IMediaRouter2 router, int requestId, long managerRequestId,
in RoutingSessionInfo oldSession, in MediaRoute2Info route,
- in @nullable Bundle sessionHints);
+ in @nullable Bundle sessionHints, in UserHandle transferInitiatorUserHandle,
+ in String transferInitiatorPackageName);
void selectRouteWithRouter2(IMediaRouter2 router, String sessionId, in MediaRoute2Info route);
void deselectRouteWithRouter2(IMediaRouter2 router, String sessionId, in MediaRoute2Info route);
void transferToRouteWithRouter2(IMediaRouter2 router, String sessionId,
@@ -84,13 +85,16 @@
void stopScan(IMediaRouter2Manager manager);
void requestCreateSessionWithManager(IMediaRouter2Manager manager, int requestId,
- in RoutingSessionInfo oldSession, in @nullable MediaRoute2Info route);
+ in RoutingSessionInfo oldSession, in @nullable MediaRoute2Info route,
+ in UserHandle transferInitiatorUserHandle, in String transferInitiatorPackageName);
void selectRouteWithManager(IMediaRouter2Manager manager, int requestId,
String sessionId, in MediaRoute2Info route);
void deselectRouteWithManager(IMediaRouter2Manager manager, int requestId,
String sessionId, in MediaRoute2Info route);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL)")
void transferToRouteWithManager(IMediaRouter2Manager manager, int requestId,
- String sessionId, in MediaRoute2Info route);
+ String sessionId, in MediaRoute2Info route,
+ in UserHandle transferInitiatorUserHandle, String transferInitiatorPackageName);
void setSessionVolumeWithManager(IMediaRouter2Manager manager, int requestId,
String sessionId, int volume);
void releaseSessionWithManager(IMediaRouter2Manager manager, int requestId, String sessionId);
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 8ad3587..0eabe66 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -19,6 +19,7 @@
import static android.media.MediaRouter2Utils.toUniqueId;
import static com.android.media.flags.Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER;
+import static com.android.media.flags.Flags.FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES;
import static com.android.media.flags.Flags.FLAG_ENABLE_NEW_MEDIA_ROUTE_2_INFO_TYPES;
import android.annotation.FlaggedApi;
@@ -479,6 +480,37 @@
public static final String FEATURE_REMOTE_GROUP_PLAYBACK =
"android.media.route.feature.REMOTE_GROUP_PLAYBACK";
+ /** Indicates the route is always suitable for media playback. */
+ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+ public static final int SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER = 0;
+
+ /**
+ * Indicates that the route is suitable for media playback only after explicit user selection.
+ */
+ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+ public static final int SUITABILITY_STATUS_SUITABLE_FOR_MANUAL_TRANSFER = 1;
+
+ /** Indicates that the route is never suitable for media playback. */
+ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+ public static final int SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER = 2;
+
+ /**
+ * Route suitability status.
+ *
+ * <p>Signals whether the route is suitable to play media.
+ *
+ * @hide
+ */
+ @IntDef(
+ value = {
+ SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER,
+ SUITABILITY_STATUS_SUITABLE_FOR_MANUAL_TRANSFER,
+ SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+ public @interface SuitabilityStatus {}
+
private final String mId;
private final CharSequence mName;
private final List<String> mFeatures;
@@ -500,6 +532,7 @@
private final String mProviderId;
private final boolean mIsVisibilityRestricted;
private final Set<String> mAllowedPackages;
+ @SuitabilityStatus private final int mSuitabilityStatus;
MediaRoute2Info(@NonNull Builder builder) {
mId = builder.mId;
@@ -521,6 +554,7 @@
mProviderId = builder.mProviderId;
mIsVisibilityRestricted = builder.mIsVisibilityRestricted;
mAllowedPackages = builder.mAllowedPackages;
+ mSuitabilityStatus = builder.mSuitabilityStatus;
}
MediaRoute2Info(@NonNull Parcel in) {
@@ -544,6 +578,7 @@
mProviderId = in.readString();
mIsVisibilityRestricted = in.readBoolean();
mAllowedPackages = Set.of(in.createString8Array());
+ mSuitabilityStatus = in.readInt();
}
/**
@@ -778,6 +813,13 @@
|| mAllowedPackages.contains(packageName);
}
+ /** Returns the route suitability status. */
+ @SuitabilityStatus
+ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+ public int getSuitabilityStatus() {
+ return mSuitabilityStatus;
+ }
+
/**
* Dumps the current state of the object to the given {@code pw} as a human-readable string.
*
@@ -809,6 +851,7 @@
pw.println(indent + "mProviderId=" + mProviderId);
pw.println(indent + "mIsVisibilityRestricted=" + mIsVisibilityRestricted);
pw.println(indent + "mAllowedPackages=" + mAllowedPackages);
+ pw.println(indent + "mSuitabilityStatus=" + mSuitabilityStatus);
}
private void dumpVolume(@NonNull PrintWriter pw, @NonNull String prefix) {
@@ -861,39 +904,74 @@
&& Objects.equals(mDeduplicationIds, other.mDeduplicationIds)
&& Objects.equals(mProviderId, other.mProviderId)
&& (mIsVisibilityRestricted == other.mIsVisibilityRestricted)
- && Objects.equals(mAllowedPackages, other.mAllowedPackages);
+ && Objects.equals(mAllowedPackages, other.mAllowedPackages)
+ && mSuitabilityStatus == other.mSuitabilityStatus;
}
@Override
public int hashCode() {
// Note: mExtras is not included.
- return Objects.hash(mId, mName, mFeatures, mType, mIsSystem, mIconUri, mDescription,
- mConnectionState, mClientPackageName, mPackageName, mVolumeHandling, mVolumeMax,
- mVolume, mAddress, mDeduplicationIds, mProviderId, mIsVisibilityRestricted,
- mAllowedPackages);
+ return Objects.hash(
+ mId,
+ mName,
+ mFeatures,
+ mType,
+ mIsSystem,
+ mIconUri,
+ mDescription,
+ mConnectionState,
+ mClientPackageName,
+ mPackageName,
+ mVolumeHandling,
+ mVolumeMax,
+ mVolume,
+ mAddress,
+ mDeduplicationIds,
+ mProviderId,
+ mIsVisibilityRestricted,
+ mAllowedPackages,
+ mSuitabilityStatus);
}
@Override
public String toString() {
// Note: mExtras is not printed here.
- StringBuilder result = new StringBuilder()
- .append("MediaRoute2Info{ ")
- .append("id=").append(getId())
- .append(", name=").append(getName())
- .append(", features=").append(getFeatures())
- .append(", iconUri=").append(getIconUri())
- .append(", description=").append(getDescription())
- .append(", connectionState=").append(getConnectionState())
- .append(", clientPackageName=").append(getClientPackageName())
- .append(", volumeHandling=").append(getVolumeHandling())
- .append(", volumeMax=").append(getVolumeMax())
- .append(", volume=").append(getVolume())
- .append(", address=").append(getAddress())
- .append(", deduplicationIds=").append(String.join(",", getDeduplicationIds()))
- .append(", providerId=").append(getProviderId())
- .append(", isVisibilityRestricted=").append(mIsVisibilityRestricted)
- .append(", allowedPackages=").append(String.join(",", mAllowedPackages))
- .append(" }");
+ StringBuilder result =
+ new StringBuilder()
+ .append("MediaRoute2Info{ ")
+ .append("id=")
+ .append(getId())
+ .append(", name=")
+ .append(getName())
+ .append(", features=")
+ .append(getFeatures())
+ .append(", iconUri=")
+ .append(getIconUri())
+ .append(", description=")
+ .append(getDescription())
+ .append(", connectionState=")
+ .append(getConnectionState())
+ .append(", clientPackageName=")
+ .append(getClientPackageName())
+ .append(", volumeHandling=")
+ .append(getVolumeHandling())
+ .append(", volumeMax=")
+ .append(getVolumeMax())
+ .append(", volume=")
+ .append(getVolume())
+ .append(", address=")
+ .append(getAddress())
+ .append(", deduplicationIds=")
+ .append(String.join(",", getDeduplicationIds()))
+ .append(", providerId=")
+ .append(getProviderId())
+ .append(", isVisibilityRestricted=")
+ .append(mIsVisibilityRestricted)
+ .append(", allowedPackages=")
+ .append(String.join(",", mAllowedPackages))
+ .append(", suitabilityStatus=")
+ .append(mSuitabilityStatus)
+ .append(" }");
return result.toString();
}
@@ -923,6 +1001,7 @@
dest.writeString(mProviderId);
dest.writeBoolean(mIsVisibilityRestricted);
dest.writeString8Array(mAllowedPackages.toArray(new String[0]));
+ dest.writeInt(mSuitabilityStatus);
}
private static String getDeviceTypeString(@Type int deviceType) {
@@ -1005,6 +1084,7 @@
private String mProviderId;
private boolean mIsVisibilityRestricted;
private Set<String> mAllowedPackages;
+ @SuitabilityStatus private int mSuitabilityStatus;
/**
* Constructor for builder to create {@link MediaRoute2Info}.
@@ -1028,6 +1108,7 @@
mFeatures = new ArrayList<>();
mDeduplicationIds = Set.of();
mAllowedPackages = Set.of();
+ mSuitabilityStatus = SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER;
}
/**
@@ -1075,6 +1156,7 @@
mProviderId = routeInfo.mProviderId;
mIsVisibilityRestricted = routeInfo.mIsVisibilityRestricted;
mAllowedPackages = routeInfo.mAllowedPackages;
+ mSuitabilityStatus = routeInfo.mSuitabilityStatus;
}
/**
@@ -1318,6 +1400,23 @@
}
/**
+ * Sets route suitability status.
+ *
+ * <p>The default value is {@link
+ * MediaRoute2Info#SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER}.
+ *
+ * <p> Apps are not supposed to set {@link
+ * MediaRoute2Info#SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER}. Publishing a non-system
+ * route with such status throws {@link SecurityException}.
+ */
+ @NonNull
+ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+ public Builder setSuitabilityStatus(@SuitabilityStatus int suitabilityStatus) {
+ mSuitabilityStatus = suitabilityStatus;
+ return this;
+ }
+
+ /**
* Builds the {@link MediaRoute2Info media route info}.
*
* @throws IllegalArgumentException if no features are added.
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index ba26df9..5e23551 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -17,6 +17,7 @@
package android.media;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+import static com.android.media.flags.Flags.FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES;
import static com.android.media.flags.Flags.FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2;
import static com.android.media.flags.Flags.FLAG_ENABLE_CROSS_USER_ROUTING_IN_MEDIA_ROUTER2;
@@ -699,15 +700,48 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
+ @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL)
public void transfer(@NonNull RoutingController controller, @NonNull MediaRoute2Info route) {
- mImpl.transfer(controller.getRoutingSessionInfo(), route);
+ mImpl.transfer(
+ controller.getRoutingSessionInfo(),
+ route,
+ android.os.Process.myUserHandle(),
+ mContext.getPackageName());
+ }
+
+ /**
+ * Transfers the media of a routing controller to the given route.
+ *
+ * <p>This will be no-op for non-system media routers.
+ *
+ * @param controller a routing controller controlling media routing.
+ * @param route the route you want to transfer the media to.
+ * @param transferInitiatorUserHandle the user handle of the app that initiated the transfer
+ * 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()}.
+ * @hide
+ */
+ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+ public void transfer(
+ @NonNull RoutingController controller,
+ @NonNull MediaRoute2Info route,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName) {
+ mImpl.transfer(
+ controller.getRoutingSessionInfo(),
+ route,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName);
}
void requestCreateController(
@NonNull RoutingController controller,
@NonNull MediaRoute2Info route,
- long managerRequestId) {
+ long managerRequestId,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName) {
final int requestId = mNextRequestId.getAndIncrement();
@@ -736,7 +770,9 @@
managerRequestId,
controller.getRoutingSessionInfo(),
route,
- controllerHints);
+ controllerHints,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName);
} catch (RemoteException ex) {
Log.e(TAG, "createControllerForTransfer: "
+ "Failed to request for creating a controller.", ex);
@@ -1053,7 +1089,11 @@
}
void onRequestCreateControllerByManagerOnHandler(
- RoutingSessionInfo oldSession, MediaRoute2Info route, long managerRequestId) {
+ RoutingSessionInfo oldSession,
+ MediaRoute2Info route,
+ long managerRequestId,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName) {
Log.i(
TAG,
TextUtils.formatSimple(
@@ -1070,7 +1110,8 @@
if (controller == null) {
return;
}
- requestCreateController(controller, route, managerRequestId);
+ requestCreateController(controller, route, managerRequestId, transferInitiatorUserHandle,
+ transferInitiatorPackageName);
}
private List<MediaRoute2Info> getSortedRoutes(
@@ -1469,6 +1510,21 @@
}
/**
+ * Returns whether the transfer was requested 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() {
+ RoutingSessionInfo sessionInfo = getRoutingSessionInfo();
+
+ UserHandle transferInitiatorUserHandle = sessionInfo.getTransferInitiatorUserHandle();
+ String transferInitiatorPackageName = sessionInfo.getTransferInitiatorPackageName();
+
+ return Objects.equals(android.os.Process.myUserHandle(), transferInitiatorUserHandle)
+ && Objects.equals(mContext.getPackageName(), transferInitiatorPackageName);
+ }
+
+ /**
* Returns the current {@link RoutingSessionInfo} associated to this controller.
*/
@NonNull
@@ -1980,14 +2036,20 @@
@Override
public void requestCreateSessionByManager(
- long managerRequestId, RoutingSessionInfo oldSession, MediaRoute2Info route) {
+ long managerRequestId,
+ RoutingSessionInfo oldSession,
+ MediaRoute2Info route,
+ UserHandle transferInitiatorUserHandle,
+ String transferInitiatorPackageName) {
mHandler.sendMessage(
obtainMessage(
MediaRouter2::onRequestCreateControllerByManagerOnHandler,
MediaRouter2.this,
oldSession,
route,
- managerRequestId));
+ managerRequestId,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName));
}
}
@@ -2027,7 +2089,11 @@
void stop();
- void transfer(RoutingSessionInfo sessionInfo, MediaRoute2Info route);
+ void transfer(
+ @NonNull RoutingSessionInfo sessionInfo,
+ @NonNull MediaRoute2Info route,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName);
List<RoutingController> getControllers();
@@ -2220,7 +2286,11 @@
List<RoutingSessionInfo> sessionInfos = getRoutingSessions();
RoutingSessionInfo targetSession = sessionInfos.get(sessionInfos.size() - 1);
- transfer(targetSession, route);
+ transfer(
+ targetSession,
+ route,
+ android.os.Process.myUserHandle(),
+ mContext.getPackageName());
}
@Override
@@ -2243,14 +2313,24 @@
*
* @param sessionInfo The {@link RoutingSessionInfo routing session} to transfer.
* @param route The {@link MediaRoute2Info route} to transfer to.
- * @see #transferToRoute(RoutingSessionInfo, MediaRoute2Info)
+ * @param transferInitiatorUserHandle The user handle of the app that initiated the
+ * transfer.
+ * @param transferInitiatorPackageName The package name if of the app that initiated the
+ * transfer.
+ * @see #transferToRoute(RoutingSessionInfo, MediaRoute2Info, UserHandle, String)
* @see #requestCreateSession(RoutingSessionInfo, MediaRoute2Info)
*/
@Override
+ @SuppressWarnings("AndroidFrameworkRequiresPermission")
public void transfer(
- @NonNull RoutingSessionInfo sessionInfo, @NonNull MediaRoute2Info route) {
+ @NonNull RoutingSessionInfo sessionInfo,
+ @NonNull MediaRoute2Info route,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName) {
Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
Objects.requireNonNull(route, "route must not be null");
+ Objects.requireNonNull(transferInitiatorUserHandle);
+ Objects.requireNonNull(transferInitiatorPackageName);
Log.v(
TAG,
@@ -2268,9 +2348,14 @@
}
if (sessionInfo.getTransferableRoutes().contains(route.getId())) {
- transferToRoute(sessionInfo, route);
+ transferToRoute(
+ sessionInfo,
+ route,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName);
} else {
- requestCreateSession(sessionInfo, route);
+ requestCreateSession(sessionInfo, route, transferInitiatorUserHandle,
+ transferInitiatorPackageName);
}
}
@@ -2282,21 +2367,30 @@
* RoutingSessionInfo routing session's} {@link RoutingSessionInfo#getTransferableRoutes()
* transferable routes list}. Otherwise, the request will fail.
*
- * <p>Use {@link #requestCreateSession(RoutingSessionInfo, MediaRoute2Info)} to request
- * an out-of-session transfer.
+ * <p>Use {@link #requestCreateSession(RoutingSessionInfo, MediaRoute2Info)} to request an
+ * out-of-session transfer.
*
* @param session The {@link RoutingSessionInfo routing session} to transfer.
* @param route The {@link MediaRoute2Info route} to transfer to. Must be one of the {@link
* RoutingSessionInfo routing session's} {@link
* RoutingSessionInfo#getTransferableRoutes() transferable routes}.
*/
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
private void transferToRoute(
- @NonNull RoutingSessionInfo session, @NonNull MediaRoute2Info route) {
+ @NonNull RoutingSessionInfo session,
+ @NonNull MediaRoute2Info route,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName) {
int requestId = createTransferRequest(session, route);
try {
mMediaRouterService.transferToRouteWithManager(
- mClient, requestId, session.getId(), route);
+ mClient,
+ requestId,
+ session.getId(),
+ route,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
@@ -2317,7 +2411,10 @@
* @param route The {@link MediaRoute2Info route} to transfer to.
*/
private void requestCreateSession(
- @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route) {
+ @NonNull RoutingSessionInfo oldSession,
+ @NonNull MediaRoute2Info route,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName) {
if (TextUtils.isEmpty(oldSession.getClientPackageName())) {
Log.w(TAG, "requestCreateSession: Can't create a session without package name.");
this.onTransferFailed(oldSession, route);
@@ -2328,7 +2425,12 @@
try {
mMediaRouterService.requestCreateSessionWithManager(
- mClient, requestId, oldSession, route);
+ mClient,
+ requestId,
+ oldSession,
+ route,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
@@ -3055,7 +3157,8 @@
return;
}
- requestCreateController(controller, route, MANAGER_REQUEST_ID_NONE);
+ requestCreateController(controller, route, MANAGER_REQUEST_ID_NONE,
+ android.os.Process.myUserHandle(), mContext.getPackageName());
}
@Override
@@ -3071,7 +3174,11 @@
* #transferTo(MediaRoute2Info)}.
*/
@Override
- public void transfer(RoutingSessionInfo sessionInfo, MediaRoute2Info route) {
+ public void transfer(
+ @NonNull RoutingSessionInfo sessionInfo,
+ @NonNull MediaRoute2Info route,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName) {
// Do nothing.
}
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 830708c..06c0996 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -18,9 +18,11 @@
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.content.Context;
import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
@@ -28,6 +30,7 @@
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -467,30 +470,42 @@
* <p>Same as {@link #transfer(RoutingSessionInfo, MediaRoute2Info)}, but resolves the routing
* session based on the provided package name.
*/
- public void transfer(@NonNull String packageName, @NonNull MediaRoute2Info route) {
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
+ public void transfer(
+ @NonNull String packageName,
+ @NonNull MediaRoute2Info route,
+ @NonNull UserHandle userHandle) {
Objects.requireNonNull(packageName, "packageName must not be null");
Objects.requireNonNull(route, "route must not be null");
List<RoutingSessionInfo> sessionInfos = getRoutingSessions(packageName);
RoutingSessionInfo targetSession = sessionInfos.get(sessionInfos.size() - 1);
- transfer(targetSession, route);
+ transfer(targetSession, route, userHandle, packageName);
}
/**
* Transfers a routing session to a media route.
+ *
* <p>{@link Callback#onTransferred} or {@link Callback#onTransferFailed} will be called
* depending on the result.
*
* @param sessionInfo the routing session info to transfer
* @param route the route transfer to
- *
+ * @param transferInitiatorUserHandle the user handle of an app initiated the transfer
+ * @param transferInitiatorPackageName the package name of an app initiated the transfer
* @see Callback#onTransferred(RoutingSessionInfo, RoutingSessionInfo)
* @see Callback#onTransferFailed(RoutingSessionInfo, MediaRoute2Info)
*/
- public void transfer(@NonNull RoutingSessionInfo sessionInfo,
- @NonNull MediaRoute2Info route) {
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
+ public void transfer(
+ @NonNull RoutingSessionInfo sessionInfo,
+ @NonNull MediaRoute2Info route,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName) {
Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
Objects.requireNonNull(route, "route must not be null");
+ Objects.requireNonNull(transferInitiatorUserHandle);
+ Objects.requireNonNull(transferInitiatorPackageName);
Log.v(TAG, "Transferring routing session. session= " + sessionInfo + ", route=" + route);
@@ -503,9 +518,11 @@
}
if (sessionInfo.getTransferableRoutes().contains(route.getId())) {
- transferToRoute(sessionInfo, route);
+ transferToRoute(
+ sessionInfo, route, transferInitiatorUserHandle, transferInitiatorPackageName);
} else {
- requestCreateSession(sessionInfo, route);
+ requestCreateSession(sessionInfo, route, transferInitiatorUserHandle,
+ transferInitiatorPackageName);
}
}
@@ -873,19 +890,30 @@
*
* @hide
*/
- private void transferToRoute(@NonNull RoutingSessionInfo session,
- @NonNull MediaRoute2Info route) {
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
+ private void transferToRoute(
+ @NonNull RoutingSessionInfo session,
+ @NonNull MediaRoute2Info route,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName) {
int requestId = createTransferRequest(session, route);
try {
mMediaRouterService.transferToRouteWithManager(
- mClient, requestId, session.getId(), route);
+ mClient,
+ requestId,
+ session.getId(),
+ route,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
- private void requestCreateSession(RoutingSessionInfo oldSession, MediaRoute2Info route) {
+ private void requestCreateSession(RoutingSessionInfo oldSession, MediaRoute2Info route,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiationPackageName) {
if (TextUtils.isEmpty(oldSession.getClientPackageName())) {
Log.w(TAG, "requestCreateSession: Can't create a session without package name.");
notifyTransferFailed(oldSession, route);
@@ -896,7 +924,8 @@
try {
mMediaRouterService.requestCreateSessionWithManager(
- mClient, requestId, oldSession, route);
+ mClient, requestId, oldSession, route, transferInitiatorUserHandle,
+ transferInitiationPackageName);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java
index a77c943..d28c26d 100644
--- a/media/java/android/media/RoutingSessionInfo.java
+++ b/media/java/android/media/RoutingSessionInfo.java
@@ -16,18 +16,25 @@
package android.media;
+import static com.android.media.flags.Flags.FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
import android.text.TextUtils;
import com.android.internal.util.Preconditions;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -55,6 +62,33 @@
private static final String KEY_GROUP_ROUTE = "androidx.mediarouter.media.KEY_GROUP_ROUTE";
private static final String KEY_VOLUME_HANDLING = "volumeHandling";
+ /**
+ * Indicates that the transfer happened by the default logic without explicit system's or user's
+ * request.
+ *
+ * <p>For example, an automatically connected Bluetooth device will have this transfer reason.
+ */
+ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+ public static final int TRANSFER_REASON_FALLBACK = 0;
+
+ /** Indicates that the transfer happened from within a privileged application. */
+ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+ public static final int TRANSFER_REASON_SYSTEM_REQUEST = 1;
+
+ /** Indicates that the transfer happened from a non-privileged app. */
+ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+ public static final int TRANSFER_REASON_APP = 2;
+
+ /**
+ * Indicates the transfer reason.
+ *
+ * @hide
+ */
+ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+ @IntDef(value = {TRANSFER_REASON_FALLBACK, TRANSFER_REASON_SYSTEM_REQUEST, TRANSFER_REASON_APP})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TransferReason {}
+
@NonNull
final String mId;
@Nullable
@@ -82,6 +116,10 @@
final Bundle mControlHints;
final boolean mIsSystemSession;
+ @TransferReason final int mTransferReason;
+
+ @Nullable final UserHandle mTransferInitiatorUserHandle;
+ @Nullable final String mTransferInitiatorPackageName;
RoutingSessionInfo(@NonNull Builder builder) {
Objects.requireNonNull(builder, "builder must not be null.");
@@ -116,6 +154,9 @@
volumeAdjustmentForRemoteGroupSessions);
mControlHints = updateVolumeHandlingInHints(builder.mControlHints, mVolumeHandling);
+ mTransferReason = builder.mTransferReason;
+ mTransferInitiatorUserHandle = builder.mTransferInitiatorUserHandle;
+ mTransferInitiatorPackageName = builder.mTransferInitiatorPackageName;
}
RoutingSessionInfo(@NonNull Parcel src) {
@@ -140,6 +181,9 @@
mControlHints = src.readBundle();
mIsSystemSession = src.readBoolean();
+ mTransferReason = src.readInt();
+ mTransferInitiatorUserHandle = src.readParcelable(null, android.os.UserHandle.class);
+ mTransferInitiatorPackageName = src.readString();
}
@Nullable
@@ -330,6 +374,27 @@
return mIsSystemSession;
}
+ /** Returns the transfer reason for this routing session. */
+ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+ @TransferReason
+ public int getTransferReason() {
+ return mTransferReason;
+ }
+
+ /** @hide */
+ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+ @Nullable
+ public UserHandle getTransferInitiatorUserHandle() {
+ return mTransferInitiatorUserHandle;
+ }
+
+ /** @hide */
+ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+ @Nullable
+ public String getTransferInitiatorPackageName() {
+ return mTransferInitiatorPackageName;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -351,6 +416,13 @@
dest.writeInt(mVolume);
dest.writeBundle(mControlHints);
dest.writeBoolean(mIsSystemSession);
+ dest.writeInt(mTransferReason);
+ if (mTransferInitiatorUserHandle != null) {
+ mTransferInitiatorUserHandle.writeToParcel(dest, /* flags= */ 0);
+ } else {
+ dest.writeParcelable(null, /* flags= */ 0);
+ }
+ dest.writeString(mTransferInitiatorPackageName);
}
/**
@@ -379,6 +451,9 @@
pw.println(indent + "mVolume=" + mVolume);
pw.println(indent + "mControlHints=" + mControlHints);
pw.println(indent + "mIsSystemSession=" + mIsSystemSession);
+ pw.println(indent + "mTransferReason=" + mTransferReason);
+ pw.println(indent + "mtransferInitiatorUserHandle=" + mTransferInitiatorUserHandle);
+ pw.println(indent + "mtransferInitiatorPackageName=" + mTransferInitiatorPackageName);
}
@Override
@@ -406,39 +481,69 @@
&& Objects.equals(mTransferableRoutes, other.mTransferableRoutes)
&& (mVolumeHandling == other.mVolumeHandling)
&& (mVolumeMax == other.mVolumeMax)
- && (mVolume == other.mVolume);
+ && (mVolume == other.mVolume)
+ && (mTransferReason == other.mTransferReason)
+ && Objects.equals(mTransferInitiatorUserHandle, other.mTransferInitiatorUserHandle)
+ && Objects.equals(
+ mTransferInitiatorPackageName, other.mTransferInitiatorPackageName);
}
@Override
public int hashCode() {
- return Objects.hash(mId, mName, mOwnerPackageName, mClientPackageName, mProviderId,
- mSelectedRoutes, mSelectableRoutes, mDeselectableRoutes, mTransferableRoutes,
- mVolumeMax, mVolumeHandling, mVolume);
+ return Objects.hash(
+ mId,
+ mName,
+ mOwnerPackageName,
+ mClientPackageName,
+ mProviderId,
+ mSelectedRoutes,
+ mSelectableRoutes,
+ mDeselectableRoutes,
+ mTransferableRoutes,
+ mVolumeMax,
+ mVolumeHandling,
+ mVolume,
+ mTransferReason,
+ mTransferInitiatorUserHandle,
+ mTransferInitiatorPackageName);
}
@Override
public String toString() {
- StringBuilder result = new StringBuilder()
- .append("RoutingSessionInfo{ ")
- .append("sessionId=").append(getId())
- .append(", name=").append(getName())
- .append(", clientPackageName=").append(getClientPackageName())
- .append(", selectedRoutes={")
- .append(String.join(",", getSelectedRoutes()))
- .append("}")
- .append(", selectableRoutes={")
- .append(String.join(",", getSelectableRoutes()))
- .append("}")
- .append(", deselectableRoutes={")
- .append(String.join(",", getDeselectableRoutes()))
- .append("}")
- .append(", transferableRoutes={")
- .append(String.join(",", getTransferableRoutes()))
- .append("}")
- .append(", volumeHandling=").append(getVolumeHandling())
- .append(", volumeMax=").append(getVolumeMax())
- .append(", volume=").append(getVolume())
- .append(" }");
+ StringBuilder result =
+ new StringBuilder()
+ .append("RoutingSessionInfo{ ")
+ .append("sessionId=")
+ .append(getId())
+ .append(", name=")
+ .append(getName())
+ .append(", clientPackageName=")
+ .append(getClientPackageName())
+ .append(", selectedRoutes={")
+ .append(String.join(",", getSelectedRoutes()))
+ .append("}")
+ .append(", selectableRoutes={")
+ .append(String.join(",", getSelectableRoutes()))
+ .append("}")
+ .append(", deselectableRoutes={")
+ .append(String.join(",", getDeselectableRoutes()))
+ .append("}")
+ .append(", transferableRoutes={")
+ .append(String.join(",", getTransferableRoutes()))
+ .append("}")
+ .append(", volumeHandling=")
+ .append(getVolumeHandling())
+ .append(", volumeMax=")
+ .append(getVolumeMax())
+ .append(", volume=")
+ .append(getVolume())
+ .append(", transferReason=")
+ .append(getTransferReason())
+ .append(", transferInitiatorUserHandle=")
+ .append(getTransferInitiatorUserHandle())
+ .append(", transferInitiatorPackageName=")
+ .append(getTransferInitiatorPackageName())
+ .append(" }");
return result.toString();
}
@@ -494,6 +599,9 @@
@Nullable
private Bundle mControlHints;
private boolean mIsSystemSession;
+ @TransferReason private int mTransferReason = TRANSFER_REASON_FALLBACK;
+ @Nullable private UserHandle mTransferInitiatorUserHandle;
+ @Nullable private String mTransferInitiatorPackageName;
/**
* Constructor for builder to create {@link RoutingSessionInfo}.
@@ -555,6 +663,9 @@
mControlHints = sessionInfo.mControlHints;
mIsSystemSession = sessionInfo.mIsSystemSession;
+ mTransferReason = sessionInfo.mTransferReason;
+ mTransferInitiatorUserHandle = sessionInfo.mTransferInitiatorUserHandle;
+ mTransferInitiatorPackageName = sessionInfo.mTransferInitiatorPackageName;
}
/**
@@ -784,6 +895,35 @@
}
/**
+ * Sets transfer reason for the current session.
+ *
+ * <p>By default the transfer reason is set to {@link
+ * RoutingSessionInfo#TRANSFER_REASON_FALLBACK}.
+ */
+ @NonNull
+ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+ public Builder setTransferReason(@TransferReason int transferReason) {
+ mTransferReason = transferReason;
+ return this;
+ }
+
+ /**
+ * Sets the user handle and package name of the process that initiated the transfer.
+ *
+ * <p>By default the transfer initiation user handle and package name are set to {@code
+ * null}.
+ */
+ @NonNull
+ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+ public Builder setTransferInitiator(
+ @Nullable UserHandle transferInitiatorUserHandle,
+ @Nullable String transferInitiatorPackageName) {
+ mTransferInitiatorUserHandle = transferInitiatorUserHandle;
+ mTransferInitiatorPackageName = transferInitiatorPackageName;
+ return this;
+ }
+
+ /**
* Builds a routing session info.
*
* @throws IllegalArgumentException if no selected routes are added.
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index e168498..b85decc 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -16,6 +16,8 @@
package android.media.audiopolicy;
+import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
+
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -34,6 +36,7 @@
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
+import android.media.FadeManagerConfiguration;
import android.media.IAudioService;
import android.media.MediaRecorder;
import android.media.projection.MediaProjection;
@@ -49,6 +52,7 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -612,6 +616,110 @@
return mRegistrationId;
}
+ /**
+ * Sets a custom {@link FadeManagerConfiguration} to handle fade cycle of players during
+ * {@link android.media.AudioManager#AUDIOFOCUS_LOSS}
+ *
+ * @param fmcForFocusLoss custom {@link FadeManagerConfiguration}
+ * @return {@link AudioManager#SUCCESS} if the update was successful,
+ * {@link AudioManager#ERROR} otherwise
+ * @throws IllegalStateException if the audio policy is not registered
+ * @hide
+ */
+ @FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION)
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ @SystemApi
+ public int setFadeManagerConfigurationForFocusLoss(
+ @NonNull FadeManagerConfiguration fmcForFocusLoss) {
+ Objects.requireNonNull(fmcForFocusLoss,
+ "FadeManagerConfiguration for focus loss cannot be null");
+
+ IAudioService service = getService();
+ synchronized (mLock) {
+ Preconditions.checkState(isAudioPolicyRegisteredLocked(),
+ "Cannot set FadeManagerConfiguration with unregistered AudioPolicy");
+
+ try {
+ return service.setFadeManagerConfigurationForFocusLoss(fmcForFocusLoss);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Received remote exception for setFadeManagerConfigurationForFocusLoss:",
+ e);
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Clear the current {@link FadeManagerConfiguration} set to handle fade cycles of players
+ * during {@link android.media.AudioManager#AUDIOFOCUS_LOSS}
+ *
+ * <p>In the absence of custom {@link FadeManagerConfiguration}, the default configurations will
+ * be used to handle fade cycles during audio focus loss.
+ *
+ * @return {@link AudioManager#SUCCESS} if the update was successful,
+ * {@link AudioManager#ERROR} otherwise
+ * @throws IllegalStateException if the audio policy is not registered
+ * @see #setFadeManagerConfigurationForFocusLoss(FadeManagerConfiguration)
+ * @hide
+ */
+ @FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION)
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ @SystemApi
+ public int clearFadeManagerConfigurationForFocusLoss() {
+ IAudioService service = getService();
+ synchronized (mLock) {
+ Preconditions.checkState(isAudioPolicyRegisteredLocked(),
+ "Cannot clear FadeManagerConfiguration from unregistered AudioPolicy");
+
+ try {
+ return service.clearFadeManagerConfigurationForFocusLoss();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Received remote exception for "
+ + "clearFadeManagerConfigurationForFocusLoss:", e);
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Get the current fade manager configuration used for fade operations during
+ * {@link android.media.AudioManager#AUDIOFOCUS_LOSS}
+ *
+ * <p>If no custom {@link FadeManagerConfiguration} is set, the default configuration currently
+ * active will be returned.
+ *
+ * @return the active {@link FadeManagerConfiguration} used during audio focus loss
+ * @throws IllegalStateException if the audio policy is not registered
+ * @see #setFadeManagerConfigurationForFocusLoss(FadeManagerConfiguration)
+ * @see #clearFadeManagerConfigurationForFocusLoss()
+ * @hide
+ */
+ @FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION)
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ @SystemApi
+ @NonNull
+ public FadeManagerConfiguration getFadeManagerConfigurationForFocusLoss() {
+ IAudioService service = getService();
+ synchronized (mLock) {
+ Preconditions.checkState(isAudioPolicyRegisteredLocked(),
+ "Cannot get FadeManagerConfiguration from unregistered AudioPolicy");
+
+ try {
+ return service.getFadeManagerConfigurationForFocusLoss();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Received remote exception for getFadeManagerConfigurationForFocusLoss:",
+ e);
+ throw e.rethrowFromSystemServer();
+
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private boolean isAudioPolicyRegisteredLocked() {
+ return mStatus == POLICY_STATUS_REGISTERED;
+ }
+
private boolean policyReadyToUse() {
synchronized (mLock) {
if (mStatus != POLICY_STATUS_REGISTERED) {
diff --git a/media/java/android/media/flags/fade_manager_configuration.aconfig b/media/java/android/media/flags/fade_manager_configuration.aconfig
deleted file mode 100644
index 100e2235..0000000
--- a/media/java/android/media/flags/fade_manager_configuration.aconfig
+++ /dev/null
@@ -1,8 +0,0 @@
-package: "com.android.media.flags"
-
-flag {
- namespace: "media_solutions"
- name: "enable_fade_manager_configuration"
- description: "Enable Fade Manager Configuration support to determine fade properties"
- bug: "307354764"
-}
\ No newline at end of file
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 07f63e5..3da52cc 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -69,3 +69,11 @@
description: "Use BluetoothDevice.getAlias to populate the name of Bluetooth MediaRoute2Infos."
bug: "314324170"
}
+
+flag {
+ name: "enable_built_in_speaker_route_suitability_statuses"
+ namespace: "media_solutions"
+ description: "Make MediaRoute2Info provide information about routes suitability for transfer."
+ bug: "279555229"
+}
+
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
index fb6bd48..f105ae9 100644
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
@@ -16,7 +16,7 @@
package com.android.audiopolicytest;
-import static com.android.media.flags.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
+import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
import static org.junit.Assert.assertThrows;
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index 8ed4bf2..c836df3 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -385,7 +385,9 @@
MediaRoute2Info routeToSelect = routes.get(ROUTE_ID1);
assertThat(routeToSelect).isNotNull();
- mManager.transfer(mPackageName, routeToSelect);
+ mManager.transfer(
+ mPackageName, routeToSelect,
+ android.os.Process.myUserHandle());
assertThat(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
assertThat(mManager.getRemoteSessions()).hasSize(1);
}
@@ -411,7 +413,9 @@
assertThat(mManager.getRoutingSessions(mPackageName)).hasSize(1);
- mManager.transfer(mPackageName, routeToSelect);
+ mManager.transfer(
+ mPackageName, routeToSelect,
+ android.os.Process.myUserHandle());
assertThat(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
List<RoutingSessionInfo> sessions = mManager.getRoutingSessions(mPackageName);
@@ -450,7 +454,11 @@
.addFeature(FEATURE_REMOTE_PLAYBACK)
.build();
- mManager.transfer(mManager.getSystemRoutingSession(null), unknownRoute);
+ mManager.transfer(
+ mManager.getSystemRoutingSession(null),
+ unknownRoute,
+ android.os.Process.myUserHandle(),
+ mContext.getPackageName());
assertThat(onSessionCreatedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)).isFalse();
assertThat(onTransferFailedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
}
@@ -484,7 +492,11 @@
assertThat(mManager.getRoutingSessions(mPackageName)).hasSize(1);
assertThat(mRouter2.getControllers()).hasSize(1);
- mManager.transfer(mManager.getRoutingSessions(mPackageName).get(0), routeToSelect);
+ mManager.transfer(
+ mManager.getRoutingSessions(mPackageName).get(0),
+ routeToSelect,
+ android.os.Process.myUserHandle(),
+ mContext.getPackageName());
assertThat(transferLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
assertThat(mManager.getRoutingSessions(mPackageName)).hasSize(2);
@@ -516,7 +528,11 @@
}
});
awaitOnRouteChangedManager(
- () -> mManager.transfer(mPackageName, routes.get(ROUTE_ID1)),
+ () ->
+ mManager.transfer(
+ mPackageName,
+ routes.get(ROUTE_ID1),
+ android.os.Process.myUserHandle()),
ROUTE_ID1,
route -> TextUtils.equals(route.getClientPackageName(), mPackageName));
assertThat(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
@@ -527,7 +543,11 @@
RoutingSessionInfo sessionInfo = sessions.get(1);
awaitOnRouteChangedManager(
- () -> mManager.transfer(mPackageName, routes.get(ROUTE_ID5_TO_TRANSFER_TO)),
+ () ->
+ mManager.transfer(
+ mPackageName,
+ routes.get(ROUTE_ID5_TO_TRANSFER_TO),
+ android.os.Process.myUserHandle()),
ROUTE_ID5_TO_TRANSFER_TO,
route -> TextUtils.equals(route.getClientPackageName(), mPackageName));
@@ -585,9 +605,11 @@
assertThat(route1).isNotNull();
assertThat(route2).isNotNull();
- mManager.transfer(mPackageName, route1);
+ mManager.transfer(
+ mPackageName, route1, android.os.Process.myUserHandle());
assertThat(successLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
- mManager.transfer(mPackageName, route2);
+ mManager.transfer(
+ mPackageName, route2, android.os.Process.myUserHandle());
assertThat(successLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
// onTransferFailed/onSessionReleased should not be called.
@@ -634,7 +656,11 @@
List<RoutingSessionInfo> sessions = mManager.getRoutingSessions(mPackageName);
RoutingSessionInfo targetSession = sessions.get(sessions.size() - 1);
- mManager.transfer(targetSession, routes.get(ROUTE_ID6_TO_BE_IGNORED));
+ mManager.transfer(
+ targetSession,
+ routes.get(ROUTE_ID6_TO_BE_IGNORED),
+ android.os.Process.myUserHandle(),
+ mContext.getPackageName());
assertThat(onSessionCreatedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)).isFalse();
assertThat(onFailedLatch.await(MediaRouter2Manager.TRANSFER_TIMEOUT_MS,
@@ -705,7 +731,10 @@
}
});
- mManager.transfer(mPackageName, routes.get(ROUTE_ID1));
+ mManager.transfer(
+ mPackageName,
+ routes.get(ROUTE_ID1),
+ android.os.Process.myUserHandle());
assertThat(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
List<RoutingSessionInfo> sessions = mManager.getRoutingSessions(mPackageName);
@@ -860,7 +889,8 @@
});
mRouter2.setOnGetControllerHintsListener(listener);
- mManager.transfer(mPackageName, route);
+ mManager.transfer(
+ mPackageName, route, android.os.Process.myUserHandle());
assertThat(hintLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
assertThat(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
@@ -905,7 +935,10 @@
}
});
- mManager.transfer(mPackageName, routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+ mManager.transfer(
+ mPackageName,
+ routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT),
+ android.os.Process.myUserHandle());
assertThat(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
index 66bd6f5..d5cf1a35 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
@@ -26,6 +26,7 @@
import androidx.compose.material.icons.outlined.PowerOff
import androidx.compose.material.icons.outlined.Shield
import androidx.compose.material.icons.outlined.WarningAmber
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@@ -78,24 +79,19 @@
imageVector = Icons.Outlined.WarningAmber,
buttons = listOf(
CardButton(text = "Action") {},
- CardButton(text = "Action", isMain = true) {},
- )
+ ),
+ tintColor = MaterialTheme.colorScheme.error,
+ containerColor = MaterialTheme.colorScheme.errorContainer,
)
)
}
@Composable
private fun SettingsCardWithoutIcon() {
- var isVisible by rememberSaveable { mutableStateOf(true) }
SettingsCard(
CardModel(
title = stringResource(R.string.sample_title),
text = stringResource(R.string.sample_text),
- isVisible = { isVisible },
- onDismiss = { isVisible = false },
- buttons = listOf(
- CardButton(text = "Action") {},
- ),
)
)
}
@@ -104,6 +100,7 @@
fun SampleSettingsCollapsibleCard() {
val context = LocalContext.current
var isVisible0 by rememberSaveable { mutableStateOf(true) }
+ var isVisible1 by rememberSaveable { mutableStateOf(true) }
val cards = remember {
mutableStateListOf(
CardModel(
@@ -114,16 +111,17 @@
onDismiss = { isVisible0 = false },
buttons = listOf(
CardButton(text = "Action") {},
- )
+ ),
),
CardModel(
title = context.getString(R.string.sample_title),
text = context.getString(R.string.sample_text),
imageVector = Icons.Outlined.Shield,
+ isVisible = { isVisible1 },
+ onDismiss = { isVisible1 = false },
buttons = listOf(
CardButton(text = "Action") {},
- CardButton(text = "Main action", isMain = true) {},
- )
+ ),
)
)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index 993cb4a..c143390 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -37,7 +37,6 @@
val itemPaddingAround = 8.dp
val itemDividerHeight = 32.dp
- val iconSmall = 16.dp
val iconLarge = 48.dp
/** The size when app icon is displayed in list. */
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt
index b18a1bc..b2a8b87 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt
@@ -16,11 +16,11 @@
package com.android.settingslib.spa.widget.card
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
data class CardButton(
val text: String,
- val isMain: Boolean = false,
val onClick: () -> Unit,
)
@@ -38,4 +38,10 @@
val onDismiss: (() -> Unit)? = null,
val buttons: List<CardButton> = emptyList(),
+
+ /** If specified, this color will be used to tint the icon and the buttons. */
+ val tintColor: Color = Color.Unspecified,
+
+ /** If specified, this color will be used to tint the icon and the buttons. */
+ val containerColor: Color = Color.Unspecified,
)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
index 7eec888..c7845fa 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
@@ -23,26 +23,26 @@
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Close
import androidx.compose.material.icons.outlined.WarningAmber
-import androidx.compose.material3.Button
-import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
@@ -72,11 +72,14 @@
}
@Composable
-fun SettingsCardContent(content: @Composable ColumnScope.() -> Unit) {
+fun SettingsCardContent(
+ containerColor: Color = Color.Unspecified,
+ content: @Composable ColumnScope.() -> Unit,
+) {
Card(
shape = CornerExtraSmall,
colors = CardDefaults.cardColors(
- containerColor = SettingsTheme.colorScheme.surface,
+ containerColor = containerColor.takeOrElse { SettingsTheme.colorScheme.surface },
),
modifier = Modifier
.fillMaxWidth()
@@ -95,37 +98,43 @@
@Composable
internal fun SettingsCardImpl(model: CardModel) {
AnimatedVisibility(visible = model.isVisible()) {
- SettingsCardContent {
+ SettingsCardContent(containerColor = model.containerColor) {
Column(
- modifier = Modifier.padding(SettingsDimension.itemPaddingStart),
+ modifier = Modifier.padding(
+ horizontal = SettingsDimension.dialogItemPaddingHorizontal,
+ vertical = SettingsDimension.itemPaddingAround,
+ ),
verticalArrangement = Arrangement.spacedBy(SettingsDimension.itemPaddingAround)
) {
- CardHeader(model.imageVector, model.onDismiss)
+ CardHeader(model.imageVector, model.tintColor, model.onDismiss)
SettingsTitle(model.title)
SettingsBody(model.text)
- Buttons(model.buttons)
+ Buttons(model.buttons, model.tintColor)
}
}
}
}
@Composable
-fun CardHeader(imageVector: ImageVector?, onDismiss: (() -> Unit)? = null) {
+fun CardHeader(imageVector: ImageVector?, iconColor: Color, onDismiss: (() -> Unit)? = null) {
+ if (imageVector != null || onDismiss != null) {
+ Spacer(Modifier.height(SettingsDimension.buttonPaddingVertical))
+ }
Row(Modifier.fillMaxWidth()) {
- CardIcon(imageVector)
+ CardIcon(imageVector, iconColor)
Spacer(modifier = Modifier.weight(1f))
DismissButton(onDismiss)
}
}
@Composable
-private fun CardIcon(imageVector: ImageVector?) {
+private fun CardIcon(imageVector: ImageVector?, color: Color) {
if (imageVector != null) {
Icon(
imageVector = imageVector,
contentDescription = null,
modifier = Modifier.size(SettingsDimension.itemIconSize),
- tint = MaterialTheme.colorScheme.primary,
+ tint = color.takeOrElse { MaterialTheme.colorScheme.primary },
)
}
}
@@ -146,52 +155,35 @@
contentDescription = stringResource(
androidx.compose.material3.R.string.m3c_snackbar_dismiss
),
- modifier = Modifier.size(SettingsDimension.iconSmall),
+ modifier = Modifier.padding(SettingsDimension.paddingSmall),
)
}
}
}
@Composable
-private fun Buttons(buttons: List<CardButton>) {
+private fun Buttons(buttons: List<CardButton>, color: Color) {
if (buttons.isNotEmpty()) {
Row(
- modifier = Modifier
- .fillMaxWidth()
- .padding(top = SettingsDimension.itemPaddingAround),
+ modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(
space = SettingsDimension.itemPaddingEnd,
alignment = Alignment.End,
),
) {
for (button in buttons) {
- Button(button)
+ Button(button, color)
}
}
+ } else {
+ Spacer(Modifier.height(SettingsDimension.itemPaddingAround))
}
}
@Composable
-private fun Button(button: CardButton) {
- if (button.isMain) {
- Button(
- onClick = button.onClick,
- colors = ButtonDefaults.buttonColors(
- containerColor = SettingsTheme.colorScheme.primaryContainer,
- ),
- ) {
- Text(
- text = button.text,
- color = SettingsTheme.colorScheme.onPrimaryContainer,
- )
- }
- } else {
- OutlinedButton(onClick = button.onClick) {
- Text(
- text = button.text,
- color = MaterialTheme.colorScheme.onSurface,
- )
- }
+private fun Button(button: CardButton, color: Color) {
+ TextButton(onClick = button.onClick) {
+ Text(text = button.text, color = color)
}
}
@@ -206,7 +198,6 @@
imageVector = Icons.Outlined.WarningAmber,
buttons = listOf(
CardButton(text = "Action") {},
- CardButton(text = "Action", isMain = true) {},
)
)
)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt
index 6e36490..c34df65 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt
@@ -141,7 +141,6 @@
imageVector = Icons.Outlined.Shield,
buttons = listOf(
CardButton(text = "Action") {},
- CardButton(text = "Main action", isMain = true) {},
)
)
)
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
index c51a9a0..8e5396f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
@@ -23,8 +23,6 @@
import static com.android.settingslib.Utils.getColorAttrDefaultColor;
import android.Manifest;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.AppGlobals;
import android.app.AppOpsManager;
@@ -52,6 +50,8 @@
import android.view.MenuItem;
import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
index 3b8f665..70ece0f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
@@ -21,7 +21,6 @@
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
-import android.annotation.NonNull;
import android.app.AppOpsManager;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
@@ -35,6 +34,7 @@
import android.widget.LinearLayout;
import android.widget.TextView;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceViewHolder;
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 107d5f8..c2be571 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -3,7 +3,6 @@
import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_USER_LABEL;
import android.annotation.ColorInt;
-import android.annotation.Nullable;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Intent;
@@ -23,10 +22,10 @@
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.drawable.Drawable;
-import android.hardware.usb.flags.Flags;
import android.hardware.usb.UsbManager;
import android.hardware.usb.UsbPort;
import android.hardware.usb.UsbPortStatus;
+import android.hardware.usb.flags.Flags;
import android.location.LocationManager;
import android.media.AudioManager;
import android.net.NetworkCapabilities;
@@ -47,6 +46,7 @@
import android.util.Log;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/DefaultAppInfo.java b/packages/SettingsLib/src/com/android/settingslib/applications/DefaultAppInfo.java
index dae48db..b0db16f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/DefaultAppInfo.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/DefaultAppInfo.java
@@ -17,7 +17,6 @@
package com.android.settingslib.applications;
import android.app.AppGlobals;
-import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -28,6 +27,8 @@
import android.os.RemoteException;
import android.util.IconDrawableFactory;
+import androidx.annotation.Nullable;
+
import com.android.settingslib.widget.CandidateInfo;
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
index fa056e2b..416b369 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
@@ -26,9 +26,9 @@
import static android.bluetooth.BluetoothAdapter.STATE_TURNING_ON;
import android.annotation.IntDef;
-import android.annotation.Nullable;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
index 1900575..cf4d6be 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
@@ -21,8 +21,6 @@
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothCsipSetCoordinator;
@@ -35,6 +33,9 @@
import android.content.Context;
import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.settingslib.R;
import java.lang.annotation.Retention;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelper.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelper.java
index c9512cd..8eaea0e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelper.java
@@ -151,7 +151,9 @@
private boolean removePreferredDeviceForStrategies(List<AudioProductStrategy> strategies) {
boolean status = true;
for (AudioProductStrategy strategy : strategies) {
- status &= mAudioManager.removePreferredDeviceForStrategy(strategy);
+ if (mAudioManager.getPreferredDeviceForStrategy(strategy) != null) {
+ status &= mAudioManager.removePreferredDeviceForStrategy(strategy);
+ }
}
return status;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
index 51164e8..57012aa 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
@@ -21,7 +21,6 @@
import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
-import android.annotation.Nullable;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
@@ -31,6 +30,7 @@
import android.os.Build;
import android.util.Log;
+import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.android.settingslib.R;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index 49ac0f8..de21c54 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -19,7 +19,6 @@
import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
import android.annotation.CallbackExecutor;
-import android.annotation.NonNull;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
@@ -44,6 +43,7 @@
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
index 34008ac..34c60a1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
@@ -20,7 +20,6 @@
import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
import android.annotation.CallbackExecutor;
-import android.annotation.NonNull;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
@@ -33,6 +32,7 @@
import android.os.Build;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import com.android.settingslib.R;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
index 3774b88..ab7a3db 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
@@ -21,7 +21,6 @@
import android.annotation.CallbackExecutor;
import android.annotation.IntRange;
-import android.annotation.NonNull;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
@@ -31,6 +30,7 @@
import android.os.Build;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import java.util.ArrayList;
diff --git a/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java b/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java
index be420ac..43c6c0d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java
@@ -16,7 +16,6 @@
package com.android.settingslib.connectivity;
-import android.annotation.NonNull;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -31,6 +30,7 @@
import android.telephony.TelephonyManager;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java
index 067afa4..f847154 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java
@@ -14,7 +14,6 @@
package com.android.settingslib.core.instrumentation;
-import android.annotation.Nullable;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.Context;
@@ -24,6 +23,7 @@
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import java.util.Map;
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java
index 2bd0b27..e974e70 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java
@@ -22,13 +22,13 @@
import static androidx.lifecycle.Lifecycle.Event.ON_START;
import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
-import android.annotation.Nullable;
import android.app.Activity;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.view.Menu;
import android.view.MenuItem;
+import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.LifecycleOwner;
diff --git a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
index bbb1ec6..0029e20 100644
--- a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
@@ -16,7 +16,6 @@
package com.android.settingslib.datetime;
-import android.annotation.Nullable;
import android.content.Context;
import android.content.res.XmlResourceParser;
import android.icu.text.TimeZoneFormat;
@@ -29,6 +28,7 @@
import android.util.Log;
import android.view.View;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.core.text.BidiFormatter;
import androidx.core.text.TextDirectionHeuristicsCompat;
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java b/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java
index 11fae24..f07daa3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java
@@ -22,7 +22,6 @@
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
-import android.annotation.NonNull;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -45,6 +44,7 @@
import android.os.Build;
import android.os.UserHandle;
+import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminController.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminController.java
index 0b3a519..f329023 100644
--- a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminController.java
@@ -16,11 +16,11 @@
package com.android.settingslib.enterprise;
-import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.content.Context;
import android.content.DialogInterface;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settingslib.RestrictedLockUtils;
diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminController.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminController.java
index 714accc..7e3395b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminController.java
@@ -16,7 +16,6 @@
package com.android.settingslib.enterprise;
-import android.annotation.NonNull;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -24,6 +23,7 @@
import android.provider.Settings;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settingslib.RestrictedLockUtils;
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java b/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
index 17c2b02..71d5809 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
@@ -16,7 +16,6 @@
package com.android.settingslib.graph;
-import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -35,6 +34,8 @@
import android.graphics.drawable.Drawable;
import android.util.TypedValue;
+import androidx.annotation.Nullable;
+
import com.android.settingslib.R;
import com.android.settingslib.Utils;
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java b/packages/SettingsLib/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java
index f01eb2a..65d53f3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java
@@ -16,7 +16,7 @@
package com.android.settingslib.graph;
-import android.annotation.NonNull;
+
import android.content.Context;
import android.content.res.Resources;
import android.graphics.PorterDuff;
@@ -25,6 +25,7 @@
import android.graphics.drawable.LayerDrawable;
import android.view.Gravity;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.settingslib.R;
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java b/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
index 4d0804e..9a19f93 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
@@ -16,8 +16,6 @@
import android.animation.ArgbEvaluator;
import android.annotation.IntRange;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Canvas;
@@ -36,6 +34,9 @@
import android.util.LayoutDirection;
import android.util.PathParser;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.settingslib.R;
import com.android.settingslib.Utils;
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtil.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtil.java
index 1712a6b..318e3dd 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtil.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtil.java
@@ -16,8 +16,6 @@
package com.android.settingslib.inputmethod;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
@@ -30,6 +28,8 @@
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragment;
import androidx.preference.PreferenceScreen;
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java
index 78ec58b..a2316435 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java
@@ -16,8 +16,6 @@
package com.android.settingslib.inputmethod;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.ContentResolver;
import android.content.Context;
@@ -32,6 +30,8 @@
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceScreen;
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java
index 4384400..5c0c979 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java
@@ -17,7 +17,6 @@
package com.android.settingslib.inputmethod;
import android.annotation.AnyThread;
-import android.annotation.NonNull;
import android.annotation.UiThread;
import android.content.ContentResolver;
import android.content.Context;
@@ -26,6 +25,8 @@
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
+import androidx.annotation.NonNull;
+
import com.android.internal.annotations.GuardedBy;
import com.android.internal.inputmethod.DirectBootAwareness;
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 52b51d7..ba40a50 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -43,8 +43,6 @@
import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.TargetApi;
import android.app.Notification;
import android.bluetooth.BluetoothAdapter;
@@ -59,6 +57,8 @@
import android.util.Log;
import androidx.annotation.DoNotInline;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.android.internal.annotations.VisibleForTesting;
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
index 0be2e0e..97bbf12 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
@@ -16,8 +16,6 @@
package com.android.settingslib.media;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.Notification;
import android.content.Context;
import android.media.MediaRoute2Info;
@@ -27,6 +25,9 @@
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -80,14 +81,17 @@
@Override
protected void transferToRoute(@NonNull MediaRoute2Info route) {
- mRouterManager.transfer(mPackageName, route);
+ // TODO: b/279555229 - provide real user handle of a caller.
+ mRouterManager.transfer(mPackageName, route, android.os.Process.myUserHandle());
}
@Override
protected boolean connectDeviceWithoutPackageName(@NonNull MediaDevice device) {
final RoutingSessionInfo info = mRouterManager.getSystemRoutingSession(null);
if (info != null) {
- mRouterManager.transfer(info, device.mRouteInfo);
+ // TODO: b/279555229 - provide real user handle and package name of a caller.
+ mRouterManager.transfer(
+ info, device.mRouteInfo, android.os.Process.myUserHandle(), mPackageName);
return true;
}
return false;
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index 80eeab5..0676ce5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -29,7 +29,6 @@
import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER;
import android.Manifest;
-import android.annotation.NonNull;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
@@ -40,6 +39,7 @@
import android.media.RouteListingPreference;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.settingslib.R;
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java
index 5e9ac5a..bcfdebe 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java
@@ -16,7 +16,6 @@
package com.android.settingslib.net;
-import android.annotation.NonNull;
import android.app.usage.NetworkStats;
import android.app.usage.NetworkStatsManager;
import android.content.Context;
@@ -27,6 +26,7 @@
import android.util.Pair;
import android.util.Range;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.loader.content.AsyncTaskLoader;
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
index 562d20d..dfa5ed1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
@@ -16,10 +16,10 @@
package com.android.settingslib.notification;
-import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.AlertDialog;
+import android.app.Flags;
import android.app.NotificationManager;
import android.content.Context;
import android.content.DialogInterface;
@@ -42,6 +42,8 @@
import android.widget.ScrollView;
import android.widget.TextView;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.PhoneWindow;
import com.android.settingslib.R;
@@ -143,9 +145,16 @@
Slog.d(TAG, "Invalid manual condition: " + tag.condition);
}
// always triggers priority-only dnd with chosen condition
- mNotificationManager.setZenMode(
- Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
- getRealConditionId(tag.condition), TAG);
+ if (Flags.modesApi()) {
+ mNotificationManager.setZenMode(
+ Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ getRealConditionId(tag.condition), TAG,
+ /* fromUser= */ true);
+ } else {
+ mNotificationManager.setZenMode(
+ Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ getRealConditionId(tag.condition), TAG);
+ }
}
});
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
index 9084aa2..e83b9bc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
@@ -22,6 +22,7 @@
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
+import android.multiuser.Flags;
import android.net.Uri;
import android.util.Log;
import android.widget.ImageView;
@@ -59,6 +60,9 @@
private static final String IMAGES_DIR = "multi_user";
private static final String NEW_USER_PHOTO_FILE_NAME = "NewUserPhoto.png";
+ private static final String AVATAR_PICKER_ACTION = "com.android.avatarpicker"
+ + ".FULL_SCREEN_ACTIVITY";
+
private final Activity mActivity;
private final ActivityStarter mActivityStarter;
private final ImageView mImageView;
@@ -105,7 +109,6 @@
onPhotoCropped(data.getData());
return true;
}
-
}
return false;
}
@@ -115,7 +118,13 @@
}
private void showAvatarPicker() {
- Intent intent = new Intent(mImageView.getContext(), AvatarPickerActivity.class);
+ Intent intent;
+ if (Flags.avatarSync()) {
+ intent = new Intent(AVATAR_PICKER_ACTION);
+ intent.addCategory(Intent.CATEGORY_DEFAULT);
+ } else {
+ intent = new Intent(mImageView.getContext(), AvatarPickerActivity.class);
+ }
intent.putExtra(AvatarPickerActivity.EXTRA_FILE_AUTHORITY, mFileAuthority);
mActivityStarter.startActivityForResult(intent, REQUEST_CODE_PICK_AVATAR);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java b/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java
index fbf8a2f..23b2cc2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java
@@ -16,8 +16,6 @@
package com.android.settingslib.volume;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
@@ -42,6 +40,9 @@
import android.os.Message;
import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.HashSet;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 303ee3c..cf45231 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -21,7 +21,6 @@
import android.annotation.IntDef;
import android.annotation.MainThread;
-import android.annotation.Nullable;
import android.app.AppGlobals;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -58,6 +57,7 @@
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.CollectionUtils;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
index 98272cc..e508526 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
@@ -15,7 +15,6 @@
*/
package com.android.settingslib.wifi;
-import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
@@ -32,6 +31,7 @@
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiRestrictionsCache.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiRestrictionsCache.java
index 7ffae40..c0ca1252 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiRestrictionsCache.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiRestrictionsCache.java
@@ -18,12 +18,12 @@
import static android.os.UserManager.DISALLOW_CONFIG_WIFI;
-import android.annotation.NonNull;
import android.content.Context;
import android.os.Bundle;
import android.os.UserManager;
import android.util.SparseArray;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import java.util.HashMap;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStateWorker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStateWorker.java
index a0c2698..14967ec 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStateWorker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStateWorker.java
@@ -22,7 +22,6 @@
import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED;
import android.annotation.AnyThread;
-import android.annotation.NonNull;
import android.annotation.TestApi;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -34,6 +33,7 @@
import android.util.Log;
import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelperTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelperTest.java
index 8b5ea30..c835244 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelperTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelperTest.java
@@ -18,8 +18,10 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -43,6 +45,7 @@
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -79,6 +82,8 @@
when(mAudioDeviceInfo.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
when(mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)).thenReturn(
new AudioDeviceInfo[]{mAudioDeviceInfo});
+ doReturn(Collections.emptyList()).when(mAudioManager).getPreferredDevicesForStrategy(
+ any(AudioProductStrategy.class));
when(mAudioStrategy.getAudioAttributesForLegacyStreamType(
AudioManager.STREAM_MUSIC))
.thenReturn((new AudioAttributes.Builder()).build());
@@ -92,7 +97,10 @@
}
@Test
- public void setPreferredDeviceRoutingStrategies_valueAuto_callRemoveStrategy() {
+ public void setPreferredDeviceRoutingStrategies_hadValueThenValueAuto_callRemoveStrategy() {
+ when(mAudioManager.getPreferredDeviceForStrategy(mAudioStrategy)).thenReturn(
+ mHearingDeviceAttribute);
+
mHelper.setPreferredDeviceRoutingStrategies(List.of(mAudioStrategy),
mHearingDeviceAttribute,
HearingAidAudioRoutingConstants.RoutingValue.AUTO);
@@ -101,6 +109,17 @@
}
@Test
+ public void setPreferredDeviceRoutingStrategies_NoValueThenValueAuto_notCallRemoveStrategy() {
+ when(mAudioManager.getPreferredDeviceForStrategy(mAudioStrategy)).thenReturn(null);
+
+ mHelper.setPreferredDeviceRoutingStrategies(List.of(mAudioStrategy),
+ mHearingDeviceAttribute,
+ HearingAidAudioRoutingConstants.RoutingValue.AUTO);
+
+ verify(mAudioManager, never()).removePreferredDeviceForStrategy(mAudioStrategy);
+ }
+
+ @Test
public void setPreferredDeviceRoutingStrategies_valueHearingDevice_callSetStrategy() {
mHelper.setPreferredDeviceRoutingStrategies(List.of(mAudioStrategy),
mHearingDeviceAttribute,
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java
index 5aee8cd..194a0e2 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java
@@ -27,9 +27,9 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.annotation.NonNull;
import android.view.View;
+import androidx.annotation.NonNull;
import androidx.preference.PreferenceGroupAdapter;
import androidx.preference.SwitchPreference;
import androidx.recyclerview.widget.RecyclerView;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java
index 1d5f1b2..819d4b3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java
@@ -16,7 +16,7 @@
package com.android.settingslib.enterprise;
-import android.annotation.Nullable;
+import androidx.annotation.Nullable;
class FakeDeviceAdminStringProvider implements DeviceAdminStringProvider {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java
index c79440e..77c46f7 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java
@@ -26,7 +26,6 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.annotation.NonNull;
import android.app.usage.NetworkStats;
import android.app.usage.NetworkStatsManager;
import android.content.Context;
@@ -37,6 +36,8 @@
import android.text.format.DateUtils;
import android.util.Range;
+import androidx.annotation.NonNull;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowPermissionChecker.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowPermissionChecker.java
index fae3aea..8448804 100644
--- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowPermissionChecker.java
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowPermissionChecker.java
@@ -16,12 +16,13 @@
package com.android.settingslib.testutils.shadow;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.AttributionSource;
import android.content.Context;
import android.content.PermissionChecker;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowRouter2Manager.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowRouter2Manager.java
index dac8142..fde378f 100644
--- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowRouter2Manager.java
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowRouter2Manager.java
@@ -15,12 +15,13 @@
*/
package com.android.settingslib.testutils.shadow;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2Manager;
import android.media.RoutingSessionInfo;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp
index adebdcd..d5814e3 100644
--- a/packages/SettingsProvider/Android.bp
+++ b/packages/SettingsProvider/Android.bp
@@ -59,10 +59,10 @@
// Note we statically link SettingsProviderLib to do some unit tests. It's not accessible otherwise
// because this test is not an instrumentation test. (because the target runs in the system process.)
"SettingsProviderLib",
-
"androidx.test.rules",
"flag-junit",
"junit",
+ "libaconfig_java_proto_lite",
"mockito-target-minus-junit4",
"platform-test-annotations",
"truth",
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index d9fe733..3027c5f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -209,6 +209,7 @@
VALIDATORS.put(Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.POWER_BUTTON_LONG_PRESS_DURATION_MS, NONE_NEGATIVE_LONG_VALIDATOR);
VALIDATORS.put(Global.STYLUS_EVER_USED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Global.MUTE_ALARM_STREAM_WITH_RINGER_MODE, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.HAS_PAY_TOKENS, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.GMS_CHECKIN_TIMEOUT_MIN, ANY_INTEGER_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 8f459c6..73c2e22 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -18,6 +18,9 @@
import static android.os.Process.FIRST_APPLICATION_UID;
+import android.aconfig.Aconfig.flag_state;
+import android.aconfig.Aconfig.parsed_flag;
+import android.aconfig.Aconfig.parsed_flags;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -147,6 +150,17 @@
*/
private static final String CONFIG_STAGED_PREFIX = "staged/";
+ private static final List<String> sAconfigTextProtoFilesOnDevice = List.of(
+ "/system/etc/aconfig_flags.pb",
+ "/system_ext/etc/aconfig_flags.pb",
+ "/product/etc/aconfig_flags.pb",
+ "/vendor/etc/aconfig_flags.pb");
+
+ /**
+ * This tag is applied to all aconfig default value-loaded flags.
+ */
+ private static final String BOOT_LOADED_DEFAULT_TAG = "BOOT_LOADED_DEFAULT";
+
// This was used in version 120 and before.
private static final String NULL_VALUE_OLD_STYLE = "null";
@@ -315,6 +329,59 @@
synchronized (mLock) {
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) {
+ loadAconfigDefaultValuesLocked();
+ }
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void loadAconfigDefaultValuesLocked() {
+ for (String fileName : sAconfigTextProtoFilesOnDevice) {
+ try (FileInputStream inputStream = new FileInputStream(fileName)) {
+ byte[] contents = inputStream.readAllBytes();
+ loadAconfigDefaultValues(contents);
+ } catch (IOException e) {
+ Slog.e(LOG_TAG, "failed to read protobuf", e);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ public void loadAconfigDefaultValues(byte[] fileContents) {
+ try {
+ parsed_flags parsedFlags = parsed_flags.parseFrom(fileContents);
+
+ if (parsedFlags == null) {
+ Slog.e(LOG_TAG, "failed to parse aconfig protobuf");
+ return;
+ }
+
+ 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());
+ }
+ }
+ } catch (IOException e) {
+ Slog.e(LOG_TAG, "failed to parse protobuf", e);
}
}
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 27ce0d4..ecac5ee 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
@@ -6,3 +6,11 @@
description: "When enabled, allows setting and displaying local overrides via adb."
bug: "298392357"
}
+
+flag {
+ name: "load_aconfig_defaults"
+ namespace: "core_experiments_team_internal"
+ description: "When enabled, loads aconfig default values into DeviceConfig on boot."
+ bug: "311155098"
+ is_fixed_read_only: true
+}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 85e8769..6ad10cc 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -350,6 +350,7 @@
Settings.Global.DSRM_DURATION_MILLIS,
Settings.Global.DSRM_ENABLED_ACTIONS,
Settings.Global.MODE_RINGER,
+ Settings.Global.MUTE_ALARM_STREAM_WITH_RINGER_MODE,
Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION,
Settings.Global.MULTI_SIM_SMS_PROMPT,
Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION,
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 02a7bc1..24625ea 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -15,6 +15,9 @@
*/
package com.android.providers.settings;
+import android.aconfig.Aconfig;
+import android.aconfig.Aconfig.parsed_flag;
+import android.aconfig.Aconfig.parsed_flags;
import android.os.Looper;
import android.test.AndroidTestCase;
import android.util.Xml;
@@ -84,6 +87,86 @@
super.tearDown();
}
+ public void testLoadValidAconfigProto() {
+ int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
+ Object lock = new Object();
+ 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
+ .newBuilder()
+ .setPackage("com.android.flags")
+ .setName("flag1")
+ .setNamespace("test_namespace")
+ .setDescription("test flag")
+ .addBug("12345678")
+ .setState(Aconfig.flag_state.DISABLED)
+ .setPermission(Aconfig.flag_permission.READ_WRITE))
+ .addParsedFlag(parsed_flag
+ .newBuilder()
+ .setPackage("com.android.flags")
+ .setName("flag2")
+ .setNamespace("test_namespace")
+ .setDescription("another test flag")
+ .addBug("12345678")
+ .setState(Aconfig.flag_state.ENABLED)
+ .setPermission(Aconfig.flag_permission.READ_WRITE))
+ .build();
+
+ synchronized (lock) {
+ settingsState.loadAconfigDefaultValues(flags.toByteArray());
+ settingsState.persistSettingsLocked();
+ }
+ settingsState.waitForHandler();
+
+ synchronized (lock) {
+ assertEquals("false",
+ settingsState.getSettingLocked(
+ "test_namespace/com.android.flags.flag1").getValue());
+ assertEquals("true",
+ settingsState.getSettingLocked(
+ "test_namespace/com.android.flags.flag2").getValue());
+ }
+ }
+
+ public void testSkipLoadingAconfigFlagWithMissingFields() {
+ int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
+ Object lock = new Object();
+ 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
+ .newBuilder()
+ .setDescription("test flag")
+ .addBug("12345678")
+ .setState(Aconfig.flag_state.DISABLED)
+ .setPermission(Aconfig.flag_permission.READ_WRITE))
+ .build();
+
+ synchronized (lock) {
+ settingsState.loadAconfigDefaultValues(flags.toByteArray());
+ settingsState.persistSettingsLocked();
+ }
+ settingsState.waitForHandler();
+
+ synchronized (lock) {
+ assertEquals(null,
+ settingsState.getSettingLocked(
+ "test_namespace/com.android.flags.flag1").getValue());
+ }
+ }
+
+ public void testInvalidAconfigProtoDoesNotCrash() {
+ SettingsState settingsState = getSettingStateObject();
+ settingsState.loadAconfigDefaultValues("invalid protobuf".getBytes());
+ }
+
public void testIsBinary() {
assertFalse(SettingsState.isBinary(" abc 日本語"));
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 6e65c16..477c42e 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -688,6 +688,9 @@
<!-- Permission required for CTS test - CtsAmbientContextDetectionServiceDeviceTest -->
<uses-permission android:name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT" />
+ <!-- Permission required for CTS test - CtsWearableSensingServiceTestCases -->
+ <uses-permission android:name="android.permission.MANAGE_WEARABLE_SENSING_SERVICE" />
+
<!-- Permission required for CTS test - CallAudioInterceptionTest -->
<uses-permission android:name="android.permission.CALL_AUDIO_INTERCEPTION" />
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 1a35f04..a03fa9b 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -256,6 +256,9 @@
<!-- launcher apps -->
<uses-permission android:name="android.permission.ACCESS_SHORTCUTS" />
+ <!-- Permission to start Launcher's widget picker activity. -->
+ <uses-permission android:name="android.permission.START_WIDGET_PICKER_ACTIVITY" />
+
<uses-permission android:name="android.permission.MODIFY_THEME_OVERLAY" />
<!-- accessibility -->
@@ -974,15 +977,6 @@
android:excludeFromRecents="true"
android:visibleToInstantApps="true"/>
- <activity android:name="com.android.systemui.communal.widgets.WidgetPickerActivity"
- android:theme="@style/Theme.EditWidgetsActivity"
- android:excludeFromRecents="true"
- android:autoRemoveFromRecents="true"
- android:showOnLockScreen="true"
- android:launchMode="singleTop"
- android:exported="false">
- </activity>
-
<activity android:name="com.android.systemui.communal.widgets.EditWidgetsActivity"
android:theme="@style/Theme.EditWidgetsActivity"
android:excludeFromRecents="true"
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index ab4fe76..98a2d9f 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -45,6 +45,13 @@
}
flag {
+ name: "notifications_improved_hun_animation"
+ namespace: "systemui"
+ description: "Adds a translateY animation, and other improvements to match the motion specs of the HUN Intro + Outro animations."
+ bug: "243302608"
+}
+
+flag {
name: "notification_lifetime_extension_refactor"
namespace: "systemui"
description: "Enables moving notification lifetime extension management from SystemUI to "
@@ -241,6 +248,13 @@
}
flag {
+ name: "switch_user_on_bg"
+ namespace: "systemui"
+ description: "Does user switching on a background thread"
+ bug: "284095720"
+}
+
+flag {
name: "status_bar_static_inout_indicators"
namespace: "systemui"
description: "(Upstream request) Always show the network activity inout indicators and "
@@ -248,3 +262,23 @@
bug: "310715220"
}
+flag {
+ name: "haptic_volume_slider"
+ namespace: "systemui"
+ description: "Adds haptic feedback to the volume slider."
+ bug: "316953430"
+}
+
+flag {
+ name: "screenshare_notification_hiding"
+ namespace: "systemui"
+ description: "Enable hiding of notifications during screenshare"
+ bug: "312784809"
+}
+
+flag {
+ name: "bluetooth_qs_tile_dialog_auto_on_toggle"
+ namespace: "systemui"
+ description: "Displays the auto on toggle in the bluetooth QS tile dialog"
+ bug: "316985153"
+}
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
index 4d53cca..cbf2496 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
@@ -21,6 +21,7 @@
import com.android.systemui.keyguard.KeyguardViewConfigurator
import com.android.systemui.keyguard.qualifiers.KeyguardRootView
import com.android.systemui.keyguard.ui.composable.LockscreenScene
+import com.android.systemui.keyguard.ui.composable.LockscreenSceneBlueprintModule
import com.android.systemui.scene.shared.model.Scene
import dagger.Binds
import dagger.Module
@@ -29,7 +30,12 @@
import javax.inject.Provider
import kotlinx.coroutines.ExperimentalCoroutinesApi
-@Module
+@Module(
+ includes =
+ [
+ LockscreenSceneBlueprintModule::class,
+ ],
+)
interface LockscreenSceneModule {
@Binds @IntoSet fun lockscreenScene(scene: LockscreenScene): Scene
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 2a9cf0f..55fc3a2 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,10 +20,12 @@
import android.util.SizeF
import android.widget.FrameLayout
import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -31,11 +33,13 @@
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
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.LazyHorizontalGrid
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
+import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Edit
@@ -98,7 +102,6 @@
modifier = modifier.fillMaxSize().background(Color.White),
) {
CommunalHubLazyGrid(
- modifier = Modifier.align(Alignment.CenterStart),
communalContent = communalContent,
viewModel = viewModel,
contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize),
@@ -138,21 +141,21 @@
@OptIn(ExperimentalFoundationApi::class)
@Composable
-private fun CommunalHubLazyGrid(
+private fun BoxScope.CommunalHubLazyGrid(
communalContent: List<CommunalContentModel>,
viewModel: BaseCommunalViewModel,
- modifier: Modifier = Modifier,
contentPadding: PaddingValues,
setGridCoordinates: (coordinates: LayoutCoordinates) -> Unit,
updateDragPositionForRemove: (offset: Offset) -> Boolean,
) {
- var gridModifier = modifier
+ 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(communalContent, viewModel)
list = contentListState.list
+ // for drag & drop operations within the communal hub grid
dragDropState =
rememberGridDragDropState(
gridState = gridState,
@@ -164,9 +167,22 @@
.fillMaxSize()
.dragContainer(dragDropState, beforeContentPadding(contentPadding))
.onGloballyPositioned { setGridCoordinates(it) }
+ // for widgets dropped from other activities
+ val dragAndDropTargetState =
+ rememberDragAndDropTargetState(
+ gridState = gridState,
+ contentListState = contentListState,
+ updateDragPositionForRemove = updateDragPositionForRemove
+ )
+
+ // A full size box in background that listens to widget drops from the picker.
+ // Since the grid has its own listener for in-grid drag events, we use a separate element
+ // for android drag events.
+ Box(Modifier.fillMaxSize().dragAndDropTarget(dragAndDropTargetState)) {}
} else {
gridModifier = gridModifier.height(Dimensions.GridHeight)
}
+
LazyHorizontalGrid(
modifier = gridModifier,
state = gridState,
@@ -309,12 +325,24 @@
) {
when (model) {
is CommunalContentModel.Widget -> WidgetContent(model, size, elevation, modifier)
+ is CommunalContentModel.WidgetPlaceholder -> WidgetPlaceholderContent(size)
is CommunalContentModel.Smartspace -> SmartspaceContent(model, modifier)
is CommunalContentModel.Tutorial -> TutorialContent(modifier)
is CommunalContentModel.Umo -> Umo(viewModel, modifier)
}
}
+/** Presents a placeholder card for the new widget being dragged and dropping into the grid. */
+@Composable
+fun WidgetPlaceholderContent(size: SizeF) {
+ Card(
+ modifier = Modifier.size(Dp(size.width), Dp(size.height)),
+ colors = CardDefaults.cardColors(containerColor = Color.Transparent),
+ border = BorderStroke(3.dp, LocalAndroidColorScheme.current.tertiaryFixed),
+ shape = RoundedCornerShape(16.dp)
+ ) {}
+}
+
@Composable
private fun WidgetContent(
model: CommunalContentModel.Widget,
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 89c5765..979991d 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
@@ -16,11 +16,10 @@
package com.android.systemui.communal.ui.compose
+import android.content.ComponentName
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
+import androidx.compose.runtime.toMutableStateList
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
@@ -32,6 +31,7 @@
return remember(communalContent) {
ContentListState(
communalContent,
+ viewModel::onAddWidget,
viewModel::onDeleteWidget,
viewModel::onReorderWidgets,
)
@@ -46,30 +46,57 @@
class ContentListState
internal constructor(
communalContent: List<CommunalContentModel>,
+ private val onAddWidget: (componentName: ComponentName, priority: Int) -> Unit,
private val onDeleteWidget: (id: Int) -> Unit,
- private val onReorderWidgets: (ids: List<Int>) -> Unit,
+ private val onReorderWidgets: (widgetIdToPriorityMap: Map<Int, Int>) -> Unit,
) {
- var list by mutableStateOf(communalContent)
+ var list = communalContent.toMutableStateList()
private set
/** Move item to a new position in the list. */
fun onMove(fromIndex: Int, toIndex: Int) {
- list = list.toMutableList().apply { add(toIndex, removeAt(fromIndex)) }
+ list.apply { add(toIndex, removeAt(fromIndex)) }
}
/** Remove widget from the list and the database. */
fun onRemove(indexToRemove: Int) {
if (list[indexToRemove] is CommunalContentModel.Widget) {
val widget = list[indexToRemove] as CommunalContentModel.Widget
- list = list.toMutableList().apply { removeAt(indexToRemove) }
+ list.apply { removeAt(indexToRemove) }
onDeleteWidget(widget.appWidgetId)
}
}
- /** Persist the new order with all the movements happened during dragging. */
- fun onSaveList() {
- val widgetIds: List<Int> =
- list.filterIsInstance<CommunalContentModel.Widget>().map { it.appWidgetId }
- onReorderWidgets(widgetIds)
+ /**
+ * Persists the new order with all the movements happened during drag operations & the new
+ * widget drop (if applicable).
+ *
+ * @param newItemComponentName name of the new widget that was dropped into the list; null if no
+ * new widget was added.
+ * @param newItemIndex index at which the a new widget was dropped into the list; null if no new
+ * widget was dropped.
+ */
+ fun onSaveList(newItemComponentName: ComponentName? = null, newItemIndex: Int? = null) {
+ // filters placeholder, but, maintains the indices of the widgets as if the placeholder was
+ // in the list. When persisted in DB, this leaves space for the new item (to be added) at
+ // the correct priority.
+ val widgetIdToPriorityMap: Map<Int, Int> =
+ list
+ .mapIndexedNotNull { index, item ->
+ if (item is CommunalContentModel.Widget) {
+ item.appWidgetId to list.size - index
+ } else {
+ null
+ }
+ }
+ .toMap()
+ // reorder and then add the new widget
+ onReorderWidgets(widgetIdToPriorityMap)
+ if (newItemComponentName != null && newItemIndex != null) {
+ onAddWidget(newItemComponentName, /*priority=*/ list.size - newItemIndex)
+ }
}
+
+ /** Returns true if the item at given index is editable. */
+ fun isItemEditable(index: Int) = list[index] is CommunalContentModel.Widget
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
new file mode 100644
index 0000000..22aa837
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.ui.compose
+
+import android.content.ClipDescription
+import android.content.ComponentName
+import android.content.Intent
+import android.view.DragEvent
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.draganddrop.dragAndDropTarget
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.scrollBy
+import androidx.compose.foundation.lazy.grid.LazyGridItemInfo
+import androidx.compose.foundation.lazy.grid.LazyGridState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableFloatStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draganddrop.DragAndDropEvent
+import androidx.compose.ui.draganddrop.DragAndDropTarget
+import androidx.compose.ui.draganddrop.mimeTypes
+import androidx.compose.ui.draganddrop.toAndroidDragEvent
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.dp
+import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.ui.compose.extensions.plus
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.isActive
+import kotlinx.coroutines.launch
+
+/**
+ * Holds state associated with dragging and dropping items from other activities into the lazy grid.
+ *
+ * @see dragAndDropTarget
+ */
+@Composable
+internal fun rememberDragAndDropTargetState(
+ gridState: LazyGridState,
+ contentListState: ContentListState,
+ updateDragPositionForRemove: (offset: Offset) -> Boolean,
+): DragAndDropTargetState {
+ val scope = rememberCoroutineScope()
+ val autoScrollSpeed = remember { mutableFloatStateOf(0f) }
+ // Threshold of distance from edges that should start auto-scroll - chosen to be a narrow value
+ // that allows differentiating intention of scrolling from intention of dragging over the first
+ // visible item.
+ val autoScrollThreshold = with(LocalDensity.current) { 60.dp.toPx() }
+ val state =
+ remember(gridState, contentListState) {
+ DragAndDropTargetState(
+ state = gridState,
+ contentListState = contentListState,
+ scope = scope,
+ autoScrollSpeed = autoScrollSpeed,
+ autoScrollThreshold = autoScrollThreshold,
+ updateDragPositionForRemove = updateDragPositionForRemove,
+ )
+ }
+ LaunchedEffect(autoScrollSpeed.floatValue) {
+ if (autoScrollSpeed.floatValue != 0f) {
+ while (isActive) {
+ gridState.scrollBy(autoScrollSpeed.floatValue)
+ delay(10)
+ }
+ }
+ }
+ return state
+}
+
+/**
+ * Attaches a listener for drag and drop events from other activities.
+ *
+ * @see androidx.compose.foundation.draganddrop.dragAndDropTarget
+ * @see DragEvent
+ */
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+internal fun Modifier.dragAndDropTarget(
+ dragDropTargetState: DragAndDropTargetState,
+): Modifier {
+ val state by rememberUpdatedState(dragDropTargetState)
+
+ return this then
+ Modifier.dragAndDropTarget(
+ shouldStartDragAndDrop = accept@{ startEvent ->
+ startEvent.mimeTypes().any { it == ClipDescription.MIMETYPE_TEXT_INTENT }
+ },
+ target =
+ object : DragAndDropTarget {
+ override fun onStarted(event: DragAndDropEvent) {
+ state.onStarted()
+ }
+
+ override fun onMoved(event: DragAndDropEvent) {
+ state.onMoved(event)
+ }
+
+ override fun onDrop(event: DragAndDropEvent): Boolean {
+ return state.onDrop(event)
+ }
+
+ override fun onEnded(event: DragAndDropEvent) {
+ state.onEnded()
+ }
+ }
+ )
+}
+
+/**
+ * Handles dropping of an item coming from a different activity (e.g. widget picker) in to the grid
+ * corresponding to the provided [LazyGridState].
+ *
+ * Adds a placeholder container to highlight the anticipated location the widget will be dropped to.
+ * When the item is held over an empty area, the placeholder appears at the end of the grid if one
+ * didn't exist already. As user moves the item over an existing item, the placeholder appears in
+ * place of that existing item. And then, the existing item is pushed over as part of re-ordering.
+ *
+ * Once item is dropped, new ordering along with the dropped item is persisted. See
+ * [ContentListState.onSaveList].
+ *
+ * Difference between this and [GridDragDropState] is that, this is used for listening to drops from
+ * other activities. [GridDragDropState] on the other hand, handles dragging of existing items in
+ * the communal hub grid.
+ */
+internal class DragAndDropTargetState(
+ private val state: LazyGridState,
+ private val contentListState: ContentListState,
+ private val scope: CoroutineScope,
+ private val autoScrollSpeed: MutableState<Float>,
+ private val autoScrollThreshold: Float,
+ private val updateDragPositionForRemove: (offset: Offset) -> Boolean,
+) {
+ /**
+ * The placeholder item that is treated as if it is being dragged across the grid. It is added
+ * to grid once drag and drop event is started and removed when event ends.
+ */
+ private var placeHolder = CommunalContentModel.WidgetPlaceholder()
+
+ private var placeHolderIndex: Int? = null
+ private var isOnRemoveButton = false
+
+ fun onStarted() {
+ // assume item will be added to the end.
+ contentListState.list.add(placeHolder)
+ placeHolderIndex = contentListState.list.size - 1
+ }
+
+ fun onMoved(event: DragAndDropEvent) {
+ val dragEvent = event.toAndroidDragEvent()
+ isOnRemoveButton = updateDragPositionForRemove(Offset(dragEvent.x, dragEvent.y))
+ if (!isOnRemoveButton) {
+ findTargetItem(dragEvent)?.apply {
+ var scrollIndex: Int? = null
+ var scrollOffset: Int? = null
+ if (placeHolderIndex == state.firstVisibleItemIndex) {
+ // Save info about the first item before the move, to neutralize the automatic
+ // keeping first item first.
+ scrollIndex = placeHolderIndex
+ scrollOffset = state.firstVisibleItemScrollOffset
+ }
+
+ autoScrollIfNearEdges(dragEvent)
+
+ if (contentListState.isItemEditable(this.index)) {
+ movePlaceholderTo(this.index)
+ placeHolderIndex = this.index
+ }
+
+ if (scrollIndex != null && scrollOffset != null) {
+ // this is needed to neutralize automatic keeping the first item first.
+ scope.launch { state.scrollToItem(scrollIndex, scrollOffset) }
+ }
+ }
+ }
+ }
+
+ fun onDrop(event: DragAndDropEvent): Boolean {
+ autoScrollSpeed.value = 0f
+ if (isOnRemoveButton) {
+ return false
+ }
+ return placeHolderIndex?.let { dropIndex ->
+ val componentName = event.maybeWidgetComponentName()
+ if (componentName != null) {
+ // Placeholder isn't removed yet to allow the setting the right priority for items
+ // before adding in the new item.
+ contentListState.onSaveList(
+ newItemComponentName = componentName,
+ newItemIndex = dropIndex
+ )
+ return@let true
+ }
+ return false
+ }
+ ?: false
+ }
+
+ fun onEnded() {
+ autoScrollSpeed.value = 0f
+ placeHolderIndex = null
+ contentListState.list.remove(placeHolder)
+ isOnRemoveButton = updateDragPositionForRemove(Offset.Zero)
+ }
+
+ private fun autoScrollIfNearEdges(dragEvent: DragEvent) {
+ val orientation = state.layoutInfo.orientation
+ val distanceFromStart =
+ if (orientation == Orientation.Horizontal) {
+ dragEvent.x
+ } else {
+ dragEvent.y
+ }
+ val distanceFromEnd =
+ if (orientation == Orientation.Horizontal) {
+ state.layoutInfo.viewportSize.width - dragEvent.x
+ } else {
+ state.layoutInfo.viewportSize.height - dragEvent.y
+ }
+ autoScrollSpeed.value =
+ when {
+ distanceFromEnd < autoScrollThreshold -> autoScrollThreshold - distanceFromEnd
+ distanceFromStart < autoScrollThreshold ->
+ -(autoScrollThreshold - distanceFromStart)
+ else -> 0f
+ }
+ }
+
+ private fun findTargetItem(dragEvent: DragEvent): LazyGridItemInfo? =
+ state.layoutInfo.visibleItemsInfo.firstOrNull { item ->
+ dragEvent.x.toInt() in item.offset.x..(item.offset + item.size).x &&
+ dragEvent.y.toInt() in item.offset.y..(item.offset + item.size).y
+ }
+
+ private fun movePlaceholderTo(index: Int) {
+ val currentIndex = contentListState.list.indexOf(placeHolder)
+ if (currentIndex != index) {
+ contentListState.onMove(currentIndex, index)
+ }
+ }
+
+ /**
+ * Parses and returns the component name of the widget that was dropped into the communal grid.
+ *
+ * Returns null if the drop event didn't include the widget information.
+ */
+ private fun DragAndDropEvent.maybeWidgetComponentName(): ComponentName? {
+ val clipData = this.toAndroidDragEvent().clipData.takeIf { it.itemCount != 0 }
+ return clipData
+ ?.getItemAt(0)
+ ?.intent
+ ?.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME, ComponentName::class.java)
+ }
+}
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 5451d05..0d460aa8 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
@@ -32,15 +32,13 @@
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.toOffset
import androidx.compose.ui.unit.toSize
import androidx.compose.ui.zIndex
-import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.ui.compose.extensions.plus
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
@@ -112,7 +110,7 @@
.firstOrNull { item ->
// grid item offset is based off grid content container so we need to deduct
// before content padding from the initial pointer position
- item.isEditable &&
+ 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
}
@@ -149,7 +147,7 @@
val targetItem =
state.layoutInfo.visibleItemsInfo.find { item ->
- item.isEditable &&
+ 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
@@ -187,10 +185,6 @@
private val LazyGridItemInfo.offsetEnd: IntOffset
get() = this.offset + this.size
- /** Whether the grid item can be dragged or be a drop target. Only widget card is editable. */
- private val LazyGridItemInfo.isEditable: Boolean
- get() = contentListState.list[this.index] is CommunalContentModel.Widget
-
/** Calculate the amount dragged out of bound on both sides. Returns 0f if not overscrolled */
private fun checkForOverscroll(startOffset: Offset, endOffset: Offset): Float {
return when {
@@ -210,14 +204,6 @@
}
}
-private operator fun IntOffset.plus(size: IntSize): IntOffset {
- return IntOffset(x + size.width, y + size.height)
-}
-
-private operator fun Offset.plus(size: Size): Offset {
- return Offset(x + size.width, y + size.height)
-}
-
fun Modifier.dragContainer(
dragDropState: GridDragDropState,
beforeContentPadding: ContentPaddingInPx
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/OffsetOperators.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/OffsetOperators.kt
new file mode 100644
index 0000000..b86c07e
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/OffsetOperators.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.communal.ui.compose.extensions
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
+
+/** Adds the given size to the x and y offsets in this [IntOffset] */
+operator fun IntOffset.plus(size: IntSize): IntOffset {
+ return IntOffset(x + size.width, y + size.height)
+}
+
+/** Adds the given size to the x and y offsets in this [Offset]. */
+operator fun Offset.plus(size: Size): Offset {
+ return Offset(x + size.width, y + size.height)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
new file mode 100644
index 0000000..2cb0034
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.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.composable
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneTransitionLayout
+import com.android.compose.animation.scene.transitions
+import com.android.systemui.keyguard.ui.composable.blueprint.LockscreenSceneBlueprint
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
+import javax.inject.Inject
+
+/**
+ * Renders the content of the lockscreen.
+ *
+ * This is separate from the [LockscreenScene] because it's meant to support usage of this UI from
+ * outside the scene container framework.
+ */
+class LockscreenContent
+@Inject
+constructor(
+ private val viewModel: LockscreenContentViewModel,
+ private val blueprints: Set<@JvmSuppressWildcards LockscreenSceneBlueprint>,
+) {
+
+ private val sceneKeyByBlueprint: Map<LockscreenSceneBlueprint, SceneKey> by lazy {
+ blueprints.associateWith { blueprint -> SceneKey(blueprint.id) }
+ }
+ private val sceneKeyByBlueprintId: Map<String, SceneKey> by lazy {
+ sceneKeyByBlueprint.entries.associate { (blueprint, sceneKey) -> blueprint.id to sceneKey }
+ }
+
+ @Composable
+ fun Content(
+ modifier: Modifier = Modifier,
+ ) {
+ val coroutineScope = rememberCoroutineScope()
+ val blueprintId by viewModel.blueprintId(coroutineScope).collectAsState()
+
+ // Switch smoothly between blueprints, any composable tagged with element() will be
+ // transition-animated between any two blueprints, if they both display the same element.
+ SceneTransitionLayout(
+ currentScene = checkNotNull(sceneKeyByBlueprintId[blueprintId]),
+ onChangeScene = {},
+ transitions =
+ transitions { sceneKeyByBlueprintId.values.forEach { sceneKey -> to(sceneKey) } },
+ modifier = modifier,
+ ) {
+ sceneKeyByBlueprint.entries.forEach { (blueprint, sceneKey) ->
+ scene(sceneKey) { with(blueprint) { Content(Modifier.fillMaxSize()) } }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index 3053654..93f31ec 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalCoroutinesApi::class, ExperimentalFoundationApi::class)
+@file:OptIn(ExperimentalFoundationApi::class)
package com.android.systemui.keyguard.ui.composable
@@ -50,14 +50,17 @@
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.scene.shared.model.UserAction
import com.android.systemui.scene.ui.composable.ComposableScene
+import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
+/** Set this to `true` to use the LockscreenContent replacement of KeyguardRootView. */
+private val UseLockscreenContent = false
+
/** The lock screen scene shows when the device is locked. */
@SysUISingleton
class LockscreenScene
@@ -66,6 +69,7 @@
@Application private val applicationScope: CoroutineScope,
private val viewModel: LockscreenSceneViewModel,
@KeyguardRootView private val viewProvider: () -> @JvmSuppressWildcards View,
+ private val lockscreenContent: Lazy<LockscreenContent>,
) : ComposableScene {
override val key = SceneKey.Lockscreen
@@ -91,6 +95,7 @@
LockscreenScene(
viewProvider = viewProvider,
viewModel = viewModel,
+ lockscreenContent = lockscreenContent,
modifier = modifier,
)
}
@@ -113,6 +118,7 @@
private fun SceneScope.LockscreenScene(
viewProvider: () -> View,
viewModel: LockscreenSceneViewModel,
+ lockscreenContent: Lazy<LockscreenContent>,
modifier: Modifier = Modifier,
) {
fun findSettingsMenu(): View {
@@ -133,16 +139,24 @@
modifier = Modifier.fillMaxSize(),
)
- AndroidView(
- factory = { _ ->
- val keyguardRootView = viewProvider()
- // Remove the KeyguardRootView from any parent it might already have in legacy code
- // just in case (a view can't have two parents).
- (keyguardRootView.parent as? ViewGroup)?.removeView(keyguardRootView)
- keyguardRootView
- },
- modifier = Modifier.fillMaxSize(),
- )
+ if (UseLockscreenContent) {
+ lockscreenContent
+ .get()
+ .Content(
+ modifier = Modifier.fillMaxSize(),
+ )
+ } else {
+ AndroidView(
+ factory = { _ ->
+ val keyguardRootView = viewProvider()
+ // Remove the KeyguardRootView from any parent it might already have in legacy
+ // code just in case (a view can't have two parents).
+ (keyguardRootView.parent as? ViewGroup)?.removeView(keyguardRootView)
+ keyguardRootView
+ },
+ modifier = Modifier.fillMaxSize(),
+ )
+ }
val notificationStackPosition by viewModel.keyguardRoot.notificationBounds.collectAsState()
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt
new file mode 100644
index 0000000..9abb50c
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.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.ui.composable
+
+import com.android.systemui.keyguard.ui.composable.blueprint.CommunalBlueprintModule
+import com.android.systemui.keyguard.ui.composable.blueprint.DefaultBlueprintModule
+import com.android.systemui.keyguard.ui.composable.blueprint.ShortcutsBesideUdfpsBlueprintModule
+import com.android.systemui.keyguard.ui.composable.blueprint.SplitShadeBlueprintModule
+import dagger.Module
+
+@Module(
+ includes =
+ [
+ CommunalBlueprintModule::class,
+ DefaultBlueprintModule::class,
+ ShortcutsBesideUdfpsBlueprintModule::class,
+ SplitShadeBlueprintModule::class,
+ ],
+)
+interface LockscreenSceneBlueprintModule
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BlueprintAlignmentLines.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BlueprintAlignmentLines.kt
new file mode 100644
index 0000000..efa8cc7
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BlueprintAlignmentLines.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.composable.blueprint
+
+import androidx.compose.ui.layout.HorizontalAlignmentLine
+import androidx.compose.ui.layout.VerticalAlignmentLine
+import kotlin.math.max
+import kotlin.math.min
+
+/**
+ * Encapsulates all blueprint alignment lines.
+ *
+ * These can be used to communicate alignment lines emitted by elements that the blueprint should
+ * consume and use to know how to constrain and/or place other elements in that blueprint.
+ *
+ * For more information, please see
+ * [the official documentation](https://developer.android.com/jetpack/compose/layouts/alignment-lines).
+ */
+object BlueprintAlignmentLines {
+
+ /**
+ * Encapsulates alignment lines produced by the lock icon element.
+ *
+ * Because the lock icon is also the same element as the under-display fingerprint sensor
+ * (UDFPS), blueprints should use its alignment lines to make sure that other elements on screen
+ * do not overlap with the lock icon.
+ */
+ object LockIcon {
+
+ /** The left edge of the lock icon. */
+ val Left =
+ VerticalAlignmentLine(
+ merger = { old, new ->
+ // When two left alignment line values are provided, choose the leftmost one:
+ min(old, new)
+ },
+ )
+
+ /** The top edge of the lock icon. */
+ val Top =
+ HorizontalAlignmentLine(
+ merger = { old, new ->
+ // When two top alignment line values are provided, choose the topmost one:
+ min(old, new)
+ },
+ )
+
+ /** The right edge of the lock icon. */
+ val Right =
+ VerticalAlignmentLine(
+ merger = { old, new ->
+ // When two right alignment line values are provided, choose the rightmost one:
+ max(old, new)
+ },
+ )
+
+ /** The bottom edge of the lock icon. */
+ val Bottom =
+ HorizontalAlignmentLine(
+ merger = { old, new ->
+ // When two bottom alignment line values are provided, choose the bottommost
+ // one:
+ max(old, new)
+ },
+ )
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
new file mode 100644
index 0000000..7eddaaf
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.composable.blueprint
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import com.android.compose.animation.scene.SceneScope
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+import javax.inject.Inject
+
+/** Renders the lockscreen scene when showing the communal glanceable hub. */
+class CommunalBlueprint @Inject constructor() : LockscreenSceneBlueprint {
+
+ override val id: String = "communal"
+
+ @Composable
+ override fun SceneScope.Content(modifier: Modifier) {
+ Box(modifier.background(Color.Black)) {
+ Text(
+ text = "TODO(b/316211368): communal blueprint",
+ color = Color.White,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ }
+ }
+}
+
+@Module
+interface CommunalBlueprintModule {
+ @Binds @IntoSet fun blueprint(blueprint: CommunalBlueprint): LockscreenSceneBlueprint
+}
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
new file mode 100644
index 0000000..7314453
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.composable.blueprint
+
+import androidx.compose.foundation.layout.Column
+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.unit.IntRect
+import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
+import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
+import com.android.systemui.keyguard.ui.composable.section.ClockSection
+import com.android.systemui.keyguard.ui.composable.section.LockSection
+import com.android.systemui.keyguard.ui.composable.section.NotificationSection
+import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
+import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+import javax.inject.Inject
+
+/**
+ * Renders the lockscreen scene when showing with the default layout (e.g. vertical phone form
+ * factor).
+ */
+class DefaultBlueprint
+@Inject
+constructor(
+ private val viewModel: LockscreenContentViewModel,
+ private val statusBarSection: StatusBarSection,
+ private val clockSection: ClockSection,
+ private val smartSpaceSection: SmartSpaceSection,
+ private val notificationSection: NotificationSection,
+ private val lockSection: LockSection,
+ private val ambientIndicationSection: AmbientIndicationSection,
+ private val bottomAreaSection: BottomAreaSection,
+) : LockscreenSceneBlueprint {
+
+ override val id: String = "default"
+
+ @Composable
+ override fun SceneScope.Content(modifier: Modifier) {
+ val isUdfpsVisible = viewModel.isUdfpsVisible
+
+ Layout(
+ content = {
+ // Constrained to above the lock icon.
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
+ with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
+ with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
+ with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
+ with(notificationSection) {
+ Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
+ }
+ if (!isUdfpsVisible) {
+ with(ambientIndicationSection) {
+ AmbientIndication(modifier = Modifier.fillMaxWidth())
+ }
+ }
+ }
+
+ with(lockSection) { LockIcon() }
+
+ // Aligned to bottom and constrained to below the lock icon.
+ Column(modifier = Modifier.fillMaxWidth()) {
+ if (isUdfpsVisible) {
+ with(ambientIndicationSection) {
+ AmbientIndication(modifier = Modifier.fillMaxWidth())
+ }
+ }
+
+ with(bottomAreaSection) { IndicationArea(modifier = Modifier.fillMaxWidth()) }
+ }
+
+ // Aligned to bottom and NOT constrained by the lock icon.
+ with(bottomAreaSection) {
+ Shortcut(isStart = true, applyPadding = true)
+ Shortcut(isStart = false, applyPadding = true)
+ }
+ },
+ modifier = modifier,
+ ) { measurables, constraints ->
+ check(measurables.size == 5)
+ val (
+ aboveLockIconMeasurable,
+ lockIconMeasurable,
+ belowLockIconMeasurable,
+ startShortcutMeasurable,
+ endShortcutMeasurable,
+ ) = measurables
+
+ val noMinConstraints =
+ constraints.copy(
+ minWidth = 0,
+ minHeight = 0,
+ )
+ val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
+ val lockIconBounds =
+ IntRect(
+ left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
+ top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
+ right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
+ bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
+ )
+
+ val aboveLockIconPlaceable =
+ aboveLockIconMeasurable.measure(
+ noMinConstraints.copy(maxHeight = lockIconBounds.top)
+ )
+ val belowLockIconPlaceable =
+ belowLockIconMeasurable.measure(
+ noMinConstraints.copy(maxHeight = constraints.maxHeight - lockIconBounds.bottom)
+ )
+ val startShortcutPleaceable = startShortcutMeasurable.measure(noMinConstraints)
+ val endShortcutPleaceable = endShortcutMeasurable.measure(noMinConstraints)
+
+ layout(constraints.maxWidth, constraints.maxHeight) {
+ aboveLockIconPlaceable.place(
+ x = 0,
+ y = 0,
+ )
+ lockIconPlaceable.place(
+ x = lockIconBounds.left,
+ y = lockIconBounds.top,
+ )
+ belowLockIconPlaceable.place(
+ x = 0,
+ y = constraints.maxHeight - belowLockIconPlaceable.height,
+ )
+ startShortcutPleaceable.place(
+ x = 0,
+ y = constraints.maxHeight - startShortcutPleaceable.height,
+ )
+ endShortcutPleaceable.place(
+ x = constraints.maxWidth - endShortcutPleaceable.width,
+ y = constraints.maxHeight - endShortcutPleaceable.height,
+ )
+ }
+ }
+ }
+}
+
+@Module
+interface DefaultBlueprintModule {
+ @Binds @IntoSet fun blueprint(blueprint: DefaultBlueprint): LockscreenSceneBlueprint
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/LockscreenSceneBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/LockscreenSceneBlueprint.kt
new file mode 100644
index 0000000..6d9cba4
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/LockscreenSceneBlueprint.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.composable.blueprint
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.android.compose.animation.scene.SceneScope
+
+/** Defines interface for classes that can render the content for a specific blueprint/layout. */
+interface LockscreenSceneBlueprint {
+
+ /** The ID that uniquely identifies this blueprint across all other blueprints. */
+ val id: String
+
+ /** Renders the content of this blueprint. */
+ @Composable fun SceneScope.Content(modifier: Modifier)
+}
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
new file mode 100644
index 0000000..4c119c7
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.composable.blueprint
+
+import androidx.compose.foundation.layout.Column
+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.unit.IntRect
+import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
+import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
+import com.android.systemui.keyguard.ui.composable.section.ClockSection
+import com.android.systemui.keyguard.ui.composable.section.LockSection
+import com.android.systemui.keyguard.ui.composable.section.NotificationSection
+import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
+import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+import javax.inject.Inject
+
+/**
+ * Renders the lockscreen scene when showing with the default layout (e.g. vertical phone form
+ * factor).
+ */
+class ShortcutsBesideUdfpsBlueprint
+@Inject
+constructor(
+ private val viewModel: LockscreenContentViewModel,
+ private val statusBarSection: StatusBarSection,
+ private val clockSection: ClockSection,
+ private val smartSpaceSection: SmartSpaceSection,
+ private val notificationSection: NotificationSection,
+ private val lockSection: LockSection,
+ private val ambientIndicationSection: AmbientIndicationSection,
+ private val bottomAreaSection: BottomAreaSection,
+) : LockscreenSceneBlueprint {
+
+ override val id: String = "shortcuts-besides-udfps"
+
+ @Composable
+ override fun SceneScope.Content(modifier: Modifier) {
+ val isUdfpsVisible = viewModel.isUdfpsVisible
+
+ Layout(
+ content = {
+ // Constrained to above the lock icon.
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
+ with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
+ with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
+ with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
+ with(notificationSection) {
+ Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
+ }
+ if (!isUdfpsVisible) {
+ with(ambientIndicationSection) {
+ AmbientIndication(modifier = Modifier.fillMaxWidth())
+ }
+ }
+ }
+
+ // Constrained to the left of the lock icon (in left-to-right layouts).
+ with(bottomAreaSection) { Shortcut(isStart = true, applyPadding = false) }
+
+ with(lockSection) { LockIcon() }
+
+ // Constrained to the right of the lock icon (in left-to-right layouts).
+ with(bottomAreaSection) { Shortcut(isStart = false, applyPadding = false) }
+
+ // Aligned to bottom and constrained to below the lock icon.
+ Column(modifier = Modifier.fillMaxWidth()) {
+ if (isUdfpsVisible) {
+ with(ambientIndicationSection) {
+ AmbientIndication(modifier = Modifier.fillMaxWidth())
+ }
+ }
+
+ with(bottomAreaSection) { IndicationArea(modifier = Modifier.fillMaxWidth()) }
+ }
+ },
+ modifier = modifier,
+ ) { measurables, constraints ->
+ check(measurables.size == 5)
+ val (
+ aboveLockIconMeasurable,
+ startSideShortcutMeasurable,
+ lockIconMeasurable,
+ endSideShortcutMeasurable,
+ belowLockIconMeasurable,
+ ) = measurables
+
+ val noMinConstraints =
+ constraints.copy(
+ minWidth = 0,
+ minHeight = 0,
+ )
+
+ val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
+ val lockIconBounds =
+ IntRect(
+ left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
+ top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
+ right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
+ bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
+ )
+
+ val aboveLockIconPlaceable =
+ aboveLockIconMeasurable.measure(
+ noMinConstraints.copy(maxHeight = lockIconBounds.top)
+ )
+ val startSideShortcutPlaceable = startSideShortcutMeasurable.measure(noMinConstraints)
+ val endSideShortcutPlaceable = endSideShortcutMeasurable.measure(noMinConstraints)
+ val belowLockIconPlaceable =
+ belowLockIconMeasurable.measure(
+ noMinConstraints.copy(maxHeight = constraints.maxHeight - lockIconBounds.bottom)
+ )
+
+ layout(constraints.maxWidth, constraints.maxHeight) {
+ aboveLockIconPlaceable.place(
+ x = 0,
+ y = 0,
+ )
+ startSideShortcutPlaceable.placeRelative(
+ x = lockIconBounds.left / 2 - startSideShortcutPlaceable.width / 2,
+ y = lockIconBounds.center.y - startSideShortcutPlaceable.height / 2,
+ )
+ lockIconPlaceable.place(
+ x = lockIconBounds.left,
+ y = lockIconBounds.top,
+ )
+ endSideShortcutPlaceable.placeRelative(
+ x =
+ lockIconBounds.right + (constraints.maxWidth - lockIconBounds.right) / 2 -
+ endSideShortcutPlaceable.width / 2,
+ y = lockIconBounds.center.y - endSideShortcutPlaceable.height / 2,
+ )
+ belowLockIconPlaceable.place(
+ x = 0,
+ y = constraints.maxHeight - belowLockIconPlaceable.height,
+ )
+ }
+ }
+ }
+}
+
+@Module
+interface ShortcutsBesideUdfpsBlueprintModule {
+ @Binds
+ @IntoSet
+ fun blueprint(blueprint: ShortcutsBesideUdfpsBlueprint): LockscreenSceneBlueprint
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
new file mode 100644
index 0000000..7545d5f
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.composable.blueprint
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import com.android.compose.animation.scene.SceneScope
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+import javax.inject.Inject
+
+/**
+ * Renders the lockscreen scene when showing with a split shade (e.g. unfolded foldable and/or
+ * tablet form factor).
+ */
+class SplitShadeBlueprint @Inject constructor() : LockscreenSceneBlueprint {
+
+ override val id: String = "split-shade"
+
+ @Composable
+ override fun SceneScope.Content(modifier: Modifier) {
+ Box(modifier.background(Color.Black)) {
+ Text(
+ text = "TODO(b/316211368): split shade blueprint",
+ color = Color.White,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ }
+ }
+}
+
+@Module
+interface SplitShadeBlueprintModule {
+ @Binds @IntoSet fun blueprint(blueprint: SplitShadeBlueprint): LockscreenSceneBlueprint
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/AmbientIndicationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/AmbientIndicationSection.kt
new file mode 100644
index 0000000..0e7ac5e
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/AmbientIndicationSection.kt
@@ -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.keyguard.ui.composable.section
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneScope
+import javax.inject.Inject
+
+class AmbientIndicationSection @Inject constructor() {
+ @Composable
+ fun SceneScope.AmbientIndication(modifier: Modifier = Modifier) {
+ MovableElement(
+ key = AmbientIndicationElementKey,
+ modifier = modifier,
+ ) {
+ Box(
+ modifier = Modifier.fillMaxWidth().background(Color.Green),
+ ) {
+ Text(
+ text = "TODO(b/316211368): Ambient indication",
+ color = Color.White,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ }
+ }
+ }
+}
+
+private val AmbientIndicationElementKey = ElementKey("AmbientIndication")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
new file mode 100644
index 0000000..db20f65
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.composable.section
+
+import android.view.View
+import android.widget.ImageView
+import androidx.annotation.IdRes
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.core.content.res.ResourcesCompat
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.animation.view.LaunchableImageView
+import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
+import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
+import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.KeyguardIndicationController
+import com.android.systemui.statusbar.VibratorHelper
+import javax.inject.Inject
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.flow.Flow
+
+class BottomAreaSection
+@Inject
+constructor(
+ private val viewModel: KeyguardQuickAffordancesCombinedViewModel,
+ private val falsingManager: FalsingManager,
+ private val vibratorHelper: VibratorHelper,
+ private val indicationController: KeyguardIndicationController,
+ private val indicationAreaViewModel: KeyguardIndicationAreaViewModel,
+ private val keyguardRootViewModel: KeyguardRootViewModel,
+) {
+ /**
+ * Renders a single lockscreen shortcut.
+ *
+ * @param isStart Whether the shortcut goes on the left (in left-to-right locales).
+ * @param applyPadding Whether to apply padding around the shortcut, this is needed if the
+ * shortcut is placed along the edges of the display.
+ */
+ @Composable
+ fun SceneScope.Shortcut(
+ isStart: Boolean,
+ applyPadding: Boolean,
+ modifier: Modifier = Modifier,
+ ) {
+ MovableElement(
+ key = if (isStart) StartButtonElementKey else EndButtonElementKey,
+ modifier = modifier,
+ ) {
+ Shortcut(
+ viewId = if (isStart) R.id.start_button else R.id.end_button,
+ viewModel = if (isStart) viewModel.startButton else viewModel.endButton,
+ transitionAlpha = viewModel.transitionAlpha,
+ falsingManager = falsingManager,
+ vibratorHelper = vibratorHelper,
+ indicationController = indicationController,
+ modifier =
+ if (applyPadding) {
+ Modifier.shortcutPadding()
+ } else {
+ Modifier
+ }
+ )
+ }
+ }
+
+ @Composable
+ fun SceneScope.IndicationArea(
+ modifier: Modifier = Modifier,
+ ) {
+ MovableElement(
+ key = IndicationAreaElementKey,
+ modifier = modifier.shortcutPadding(),
+ ) {
+ IndicationArea(
+ indicationAreaViewModel = indicationAreaViewModel,
+ keyguardRootViewModel = keyguardRootViewModel,
+ indicationController = indicationController,
+ )
+ }
+ }
+
+ @Composable
+ fun shortcutSizeDp(): DpSize {
+ return DpSize(
+ width = dimensionResource(R.dimen.keyguard_affordance_fixed_width),
+ height = dimensionResource(R.dimen.keyguard_affordance_fixed_height),
+ )
+ }
+
+ @Composable
+ private fun Shortcut(
+ @IdRes viewId: Int,
+ viewModel: Flow<KeyguardQuickAffordanceViewModel>,
+ transitionAlpha: Flow<Float>,
+ falsingManager: FalsingManager,
+ vibratorHelper: VibratorHelper,
+ indicationController: KeyguardIndicationController,
+ modifier: Modifier = Modifier,
+ ) {
+ val (binding, setBinding) = mutableStateOf<KeyguardQuickAffordanceViewBinder.Binding?>(null)
+
+ AndroidView(
+ factory = { context ->
+ val padding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.keyguard_affordance_fixed_padding
+ )
+ val view =
+ LaunchableImageView(context, null).apply {
+ id = viewId
+ scaleType = ImageView.ScaleType.FIT_CENTER
+ background =
+ ResourcesCompat.getDrawable(
+ context.resources,
+ R.drawable.keyguard_bottom_affordance_bg,
+ context.theme
+ )
+ foreground =
+ ResourcesCompat.getDrawable(
+ context.resources,
+ R.drawable.keyguard_bottom_affordance_selected_border,
+ context.theme
+ )
+ visibility = View.INVISIBLE
+ setPadding(padding, padding, padding, padding)
+ }
+
+ setBinding(
+ KeyguardQuickAffordanceViewBinder.bind(
+ view,
+ viewModel,
+ transitionAlpha,
+ falsingManager,
+ vibratorHelper,
+ ) {
+ indicationController.showTransientIndication(it)
+ }
+ )
+
+ view
+ },
+ onRelease = { binding?.destroy() },
+ modifier =
+ modifier.size(
+ width = shortcutSizeDp().width,
+ height = shortcutSizeDp().height,
+ )
+ )
+ }
+
+ @Composable
+ private fun IndicationArea(
+ indicationAreaViewModel: KeyguardIndicationAreaViewModel,
+ keyguardRootViewModel: KeyguardRootViewModel,
+ indicationController: KeyguardIndicationController,
+ modifier: Modifier = Modifier,
+ ) {
+ val (disposable, setDisposable) = mutableStateOf<DisposableHandle?>(null)
+
+ AndroidView(
+ factory = { context ->
+ val view = KeyguardIndicationArea(context, null)
+ setDisposable(
+ KeyguardIndicationAreaBinder.bind(
+ view = view,
+ viewModel = indicationAreaViewModel,
+ keyguardRootViewModel = keyguardRootViewModel,
+ indicationController = indicationController,
+ )
+ )
+ view
+ },
+ onRelease = { disposable?.dispose() },
+ modifier = modifier.fillMaxWidth(),
+ )
+ }
+
+ @Composable
+ private fun Modifier.shortcutPadding(): Modifier {
+ return this.padding(
+ horizontal = dimensionResource(R.dimen.keyguard_affordance_horizontal_offset)
+ )
+ .padding(bottom = dimensionResource(R.dimen.keyguard_affordance_vertical_offset))
+ }
+}
+
+private val StartButtonElementKey = ElementKey("StartButton")
+private val EndButtonElementKey = ElementKey("EndButton")
+private val IndicationAreaElementKey = ElementKey("IndicationArea")
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
new file mode 100644
index 0000000..eaf8063
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.composable.section
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+import javax.inject.Inject
+
+class ClockSection
+@Inject
+constructor(
+ private val viewModel: KeyguardClockViewModel,
+) {
+ @Composable
+ fun SceneScope.SmallClock(modifier: Modifier = Modifier) {
+ if (viewModel.useLargeClock) {
+ return
+ }
+
+ MovableElement(
+ key = ClockElementKey,
+ modifier = modifier,
+ ) {
+ Box(
+ modifier = Modifier.fillMaxWidth().background(Color.Magenta),
+ ) {
+ Text(
+ text = "TODO(b/316211368): Small clock",
+ color = Color.White,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ }
+ }
+ }
+
+ @Composable
+ fun SceneScope.LargeClock(modifier: Modifier = Modifier) {
+ if (!viewModel.useLargeClock) {
+ return
+ }
+
+ MovableElement(
+ key = ClockElementKey,
+ modifier = modifier,
+ ) {
+ Box(
+ modifier = Modifier.fillMaxWidth().background(Color.Blue),
+ ) {
+ Text(
+ text = "TODO(b/316211368): Large clock",
+ color = Color.White,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ }
+ }
+ }
+}
+
+private val ClockElementKey = ElementKey("Clock")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
new file mode 100644
index 0000000..d93863d
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.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.keyguard.ui.composable.section
+
+import android.content.Context
+import android.util.DisplayMetrics
+import android.view.WindowManager
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntRect
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.biometrics.AuthController
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+class LockSection
+@Inject
+constructor(
+ private val windowManager: WindowManager,
+ private val authController: AuthController,
+ private val featureFlags: FeatureFlagsClassic,
+) {
+ @Composable
+ fun SceneScope.LockIcon(modifier: Modifier = Modifier) {
+ MovableElement(
+ key = LockIconElementKey,
+ modifier = modifier,
+ ) {
+ val context = LocalContext.current
+ Box(
+ modifier =
+ Modifier.background(Color.Red).layout { measurable, _ ->
+ val lockIconBounds = lockIconBounds(context)
+ val placeable =
+ measurable.measure(
+ Constraints.fixed(
+ width = lockIconBounds.width,
+ height = lockIconBounds.height,
+ )
+ )
+ layout(
+ width = placeable.width,
+ height = placeable.height,
+ alignmentLines =
+ mapOf(
+ BlueprintAlignmentLines.LockIcon.Left to lockIconBounds.left,
+ BlueprintAlignmentLines.LockIcon.Top to lockIconBounds.top,
+ BlueprintAlignmentLines.LockIcon.Right to lockIconBounds.right,
+ BlueprintAlignmentLines.LockIcon.Bottom to
+ lockIconBounds.bottom,
+ ),
+ ) {
+ placeable.place(0, 0)
+ }
+ },
+ ) {
+ Text(
+ text = "TODO(b/316211368): Lock",
+ color = Color.White,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ }
+ }
+ }
+
+ /**
+ * Returns the bounds of the lock icon, in window view coordinates.
+ *
+ * On devices that support UDFPS (under-display fingerprint sensor), the bounds of the icon are
+ * the same as the bounds of the sensor.
+ */
+ private fun lockIconBounds(
+ context: Context,
+ ): IntRect {
+ val windowViewBounds = windowManager.currentWindowMetrics.bounds
+ var widthPx = windowViewBounds.right.toFloat()
+ if (featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE)) {
+ val insets = windowManager.currentWindowMetrics.windowInsets
+ // Assumed to be initially neglected as there are no left or right insets in portrait.
+ // However, on landscape, these insets need to included when calculating the midpoint.
+ @Suppress("DEPRECATION")
+ widthPx -= (insets.systemWindowInsetLeft + insets.systemWindowInsetRight).toFloat()
+ }
+ val defaultDensity =
+ DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
+ DisplayMetrics.DENSITY_DEFAULT.toFloat()
+ val lockIconRadiusPx = (defaultDensity * 36).toInt()
+
+ val udfpsLocation = authController.udfpsLocation
+ val (center, radius) =
+ if (authController.isUdfpsSupported && udfpsLocation != null) {
+ Pair(
+ IntOffset(
+ x = udfpsLocation.x,
+ y = udfpsLocation.y,
+ ),
+ authController.udfpsRadius.toInt(),
+ )
+ } else {
+ val scaleFactor = authController.scaleFactor
+ val bottomPaddingPx =
+ context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom)
+ val heightPx = windowViewBounds.bottom.toFloat()
+
+ Pair(
+ IntOffset(
+ x = (widthPx / 2).toInt(),
+ y =
+ (heightPx - ((bottomPaddingPx + lockIconRadiusPx) * scaleFactor))
+ .toInt(),
+ ),
+ (lockIconRadiusPx * scaleFactor).toInt(),
+ )
+ }
+
+ return IntRect(center, radius)
+ }
+}
+
+private val LockIconElementKey = ElementKey("LockIcon")
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
new file mode 100644
index 0000000..f135be2
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -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.keyguard.ui.composable.section
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneScope
+import javax.inject.Inject
+
+class NotificationSection @Inject constructor() {
+ @Composable
+ fun SceneScope.Notifications(modifier: Modifier = Modifier) {
+ MovableElement(
+ key = NotificationsElementKey,
+ modifier = modifier,
+ ) {
+ Box(
+ modifier = Modifier.fillMaxSize().background(Color.Yellow),
+ ) {
+ Text(
+ text = "TODO(b/316211368): Notifications",
+ color = Color.White,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ }
+ }
+ }
+}
+
+private val NotificationsElementKey = ElementKey("Notifications")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
new file mode 100644
index 0000000..ca03af5
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.composable.section
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneScope
+import javax.inject.Inject
+
+class SmartSpaceSection @Inject constructor() {
+ @Composable
+ fun SceneScope.SmartSpace(modifier: Modifier = Modifier) {
+ MovableElement(key = SmartSpaceElementKey, modifier = modifier) {
+ Box(
+ modifier = Modifier.fillMaxWidth().background(Color.Cyan),
+ ) {
+ Text(
+ text = "TODO(b/316211368): Smart space",
+ color = Color.White,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ }
+ }
+ }
+}
+
+private val SmartSpaceElementKey = ElementKey("SmartSpace")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt
new file mode 100644
index 0000000..6811eb4
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.composable.section
+
+import android.annotation.SuppressLint
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneScope
+import com.android.compose.modifiers.height
+import com.android.keyguard.dagger.KeyguardStatusBarViewComponent
+import com.android.systemui.res.R
+import com.android.systemui.shade.NotificationPanelView
+import com.android.systemui.shade.ShadeViewStateProvider
+import com.android.systemui.statusbar.phone.KeyguardStatusBarView
+import com.android.systemui.util.Utils
+import dagger.Lazy
+import javax.inject.Inject
+
+class StatusBarSection
+@Inject
+constructor(
+ private val componentFactory: KeyguardStatusBarViewComponent.Factory,
+ private val notificationPanelView: Lazy<NotificationPanelView>,
+) {
+ @Composable
+ fun SceneScope.StatusBar(modifier: Modifier = Modifier) {
+ val context = LocalContext.current
+
+ MovableElement(
+ key = StatusBarElementKey,
+ modifier = modifier,
+ ) {
+ AndroidView(
+ factory = {
+ notificationPanelView.get().findViewById<View>(R.id.keyguard_header)?.let {
+ (it.parent as ViewGroup).removeView(it)
+ }
+
+ val provider =
+ object : ShadeViewStateProvider {
+ override val lockscreenShadeDragProgress: Float = 0f
+ override val panelViewExpandedHeight: Float = 0f
+ override fun shouldHeadsUpBeVisible(): Boolean {
+ return false
+ }
+ }
+
+ @SuppressLint("InflateParams")
+ val view =
+ LayoutInflater.from(context)
+ .inflate(
+ R.layout.keyguard_status_bar,
+ null,
+ false,
+ ) as KeyguardStatusBarView
+ componentFactory.build(view, provider).keyguardStatusBarViewController.init()
+ view
+ },
+ modifier =
+ Modifier.fillMaxWidth().height {
+ Utils.getStatusBarHeaderHeightKeyguard(context)
+ },
+ )
+ }
+ }
+}
+
+private val StatusBarElementKey = ElementKey("StatusBar")
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 9d9b0a9..a85d9bf 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -16,6 +16,7 @@
package com.android.compose.animation.scene
+import android.graphics.Picture
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
@@ -66,12 +67,21 @@
* The movable content of this element, if this element is composed using
* [SceneScope.MovableElement].
*/
- val movableContent by
- // This is only accessed from the composition (main) thread, so no need to use the default
- // lock of lazy {} to synchronize.
- lazy(mode = LazyThreadSafetyMode.NONE) {
- movableContentOf { content: @Composable () -> Unit -> content() }
- }
+ private var _movableContent: (@Composable (@Composable () -> Unit) -> Unit)? = null
+ val movableContent: @Composable (@Composable () -> Unit) -> Unit
+ get() =
+ _movableContent
+ ?: movableContentOf { content: @Composable () -> Unit -> content() }
+ .also { _movableContent = it }
+
+ /**
+ * The [Picture] to which we save the last drawing commands of this element, if it is movable.
+ * This is necessary because the content of this element might not be composed in the scene it
+ * should currently be drawn.
+ */
+ private var _picture: Picture? = null
+ val picture: Picture
+ get() = _picture ?: Picture().also { _picture = it }
override fun toString(): String {
return "Element(key=$key)"
@@ -521,11 +531,6 @@
sceneValues.targetOffset = targetOffsetInScene
}
- // No need to place the element in this scene if we don't want to draw it anyways.
- if (!shouldDrawElement(layoutImpl, scene, element)) {
- return
- }
-
val currentOffset = lookaheadScopeCoordinates.localPositionOf(coords, Offset.Zero)
val lastSharedValues = element.lastSharedValues
val lastValues = sceneValues.lastValues
@@ -548,6 +553,13 @@
lastSharedValues.offset = targetOffset
lastValues.offset = targetOffset
+ // No need to place the element in this scene if we don't want to draw it anyways. Note that
+ // it's still important to compute the target offset and update lastValues, otherwise it
+ // will be out of date.
+ if (!shouldDrawElement(layoutImpl, scene, element)) {
+ return
+ }
+
val offset = (targetOffset - currentOffset).round()
if (isElementOpaque(layoutImpl, element, scene, sceneValues)) {
// TODO(b/291071158): Call placeWithLayer() if offset != IntOffset.Zero and size is not
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
index 306f276..49df2f6 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
@@ -16,7 +16,6 @@
package com.android.compose.animation.scene
-import android.graphics.Picture
import android.util.Log
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
@@ -60,7 +59,7 @@
// The [Picture] to which we save the last drawing commands of this element. This is
// necessary because the content of this element might not be composed in this scene, in
// which case we still need to draw it.
- val picture = remember { Picture() }
+ val picture = element.picture
// Whether we should compose the movable element here. The scene picker logic to know in
// which scene we should compose/draw a movable element might depend on the current
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index 6a7a3a0..30e50a9 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
@@ -34,6 +34,7 @@
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.zIndex
+import com.android.compose.animation.scene.modifiers.noResizeDuringTransitions
/** A scene in a [SceneTransitionLayout]. */
@Stable
@@ -152,4 +153,8 @@
bounds: ElementKey,
shape: Shape
): Modifier = punchHole(layoutImpl, element, bounds, shape)
+
+ override fun Modifier.noResizeDuringTransitions(): Modifier {
+ return noResizeDuringTransitions(layoutState = layoutImpl.state)
+ }
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 3608e37..5eb339e 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -65,11 +65,11 @@
SceneTransitionLayoutForTesting(
currentScene,
onChangeScene,
+ modifier,
transitions,
state,
edgeDetector,
transitionInterceptionThreshold,
- modifier,
onLayoutImpl = null,
scenes,
)
@@ -205,6 +205,12 @@
* the result.
*/
fun Modifier.punchHole(element: ElementKey, bounds: ElementKey, shape: Shape): Modifier
+
+ /**
+ * Don't resize during transitions. This can for instance be used to make sure that scrollable
+ * lists keep a constant size during transitions even if its elements are growing/shrinking.
+ */
+ fun Modifier.noResizeDuringTransitions(): Modifier
}
// TODO(b/291053742): Add animateSharedValueAsState(targetValue) without any ValueKey and ElementKey
@@ -257,12 +263,12 @@
internal fun SceneTransitionLayoutForTesting(
currentScene: SceneKey,
onChangeScene: (SceneKey) -> Unit,
- transitions: SceneTransitions,
- state: SceneTransitionLayoutState,
- edgeDetector: EdgeDetector,
- transitionInterceptionThreshold: Float,
- modifier: Modifier,
- onLayoutImpl: ((SceneTransitionLayoutImpl) -> Unit)?,
+ modifier: Modifier = Modifier,
+ transitions: SceneTransitions = transitions {},
+ state: SceneTransitionLayoutState = remember { SceneTransitionLayoutState(currentScene) },
+ edgeDetector: EdgeDetector = DefaultEdgeDetector,
+ transitionInterceptionThreshold: Float = 0f,
+ onLayoutImpl: ((SceneTransitionLayoutImpl) -> Unit)? = null,
scenes: SceneTransitionLayoutScope.() -> Unit,
) {
val density = LocalDensity.current
@@ -280,6 +286,10 @@
.also { onLayoutImpl?.invoke(it) }
}
+ // TODO(b/317014852): Move this into the SideEffect {} again once STLImpl.scenes is not a
+ // SnapshotStateMap anymore.
+ layoutImpl.updateScenes(scenes)
+
val targetSceneChannel = remember { Channel<SceneKey>(Channel.CONFLATED) }
SideEffect {
if (state != layoutImpl.state) {
@@ -293,7 +303,6 @@
(state as SceneTransitionLayoutStateImpl).transitions = transitions
layoutImpl.density = density
layoutImpl.edgeDetector = edgeDetector
- layoutImpl.updateScenes(scenes)
state.transitions = transitions
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index c99c325..45e1a0f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -46,7 +46,12 @@
builder: SceneTransitionLayoutScope.() -> Unit,
coroutineScope: CoroutineScope,
) {
- internal val scenes = mutableMapOf<SceneKey, Scene>()
+ /**
+ * The map of [Scene]s.
+ *
+ * TODO(b/317014852): Make this a normal MutableMap instead.
+ */
+ internal val scenes = SnapshotStateMap<SceneKey, Scene>()
/**
* The map of [Element]s.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/modifiers/Size.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/modifiers/Size.kt
new file mode 100644
index 0000000..bd36cb8
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/modifiers/Size.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene.modifiers
+
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.intermediateLayout
+import androidx.compose.ui.unit.Constraints
+import com.android.compose.animation.scene.SceneTransitionLayoutState
+
+@OptIn(ExperimentalComposeUiApi::class)
+internal fun Modifier.noResizeDuringTransitions(layoutState: SceneTransitionLayoutState): Modifier {
+ return intermediateLayout { measurable, constraints ->
+ if (layoutState.currentTransition == null) {
+ return@intermediateLayout measurable.measure(constraints).run {
+ layout(width, height) { place(0, 0) }
+ }
+ }
+
+ // Make sure that this layout node has the same size than when we are at rest.
+ val sizeAtRest = lookaheadSize
+ measurable.measure(Constraints.fixed(sizeAtRest.width, sizeAtRest.height)).run {
+ layout(width, height) { place(0, 0) }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index d332910..da5a0a0 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -16,6 +16,7 @@
package com.android.compose.animation.scene
+import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Box
@@ -30,16 +31,21 @@
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.layout.intermediateLayout
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.test.subjects.DpOffsetSubject
+import com.android.compose.test.subjects.assertThat
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@@ -259,11 +265,6 @@
SceneTransitionLayoutForTesting(
currentScene = currentScene,
onChangeScene = { currentScene = it },
- transitions = remember { transitions {} },
- state = remember { SceneTransitionLayoutState(currentScene) },
- edgeDetector = DefaultEdgeDetector,
- modifier = Modifier,
- transitionInterceptionThreshold = 0f,
onLayoutImpl = { nullableLayoutImpl = it },
) {
scene(TestScenes.SceneA) { /* Nothing */}
@@ -429,11 +430,6 @@
SceneTransitionLayoutForTesting(
currentScene = TestScenes.SceneA,
onChangeScene = {},
- transitions = remember { transitions {} },
- state = remember { SceneTransitionLayoutState(TestScenes.SceneA) },
- edgeDetector = DefaultEdgeDetector,
- modifier = Modifier,
- transitionInterceptionThreshold = 0f,
onLayoutImpl = { nullableLayoutImpl = it },
) {
scene(TestScenes.SceneA) { Box(Modifier.element(key)) }
@@ -484,11 +480,6 @@
SceneTransitionLayoutForTesting(
currentScene = TestScenes.SceneA,
onChangeScene = {},
- transitions = remember { transitions {} },
- state = remember { SceneTransitionLayoutState(TestScenes.SceneA) },
- edgeDetector = DefaultEdgeDetector,
- modifier = Modifier,
- transitionInterceptionThreshold = 0f,
onLayoutImpl = { nullableLayoutImpl = it },
) {
scene(TestScenes.SceneA) {
@@ -574,4 +565,86 @@
after { assertThat(fooCompositions).isEqualTo(1) }
}
}
+
+ @Test
+ fun sharedElementOffsetIsUpdatedEvenWhenNotPlaced() {
+ var nullableLayoutImpl: SceneTransitionLayoutImpl? = null
+ var density: Density? = null
+
+ fun layoutImpl() = nullableLayoutImpl ?: error("nullableLayoutImpl was not set")
+
+ fun density() = density ?: error("density was not set")
+
+ fun Offset.toDpOffset() = with(density()) { DpOffset(x.toDp(), y.toDp()) }
+
+ fun foo() = layoutImpl().elements[TestElements.Foo] ?: error("Foo not in elements map")
+
+ fun Element.lastSharedOffset() = lastSharedValues.offset.toDpOffset()
+
+ fun Element.lastOffsetIn(scene: SceneKey) =
+ (sceneValues[scene] ?: error("$scene not in sceneValues map"))
+ .lastValues
+ .offset
+ .toDpOffset()
+
+ rule.testTransition(
+ from = TestScenes.SceneA,
+ to = TestScenes.SceneB,
+ transitionLayout = { currentScene, onChangeScene ->
+ density = LocalDensity.current
+
+ SceneTransitionLayoutForTesting(
+ currentScene = currentScene,
+ onChangeScene = onChangeScene,
+ onLayoutImpl = { nullableLayoutImpl = it },
+ transitions =
+ transitions {
+ from(TestScenes.SceneA, to = TestScenes.SceneB) {
+ spec = tween(durationMillis = 4 * 16, easing = LinearEasing)
+ }
+ }
+ ) {
+ scene(TestScenes.SceneA) { Box(Modifier.element(TestElements.Foo)) }
+ scene(TestScenes.SceneB) {
+ Box(Modifier.offset(x = 40.dp, y = 80.dp).element(TestElements.Foo))
+ }
+ }
+ }
+ ) {
+ val tolerance = DpOffsetSubject.DefaultTolerance
+
+ before {
+ val expected = DpOffset(0.dp, 0.dp)
+ assertThat(foo().lastSharedOffset()).isWithin(tolerance).of(expected)
+ assertThat(foo().lastOffsetIn(TestScenes.SceneA)).isWithin(tolerance).of(expected)
+ }
+
+ at(16) {
+ val expected = DpOffset(10.dp, 20.dp)
+ assertThat(foo().lastSharedOffset()).isWithin(tolerance).of(expected)
+ assertThat(foo().lastOffsetIn(TestScenes.SceneA)).isWithin(tolerance).of(expected)
+ assertThat(foo().lastOffsetIn(TestScenes.SceneB)).isWithin(tolerance).of(expected)
+ }
+
+ at(32) {
+ val expected = DpOffset(20.dp, 40.dp)
+ assertThat(foo().lastSharedOffset()).isWithin(tolerance).of(expected)
+ assertThat(foo().lastOffsetIn(TestScenes.SceneA)).isWithin(tolerance).of(expected)
+ assertThat(foo().lastOffsetIn(TestScenes.SceneB)).isWithin(tolerance).of(expected)
+ }
+
+ at(48) {
+ val expected = DpOffset(30.dp, 60.dp)
+ assertThat(foo().lastSharedOffset()).isWithin(tolerance).of(expected)
+ assertThat(foo().lastOffsetIn(TestScenes.SceneA)).isWithin(tolerance).of(expected)
+ assertThat(foo().lastOffsetIn(TestScenes.SceneB)).isWithin(tolerance).of(expected)
+ }
+
+ after {
+ val expected = DpOffset(40.dp, 80.dp)
+ assertThat(foo().lastSharedOffset()).isWithin(tolerance).of(expected)
+ assertThat(foo().lastOffsetIn(TestScenes.SceneB)).isWithin(tolerance).of(expected)
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/modifiers/SizeTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/modifiers/SizeTest.kt
new file mode 100644
index 0000000..2c159d1
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/modifiers/SizeTest.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene.modifiers
+
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestElements
+import com.android.compose.animation.scene.element
+import com.android.compose.animation.scene.testTransition
+import com.android.compose.test.assertSizeIsEqualTo
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SizeTest {
+ @get:Rule val rule = createComposeRule()
+
+ @Test
+ fun noResizeDuringTransitions() {
+ // The tag for the parent of the shared Foo element.
+ val parentTag = "parent"
+
+ rule.testTransition(
+ fromSceneContent = { Box(Modifier.element(TestElements.Foo).size(100.dp)) },
+ toSceneContent = {
+ // Don't resize the parent of Foo during transitions so that it's always the same
+ // size as when there is no transition (200dp).
+ Box(Modifier.noResizeDuringTransitions().testTag(parentTag)) {
+ Box(Modifier.element(TestElements.Foo).size(200.dp))
+ }
+ },
+ transition = { spec = tween(durationMillis = 4 * 16, easing = LinearEasing) },
+ ) {
+ at(16) { rule.onNodeWithTag(parentTag).assertSizeIsEqualTo(200.dp, 200.dp) }
+ at(32) { rule.onNodeWithTag(parentTag).assertSizeIsEqualTo(200.dp, 200.dp) }
+ at(48) { rule.onNodeWithTag(parentTag).assertSizeIsEqualTo(200.dp, 200.dp) }
+ after { rule.onNodeWithTag(parentTag).assertSizeIsEqualTo(200.dp, 200.dp) }
+ }
+ }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index b9d6643..2bfa7d9 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -33,6 +33,8 @@
import com.android.systemui.animation.GlyphCallback
import com.android.systemui.animation.TextAnimator
import com.android.systemui.customization.R
+import com.android.systemui.log.core.LogcatOnlyMessageBuffer
+import com.android.systemui.log.core.LogLevel
import com.android.systemui.log.core.Logger
import com.android.systemui.log.core.MessageBuffer
import java.io.PrintWriter
@@ -51,12 +53,13 @@
defStyleAttr: Int = 0,
defStyleRes: Int = 0
) : TextView(context, attrs, defStyleAttr, defStyleRes) {
- var messageBuffer: MessageBuffer? = null
- set(value) {
- logger = if (value != null) Logger(value, TAG) else null
- }
-
- private var logger: Logger? = null
+ // To protect us from issues from this being null while the TextView constructor is running, we
+ // implement the get method and ensure a value is returned before initialization is complete.
+ private var logger = DEFAULT_LOGGER
+ get() = field ?: DEFAULT_LOGGER
+ var messageBuffer: MessageBuffer
+ get() = logger.buffer
+ set(value) { logger = Logger(value, TAG) }
private val time = Calendar.getInstance()
@@ -133,8 +136,8 @@
}
override fun onAttachedToWindow() {
+ logger.d("onAttachedToWindow")
super.onAttachedToWindow()
- logger?.d("onAttachedToWindow")
refreshFormat()
}
@@ -150,13 +153,13 @@
time.timeInMillis = timeOverrideInMillis ?: System.currentTimeMillis()
contentDescription = DateFormat.format(descFormat, time)
val formattedText = DateFormat.format(format, time)
- logger?.d({ "refreshTime: new formattedText=$str1" }) { str1 = formattedText?.toString() }
+ logger.d({ "refreshTime: new formattedText=$str1" }) { str1 = formattedText?.toString() }
// Setting text actually triggers a layout pass (because the text view is set to
// wrap_content width and TextView always relayouts for this). Avoid needless
// relayout if the text didn't actually change.
if (!TextUtils.equals(text, formattedText)) {
text = formattedText
- logger?.d({ "refreshTime: done setting new time text to: $str1" }) {
+ logger.d({ "refreshTime: done setting new time text to: $str1" }) {
str1 = formattedText?.toString()
}
// Because the TextLayout may mutate under the hood as a result of the new text, we
@@ -165,21 +168,22 @@
// without being notified TextInterpolator being notified.
if (layout != null) {
textAnimator?.updateLayout(layout)
- logger?.d("refreshTime: done updating textAnimator layout")
+ logger.d("refreshTime: done updating textAnimator layout")
}
requestLayout()
- logger?.d("refreshTime: after requestLayout")
+ logger.d("refreshTime: after requestLayout")
}
}
fun onTimeZoneChanged(timeZone: TimeZone?) {
+ logger.d({ "onTimeZoneChanged($str1)" }) { str1 = timeZone?.toString() }
time.timeZone = timeZone
refreshFormat()
- logger?.d({ "onTimeZoneChanged newTimeZone=$str1" }) { str1 = timeZone?.toString() }
}
@SuppressLint("DrawAllocation")
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ logger.d("onMeasure")
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val animator = textAnimator
if (animator == null) {
@@ -189,10 +193,10 @@
} else {
animator.updateLayout(layout)
}
- logger?.d("onMeasure")
}
override fun onDraw(canvas: Canvas) {
+ logger.d({ "onDraw($str1)"}) { str1 = text.toString() }
// Use textAnimator to render text if animation is enabled.
// Otherwise default to using standard draw functions.
if (isAnimationEnabled) {
@@ -201,22 +205,23 @@
} else {
super.onDraw(canvas)
}
- logger?.d("onDraw")
}
override fun invalidate() {
+ @Suppress("UNNECESSARY_SAFE_CALL")
+ // logger won't be initialized when called by TextView's constructor
+ logger.d("invalidate")
super.invalidate()
- logger?.d("invalidate")
}
override fun onTextChanged(
- text: CharSequence,
- start: Int,
- lengthBefore: Int,
- lengthAfter: Int
+ text: CharSequence,
+ start: Int,
+ lengthBefore: Int,
+ lengthAfter: Int
) {
+ logger.d({ "onTextChanged($str1)" }) { str1 = text.toString() }
super.onTextChanged(text, start, lengthBefore, lengthAfter)
- logger?.d({ "onTextChanged text=$str1" }) { str1 = text.toString() }
}
fun setLineSpacingScale(scale: Float) {
@@ -230,7 +235,7 @@
}
fun animateColorChange() {
- logger?.d("animateColorChange")
+ logger.d("animateColorChange")
setTextStyle(
weight = lockScreenWeight,
textSize = -1f,
@@ -252,7 +257,7 @@
}
fun animateAppearOnLockscreen() {
- logger?.d("animateAppearOnLockscreen")
+ logger.d("animateAppearOnLockscreen")
setTextStyle(
weight = dozingWeight,
textSize = -1f,
@@ -278,7 +283,7 @@
if (isAnimationEnabled && textAnimator == null) {
return
}
- logger?.d("animateFoldAppear")
+ logger.d("animateFoldAppear")
setTextStyle(
weight = lockScreenWeightInternal,
textSize = -1f,
@@ -305,7 +310,7 @@
// Skip charge animation if dozing animation is already playing.
return
}
- logger?.d("animateCharge")
+ logger.d("animateCharge")
val startAnimPhase2 = Runnable {
setTextStyle(
weight = if (isDozing()) dozingWeight else lockScreenWeight,
@@ -329,7 +334,7 @@
}
fun animateDoze(isDozing: Boolean, animate: Boolean) {
- logger?.d("animateDoze")
+ logger.d("animateDoze")
setTextStyle(
weight = if (isDozing) dozingWeight else lockScreenWeight,
textSize = -1f,
@@ -448,7 +453,7 @@
isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12
else -> DOUBLE_LINE_FORMAT_12_HOUR
}
- logger?.d({ "refreshFormat format=$str1" }) { str1 = format?.toString() }
+ logger.d({ "refreshFormat($str1)" }) { str1 = format?.toString() }
descFormat = if (use24HourFormat) Patterns.sClockView24 else Patterns.sClockView12
refreshTime()
@@ -552,6 +557,8 @@
companion object {
private val TAG = AnimatableClockView::class.simpleName!!
+ private val DEFAULT_LOGGER = Logger(LogcatOnlyMessageBuffer(LogLevel.WARNING), TAG)
+
const val ANIMATION_DURATION_FOLD_TO_AOD: Int = 600
private const val DOUBLE_LINE_FORMAT_12_HOUR = "hh\nmm"
private const val DOUBLE_LINE_FORMAT_24_HOUR = "HH\nmm"
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 cdd074d..41bde52 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
@@ -21,20 +21,16 @@
import android.net.Uri
import android.os.UserHandle
import android.provider.Settings
-import android.util.Log
import androidx.annotation.OpenForTesting
-import com.android.systemui.log.LogMessageImpl
import com.android.systemui.log.core.LogLevel
-import com.android.systemui.log.core.LogMessage
+import com.android.systemui.log.core.LogcatOnlyMessageBuffer
import com.android.systemui.log.core.Logger
-import com.android.systemui.log.core.MessageBuffer
-import com.android.systemui.log.core.MessageInitializer
-import com.android.systemui.log.core.MessagePrinter
import com.android.systemui.plugins.PluginLifecycleManager
import com.android.systemui.plugins.PluginListener
import com.android.systemui.plugins.PluginManager
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockId
+import com.android.systemui.plugins.clocks.ClockMessageBuffers
import com.android.systemui.plugins.clocks.ClockMetadata
import com.android.systemui.plugins.clocks.ClockProvider
import com.android.systemui.plugins.clocks.ClockProviderPlugin
@@ -77,32 +73,6 @@
return result ?: value
}
-private val TMP_MESSAGE: LogMessage by lazy { LogMessageImpl.Factory.create() }
-
-private inline fun Logger?.tryLog(
- tag: String,
- level: LogLevel,
- messageInitializer: MessageInitializer,
- noinline messagePrinter: MessagePrinter,
- ex: Throwable? = null,
-) {
- if (this != null) {
- // Wrap messagePrinter to convert it from crossinline to noinline
- this.log(level, messagePrinter, ex, messageInitializer)
- } else {
- messageInitializer(TMP_MESSAGE)
- val msg = messagePrinter(TMP_MESSAGE)
- when (level) {
- LogLevel.VERBOSE -> Log.v(tag, msg, ex)
- LogLevel.DEBUG -> Log.d(tag, msg, ex)
- LogLevel.INFO -> Log.i(tag, msg, ex)
- LogLevel.WARNING -> Log.w(tag, msg, ex)
- LogLevel.ERROR -> Log.e(tag, msg, ex)
- LogLevel.WTF -> Log.wtf(tag, msg, ex)
- }
- }
-}
-
/** ClockRegistry aggregates providers and plugins */
open class ClockRegistry(
val context: Context,
@@ -114,12 +84,15 @@
val handleAllUsers: Boolean,
defaultClockProvider: ClockProvider,
val fallbackClockId: ClockId = DEFAULT_CLOCK_ID,
- messageBuffer: MessageBuffer? = null,
+ val clockBuffers: ClockMessageBuffers? = null,
val keepAllLoaded: Boolean,
subTag: String,
var isTransitClockEnabled: Boolean = false,
) {
private val TAG = "${ClockRegistry::class.simpleName} ($subTag)"
+ private val logger: Logger =
+ Logger(clockBuffers?.infraMessageBuffer ?: LogcatOnlyMessageBuffer(LogLevel.DEBUG), TAG)
+
interface ClockChangeListener {
// Called when the active clock changes
fun onCurrentClockChanged() {}
@@ -128,7 +101,6 @@
fun onAvailableClocksChanged() {}
}
- private val logger: Logger? = if (messageBuffer != null) Logger(messageBuffer, TAG) else null
private val availableClocks = ConcurrentHashMap<ClockId, ClockInfo>()
private val clockChangeListeners = mutableListOf<ClockChangeListener>()
private val settingObserver =
@@ -157,21 +129,15 @@
val knownClocks = KNOWN_PLUGINS.get(manager.getPackage())
if (knownClocks == null) {
- logger.tryLog(
- TAG,
- LogLevel.WARNING,
- { str1 = manager.getPackage() },
- { "Loading unrecognized clock package: $str1" }
- )
+ logger.w({ "Loading unrecognized clock package: $str1" }) {
+ str1 = manager.getPackage()
+ }
return true
}
- logger.tryLog(
- TAG,
- LogLevel.INFO,
- { str1 = manager.getPackage() },
- { "Skipping initial load of known clock package package: $str1" }
- )
+ logger.i({ "Skipping initial load of known clock package package: $str1" }) {
+ str1 = manager.getPackage()
+ }
var isCurrentClock = false
var isClockListChanged = false
@@ -185,19 +151,14 @@
}
if (manager != info.manager) {
- logger.tryLog(
- TAG,
- LogLevel.ERROR,
- {
- str1 = id
- str2 = info.manager.toString()
- str3 = manager.toString()
- },
- {
- "Clock Id conflict on attach: " +
- "$str1 is double registered by $str2 and $str3"
- }
- )
+ logger.e({
+ "Clock Id conflict on attach: " +
+ "$str1 is double registered by $str2 and $str3"
+ }) {
+ str1 = id
+ str2 = info.manager.toString()
+ str3 = manager.toString()
+ }
continue
}
@@ -219,6 +180,8 @@
pluginContext: Context,
manager: PluginLifecycleManager<ClockProviderPlugin>
) {
+ plugin.initialize(clockBuffers)
+
var isClockListChanged = false
for (clock in plugin.getClocks()) {
val id = clock.clockId
@@ -233,19 +196,14 @@
}
if (manager != info.manager) {
- logger.tryLog(
- TAG,
- LogLevel.ERROR,
- {
- str1 = id
- str2 = info.manager.toString()
- str3 = manager.toString()
- },
- {
- "Clock Id conflict on load: " +
- "$str1 is double registered by $str2 and $str3"
- }
- )
+ logger.e({
+ "Clock Id conflict on load: " +
+ "$str1 is double registered by $str2 and $str3"
+ }) {
+ str1 = id
+ str2 = info.manager.toString()
+ str3 = manager.toString()
+ }
manager.unloadPlugin()
continue
}
@@ -268,19 +226,14 @@
val id = clock.clockId
val info = availableClocks[id]
if (info?.manager != manager) {
- logger.tryLog(
- TAG,
- LogLevel.ERROR,
- {
- str1 = id
- str2 = info?.manager.toString()
- str3 = manager.toString()
- },
- {
- "Clock Id conflict on unload: " +
- "$str1 is double registered by $str2 and $str3"
- }
- )
+ logger.e({
+ "Clock Id conflict on unload: " +
+ "$str1 is double registered by $str2 and $str3"
+ }) {
+ str1 = id
+ str2 = info?.manager.toString()
+ str3 = manager.toString()
+ }
continue
}
info.provider = null
@@ -350,7 +303,7 @@
ClockSettings.deserialize(json)
} catch (ex: Exception) {
- logger.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to parse clock settings" }, ex)
+ logger.e("Failed to parse clock settings", ex)
null
}
settings = result
@@ -379,7 +332,7 @@
)
}
} catch (ex: Exception) {
- logger.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to set clock settings" }, ex)
+ logger.e("Failed to set clock settings", ex)
}
settings = value
}
@@ -451,7 +404,8 @@
}
init {
- // Register default clock designs
+ // Initialize & register default clock designs
+ defaultClockProvider.initialize(clockBuffers)
for (clock in defaultClockProvider.getClocks()) {
availableClocks[clock.clockId] = ClockInfo(clock, defaultClockProvider, null)
}
@@ -514,12 +468,7 @@
fun verifyLoadedProviders() {
val shouldSchedule = isQueued.compareAndSet(false, true)
if (!shouldSchedule) {
- logger.tryLog(
- TAG,
- LogLevel.VERBOSE,
- {},
- { "verifyLoadedProviders: shouldSchedule=false" }
- )
+ logger.v("verifyLoadedProviders: shouldSchedule=false")
return
}
@@ -528,12 +477,7 @@
synchronized(availableClocks) {
isQueued.set(false)
if (keepAllLoaded) {
- logger.tryLog(
- TAG,
- LogLevel.INFO,
- {},
- { "verifyLoadedProviders: keepAllLoaded=true" }
- )
+ logger.i("verifyLoadedProviders: keepAllLoaded=true")
// Enforce that all plugins are loaded if requested
for ((_, info) in availableClocks) {
info.manager?.loadPlugin()
@@ -543,12 +487,7 @@
val currentClock = availableClocks[currentClockId]
if (currentClock == null) {
- logger.tryLog(
- TAG,
- LogLevel.INFO,
- {},
- { "verifyLoadedProviders: currentClock=null" }
- )
+ logger.i("verifyLoadedProviders: currentClock=null")
// Current Clock missing, load no plugins and use default
for ((_, info) in availableClocks) {
info.manager?.unloadPlugin()
@@ -556,12 +495,7 @@
return@launch
}
- logger.tryLog(
- TAG,
- LogLevel.INFO,
- {},
- { "verifyLoadedProviders: load currentClock" }
- )
+ logger.i("verifyLoadedProviders: load currentClock")
val currentManager = currentClock.manager
currentManager?.loadPlugin()
@@ -577,30 +511,26 @@
private fun onConnected(info: ClockInfo) {
val isCurrent = currentClockId == info.metadata.clockId
- logger.tryLog(
- TAG,
+ logger.log(
if (isCurrent) LogLevel.INFO else LogLevel.DEBUG,
- {
- str1 = info.metadata.clockId
- str2 = info.manager.toString()
- bool1 = isCurrent
- },
{ "Connected $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
- )
+ ) {
+ str1 = info.metadata.clockId
+ str2 = info.manager.toString()
+ bool1 = isCurrent
+ }
}
private fun onLoaded(info: ClockInfo) {
val isCurrent = currentClockId == info.metadata.clockId
- logger.tryLog(
- TAG,
+ logger.log(
if (isCurrent) LogLevel.INFO else LogLevel.DEBUG,
- {
- str1 = info.metadata.clockId
- str2 = info.manager.toString()
- bool1 = isCurrent
- },
{ "Loaded $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
- )
+ ) {
+ str1 = info.metadata.clockId
+ str2 = info.manager.toString()
+ bool1 = isCurrent
+ }
if (isCurrent) {
triggerOnCurrentClockChanged()
@@ -609,16 +539,14 @@
private fun onUnloaded(info: ClockInfo) {
val isCurrent = currentClockId == info.metadata.clockId
- logger.tryLog(
- TAG,
+ logger.log(
if (isCurrent) LogLevel.WARNING else LogLevel.DEBUG,
- {
- str1 = info.metadata.clockId
- str2 = info.manager.toString()
- bool1 = isCurrent
- },
{ "Unloaded $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
- )
+ ) {
+ str1 = info.metadata.clockId
+ str2 = info.manager.toString()
+ bool1 = isCurrent
+ }
if (isCurrent) {
triggerOnCurrentClockChanged()
@@ -627,16 +555,14 @@
private fun onDisconnected(info: ClockInfo) {
val isCurrent = currentClockId == info.metadata.clockId
- logger.tryLog(
- TAG,
+ logger.log(
if (isCurrent) LogLevel.INFO else LogLevel.DEBUG,
- {
- str1 = info.metadata.clockId
- str2 = info.manager.toString()
- bool1 = isCurrent
- },
{ "Disconnected $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
- )
+ ) {
+ str1 = info.metadata.clockId
+ str2 = info.manager.toString()
+ bool1 = isCurrent
+ }
}
fun getClocks(): List<ClockMetadata> {
@@ -676,23 +602,13 @@
if (isEnabled && clockId.isNotEmpty()) {
val clock = createClock(clockId)
if (clock != null) {
- logger.tryLog(TAG, LogLevel.INFO, { str1 = clockId }, { "Rendering clock $str1" })
+ logger.i({ "Rendering clock $str1" }) { str1 = clockId }
return clock
} else if (availableClocks.containsKey(clockId)) {
- logger.tryLog(
- TAG,
- LogLevel.WARNING,
- { str1 = clockId },
- { "Clock $str1 not loaded; using default" }
- )
+ logger.w({ "Clock $str1 not loaded; using default" }) { str1 = clockId }
verifyLoadedProviders()
} else {
- logger.tryLog(
- TAG,
- LogLevel.ERROR,
- { str1 = clockId },
- { "Clock $str1 not found; using default" }
- )
+ logger.e({ "Clock $str1 not found; using default" }) { str1 = clockId }
}
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index 01c03b1..99d3216 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -33,6 +33,7 @@
import com.android.systemui.plugins.clocks.ClockFaceConfig
import com.android.systemui.plugins.clocks.ClockFaceController
import com.android.systemui.plugins.clocks.ClockFaceEvents
+import com.android.systemui.plugins.clocks.ClockMessageBuffers
import com.android.systemui.plugins.clocks.ClockSettings
import com.android.systemui.plugins.clocks.DefaultClockFaceLayout
import com.android.systemui.plugins.clocks.WeatherData
@@ -41,8 +42,6 @@
import java.util.Locale
import java.util.TimeZone
-private val TAG = DefaultClockController::class.simpleName
-
/**
* Controls the default clock visuals.
*
@@ -56,6 +55,7 @@
private val settings: ClockSettings?,
private val hasStepClockAnimation: Boolean = false,
private val migratedClocks: Boolean = false,
+ messageBuffers: ClockMessageBuffers? = null,
) : ClockController {
override val smallClock: DefaultClockFaceController
override val largeClock: LargeClockFaceController
@@ -83,13 +83,15 @@
DefaultClockFaceController(
layoutInflater.inflate(R.layout.clock_default_small, parent, false)
as AnimatableClockView,
- settings?.seedColor
+ settings?.seedColor,
+ messageBuffers?.smallClockMessageBuffer
)
largeClock =
LargeClockFaceController(
layoutInflater.inflate(R.layout.clock_default_large, parent, false)
as AnimatableClockView,
- settings?.seedColor
+ settings?.seedColor,
+ messageBuffers?.largeClockMessageBuffer
)
clocks = listOf(smallClock.view, largeClock.view)
@@ -110,6 +112,7 @@
open inner class DefaultClockFaceController(
override val view: AnimatableClockView,
var seedColor: Int?,
+ messageBuffer: MessageBuffer?,
) : ClockFaceController {
// MAGENTA is a placeholder, and will be assigned correctly in initialize
@@ -120,12 +123,6 @@
override val config = ClockFaceConfig()
override val layout = DefaultClockFaceLayout(view)
- override var messageBuffer: MessageBuffer?
- get() = view.messageBuffer
- set(value) {
- view.messageBuffer = value
- }
-
override var animations: DefaultClockAnimations = DefaultClockAnimations(view, 0f, 0f)
internal set
@@ -134,6 +131,7 @@
currentColor = seedColor!!
}
view.setColors(DOZE_COLOR, currentColor)
+ messageBuffer?.let { view.messageBuffer = it }
}
override val events =
@@ -188,7 +186,8 @@
inner class LargeClockFaceController(
view: AnimatableClockView,
seedColor: Int?,
- ) : DefaultClockFaceController(view, seedColor) {
+ messageBuffer: MessageBuffer?,
+ ) : DefaultClockFaceController(view, seedColor, messageBuffer) {
override val layout = DefaultClockFaceLayout(view)
override val config =
ClockFaceConfig(hasCustomPositionUpdatedAnimation = hasStepClockAnimation)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index a219be5..20f87a0 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -20,6 +20,7 @@
import com.android.systemui.customization.R
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockId
+import com.android.systemui.plugins.clocks.ClockMessageBuffers
import com.android.systemui.plugins.clocks.ClockMetadata
import com.android.systemui.plugins.clocks.ClockProvider
import com.android.systemui.plugins.clocks.ClockSettings
@@ -35,6 +36,12 @@
val hasStepClockAnimation: Boolean = false,
val migratedClocks: Boolean = false
) : ClockProvider {
+ private var messageBuffers: ClockMessageBuffers? = null
+
+ override fun initialize(buffers: ClockMessageBuffers?) {
+ messageBuffers = buffers
+ }
+
override fun getClocks(): List<ClockMetadata> = listOf(ClockMetadata(DEFAULT_CLOCK_ID))
override fun createClock(settings: ClockSettings): ClockController {
@@ -49,6 +56,7 @@
settings,
hasStepClockAnimation,
migratedClocks,
+ messageBuffers,
)
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
index 0b7c3f9..26da1f0 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
@@ -17,56 +17,39 @@
package com.android.systemui.shared.notifications.data.repository
import android.provider.Settings
-import com.android.systemui.shared.notifications.shared.model.NotificationSettingsModel
import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
-/** Provides access to state related to notifications. */
+/** Provides access to state related to notification settings. */
class NotificationSettingsRepository(
scope: CoroutineScope,
private val backgroundDispatcher: CoroutineDispatcher,
private val secureSettingsRepository: SecureSettingsRepository,
) {
/** The current state of the notification setting. */
- val settings: SharedFlow<NotificationSettingsModel> =
+ val isShowNotificationsOnLockScreenEnabled: StateFlow<Boolean> =
secureSettingsRepository
.intSetting(
name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
)
- .map { lockScreenShowNotificationsInt ->
- NotificationSettingsModel(
- isShowNotificationsOnLockScreenEnabled = lockScreenShowNotificationsInt == 1,
- )
- }
- .shareIn(
+ .map { it == 1 }
+ .stateIn(
scope = scope,
started = SharingStarted.WhileSubscribed(),
- replay = 1,
+ initialValue = false,
)
- suspend fun getSettings(): NotificationSettingsModel {
- return withContext(backgroundDispatcher) {
- NotificationSettingsModel(
- isShowNotificationsOnLockScreenEnabled =
- secureSettingsRepository.get(
- name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
- defaultValue = 0,
- ) == 1
- )
- }
- }
-
- suspend fun setSettings(model: NotificationSettingsModel) {
+ suspend fun setShowNotificationsOnLockscreenEnabled(enabled: Boolean) {
withContext(backgroundDispatcher) {
secureSettingsRepository.set(
name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
- value = if (model.isShowNotificationsOnLockScreenEnabled) 1 else 0,
+ value = if (enabled) 1 else 0,
)
}
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt
index 21f3aca..9ec6ec8 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt
@@ -17,32 +17,23 @@
package com.android.systemui.shared.notifications.domain.interactor
import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
-import com.android.systemui.shared.notifications.shared.model.NotificationSettingsModel
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
/** Encapsulates business logic for interacting with notification settings. */
class NotificationSettingsInteractor(
private val repository: NotificationSettingsRepository,
) {
- /** The current state of the notification setting. */
- val settings: Flow<NotificationSettingsModel> = repository.settings
+ /** Should notifications be visible on the lockscreen? */
+ val isShowNotificationsOnLockScreenEnabled: StateFlow<Boolean> =
+ repository.isShowNotificationsOnLockScreenEnabled
+
+ suspend fun setShowNotificationsOnLockscreenEnabled(enabled: Boolean) {
+ repository.setShowNotificationsOnLockscreenEnabled(enabled)
+ }
/** Toggles the setting to show or hide notifications on the lock screen. */
- suspend fun toggleShowNotificationsOnLockScreenEnabled() {
- val currentModel = repository.getSettings()
- setSettings(
- currentModel.copy(
- isShowNotificationsOnLockScreenEnabled =
- !currentModel.isShowNotificationsOnLockScreenEnabled,
- )
- )
- }
-
- suspend fun setSettings(model: NotificationSettingsModel) {
- repository.setSettings(model)
- }
-
- suspend fun getSettings(): NotificationSettingsModel {
- return repository.getSettings()
+ suspend fun toggleShowNotificationsOnLockscreenEnabled() {
+ val current = repository.isShowNotificationsOnLockScreenEnabled.value
+ repository.setShowNotificationsOnLockscreenEnabled(!current)
}
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/shared/model/NotificationSettingsModel.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/shared/model/NotificationSettingsModel.kt
deleted file mode 100644
index 7e35360..0000000
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/shared/model/NotificationSettingsModel.kt
+++ /dev/null
@@ -1,24 +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.shared.notifications.shared.model
-
-/** Models notification settings. */
-data class NotificationSettingsModel(
- /** Whether notifications are shown on the lock screen. */
- val isShowNotificationsOnLockScreenEnabled: Boolean = false,
-)
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/core/LogcatOnlyMessageBuffer.kt b/packages/SystemUI/log/src/com/android/systemui/log/core/LogcatOnlyMessageBuffer.kt
new file mode 100644
index 0000000..006b521
--- /dev/null
+++ b/packages/SystemUI/log/src/com/android/systemui/log/core/LogcatOnlyMessageBuffer.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.core
+
+import android.util.Log
+import com.android.systemui.log.LogMessageImpl
+
+/**
+ * A simple implementation of [MessageBuffer] that forwards messages to [android.util.Log]
+ * immediately. This defeats the intention behind [LogBuffer] and should only be used when
+ * [LogBuffer]s are unavailable in a certain context.
+ */
+class LogcatOnlyMessageBuffer(
+ val targetLogLevel: LogLevel,
+) : MessageBuffer {
+ private val singleMessage = LogMessageImpl.Factory.create()
+ private var isObtained: Boolean = false
+
+ @Synchronized
+ override fun obtain(
+ tag: String,
+ level: LogLevel,
+ messagePrinter: MessagePrinter,
+ exception: Throwable?,
+ ): LogMessage {
+ if (isObtained) {
+ throw UnsupportedOperationException(
+ "Message has already been obtained. Call order is incorrect."
+ )
+ }
+
+ singleMessage.reset(tag, level, System.currentTimeMillis(), messagePrinter, exception)
+ isObtained = true
+ return singleMessage
+ }
+
+ @Synchronized
+ override fun commit(message: LogMessage) {
+ if (singleMessage != message) {
+ throw IllegalArgumentException("Message argument is not the expected message.")
+ }
+ if (!isObtained) {
+ throw UnsupportedOperationException(
+ "Message has not been obtained. Call order is incorrect."
+ )
+ }
+
+ if (message.level >= targetLogLevel) {
+ val strMessage = message.messagePrinter(message)
+ when (message.level) {
+ LogLevel.VERBOSE -> Log.v(message.tag, strMessage, message.exception)
+ LogLevel.DEBUG -> Log.d(message.tag, strMessage, message.exception)
+ LogLevel.INFO -> Log.i(message.tag, strMessage, message.exception)
+ LogLevel.WARNING -> Log.w(message.tag, strMessage, message.exception)
+ LogLevel.ERROR -> Log.e(message.tag, strMessage, message.exception)
+ LogLevel.WTF -> Log.wtf(message.tag, strMessage, message.exception)
+ }
+ }
+
+ isObtained = false
+ }
+}
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 64ddbc7..c961be9 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
@@ -35,8 +35,10 @@
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.function.Function
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runCurrent
@@ -58,6 +60,7 @@
private val testUtils = SceneTestUtils(this)
private val testScope = testUtils.testScope
+ private val clock = FakeSystemClock()
private val userRepository = FakeUserRepository()
private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository
@@ -78,8 +81,10 @@
underTest =
AuthenticationRepositoryImpl(
applicationScope = testScope.backgroundScope,
- getSecurityMode = getSecurityMode,
backgroundDispatcher = testUtils.testDispatcher,
+ flags = testUtils.sceneContainerFlags,
+ clock = clock,
+ getSecurityMode = getSecurityMode,
userRepository = userRepository,
lockPatternUtils = lockPatternUtils,
broadcastDispatcher = fakeBroadcastDispatcher,
@@ -141,22 +146,6 @@
}
@Test
- fun reportAuthenticationAttempt_emitsAuthenticationChallengeResult() =
- testScope.runTest {
- val authenticationChallengeResults by
- collectValues(underTest.authenticationChallengeResult)
-
- runCurrent()
- underTest.reportAuthenticationAttempt(true)
- runCurrent()
- underTest.reportAuthenticationAttempt(false)
- runCurrent()
- underTest.reportAuthenticationAttempt(true)
-
- assertThat(authenticationChallengeResults).isEqualTo(listOf(true, false, true))
- }
-
- @Test
fun isPinEnhancedPrivacyEnabled() =
testScope.runTest {
whenever(lockPatternUtils.isPinEnhancedPrivacyEnabled(USER_INFOS[0].id))
@@ -172,6 +161,45 @@
assertThat(values.last()).isTrue()
}
+ @Test
+ fun lockoutEndTimestamp() =
+ testScope.runTest {
+ val lockoutEndMs = clock.elapsedRealtime() + 30.seconds.inWholeMilliseconds
+ whenever(lockPatternUtils.getLockoutAttemptDeadline(USER_INFOS[0].id))
+ .thenReturn(lockoutEndMs)
+ whenever(lockPatternUtils.getLockoutAttemptDeadline(USER_INFOS[1].id)).thenReturn(0)
+
+ // Switch to a user who is not locked-out.
+ userRepository.setSelectedUserInfo(USER_INFOS[1])
+ assertThat(underTest.lockoutEndTimestamp).isNull()
+
+ // Switch back to the locked-out user, verify the timestamp is up-to-date.
+ userRepository.setSelectedUserInfo(USER_INFOS[0])
+ assertThat(underTest.lockoutEndTimestamp).isEqualTo(lockoutEndMs)
+
+ // After the lockout expires, null is returned.
+ clock.setElapsedRealtime(lockoutEndMs)
+ assertThat(underTest.lockoutEndTimestamp).isNull()
+ }
+
+ @Test
+ fun hasLockoutOccurred() =
+ testScope.runTest {
+ val hasLockoutOccurred by collectLastValue(underTest.hasLockoutOccurred)
+ assertThat(hasLockoutOccurred).isFalse()
+
+ underTest.reportLockoutStarted(1000)
+ assertThat(hasLockoutOccurred).isTrue()
+
+ clock.setElapsedRealtime(clock.elapsedRealtime() + 60.seconds.inWholeMilliseconds)
+
+ underTest.reportAuthenticationAttempt(isSuccessful = false)
+ assertThat(hasLockoutOccurred).isTrue()
+
+ underTest.reportAuthenticationAttempt(isSuccessful = true)
+ assertThat(hasLockoutOccurred).isFalse()
+ }
+
private fun setSecurityModeAndDispatchBroadcast(
securityMode: KeyguardSecurityModel.SecurityMode,
) {
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 08cd7ed..c113b37 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
@@ -21,14 +21,18 @@
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.AuthenticationLockoutModel
-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.AuthenticationPatternCoordinate
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.scene.SceneTestUtils
import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.currentTime
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -43,21 +47,23 @@
private val testScope = utils.testScope
private val underTest = utils.authenticationInteractor()
+ private val onAuthenticationResult by
+ testScope.collectLastValue(underTest.onAuthenticationResult)
+ private val failedAuthenticationAttempts by
+ testScope.collectLastValue(underTest.failedAuthenticationAttempts)
+
@Test
fun authenticationMethod() =
testScope.runTest {
val authMethod by collectLastValue(underTest.authenticationMethod)
runCurrent()
- assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Pin)
- assertThat(underTest.getAuthenticationMethod()).isEqualTo(AuthenticationMethodModel.Pin)
+ assertThat(authMethod).isEqualTo(Pin)
+ assertThat(underTest.getAuthenticationMethod()).isEqualTo(Pin)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password
- )
+ utils.authenticationRepository.setAuthenticationMethod(Password)
- assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Password)
- assertThat(underTest.getAuthenticationMethod())
- .isEqualTo(AuthenticationMethodModel.Password)
+ assertThat(authMethod).isEqualTo(Password)
+ assertThat(underTest.getAuthenticationMethod()).isEqualTo(Password)
}
@Test
@@ -66,51 +72,45 @@
val authMethod by collectLastValue(underTest.authenticationMethod)
runCurrent()
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ utils.authenticationRepository.setAuthenticationMethod(None)
- assertThat(authMethod).isEqualTo(AuthenticationMethodModel.None)
- assertThat(underTest.getAuthenticationMethod())
- .isEqualTo(AuthenticationMethodModel.None)
+ assertThat(authMethod).isEqualTo(None)
+ assertThat(underTest.getAuthenticationMethod()).isEqualTo(None)
}
@Test
fun authenticate_withCorrectPin_succeeds() =
testScope.runTest {
- val lockout by collectLastValue(underTest.lockout)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.setAuthenticationMethod(Pin)
- assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
- .isEqualTo(AuthenticationResult.SUCCEEDED)
- assertThat(lockout).isNull()
- assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0)
+ assertSucceeded(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
}
@Test
fun authenticate_withIncorrectPin_fails() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.setAuthenticationMethod(Pin)
- assertThat(underTest.authenticate(listOf(9, 8, 7, 6, 5, 4)))
- .isEqualTo(AuthenticationResult.FAILED)
+ assertFailed(underTest.authenticate(listOf(9, 8, 7, 6, 5, 4)))
}
@Test(expected = IllegalArgumentException::class)
fun authenticate_withEmptyPin_throwsException() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.setAuthenticationMethod(Pin)
underTest.authenticate(listOf())
}
@Test
fun authenticate_withCorrectMaxLengthPin_succeeds() =
testScope.runTest {
- val pin = List(16) { 9 }
+ val correctMaxLengthPin = List(16) { 9 }
utils.authenticationRepository.apply {
- setAuthenticationMethod(AuthenticationMethodModel.Pin)
- overrideCredential(pin)
+ setAuthenticationMethod(Pin)
+ overrideCredential(correctMaxLengthPin)
}
- assertThat(underTest.authenticate(pin)).isEqualTo(AuthenticationResult.SUCCEEDED)
+ assertSucceeded(underTest.authenticate(correctMaxLengthPin))
}
@Test
@@ -122,88 +122,64 @@
// If the policy changes, there is work to do in SysUI.
assertThat(DevicePolicyManager.MAX_PASSWORD_LENGTH).isLessThan(17)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- assertThat(underTest.authenticate(List(17) { 9 }))
- .isEqualTo(AuthenticationResult.FAILED)
+ utils.authenticationRepository.setAuthenticationMethod(Pin)
+
+ assertFailed(underTest.authenticate(List(17) { 9 }))
}
@Test
fun authenticate_withCorrectPassword_succeeds() =
testScope.runTest {
- val lockout by collectLastValue(underTest.lockout)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password
- )
+ utils.authenticationRepository.setAuthenticationMethod(Password)
- assertThat(underTest.authenticate("password".toList()))
- .isEqualTo(AuthenticationResult.SUCCEEDED)
- assertThat(lockout).isNull()
- assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0)
+ assertSucceeded(underTest.authenticate("password".toList()))
}
@Test
fun authenticate_withIncorrectPassword_fails() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password
- )
+ utils.authenticationRepository.setAuthenticationMethod(Password)
- assertThat(underTest.authenticate("alohomora".toList()))
- .isEqualTo(AuthenticationResult.FAILED)
+ assertFailed(underTest.authenticate("alohomora".toList()))
}
@Test
fun authenticate_withCorrectPattern_succeeds() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pattern
- )
+ utils.authenticationRepository.setAuthenticationMethod(Pattern)
- assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN))
- .isEqualTo(AuthenticationResult.SUCCEEDED)
+ assertSucceeded(underTest.authenticate(FakeAuthenticationRepository.PATTERN))
}
@Test
fun authenticate_withIncorrectPattern_fails() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pattern
- )
-
- assertThat(
- underTest.authenticate(
- listOf(
- AuthenticationPatternCoordinate(x = 2, y = 0),
- AuthenticationPatternCoordinate(x = 2, y = 1),
- AuthenticationPatternCoordinate(x = 2, y = 2),
- AuthenticationPatternCoordinate(x = 1, y = 2),
- )
- )
+ utils.authenticationRepository.setAuthenticationMethod(Pattern)
+ val wrongPattern =
+ listOf(
+ AuthenticationPatternCoordinate(x = 2, y = 0),
+ AuthenticationPatternCoordinate(x = 2, y = 1),
+ AuthenticationPatternCoordinate(x = 2, y = 2),
+ AuthenticationPatternCoordinate(x = 1, y = 2),
)
- .isEqualTo(AuthenticationResult.FAILED)
+
+ assertFailed(underTest.authenticate(wrongPattern))
}
@Test
fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNull() =
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
- val lockout by collectLastValue(underTest.lockout)
utils.authenticationRepository.apply {
- setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(true)
}
assertThat(isAutoConfirmEnabled).isTrue()
+ val shorterPin =
+ FakeAuthenticationRepository.DEFAULT_PIN.toMutableList().apply { removeLast() }
- assertThat(
- underTest.authenticate(
- FakeAuthenticationRepository.DEFAULT_PIN.toMutableList().apply {
- removeLast()
- },
- tryAutoConfirm = true
- )
- )
- .isEqualTo(AuthenticationResult.SKIPPED)
- assertThat(lockout).isNull()
+ assertSkipped(underTest.authenticate(shorterPin, tryAutoConfirm = true))
+ assertThat(underTest.lockoutEndTimestamp).isNull()
assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0)
}
@@ -212,18 +188,17 @@
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
utils.authenticationRepository.apply {
- setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(true)
}
assertThat(isAutoConfirmEnabled).isTrue()
- assertThat(
- underTest.authenticate(
- FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 },
- tryAutoConfirm = true
- )
- )
- .isEqualTo(AuthenticationResult.FAILED)
+ val wrongPin = FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 }
+
+ assertFailed(
+ underTest.authenticate(wrongPin, tryAutoConfirm = true),
+ assertNoResultEvents = true,
+ )
}
@Test
@@ -231,18 +206,17 @@
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
utils.authenticationRepository.apply {
- setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(true)
}
assertThat(isAutoConfirmEnabled).isTrue()
- assertThat(
- underTest.authenticate(
- FakeAuthenticationRepository.DEFAULT_PIN + listOf(7),
- tryAutoConfirm = true
- )
- )
- .isEqualTo(AuthenticationResult.FAILED)
+ val longerPin = FakeAuthenticationRepository.DEFAULT_PIN + listOf(7)
+
+ assertFailed(
+ underTest.authenticate(longerPin, tryAutoConfirm = true),
+ assertNoResultEvents = true,
+ )
}
@Test
@@ -250,69 +224,54 @@
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
utils.authenticationRepository.apply {
- setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(true)
}
assertThat(isAutoConfirmEnabled).isTrue()
- assertThat(
- underTest.authenticate(
- FakeAuthenticationRepository.DEFAULT_PIN,
- tryAutoConfirm = true
- )
- )
- .isEqualTo(AuthenticationResult.SUCCEEDED)
+ val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
+
+ assertSucceeded(underTest.authenticate(correctPin, tryAutoConfirm = true))
}
@Test
fun tryAutoConfirm_withAutoConfirmCorrectPinButDuringLockout_returnsNull() =
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
- val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked)
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
utils.authenticationRepository.apply {
- setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(true)
- setLockoutDuration(42)
+ reportLockoutStarted(42)
}
- val authResult =
- underTest.authenticate(
- FakeAuthenticationRepository.DEFAULT_PIN,
- tryAutoConfirm = true
- )
+ val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
- assertThat(authResult).isEqualTo(AuthenticationResult.SKIPPED)
+ assertSkipped(underTest.authenticate(correctPin, tryAutoConfirm = true))
assertThat(isAutoConfirmEnabled).isFalse()
- assertThat(isUnlocked).isFalse()
assertThat(hintedPinLength).isNull()
+ assertThat(underTest.lockoutEndTimestamp).isNotNull()
}
@Test
fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNull() =
testScope.runTest {
utils.authenticationRepository.apply {
- setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(false)
}
- assertThat(
- underTest.authenticate(
- FakeAuthenticationRepository.DEFAULT_PIN,
- tryAutoConfirm = true
- )
- )
- .isEqualTo(AuthenticationResult.SKIPPED)
+
+ val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
+
+ assertSkipped(underTest.authenticate(correctPin, tryAutoConfirm = true))
}
@Test
fun tryAutoConfirm_withoutCorrectPassword_returnsNull() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password
- )
+ utils.authenticationRepository.setAuthenticationMethod(Password)
- assertThat(underTest.authenticate("password".toList(), tryAutoConfirm = true))
- .isEqualTo(AuthenticationResult.SKIPPED)
+ assertSkipped(underTest.authenticate("password".toList(), tryAutoConfirm = true))
}
@Test
@@ -337,7 +296,6 @@
fun isAutoConfirmEnabled_featureEnabledButDisabledByLockout() =
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
- val lockout by collectLastValue(underTest.lockout)
utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
// The feature is enabled.
@@ -345,92 +303,104 @@
// Make many wrong attempts to trigger lockout.
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
- underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
+ assertFailed(underTest.authenticate(listOf(5, 6, 7))) // Wrong PIN
}
- assertThat(lockout).isNotNull()
+ assertThat(underTest.lockoutEndTimestamp).isNotNull()
assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1)
// Lockout disabled auto-confirm.
assertThat(isAutoConfirmEnabled).isFalse()
// Move the clock forward one more second, to completely finish the lockout period:
- advanceTimeBy(FakeAuthenticationRepository.LOCKOUT_DURATION_MS + 1000L)
- assertThat(lockout).isNull()
+ advanceTimeBy(
+ FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS.seconds.plus(1.seconds)
+ )
+ assertThat(underTest.lockoutEndTimestamp).isNull()
// Auto-confirm is still disabled, because lockout occurred at least once in this
// session.
assertThat(isAutoConfirmEnabled).isFalse()
// Correct PIN and unlocks successfully, resetting the 'session'.
- assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
- .isEqualTo(AuthenticationResult.SUCCEEDED)
+ assertSucceeded(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
// Auto-confirm is re-enabled.
assertThat(isAutoConfirmEnabled).isTrue()
-
- assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1)
}
@Test
- fun lockout() =
+ fun failedAuthenticationAttempts() =
testScope.runTest {
- val lockout by collectLastValue(underTest.lockout)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)
- assertThat(lockout).isNull()
+ val failedAuthenticationAttempts by
+ collectLastValue(underTest.failedAuthenticationAttempts)
+
+ utils.authenticationRepository.setAuthenticationMethod(Pin)
+ val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
+
+ assertSucceeded(underTest.authenticate(correctPin))
+ assertThat(failedAuthenticationAttempts).isEqualTo(0)
+
+ // Make many wrong attempts, leading to lockout:
+ repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { index ->
+ underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
+ assertThat(failedAuthenticationAttempts).isEqualTo(index + 1)
+ }
+
+ // Correct PIN, but locked out, so doesn't attempt it:
+ assertSkipped(underTest.authenticate(correctPin), assertNoResultEvents = false)
+ assertThat(failedAuthenticationAttempts)
+ .isEqualTo(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT)
+
+ // Move the clock forward to finish the lockout period:
+ advanceTimeBy(FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS.seconds)
+ assertThat(failedAuthenticationAttempts)
+ .isEqualTo(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT)
+
+ // Correct PIN and no longer locked out so unlocks successfully:
+ assertSucceeded(underTest.authenticate(correctPin))
+ assertThat(failedAuthenticationAttempts).isEqualTo(0)
+ }
+
+ @Test
+ fun lockoutEndTimestamp() =
+ testScope.runTest {
+ utils.authenticationRepository.setAuthenticationMethod(Pin)
+ val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
+
+ underTest.authenticate(correctPin)
+ assertThat(underTest.lockoutEndTimestamp).isNull()
// Make many wrong attempts, but just shy of what's needed to get locked out:
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
- assertThat(lockout).isNull()
+ assertThat(underTest.lockoutEndTimestamp).isNull()
}
// Make one more wrong attempt, leading to lockout:
underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
- assertThat(lockout)
- .isEqualTo(
- AuthenticationLockoutModel(
- failedAttemptCount =
- FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT,
- remainingSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS,
- )
- )
+
+ val expectedLockoutEndTimestamp =
+ testScope.currentTime + FakeAuthenticationRepository.LOCKOUT_DURATION_MS
+ assertThat(underTest.lockoutEndTimestamp).isEqualTo(expectedLockoutEndTimestamp)
assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1)
// Correct PIN, but locked out, so doesn't attempt it:
- assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
- .isEqualTo(AuthenticationResult.SKIPPED)
- assertThat(lockout)
- .isEqualTo(
- AuthenticationLockoutModel(
- failedAttemptCount =
- FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT,
- remainingSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS,
- )
- )
+ assertSkipped(underTest.authenticate(correctPin), assertNoResultEvents = false)
+ assertThat(underTest.lockoutEndTimestamp).isEqualTo(expectedLockoutEndTimestamp)
// Move the clock forward to ALMOST skip the lockout, leaving one second to go:
- val lockoutTimeoutSec = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS
- repeat(FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS - 1) { time ->
- advanceTimeBy(1000)
- assertThat(lockout)
- .isEqualTo(
- AuthenticationLockoutModel(
- failedAttemptCount =
- FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT,
- remainingSeconds = lockoutTimeoutSec - (time + 1),
- )
- )
+ repeat(FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS - 1) {
+ advanceTimeBy(1.seconds)
+ assertThat(underTest.lockoutEndTimestamp).isEqualTo(expectedLockoutEndTimestamp)
}
// Move the clock forward one more second, to completely finish the lockout period:
- advanceTimeBy(1000)
- assertThat(lockout).isNull()
+ advanceTimeBy(1.seconds)
+ assertThat(underTest.lockoutEndTimestamp).isNull()
// Correct PIN and no longer locked out so unlocks successfully:
- assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
- .isEqualTo(AuthenticationResult.SUCCEEDED)
- assertThat(lockout).isNull()
+ assertSucceeded(underTest.authenticate(correctPin))
+ assertThat(underTest.lockoutEndTimestamp).isNull()
}
@Test
@@ -438,7 +408,7 @@
testScope.runTest {
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
utils.authenticationRepository.apply {
- setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(false)
}
@@ -450,7 +420,7 @@
testScope.runTest {
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
utils.authenticationRepository.apply {
- setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ setAuthenticationMethod(Pin)
overrideCredential(
buildList {
repeat(utils.authenticationRepository.hintedPinLength - 1) { add(it + 1) }
@@ -467,7 +437,7 @@
testScope.runTest {
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
utils.authenticationRepository.apply {
- setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(true)
overrideCredential(
buildList {
@@ -484,7 +454,7 @@
testScope.runTest {
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
utils.authenticationRepository.apply {
- setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ setAuthenticationMethod(Pin)
overrideCredential(
buildList {
repeat(utils.authenticationRepository.hintedPinLength + 1) { add(it + 1) }
@@ -499,18 +469,45 @@
@Test
fun authenticate_withTooShortPassword() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password
- )
- assertThat(
- underTest.authenticate(
- buildList {
- repeat(utils.authenticationRepository.minPasswordLength - 1) { time ->
- add("$time")
- }
- }
- )
- )
- .isEqualTo(AuthenticationResult.SKIPPED)
+ utils.authenticationRepository.setAuthenticationMethod(Password)
+
+ val tooShortPassword = buildList {
+ repeat(utils.authenticationRepository.minPasswordLength - 1) { time ->
+ add("$time")
+ }
+ }
+ assertSkipped(underTest.authenticate(tooShortPassword))
}
+
+ private fun assertSucceeded(authenticationResult: AuthenticationResult) {
+ assertThat(authenticationResult).isEqualTo(AuthenticationResult.SUCCEEDED)
+ assertThat(onAuthenticationResult).isTrue()
+ assertThat(underTest.lockoutEndTimestamp).isNull()
+ assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0)
+ assertThat(failedAuthenticationAttempts).isEqualTo(0)
+ }
+
+ private fun assertFailed(
+ authenticationResult: AuthenticationResult,
+ assertNoResultEvents: Boolean = false,
+ ) {
+ assertThat(authenticationResult).isEqualTo(AuthenticationResult.FAILED)
+ if (assertNoResultEvents) {
+ assertThat(onAuthenticationResult).isNull()
+ } else {
+ assertThat(onAuthenticationResult).isFalse()
+ }
+ }
+
+ private fun assertSkipped(
+ authenticationResult: AuthenticationResult,
+ assertNoResultEvents: Boolean = true,
+ ) {
+ assertThat(authenticationResult).isEqualTo(AuthenticationResult.SKIPPED)
+ if (assertNoResultEvents) {
+ assertThat(onAuthenticationResult).isNull()
+ } else {
+ assertThat(onAuthenticationResult).isNotNull()
+ }
+ }
}
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 a726b7c..b0beab9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -47,6 +47,7 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
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.LockscreenShadeTransitionController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.phone.SystemUIDialogManager
@@ -54,7 +55,6 @@
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
-import com.android.systemui.util.settings.SecureSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.Before
@@ -101,7 +101,6 @@
@Mock
private lateinit var unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController
@Mock private lateinit var udfpsDisplayMode: UdfpsDisplayModeProvider
- @Mock private lateinit var secureSettings: SecureSettings
@Mock private lateinit var controllerCallback: IUdfpsOverlayControllerCallback
@Mock private lateinit var udfpsController: UdfpsController
@Mock private lateinit var udfpsView: UdfpsView
@@ -117,6 +116,7 @@
@Mock
private lateinit var udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate
@Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+ @Mock private lateinit var shadeInteractor: ShadeInteractor
@Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true }
@@ -174,6 +174,7 @@
mSelectedUserInteractor,
{ deviceEntryUdfpsTouchOverlayViewModel },
{ defaultUdfpsTouchOverlayViewModel },
+ shadeInteractor
)
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 dddcf18..e5da1f8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -90,11 +90,11 @@
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.FalsingManager;
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.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -124,8 +124,6 @@
import java.util.ArrayList;
import java.util.List;
-import javax.inject.Provider;
-
@SmallTest
@RunWith(AndroidJUnit4.class)
@RunWithLooper(setAsMainLooper = true)
@@ -208,6 +206,8 @@
@Mock
private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
@Mock
+ private ShadeInteractor mShadeInteractor;
+ @Mock
private SinglePointerTouchProcessor mSinglePointerTouchProcessor;
@Mock
private SessionTracker mSessionTracker;
@@ -216,8 +216,6 @@
@Mock
private UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
@Mock
- private Provider<UdfpsKeyguardViewModels> mUdfpsKeyguardViewModels;
- @Mock
private SelectedUserInteractor mSelectedUserInteractor;
// Capture listeners so that they can be used to send events
@@ -333,13 +331,13 @@
mActivityLaunchAnimator,
mBiometricExecutor,
mPrimaryBouncerInteractor,
+ mShadeInteractor,
mSinglePointerTouchProcessor,
mSessionTracker,
mAlternateBouncerInteractor,
mInputManager,
mock(KeyguardFaceAuthInteractor.class),
mUdfpsKeyguardAccessibilityDelegate,
- mUdfpsKeyguardViewModels,
mSelectedUserInteractor,
mFpsUnlockTracker,
mKeyguardTransitionInteractor,
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 ac16c13..13b53a8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
@@ -36,6 +36,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
@@ -70,6 +71,7 @@
protected @Mock UdfpsController mUdfpsController;
protected @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor;
+ protected @Mock ShadeInteractor mShadeInteractor;
protected @Mock AlternateBouncerInteractor mAlternateBouncerInteractor;
protected @Mock UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
protected @Mock SelectedUserInteractor mSelectedUserInteractor;
@@ -149,7 +151,8 @@
mAlternateBouncerInteractor,
mUdfpsKeyguardAccessibilityDelegate,
mSelectedUserInteractor,
- mKeyguardTransitionInteractor);
+ mKeyguardTransitionInteractor,
+ mShadeInteractor);
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 0ab596c..1f8854f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
@@ -616,4 +616,28 @@
.onDozeAmountChanged(eq(.3f), eq(.3f), eq(ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN))
job.cancel()
}
+
+ @Test
+ fun bouncerToAod_dozeAmountChanged() =
+ testScope.runTest {
+ // GIVEN view is attached
+ mController.onViewAttached()
+ Mockito.reset(mView)
+
+ val job = mController.listenForPrimaryBouncerToAodTransitions(this)
+ // WHEN alternate bouncer to aod transition in progress
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.AOD,
+ value = .3f,
+ transitionState = TransitionState.RUNNING
+ )
+ )
+ runCurrent()
+
+ // THEN doze amount is updated to
+ verify(mView).onDozeAmountChanged(eq(.3f), eq(.3f), eq(ANIMATE_APPEAR_ON_SCREEN_OFF))
+ job.cancel()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
index 0870122..adf4fc6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
@@ -32,18 +32,15 @@
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.time.SystemClock
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
-import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class AlternateBouncerInteractorTest : SysuiTestCase() {
@@ -167,7 +164,6 @@
}
@Test
- @Ignore("b/287599719")
fun canShowAlternateBouncerForFingerprint_rearFps() {
mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
initializeUnderTest()
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 9b1df7c..99c1874 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
@@ -21,15 +21,15 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.domain.interactor.AuthenticationResult
-import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
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.keyguard.domain.interactor.KeyguardFaceAuthInteractor
import com.android.systemui.res.R
import com.android.systemui.scene.SceneTestUtils
import com.google.common.truth.Truth.assertThat
-import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
@@ -79,7 +79,7 @@
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
runCurrent()
underTest.clearMessage()
- assertThat(message).isEmpty()
+ assertThat(message).isNull()
underTest.resetMessage()
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
@@ -149,7 +149,7 @@
// Incomplete input.
assertThat(underTest.authenticate(listOf(1, 2), tryAutoConfirm = true))
.isEqualTo(AuthenticationResult.SKIPPED)
- assertThat(message).isEmpty()
+ assertThat(message).isNull()
// Correct input.
assertThat(
@@ -159,7 +159,7 @@
)
)
.isEqualTo(AuthenticationResult.SKIPPED)
- assertThat(message).isEmpty()
+ assertThat(message).isNull()
}
@Test
@@ -246,57 +246,40 @@
}
@Test
- fun lockout() =
+ fun lockoutStarted() =
testScope.runTest {
- val lockout by collectLastValue(underTest.lockout)
+ val lockoutStartedEvents by collectValues(underTest.onLockoutStarted)
val message by collectLastValue(underTest.message)
+
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- assertThat(lockout).isNull()
+ assertThat(lockoutStartedEvents).isEmpty()
+
+ // Try the wrong PIN repeatedly, until lockout is triggered:
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { times ->
// Wrong PIN.
assertThat(underTest.authenticate(listOf(6, 7, 8, 9)))
.isEqualTo(AuthenticationResult.FAILED)
if (times < FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
- assertThat(message).isEqualTo(MESSAGE_WRONG_PIN)
+ assertThat(lockoutStartedEvents).isEmpty()
+ assertThat(message).isNotEmpty()
}
}
- assertThat(lockout)
- .isEqualTo(
- AuthenticationLockoutModel(
- failedAttemptCount =
- FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT,
- remainingSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS,
- )
- )
- assertTryAgainMessage(
- message,
- FakeAuthenticationRepository.LOCKOUT_DURATION_MS.milliseconds.inWholeSeconds.toInt()
- )
+ assertThat(authenticationInteractor.lockoutEndTimestamp).isNotNull()
+ assertThat(lockoutStartedEvents.size).isEqualTo(1)
+ assertThat(message).isNull()
- // Correct PIN, but locked out, so doesn't change away from the bouncer scene:
- assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
- .isEqualTo(AuthenticationResult.SKIPPED)
- assertTryAgainMessage(
- message,
- FakeAuthenticationRepository.LOCKOUT_DURATION_MS.milliseconds.inWholeSeconds.toInt()
- )
+ // Advance the time to finish the lockout:
+ advanceTimeBy(FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS.seconds)
+ assertThat(authenticationInteractor.lockoutEndTimestamp).isNull()
+ assertThat(message).isNull()
+ assertThat(lockoutStartedEvents.size).isEqualTo(1)
- lockout?.remainingSeconds?.let { seconds ->
- repeat(seconds) { time ->
- advanceTimeBy(1000)
- val remainingTimeSec = seconds - time - 1
- if (remainingTimeSec > 0) {
- assertTryAgainMessage(message, remainingTimeSec)
- }
- }
+ // Trigger lockout again:
+ repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
+ // Wrong PIN.
+ underTest.authenticate(listOf(6, 7, 8, 9))
}
- assertThat(message).isEqualTo("")
- assertThat(lockout).isNull()
-
- // Correct PIN and no longer locked out so changes to the Gone scene:
- assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
- .isEqualTo(AuthenticationResult.SUCCEEDED)
- assertThat(lockout).isNull()
+ assertThat(lockoutStartedEvents.size).isEqualTo(2)
}
@Test
@@ -326,13 +309,6 @@
verify(keyguardFaceAuthInteractor).onPrimaryBouncerUserInput()
}
- private fun assertTryAgainMessage(
- message: String?,
- time: Int,
- ) {
- assertThat(message).isEqualTo("Try again in $time seconds.")
- }
-
companion object {
private const val MESSAGE_ENTER_YOUR_PIN = "Enter your PIN"
private const val MESSAGE_ENTER_YOUR_PASSWORD = "Enter your password"
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 16a9359..4be9b0a 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
@@ -33,6 +33,7 @@
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.currentTime
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -135,19 +136,47 @@
fun message() =
testScope.runTest {
val message by collectLastValue(underTest.message)
- val lockout by collectLastValue(bouncerInteractor.lockout)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
assertThat(message?.isUpdateAnimated).isTrue()
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
- // Wrong PIN.
- bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
+ bouncerInteractor.authenticate(WRONG_PIN)
}
assertThat(message?.isUpdateAnimated).isFalse()
- lockout?.remainingSeconds?.let { remainingSeconds ->
- advanceTimeBy(remainingSeconds.seconds.inWholeMilliseconds)
+ val lockoutEndMs = authenticationInteractor.lockoutEndTimestamp ?: 0
+ advanceTimeBy(lockoutEndMs - testScope.currentTime)
+ assertThat(message?.isUpdateAnimated).isTrue()
+ }
+
+ @Test
+ fun lockoutMessage() =
+ testScope.runTest {
+ val authMethodViewModel by collectLastValue(underTest.authMethodViewModel)
+ val message by collectLastValue(underTest.message)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ assertThat(utils.authenticationRepository.lockoutEndTimestamp).isNull()
+ assertThat(authMethodViewModel?.lockoutMessageId).isNotNull()
+
+ repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { times ->
+ bouncerInteractor.authenticate(WRONG_PIN)
+ if (times < FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
+ assertThat(message?.text).isEqualTo(bouncerInteractor.message.value)
+ assertThat(message?.isUpdateAnimated).isTrue()
+ }
}
+ val lockoutSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS
+ assertTryAgainMessage(message?.text, lockoutSeconds)
+ assertThat(message?.isUpdateAnimated).isFalse()
+
+ repeat(FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS) { time ->
+ advanceTimeBy(1.seconds)
+ val remainingSeconds = lockoutSeconds - time - 1
+ if (remainingSeconds > 0) {
+ assertTryAgainMessage(message?.text, remainingSeconds)
+ }
+ }
+ assertThat(message?.text).isEmpty()
assertThat(message?.isUpdateAnimated).isTrue()
}
@@ -160,32 +189,30 @@
authViewModel?.isInputEnabled ?: emptyFlow()
}
)
- val lockout by collectLastValue(bouncerInteractor.lockout)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
assertThat(isInputEnabled).isTrue()
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
- // Wrong PIN.
- bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
+ bouncerInteractor.authenticate(WRONG_PIN)
}
assertThat(isInputEnabled).isFalse()
- lockout?.remainingSeconds?.let { remainingSeconds ->
- advanceTimeBy(remainingSeconds.seconds.inWholeMilliseconds)
- }
+ val lockoutEndMs = authenticationInteractor.lockoutEndTimestamp ?: 0
+ advanceTimeBy(lockoutEndMs - testScope.currentTime)
assertThat(isInputEnabled).isTrue()
}
@Test
fun dialogMessage() =
testScope.runTest {
+ val authMethodViewModel by collectLastValue(underTest.authMethodViewModel)
val dialogMessage by collectLastValue(underTest.dialogMessage)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ assertThat(authMethodViewModel?.lockoutMessageId).isNotNull()
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
- // Wrong PIN.
assertThat(dialogMessage).isNull()
- bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
+ bouncerInteractor.authenticate(WRONG_PIN)
}
assertThat(dialogMessage).isNotEmpty()
@@ -241,4 +268,15 @@
AuthenticationMethodModel.Sim,
)
}
+
+ private fun assertTryAgainMessage(
+ message: String?,
+ time: Int,
+ ) {
+ assertThat(message).isEqualTo("Try again in $time seconds.")
+ }
+
+ companion object {
+ private val WRONG_PIN = FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 }
+ }
}
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 6d6baa5..64e6e57 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,7 +19,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
@@ -28,6 +27,7 @@
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -45,11 +45,7 @@
private val utils = SceneTestUtils(this)
private val testScope = utils.testScope
- private val authenticationRepository = utils.authenticationRepository
- private val authenticationInteractor =
- utils.authenticationInteractor(
- repository = authenticationRepository,
- )
+ private val authenticationInteractor = utils.authenticationInteractor()
private val sceneInteractor = utils.sceneInteractor()
private val bouncerInteractor =
utils.bouncerInteractor(
@@ -61,12 +57,13 @@
authenticationInteractor = authenticationInteractor,
actionButtonInteractor = utils.bouncerActionButtonInteractor(),
)
+ private val isInputEnabled = MutableStateFlow(true)
private val underTest =
PasswordBouncerViewModel(
viewModelScope = testScope.backgroundScope,
interactor = bouncerInteractor,
- isInputEnabled = MutableStateFlow(true).asStateFlow(),
+ isInputEnabled.asStateFlow(),
)
@Before
@@ -123,8 +120,7 @@
@Test
fun onAuthenticateKeyPressed_whenCorrect() =
testScope.runTest {
- val authResult by
- collectLastValue(authenticationInteractor.authenticationChallengeResult)
+ val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult)
lockDeviceAndOpenPasswordBouncer()
underTest.onPasswordInputChanged("password")
@@ -169,8 +165,7 @@
@Test
fun onAuthenticateKeyPressed_correctAfterWrong() =
testScope.runTest {
- val authResult by
- collectLastValue(authenticationInteractor.authenticationChallengeResult)
+ val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult)
val message by collectLastValue(bouncerViewModel.message)
val password by collectLastValue(underTest.password)
lockDeviceAndOpenPasswordBouncer()
@@ -333,19 +328,15 @@
) {
if (isLockedOut) {
repeat(failedAttemptCount) {
- authenticationRepository.reportAuthenticationAttempt(false)
+ utils.authenticationRepository.reportAuthenticationAttempt(false)
}
- val remainingTimeSeconds = 30
- authenticationRepository.setLockoutDuration(remainingTimeSeconds * 1000)
- authenticationRepository.lockout.value =
- AuthenticationLockoutModel(
- failedAttemptCount = failedAttemptCount,
- remainingSeconds = remainingTimeSeconds,
- )
+ utils.authenticationRepository.reportLockoutStarted(
+ 30.seconds.inWholeMilliseconds.toInt()
+ )
} else {
- authenticationRepository.reportAuthenticationAttempt(true)
- authenticationRepository.lockout.value = null
+ utils.authenticationRepository.reportAuthenticationAttempt(true)
}
+ isInputEnabled.value = !isLockedOut
runCurrent()
}
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 8971423..ed76099 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
@@ -111,8 +111,7 @@
@Test
fun onDragEnd_whenCorrect() =
testScope.runTest {
- val authResult by
- collectLastValue(authenticationInteractor.authenticationChallengeResult)
+ val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult)
val selectedDots by collectLastValue(underTest.selectedDots)
val currentDot by collectLastValue(underTest.currentDot)
lockDeviceAndOpenPatternBouncer()
@@ -334,8 +333,7 @@
@Test
fun onDragEnd_correctAfterWrong() =
testScope.runTest {
- val authResult by
- collectLastValue(authenticationInteractor.authenticationChallengeResult)
+ val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult)
val message by collectLastValue(bouncerViewModel.message)
val selectedDots by collectLastValue(underTest.selectedDots)
val currentDot by collectLastValue(underTest.currentDot)
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 c30e405..db98d76 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
@@ -201,8 +201,7 @@
@Test
fun onAuthenticateButtonClicked_whenCorrect() =
testScope.runTest {
- val authResult by
- collectLastValue(authenticationInteractor.authenticationChallengeResult)
+ val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult)
lockDeviceAndOpenPinBouncer()
FakeAuthenticationRepository.DEFAULT_PIN.forEach(underTest::onPinButtonClicked)
@@ -236,8 +235,7 @@
@Test
fun onAuthenticateButtonClicked_correctAfterWrong() =
testScope.runTest {
- val authResult by
- collectLastValue(authenticationInteractor.authenticationChallengeResult)
+ val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult)
val message by collectLastValue(bouncerViewModel.message)
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
lockDeviceAndOpenPinBouncer()
@@ -265,8 +263,7 @@
fun onAutoConfirm_whenCorrect() =
testScope.runTest {
utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
- val authResult by
- collectLastValue(authenticationInteractor.authenticationChallengeResult)
+ val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult)
lockDeviceAndOpenPinBouncer()
FakeAuthenticationRepository.DEFAULT_PIN.forEach(underTest::onPinButtonClicked)
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 182712a..ddaa488 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
@@ -208,11 +208,11 @@
val repository = initCommunalWidgetRepository()
runCurrent()
- val ids = listOf(104, 103, 101)
- repository.updateWidgetOrder(ids)
+ val widgetIdToPriorityMap = mapOf(104 to 1, 103 to 2, 101 to 3)
+ repository.updateWidgetOrder(widgetIdToPriorityMap)
runCurrent()
- verify(communalWidgetDao).updateWidgetOrder(ids)
+ verify(communalWidgetDao).updateWidgetOrder(widgetIdToPriorityMap)
}
@Test
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 d3049d9..565049b 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
@@ -99,7 +99,7 @@
}
@Test
- fun reportSuccessfulAuthentication_shouldUpdateIsUnlocked() =
+ fun reportSuccessfulAuthentication_updatesIsUnlocked() =
testScope.runTest {
val isUnlocked by collectLastValue(underTest.isUnlocked)
assertThat(isUnlocked).isFalse()
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 910097e..ea19cb7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -19,6 +19,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.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.coroutines.collectValues
@@ -346,12 +347,14 @@
}
@Test
- fun successfulAuthenticationChallengeAttempt_updatedIsUnlockedState() =
+ fun successfulAuthenticationChallengeAttempt_updatesIsUnlockedState() =
testScope.runTest {
val isUnlocked by collectLastValue(underTest.isUnlocked)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.deviceEntryRepository.setLockscreenEnabled(true)
assertThat(isUnlocked).isFalse()
- utils.authenticationRepository.reportAuthenticationAttempt(true)
+ authenticationInteractor.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)
assertThat(isUnlocked).isTrue()
}
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
new file mode 100644
index 0000000..9daf186
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt
@@ -0,0 +1,110 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+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.collect.Range
+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 AlternateBouncerToAodTransitionViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
+ private val biometricSettingsRepository = kosmos.biometricSettingsRepository
+ private val underTest = kosmos.alternateBouncerToAodTransitionViewModel
+
+ @Test
+ fun deviceEntryParentViewAppear() =
+ testScope.runTest {
+ fingerprintPropertyRepository.setProperties(
+ sensorId = 0,
+ strength = SensorStrength.STRONG,
+ sensorType = FingerprintSensorType.UDFPS_OPTICAL,
+ sensorLocations = emptyMap(),
+ )
+ biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ 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(1f) }
+ }
+
+ @Test
+ fun deviceEntryBackgroundViewDisappear() =
+ testScope.runTest {
+ val values by collectValues(underTest.deviceEntryBackgroundViewAlpha)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ listOf(
+ step(0f, TransitionState.STARTED),
+ step(0f),
+ step(0.1f),
+ step(0.2f),
+ step(0.3f),
+ step(1f),
+ ),
+ testScope,
+ )
+
+ assertThat(values.size).isEqualTo(6)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
+ }
+
+ private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = AOD,
+ value = value,
+ transitionState = state,
+ ownerName = "AlternateBouncerToAodTransitionViewModelTest"
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt
new file mode 100644
index 0000000..3f7e0df
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.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.KeyguardState.GONE
+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 AlternateBouncerToGoneTransitionViewModelTest : SysuiTestCase() {
+ 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.alternateBouncerToGoneTransitionViewModel
+
+ @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 = GONE,
+ value = value,
+ transitionState = state,
+ ownerName = "AlternateBouncerToGoneTransitionViewModelTest"
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
index cf076c5..a84b9fa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
@@ -24,16 +24,19 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
-import com.android.systemui.qs.external.FakeCustomTileStatePersister
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.external.TileServiceKey
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat
import com.android.systemui.qs.tiles.impl.custom.commons.copy
+import com.android.systemui.qs.tiles.impl.custom.customTileStatePersister
import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
+import com.android.systemui.qs.tiles.impl.custom.packageManagerAdapterFacade
+import com.android.systemui.qs.tiles.impl.custom.tileSpec
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -44,194 +47,260 @@
@OptIn(ExperimentalCoroutinesApi::class)
class CustomTileRepositoryTest : SysuiTestCase() {
- private val testScope = TestScope()
-
- private val persister = FakeCustomTileStatePersister()
-
+ private val kosmos = Kosmos().apply { tileSpec = TileSpec.create(TEST_COMPONENT) }
private val underTest: CustomTileRepository =
- CustomTileRepositoryImpl(
- TileSpec.create(TEST_COMPONENT),
- persister,
- testScope.testScheduler,
- )
+ with(kosmos) {
+ CustomTileRepositoryImpl(
+ tileSpec,
+ customTileStatePersister,
+ packageManagerAdapterFacade.packageManagerAdapter,
+ testScope.testScheduler,
+ )
+ }
@Test
fun persistableTileIsRestoredForUser() =
- testScope.runTest {
- persister.persistState(TEST_TILE_KEY_1, TEST_TILE_1)
- persister.persistState(TEST_TILE_KEY_2, TEST_TILE_2)
+ with(kosmos) {
+ testScope.runTest {
+ customTileStatePersister.persistState(TEST_TILE_KEY_1, TEST_TILE_1)
+ customTileStatePersister.persistState(TEST_TILE_KEY_2, TEST_TILE_2)
- underTest.restoreForTheUserIfNeeded(TEST_USER_1, true)
- runCurrent()
+ underTest.restoreForTheUserIfNeeded(TEST_USER_1, true)
+ runCurrent()
- assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
- assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
+ assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
+ assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
+ }
}
@Test
fun notPersistableTileIsNotRestored() =
- testScope.runTest {
- persister.persistState(TEST_TILE_KEY_1, TEST_TILE_1)
- val tiles = collectValues(underTest.getTiles(TEST_USER_1))
+ with(kosmos) {
+ testScope.runTest {
+ customTileStatePersister.persistState(TEST_TILE_KEY_1, TEST_TILE_1)
+ val tiles = collectValues(underTest.getTiles(TEST_USER_1))
- underTest.restoreForTheUserIfNeeded(TEST_USER_1, false)
- runCurrent()
+ underTest.restoreForTheUserIfNeeded(TEST_USER_1, false)
+ runCurrent()
- assertThat(tiles()).isEmpty()
+ assertThat(tiles()).isEmpty()
+ }
}
@Test
fun emptyPersistedStateIsHandled() =
- testScope.runTest {
- val tiles = collectValues(underTest.getTiles(TEST_USER_1))
+ with(kosmos) {
+ testScope.runTest {
+ val tiles = collectValues(underTest.getTiles(TEST_USER_1))
- underTest.restoreForTheUserIfNeeded(TEST_USER_1, true)
- runCurrent()
+ underTest.restoreForTheUserIfNeeded(TEST_USER_1, true)
+ runCurrent()
- assertThat(tiles()).isEmpty()
+ assertThat(tiles()).isEmpty()
+ }
}
@Test
fun updatingWithPersistableTilePersists() =
- testScope.runTest {
- underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+ runCurrent()
- assertThat(persister.readState(TEST_TILE_KEY_1)).isEqualTo(TEST_TILE_1)
+ assertThat(customTileStatePersister.readState(TEST_TILE_KEY_1))
+ .isEqualTo(TEST_TILE_1)
+ }
}
@Test
fun updatingWithNotPersistableTileDoesntPersist() =
- testScope.runTest {
- underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, false)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, false)
+ runCurrent()
- assertThat(persister.readState(TEST_TILE_KEY_1)).isNull()
+ assertThat(customTileStatePersister.readState(TEST_TILE_KEY_1)).isNull()
+ }
}
@Test
fun updateWithTileEmits() =
- testScope.runTest {
- underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+ runCurrent()
- assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
- assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
+ assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
+ assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
+ }
}
@Test
fun updatingPeristableWithDefaultsPersists() =
- testScope.runTest {
- underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
+ runCurrent()
- assertThat(persister.readState(TEST_TILE_KEY_1)).isEqualTo(TEST_TILE_1)
+ assertThat(customTileStatePersister.readState(TEST_TILE_KEY_1))
+ .isEqualTo(TEST_TILE_1)
+ }
}
@Test
fun updatingNotPersistableWithDefaultsDoesntPersist() =
- testScope.runTest {
- underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, false)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, false)
+ runCurrent()
- assertThat(persister.readState(TEST_TILE_KEY_1)).isNull()
+ assertThat(customTileStatePersister.readState(TEST_TILE_KEY_1)).isNull()
+ }
}
@Test
fun updatingPeristableWithErrorDefaultsDoesntPersist() =
- testScope.runTest {
- underTest.updateWithDefaults(TEST_USER_1, CustomTileDefaults.Error, true)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithDefaults(TEST_USER_1, CustomTileDefaults.Error, true)
+ runCurrent()
- assertThat(persister.readState(TEST_TILE_KEY_1)).isNull()
+ assertThat(customTileStatePersister.readState(TEST_TILE_KEY_1)).isNull()
+ }
}
@Test
fun updateWithDefaultsEmits() =
- testScope.runTest {
- underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
+ runCurrent()
- assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
- assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
+ assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
+ assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
+ }
}
@Test
fun getTileForAnotherUserReturnsNull() =
- testScope.runTest {
- underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+ runCurrent()
- assertThat(underTest.getTile(TEST_USER_2)).isNull()
+ assertThat(underTest.getTile(TEST_USER_2)).isNull()
+ }
}
@Test
fun getTilesForAnotherUserEmpty() =
- testScope.runTest {
- val tiles = collectValues(underTest.getTiles(TEST_USER_2))
+ with(kosmos) {
+ testScope.runTest {
+ val tiles = collectValues(underTest.getTiles(TEST_USER_2))
- underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
- runCurrent()
+ underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+ runCurrent()
- assertThat(tiles()).isEmpty()
+ assertThat(tiles()).isEmpty()
+ }
}
@Test
fun updatingWithTileForTheSameUserAddsData() =
- testScope.runTest {
- underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+ runCurrent()
- underTest.updateWithTile(TEST_USER_1, Tile().apply { subtitle = "test_subtitle" }, true)
- runCurrent()
+ underTest.updateWithTile(
+ TEST_USER_1,
+ Tile().apply { subtitle = "test_subtitle" },
+ true
+ )
+ runCurrent()
- val expectedTile = TEST_TILE_1.copy().apply { subtitle = "test_subtitle" }
- assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(expectedTile)
- assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(expectedTile)
+ val expectedTile = TEST_TILE_1.copy().apply { subtitle = "test_subtitle" }
+ assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(expectedTile)
+ assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(expectedTile)
+ }
}
@Test
fun updatingWithTileForAnotherUserOverridesTile() =
- testScope.runTest {
- underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+ runCurrent()
- val tiles = collectValues(underTest.getTiles(TEST_USER_2))
- underTest.updateWithTile(TEST_USER_2, TEST_TILE_2, true)
- runCurrent()
+ val tiles = collectValues(underTest.getTiles(TEST_USER_2))
+ underTest.updateWithTile(TEST_USER_2, TEST_TILE_2, true)
+ runCurrent()
- assertThat(underTest.getTile(TEST_USER_2)).isEqualTo(TEST_TILE_2)
- assertThat(tiles()).hasSize(1)
- assertThat(tiles().last()).isEqualTo(TEST_TILE_2)
+ assertThat(underTest.getTile(TEST_USER_2)).isEqualTo(TEST_TILE_2)
+ assertThat(tiles()).hasSize(1)
+ assertThat(tiles().last()).isEqualTo(TEST_TILE_2)
+ }
}
@Test
fun updatingWithDefaultsForTheSameUserAddsData() =
- testScope.runTest {
- underTest.updateWithTile(TEST_USER_1, Tile().apply { subtitle = "test_subtitle" }, true)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithTile(
+ TEST_USER_1,
+ Tile().apply { subtitle = "test_subtitle" },
+ true
+ )
+ runCurrent()
- underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
- runCurrent()
+ underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
+ runCurrent()
- val expectedTile = TEST_TILE_1.copy().apply { subtitle = "test_subtitle" }
- assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(expectedTile)
- assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(expectedTile)
+ val expectedTile = TEST_TILE_1.copy().apply { subtitle = "test_subtitle" }
+ assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(expectedTile)
+ assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(expectedTile)
+ }
}
@Test
fun updatingWithDefaultsForAnotherUserOverridesTile() =
- testScope.runTest {
- underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
+ runCurrent()
- val tiles = collectValues(underTest.getTiles(TEST_USER_2))
- underTest.updateWithDefaults(TEST_USER_2, TEST_DEFAULTS_2, true)
- runCurrent()
+ val tiles = collectValues(underTest.getTiles(TEST_USER_2))
+ underTest.updateWithDefaults(TEST_USER_2, TEST_DEFAULTS_2, true)
+ runCurrent()
- assertThat(underTest.getTile(TEST_USER_2)).isEqualTo(TEST_TILE_2)
- assertThat(tiles()).hasSize(1)
- assertThat(tiles().last()).isEqualTo(TEST_TILE_2)
+ assertThat(underTest.getTile(TEST_USER_2)).isEqualTo(TEST_TILE_2)
+ assertThat(tiles()).hasSize(1)
+ assertThat(tiles().last()).isEqualTo(TEST_TILE_2)
+ }
+ }
+
+ @Test
+ fun isActiveFollowsPackageManagerAdapter() =
+ with(kosmos) {
+ testScope.runTest {
+ packageManagerAdapterFacade.setIsActive(false)
+ assertThat(underTest.isTileActive()).isFalse()
+
+ packageManagerAdapterFacade.setIsActive(true)
+ assertThat(underTest.isTileActive()).isTrue()
+ }
+ }
+
+ @Test
+ fun isToggleableFollowsPackageManagerAdapter() =
+ with(kosmos) {
+ testScope.runTest {
+ packageManagerAdapterFacade.setIsToggleable(false)
+ assertThat(underTest.isTileToggleable()).isFalse()
+
+ packageManagerAdapterFacade.setIsToggleable(true)
+ assertThat(underTest.isTileToggleable()).isTrue()
+ }
}
private companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
index eebb145..90779cb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
@@ -25,157 +25,159 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
-import com.android.systemui.qs.external.FakeCustomTileStatePersister
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.external.TileServiceKey
-import com.android.systemui.qs.external.TileServiceManager
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat
+import com.android.systemui.qs.tiles.impl.custom.customTileDefaultsRepository
+import com.android.systemui.qs.tiles.impl.custom.customTileRepository
+import com.android.systemui.qs.tiles.impl.custom.customTileStatePersister
import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
-import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileDefaultsRepository
-import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileRepository
-import com.android.systemui.util.mockito.whenever
+import com.android.systemui.qs.tiles.impl.custom.tileSpec
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
@OptIn(ExperimentalCoroutinesApi::class)
class CustomTileInteractorTest : SysuiTestCase() {
- @Mock private lateinit var tileServiceManager: TileServiceManager
+ private val kosmos = Kosmos().apply { tileSpec = TileSpec.create(TEST_COMPONENT) }
- private val testScope = TestScope()
-
- private val defaultsRepository = FakeCustomTileDefaultsRepository()
- private val customTileStatePersister = FakeCustomTileStatePersister()
- private val customTileRepository =
- FakeCustomTileRepository(
- TEST_TILE_SPEC,
- customTileStatePersister,
- testScope.testScheduler,
- )
-
- private lateinit var underTest: CustomTileInteractor
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
-
- underTest =
+ private val underTest: CustomTileInteractor =
+ with(kosmos) {
CustomTileInteractor(
- TEST_USER,
- defaultsRepository,
+ customTileDefaultsRepository,
customTileRepository,
- tileServiceManager,
testScope.backgroundScope,
testScope.testScheduler,
)
- }
+ }
@Test
fun activeTileIsAvailableAfterRestored() =
- testScope.runTest {
- whenever(tileServiceManager.isActiveTile).thenReturn(true)
- customTileStatePersister.persistState(
- TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
- TEST_TILE,
- )
+ with(kosmos) {
+ testScope.runTest {
+ customTileRepository.setTileActive(true)
+ customTileStatePersister.persistState(
+ TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
+ TEST_TILE,
+ )
- underTest.init()
+ underTest.initForUser(TEST_USER)
- assertThat(underTest.tile).isEqualTo(TEST_TILE)
- assertThat(underTest.tiles.first()).isEqualTo(TEST_TILE)
+ assertThat(underTest.getTile(TEST_USER)).isEqualTo(TEST_TILE)
+ assertThat(underTest.getTiles(TEST_USER).first()).isEqualTo(TEST_TILE)
+ }
}
@Test
fun notActiveTileIsAvailableAfterUpdated() =
- testScope.runTest {
- whenever(tileServiceManager.isActiveTile).thenReturn(false)
- customTileStatePersister.persistState(
- TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
- TEST_TILE,
- )
- val tiles = collectValues(underTest.tiles)
- val initJob = launch { underTest.init() }
+ with(kosmos) {
+ testScope.runTest {
+ customTileRepository.setTileActive(false)
+ customTileStatePersister.persistState(
+ TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
+ TEST_TILE,
+ )
+ val tiles = collectValues(underTest.getTiles(TEST_USER))
+ val initJob = launch { underTest.initForUser(TEST_USER) }
- underTest.updateTile(TEST_TILE)
- runCurrent()
- initJob.join()
+ underTest.updateTile(TEST_TILE)
+ runCurrent()
+ initJob.join()
- assertThat(tiles()).hasSize(1)
- assertThat(tiles().last()).isEqualTo(TEST_TILE)
+ assertThat(tiles()).hasSize(1)
+ assertThat(tiles().last()).isEqualTo(TEST_TILE)
+ }
}
@Test
fun notActiveTileIsAvailableAfterDefaultsUpdated() =
- testScope.runTest {
- whenever(tileServiceManager.isActiveTile).thenReturn(false)
- customTileStatePersister.persistState(
- TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
- TEST_TILE,
- )
- val tiles = collectValues(underTest.tiles)
- val initJob = launch { underTest.init() }
+ with(kosmos) {
+ testScope.runTest {
+ customTileRepository.setTileActive(false)
+ customTileStatePersister.persistState(
+ TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
+ TEST_TILE,
+ )
+ val tiles = collectValues(underTest.getTiles(TEST_USER))
+ val initJob = launch { underTest.initForUser(TEST_USER) }
- defaultsRepository.putDefaults(TEST_USER, TEST_COMPONENT, TEST_DEFAULTS)
- defaultsRepository.requestNewDefaults(TEST_USER, TEST_COMPONENT)
- runCurrent()
- initJob.join()
+ customTileDefaultsRepository.putDefaults(TEST_USER, TEST_COMPONENT, TEST_DEFAULTS)
+ customTileDefaultsRepository.requestNewDefaults(TEST_USER, TEST_COMPONENT)
+ runCurrent()
+ initJob.join()
- assertThat(tiles()).hasSize(1)
- assertThat(tiles().last()).isEqualTo(TEST_TILE)
+ assertThat(tiles()).hasSize(1)
+ assertThat(tiles().last()).isEqualTo(TEST_TILE)
+ }
}
@Test(expected = IllegalStateException::class)
- fun getTileBeforeInitThrows() = testScope.runTest { underTest.tile }
+ fun getTileBeforeInitThrows() =
+ with(kosmos) { testScope.runTest { underTest.getTile(TEST_USER) } }
@Test
fun initSuspendsForActiveTileNotRestoredAndNotUpdated() =
- testScope.runTest {
- whenever(tileServiceManager.isActiveTile).thenReturn(true)
- val tiles = collectValues(underTest.tiles)
+ with(kosmos) {
+ testScope.runTest {
+ customTileRepository.setTileActive(true)
+ val tiles = collectValues(underTest.getTiles(TEST_USER))
- val initJob = backgroundScope.launch { underTest.init() }
- advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS)
+ val initJob = backgroundScope.launch { underTest.initForUser(TEST_USER) }
+ advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS)
- // Is still suspended
- assertThat(initJob.isActive).isTrue()
- assertThat(tiles()).isEmpty()
+ // Is still suspended
+ assertThat(initJob.isActive).isTrue()
+ assertThat(tiles()).isEmpty()
+ }
}
@Test
fun initSuspendedForNotActiveTileWithoutUpdates() =
- testScope.runTest {
- whenever(tileServiceManager.isActiveTile).thenReturn(false)
- customTileStatePersister.persistState(
- TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
- TEST_TILE,
- )
- val tiles = collectValues(underTest.tiles)
+ with(kosmos) {
+ testScope.runTest {
+ customTileRepository.setTileActive(false)
+ customTileStatePersister.persistState(
+ TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
+ TEST_TILE,
+ )
+ val tiles = collectValues(underTest.getTiles(TEST_USER))
- val initJob = backgroundScope.launch { underTest.init() }
- advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS)
+ val initJob = backgroundScope.launch { underTest.initForUser(TEST_USER) }
+ advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS)
- // Is still suspended
- assertThat(initJob.isActive).isTrue()
- assertThat(tiles()).isEmpty()
+ // Is still suspended
+ assertThat(initJob.isActive).isTrue()
+ assertThat(tiles()).isEmpty()
+ }
}
+ @Test
+ fun toggleableFollowsTheRepository() {
+ with(kosmos) {
+ testScope.runTest {
+ customTileRepository.setTileToggleable(false)
+ assertThat(underTest.isTileToggleable()).isFalse()
+
+ customTileRepository.setTileToggleable(true)
+ assertThat(underTest.isTileToggleable()).isTrue()
+ }
+ }
+ }
+
private companion object {
val TEST_COMPONENT = ComponentName("test.pkg", "test.cls")
- val TEST_TILE_SPEC = TileSpec.create(TEST_COMPONENT)
val TEST_USER = UserHandle.of(1)!!
val TEST_TILE =
Tile().apply {
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 c110de9..224903f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -265,8 +265,8 @@
falsingCollector = utils.falsingCollector(),
powerInteractor = powerInteractor,
bouncerInteractor = bouncerInteractor,
- simBouncerInteractor = utils.simBouncerInteractor,
- authenticationInteractor = utils.authenticationInteractor()
+ simBouncerInteractor = dagger.Lazy { utils.simBouncerInteractor },
+ authenticationInteractor = dagger.Lazy { utils.authenticationInteractor() }
)
startable.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 61d55f0..2e4986d 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
@@ -90,8 +90,8 @@
falsingCollector = falsingCollector,
powerInteractor = powerInteractor,
bouncerInteractor = bouncerInteractor,
- simBouncerInteractor = utils.simBouncerInteractor,
- authenticationInteractor = authenticationInteractor,
+ simBouncerInteractor = dagger.Lazy { utils.simBouncerInteractor },
+ authenticationInteractor = dagger.Lazy { authenticationInteractor },
)
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index 2ecf01f..1237347 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -16,7 +16,8 @@
package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
-import android.platform.test.flag.junit.SetFlagsRule
+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.settingslib.AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION
@@ -60,8 +61,6 @@
private lateinit var underTest: WifiViewModel
- private val setFlagsRule = SetFlagsRule()
-
@Mock private lateinit var tableLogBuffer: TableLogBuffer
@Mock private lateinit var connectivityConstants: ConnectivityConstants
@Mock private lateinit var wifiConstants: WifiConstants
@@ -187,11 +186,9 @@
}
@Test
+ @DisableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
fun activity_nullSsid_outputsFalse_staticFlagOff() =
testScope.runTest {
- // GIVEN flag is disabled
- setFlagsRule.disableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
-
whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
@@ -214,11 +211,9 @@
}
@Test
+ @EnableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
fun activity_nullSsid_outputsFalse_staticFlagOn() =
testScope.runTest {
- // GIVEN flag is enabled
- setFlagsRule.enableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
-
whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
@@ -371,11 +366,9 @@
}
@Test
+ @DisableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
fun activityContainer_inAndOutFalse_outputsTrue_staticFlagOff() =
testScope.runTest {
- // GIVEN the flag is off
- setFlagsRule.disableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
-
whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
@@ -389,11 +382,9 @@
}
@Test
+ @EnableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
fun activityContainer_inAndOutFalse_outputsTrue_staticFlagOn() =
testScope.runTest {
- // GIVEN the flag is on
- setFlagsRule.enableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
-
whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
index 1c5f221..4436be7 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
@@ -41,16 +41,13 @@
/** Interface for building clocks and providing information about those clocks */
interface ClockProvider {
+ /** Initializes the clock provider with debug log buffers */
+ fun initialize(buffers: ClockMessageBuffers?)
+
/** Returns metadata for all clocks this provider knows about */
fun getClocks(): List<ClockMetadata>
/** Initializes and returns the target clock design */
- @Deprecated("Use overload with ClockSettings")
- fun createClock(id: ClockId): ClockController {
- return createClock(ClockSettings(id, null))
- }
-
- /** Initializes and returns the target clock design */
fun createClock(settings: ClockSettings): ClockController
/** A static thumbnail for rendering in some examples */
@@ -98,11 +95,20 @@
/** Triggers for various animations */
val animations: ClockAnimations
-
- /** Some clocks may log debug information */
- var messageBuffer: MessageBuffer?
}
+/** For clocks that want to report debug information */
+data class ClockMessageBuffers(
+ /** Message buffer for general infra */
+ val infraMessageBuffer: MessageBuffer,
+
+ /** Message buffer for small clock renering */
+ val smallClockMessageBuffer: MessageBuffer,
+
+ /** Message buffer for large clock rendering */
+ val largeClockMessageBuffer: MessageBuffer,
+)
+
/** Specifies layout information for the */
interface ClockFaceLayout {
/** All clock views to add to the root constraint layout before applying constraints. */
diff --git a/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml b/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml
index 7b90270..50d5607 100644
--- a/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml
+++ b/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml
@@ -15,7 +15,7 @@
~
-->
-<FrameLayout
+<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:sysui="http://schemas.android.com/apk/res-auto"
android:id="@+id/alternate_bouncer"
@@ -32,4 +32,4 @@
android:importantForAccessibility="no"
sysui:ignoreRightInset="true"
/>
-</FrameLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/drawable/ic_memory.xml b/packages/SystemUI/res/drawable/ic_memory.xml
deleted file mode 100644
index ada36c5..0000000
--- a/packages/SystemUI/res/drawable/ic_memory.xml
+++ /dev/null
@@ -1,28 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:pathData="M16.0,5.0l-8.0,0.0l0.0,14.0l8.0,0.0z"
- android:fillAlpha="0.5"
- android:fillColor="#000000"/>
- <path
- android:pathData="M6,9 L6,7 L4,7 L4,5 L6,5 C6,3.9 6.9,3 8,3 L16,3 C17.1,3 18,3.9 18,5 L20,5 L20,7 L18,7 L18,9 L20,9 L20,11 L18,11 L18,13 L20,13 L20,15 L18,15 L18,17 L20,17 L20,19 L18,19 C18,20.1 17.1,21 16,21 L8,21 C6.9,21 6,20.1 6,19 L4,19 L4,17 L6,17 L6,15 L4,15 L4,13 L6,13 L6,11 L4,11 L4,9 L6,9 Z M16,19 L16,5 L8,5 L8,19 L16,19 Z"
- android:fillColor="#000000"/>
-</vector>
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index 87c31c8..dca84b9 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -116,6 +116,10 @@
android:inflatedId="@+id/multi_shade"
android:layout="@layout/multi_shade" />
+ <include layout="@layout/alternate_bouncer"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
<com.android.systemui.biometrics.AuthRippleView
android:id="@+id/auth_ripple"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/widget_picker.xml b/packages/SystemUI/res/layout/widget_picker.xml
deleted file mode 100644
index 21dc224..0000000
--- a/packages/SystemUI/res/layout/widget_picker.xml
+++ /dev/null
@@ -1,30 +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.
- -->
-
-<HorizontalScrollView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <LinearLayout
- android:id="@+id/widgets_container"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:gravity="center_vertical"
- android:orientation="horizontal">
- </LinearLayout>
-
-</HorizontalScrollView>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index deed6c8..e01a2aa 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -480,9 +480,6 @@
This name is in the ComponentName flattened format (package/class) -->
<string name="config_remoteCopyPackage" translatable="false"></string>
- <!-- On debuggable builds, alert the user if SystemUI PSS goes over this number (in kb) -->
- <integer name="watch_heap_limit">256000</integer>
-
<!-- SystemUI Plugins that can be loaded on user builds. -->
<string-array name="config_pluginAllowlist" translatable="false">
<item>com.android.systemui</item>
@@ -966,14 +963,6 @@
<bool name="config_edgeToEdgeBottomSheetDialog">true</bool>
<!--
- Whether the scene container framework is enabled.
-
- The scene container framework is a newer (2023) way to organize the various "scenes" between the
- bouncer, lockscreen, shade, and quick settings.
- -->
- <bool name="config_sceneContainerFrameworkEnabled">true</bool>
-
- <!--
Time in milliseconds the user has to touch the side FPS sensor to successfully authenticate
TODO(b/302332976) Get this value from the HAL if they can provide an API for it.
-->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index b947801..ee89ede 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -274,6 +274,10 @@
<!-- Side padding on the side of notifications -->
<dimen name="notification_side_paddings">16dp</dimen>
+ <!-- Starting translateY offset of the HUN appear and disappear animations. Indicates
+ the amount by the view is positioned above the screen before the animation starts. -->
+ <dimen name="heads_up_appear_y_above_screen">32dp</dimen>
+
<!-- padding between the heads up and the statusbar -->
<dimen name="heads_up_status_bar_padding">8dp</dimen>
@@ -1905,4 +1909,7 @@
<!-- Bouncer user switcher margins -->
<dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen>
<dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen>
+
+ <!-- UDFPS view attributes -->
+ <dimen name="udfps_icon_size">6mm</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index cf63cc7..d511cab 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -259,4 +259,11 @@
<item type="id" name="device_entry_icon_view" />
<item type="id" name="device_entry_icon_fg" />
<item type="id" name="device_entry_icon_bg" />
+
+ <!--Id for the device-entry UDFPS icon that lives in the alternate bouncer. -->
+ <item type="id" name="alternate_bouncer_udfps_icon_view" />
+
+ <!-- Id for the udfps accessibility overlay -->
+ <item type="id" name="udfps_accessibility_overlay" />
+ <item type="id" name="udfps_accessibility_overlay_top_guideline" />
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index e10925d..f4b25a7 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2397,10 +2397,6 @@
<!-- URl of the webpage that explains battery saver. -->
<string name="help_uri_battery_saver_learn_more_link_target" translatable="false"></string>
- <!-- Name for a quick settings tile, used only by platform developers, to extract the SystemUI process memory and send it to another
- app for debugging. Will not be seen by users. [CHAR LIMIT=20] -->
- <string name="heap_dump_tile_name">Dump SysUI Heap</string>
-
<!-- Title for the privacy indicators dialog, only appears as part of a11y descriptions [CHAR LIMIT=NONE] -->
<string name="ongoing_privacy_dialog_a11y_title">In use</string>
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
index 8ad32b4..a00cdfa 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
@@ -30,6 +30,10 @@
fun isUdfps(): Boolean {
return (this == UDFPS_OPTICAL) || (this == UDFPS_ULTRASONIC)
}
+
+ fun isPowerButton(): Boolean {
+ return this == POWER_BUTTON
+ }
}
/** Convert [this] to corresponding [FingerprintSensorType] */
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index d8c1e41..131eb6b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -367,7 +367,7 @@
/**
* Corner radius that should be used on windows in order to cover the display.
* These values are expressed in pixels because they should not respect display or font
- * scaling, this means that we don't have to reload them on config changes.
+ * scaling. The corner radius may change when folding/unfolding the device.
*/
public static float getWindowCornerRadius(Context context) {
return ScreenDecorationsUtils.getWindowCornerRadius(context);
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 76abad8..bcc2044 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -45,12 +45,11 @@
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
import com.android.systemui.log.core.LogLevel.DEBUG
-import com.android.systemui.log.dagger.KeyguardLargeClockLog
-import com.android.systemui.log.dagger.KeyguardSmallClockLog
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockFaceController
+import com.android.systemui.plugins.clocks.ClockMessageBuffers
import com.android.systemui.plugins.clocks.ClockTickRate
import com.android.systemui.plugins.clocks.AlarmData
import com.android.systemui.plugins.clocks.WeatherData
@@ -91,117 +90,120 @@
private val context: Context,
@Main private val mainExecutor: DelayableExecutor,
@Background private val bgExecutor: Executor,
- @KeyguardSmallClockLog private val smallLogBuffer: LogBuffer?,
- @KeyguardLargeClockLog private val largeLogBuffer: LogBuffer?,
+ private val clockBuffers: ClockMessageBuffers,
private val featureFlags: FeatureFlags,
private val zenModeController: ZenModeController,
) {
+ var loggers = listOf(
+ clockBuffers.infraMessageBuffer,
+ clockBuffers.smallClockMessageBuffer,
+ clockBuffers.largeClockMessageBuffer
+ ).map { Logger(it, TAG) }
+
var clock: ClockController? = null
+ get() = field
set(value) {
- smallClockOnAttachStateChangeListener?.let {
- field?.smallClock?.view?.removeOnAttachStateChangeListener(it)
+ disconnectClock(field)
+ field = value
+ connectClock(value)
+ }
+
+ private fun disconnectClock(clock: ClockController?) {
+ if (clock == null) { return; }
+ smallClockOnAttachStateChangeListener?.let {
+ clock.smallClock.view.removeOnAttachStateChangeListener(it)
+ smallClockFrame?.viewTreeObserver
+ ?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
+ }
+ largeClockOnAttachStateChangeListener?.let {
+ clock.largeClock.view.removeOnAttachStateChangeListener(it)
+ }
+ }
+
+ private fun connectClock(clock: ClockController?) {
+ if (clock == null) { return; }
+ val clockStr = clock.toString()
+ loggers.forEach { it.d({ "New Clock: $str1" }) { str1 = clockStr } }
+
+ clock.initialize(resources, dozeAmount, 0f)
+
+ if (!regionSamplingEnabled) {
+ updateColors()
+ } else {
+ smallRegionSampler = createRegionSampler(
+ clock.smallClock.view,
+ mainExecutor,
+ bgExecutor,
+ regionSamplingEnabled,
+ isLockscreen = true,
+ ::updateColors
+ ).apply { startRegionSampler() }
+
+ largeRegionSampler = createRegionSampler(
+ clock.largeClock.view,
+ mainExecutor,
+ bgExecutor,
+ regionSamplingEnabled,
+ isLockscreen = true,
+ ::updateColors
+ ).apply { startRegionSampler() }
+
+ updateColors()
+ }
+ updateFontSizes()
+ updateTimeListeners()
+
+ weatherData?.let {
+ if (WeatherData.DEBUG) {
+ Log.i(TAG, "Pushing cached weather data to new clock: $it")
+ }
+ clock.events.onWeatherDataChanged(it)
+ }
+ zenData?.let {
+ clock.events.onZenDataChanged(it)
+ }
+ alarmData?.let {
+ clock.events.onAlarmDataChanged(it)
+ }
+
+ smallClockOnAttachStateChangeListener = object : OnAttachStateChangeListener {
+ var pastVisibility: Int? = null
+ override fun onViewAttachedToWindow(view: View) {
+ clock.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+ // Match the asing for view.parent's layout classes.
+ smallClockFrame = (view.parent as ViewGroup)?.also { frame ->
+ pastVisibility = frame.visibility
+ onGlobalLayoutListener = OnGlobalLayoutListener {
+ val currentVisibility = frame.visibility
+ if (pastVisibility != currentVisibility) {
+ pastVisibility = currentVisibility
+ // when small clock is visible,
+ // recalculate bounds and sample
+ if (currentVisibility == View.VISIBLE) {
+ smallRegionSampler?.stopRegionSampler()
+ smallRegionSampler?.startRegionSampler()
+ }
+ }
+ }
+ frame.viewTreeObserver.addOnGlobalLayoutListener(onGlobalLayoutListener)
+ }
+ }
+
+ override fun onViewDetachedFromWindow(p0: View) {
smallClockFrame?.viewTreeObserver
?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
}
- largeClockOnAttachStateChangeListener?.let {
- field?.largeClock?.view?.removeOnAttachStateChangeListener(it)
- }
-
- field = value
- if (value != null) {
- smallLogBuffer?.log(TAG, DEBUG, {}, { "New Clock" })
- value.smallClock.messageBuffer = smallLogBuffer
- largeLogBuffer?.log(TAG, DEBUG, {}, { "New Clock" })
- value.largeClock.messageBuffer = largeLogBuffer
-
- value.initialize(resources, dozeAmount, 0f)
-
- if (!regionSamplingEnabled) {
- updateColors()
- } else {
- clock?.let {
- smallRegionSampler = createRegionSampler(
- it.smallClock.view,
- mainExecutor,
- bgExecutor,
- regionSamplingEnabled,
- isLockscreen = true,
- ::updateColors
- )?.apply { startRegionSampler() }
-
- largeRegionSampler = createRegionSampler(
- it.largeClock.view,
- mainExecutor,
- bgExecutor,
- regionSamplingEnabled,
- isLockscreen = true,
- ::updateColors
- )?.apply { startRegionSampler() }
-
- updateColors()
- }
- }
- updateFontSizes()
- updateTimeListeners()
- weatherData?.let {
- if (WeatherData.DEBUG) {
- Log.i(TAG, "Pushing cached weather data to new clock: $it")
- }
- value.events.onWeatherDataChanged(it)
- }
- zenData?.let {
- value.events.onZenDataChanged(it)
- }
- alarmData?.let {
- value.events.onAlarmDataChanged(it)
- }
-
- smallClockOnAttachStateChangeListener =
- object : OnAttachStateChangeListener {
- var pastVisibility: Int? = null
- override fun onViewAttachedToWindow(view: View) {
- value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
- // Match the asing for view.parent's layout classes.
- smallClockFrame = view.parent as ViewGroup
- smallClockFrame?.let { frame ->
- pastVisibility = frame.visibility
- onGlobalLayoutListener = OnGlobalLayoutListener {
- val currentVisibility = frame.visibility
- if (pastVisibility != currentVisibility) {
- pastVisibility = currentVisibility
- // when small clock is visible,
- // recalculate bounds and sample
- if (currentVisibility == View.VISIBLE) {
- smallRegionSampler?.stopRegionSampler()
- smallRegionSampler?.startRegionSampler()
- }
- }
- }
- frame.viewTreeObserver
- .addOnGlobalLayoutListener(onGlobalLayoutListener)
- }
- }
-
- override fun onViewDetachedFromWindow(p0: View) {
- smallClockFrame?.viewTreeObserver
- ?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
- }
- }
- value.smallClock.view
- .addOnAttachStateChangeListener(smallClockOnAttachStateChangeListener)
-
- largeClockOnAttachStateChangeListener =
- object : OnAttachStateChangeListener {
- override fun onViewAttachedToWindow(p0: View) {
- value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
- }
- override fun onViewDetachedFromWindow(p0: View) {
- }
- }
- value.largeClock.view
- .addOnAttachStateChangeListener(largeClockOnAttachStateChangeListener)
- }
}
+ clock.smallClock.view.addOnAttachStateChangeListener(smallClockOnAttachStateChangeListener)
+
+ largeClockOnAttachStateChangeListener = object : OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(p0: View) {
+ clock.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+ }
+ override fun onViewDetachedFromWindow(p0: View) {}
+ }
+ clock.largeClock.view.addOnAttachStateChangeListener(largeClockOnAttachStateChangeListener)
+ }
@VisibleForTesting
var smallClockOnAttachStateChangeListener: OnAttachStateChangeListener? = null
@@ -247,6 +249,7 @@
largeClock.events.onRegionDarknessChanged(isRegionDark)
}
}
+
protected open fun createRegionSampler(
sampledView: View,
mainExecutor: Executor?,
@@ -254,7 +257,7 @@
regionSamplingEnabled: Boolean,
isLockscreen: Boolean,
updateColors: () -> Unit
- ): RegionSampler? {
+ ): RegionSampler {
return RegionSampler(
sampledView,
mainExecutor,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index cdd7b80..74b975c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -39,7 +39,6 @@
import androidx.annotation.VisibleForTesting;
import com.android.systemui.Dumpable;
-import com.android.systemui.common.ui.ConfigurationState;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
@@ -48,9 +47,7 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
-import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder;
import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager;
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel;
import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.core.LogLevel;
import com.android.systemui.log.dagger.KeyguardClockLog;
@@ -62,17 +59,11 @@
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore;
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder;
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker;
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerAlwaysOnDisplayViewBinder;
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
-import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
-import com.android.systemui.statusbar.ui.SystemBarUtilsState;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.settings.SecureSettings;
@@ -102,14 +93,7 @@
private final DumpManager mDumpManager;
private final ClockEventController mClockEventController;
private final LogBuffer mLogBuffer;
- private final NotificationIconContainerAlwaysOnDisplayViewModel mAodIconsViewModel;
- private final KeyguardRootViewModel mKeyguardRootViewModel;
- private final ConfigurationState mConfigurationState;
- private final SystemBarUtilsState mSystemBarUtilsState;
- private final DozeParameters mDozeParameters;
- private final ScreenOffAnimationController mScreenOffAnimationController;
- private final AlwaysOnDisplayNotificationIconViewStore mAodIconViewStore;
- private final StatusBarIconViewBindingFailureTracker mIconViewBindingFailureTracker;
+ private final NotificationIconContainerAlwaysOnDisplayViewBinder mNicViewBinder;
private FrameLayout mSmallClockFrame; // top aligned clock
private FrameLayout mLargeClockFrame; // centered clock
@@ -183,9 +167,7 @@
KeyguardSliceViewController keyguardSliceViewController,
NotificationIconAreaController notificationIconAreaController,
LockscreenSmartspaceController smartspaceController,
- SystemBarUtilsState systemBarUtilsState,
- ScreenOffAnimationController screenOffAnimationController,
- StatusBarIconViewBindingFailureTracker iconViewBindingFailureTracker,
+ NotificationIconContainerAlwaysOnDisplayViewBinder nicViewBinder,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
SecureSettings secureSettings,
@Main DelayableExecutor uiExecutor,
@@ -193,11 +175,6 @@
DumpManager dumpManager,
ClockEventController clockEventController,
@KeyguardClockLog LogBuffer logBuffer,
- NotificationIconContainerAlwaysOnDisplayViewModel aodIconsViewModel,
- KeyguardRootViewModel keyguardRootViewModel,
- ConfigurationState configurationState,
- DozeParameters dozeParameters,
- AlwaysOnDisplayNotificationIconViewStore aodIconViewStore,
KeyguardInteractor keyguardInteractor,
KeyguardClockInteractor keyguardClockInteractor,
FeatureFlagsClassic featureFlags,
@@ -208,9 +185,7 @@
mKeyguardSliceViewController = keyguardSliceViewController;
mNotificationIconAreaController = notificationIconAreaController;
mSmartspaceController = smartspaceController;
- mSystemBarUtilsState = systemBarUtilsState;
- mScreenOffAnimationController = screenOffAnimationController;
- mIconViewBindingFailureTracker = iconViewBindingFailureTracker;
+ mNicViewBinder = nicViewBinder;
mSecureSettings = secureSettings;
mUiExecutor = uiExecutor;
mBgExecutor = bgExecutor;
@@ -218,11 +193,6 @@
mDumpManager = dumpManager;
mClockEventController = clockEventController;
mLogBuffer = logBuffer;
- mAodIconsViewModel = aodIconsViewModel;
- mKeyguardRootViewModel = keyguardRootViewModel;
- mConfigurationState = configurationState;
- mDozeParameters = dozeParameters;
- mAodIconViewStore = aodIconViewStore;
mView.setLogBuffer(mLogBuffer);
mFeatureFlags = featureFlags;
mKeyguardInteractor = keyguardInteractor;
@@ -619,28 +589,7 @@
mAodIconsBindHandle.dispose();
}
if (nic != null) {
- final DisposableHandle viewHandle =
- NotificationIconContainerViewBinder.bindWhileAttached(
- nic,
- mAodIconsViewModel,
- mConfigurationState,
- mSystemBarUtilsState,
- mIconViewBindingFailureTracker,
- mAodIconViewStore);
- final DisposableHandle visHandle = KeyguardRootViewBinder.bindAodIconVisibility(
- nic,
- mKeyguardRootViewModel.isNotifIconContainerVisible(),
- mConfigurationState,
- mFeatureFlags,
- mScreenOffAnimationController);
- if (visHandle == null) {
- mAodIconsBindHandle = viewHandle;
- } else {
- mAodIconsBindHandle = () -> {
- viewHandle.dispose();
- visHandle.dispose();
- };
- }
+ mAodIconsBindHandle = mNicViewBinder.bindWhileAttached(nic);
mAodIconContainer = nic;
}
} else {
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
index 661ce2c..878a5d8 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -28,9 +28,8 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
-import com.android.systemui.log.LogBuffer;
-import com.android.systemui.log.dagger.KeyguardClockLog;
import com.android.systemui.plugins.PluginManager;
+import com.android.systemui.plugins.clocks.ClockMessageBuffers;
import com.android.systemui.res.R;
import com.android.systemui.shared.clocks.ClockRegistry;
import com.android.systemui.shared.clocks.DefaultClockProvider;
@@ -56,7 +55,7 @@
FeatureFlags featureFlags,
@Main Resources resources,
LayoutInflater layoutInflater,
- @KeyguardClockLog LogBuffer logBuffer) {
+ ClockMessageBuffers clockBuffers) {
ClockRegistry registry = new ClockRegistry(
context,
pluginManager,
@@ -72,7 +71,7 @@
featureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION),
migrateClocksToBlueprint()),
context.getString(R.string.lockscreen_clock_id_fallback),
- logBuffer,
+ clockBuffers,
/* keepAllLoaded = */ false,
/* subTag = */ "System",
/* isTransitClockEnabled = */ featureFlags.isEnabled(Flags.TRANSIT_CLOCK));
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index fda23b7f..f2b55f4 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -24,13 +24,13 @@
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockscreenCredential
import com.android.keyguard.KeyguardSecurityModel
-import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationResultModel
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.pairwise
@@ -43,9 +43,7 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
@@ -60,12 +58,6 @@
/** Defines interface for classes that can access authentication-related application state. */
interface AuthenticationRepository {
/**
- * Emits the result whenever a PIN/Pattern/Password security challenge is attempted by the user
- * in order to unlock the device.
- */
- val authenticationChallengeResult: SharedFlow<Boolean>
-
- /**
* The exact length a PIN should be for us to enable PIN length hinting.
*
* A PIN that's shorter or longer than this is not eligible for the UI to render hints showing
@@ -80,16 +72,6 @@
val isPatternVisible: StateFlow<Boolean>
/**
- * The current authentication lockout (aka "throttling") state, set when the user has to wait
- * before being able to try another authentication attempt. `null` indicates throttling isn't
- * active.
- */
- val lockout: MutableStateFlow<AuthenticationLockoutModel?>
-
- /** Whether throttling has occurred at least once since the last successful authentication. */
- val hasLockoutOccurred: MutableStateFlow<Boolean>
-
- /**
* Whether the auto confirm feature is enabled for the currently-selected user.
*
* Note that the length of the PIN is also important to take into consideration, please see
@@ -98,6 +80,28 @@
val isAutoConfirmFeatureEnabled: StateFlow<Boolean>
/**
+ * The number of failed authentication attempts for the selected user since their last
+ * successful authentication.
+ */
+ val failedAuthenticationAttempts: StateFlow<Int>
+
+ /**
+ * Timestamp for when the current lockout (aka "throttling") will end, allowing the user to
+ * attempt authentication again. Returns `null` if no lockout is active.
+ *
+ * Note that the value is in milliseconds and matches [SystemClock.elapsedRealtime].
+ *
+ * Also note that the value may change when the selected user is changed.
+ */
+ val lockoutEndTimestamp: Long?
+
+ /**
+ * Whether lockout has occurred at least once since the last successful authentication of any
+ * user.
+ */
+ val hasLockoutOccurred: StateFlow<Boolean>
+
+ /**
* The currently-configured authentication method. This determines how the authentication
* challenge needs to be completed in order to unlock an otherwise locked device.
*
@@ -142,23 +146,6 @@
/** Reports that the user has entered a temporary device lockout (throttling). */
suspend fun reportLockoutStarted(durationMs: Int)
- /** Returns the current number of failed authentication attempts. */
- suspend fun getFailedAuthenticationAttemptCount(): Int
-
- /**
- * Returns the timestamp for when the current lockout will end, allowing the user to attempt
- * authentication again.
- *
- * Note that this is in milliseconds and it matches [SystemClock.elapsedRealtime].
- */
- suspend fun getLockoutEndTimestamp(): Long
-
- /**
- * Sets the lockout timeout duration (time during which the user should not be allowed to
- * attempt authentication).
- */
- suspend fun setLockoutDuration(durationMs: Int)
-
/**
* Checks the given [LockscreenCredential] to see if it's correct, returning an
* [AuthenticationResultModel] representing what happened.
@@ -172,6 +159,8 @@
constructor(
@Application private val applicationScope: CoroutineScope,
@Background private val backgroundDispatcher: CoroutineDispatcher,
+ flags: SceneContainerFlags,
+ private val clock: SystemClock,
private val getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>,
private val userRepository: UserRepository,
private val lockPatternUtils: LockPatternUtils,
@@ -179,8 +168,6 @@
mobileConnectionsRepository: MobileConnectionsRepository,
) : AuthenticationRepository {
- override val authenticationChallengeResult = MutableSharedFlow<Boolean>()
-
override val hintedPinLength: Int = 6
override val isPatternVisible: StateFlow<Boolean> =
@@ -189,10 +176,6 @@
getFreshValue = lockPatternUtils::isVisiblePatternEnabled,
)
- override val lockout: MutableStateFlow<AuthenticationLockoutModel?> = MutableStateFlow(null)
-
- override val hasLockoutOccurred: MutableStateFlow<Boolean> = MutableStateFlow(false)
-
override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> =
refreshingFlow(
initialValue = false,
@@ -234,6 +217,31 @@
getFreshValue = { userId -> lockPatternUtils.isPinEnhancedPrivacyEnabled(userId) },
)
+ private val _failedAuthenticationAttempts = MutableStateFlow(0)
+ override val failedAuthenticationAttempts: StateFlow<Int> =
+ _failedAuthenticationAttempts.asStateFlow()
+
+ override val lockoutEndTimestamp: Long?
+ get() =
+ lockPatternUtils.getLockoutAttemptDeadline(selectedUserId).takeIf {
+ clock.elapsedRealtime() < it
+ }
+
+ private val _hasLockoutOccurred = MutableStateFlow(false)
+ override val hasLockoutOccurred: StateFlow<Boolean> = _hasLockoutOccurred.asStateFlow()
+
+ init {
+ if (flags.isEnabled()) {
+ // Hydrate failedAuthenticationAttempts initially and whenever the selected user
+ // changes.
+ applicationScope.launch {
+ userRepository.selectedUserInfo.collect {
+ _failedAuthenticationAttempts.value = getFailedAuthenticationAttemptCount()
+ }
+ }
+ }
+ }
+
override suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
return withContext(backgroundDispatcher) {
blockingAuthenticationMethodInternal(selectedUserId)
@@ -248,35 +256,20 @@
withContext(backgroundDispatcher) {
if (isSuccessful) {
lockPatternUtils.reportSuccessfulPasswordAttempt(selectedUserId)
+ _hasLockoutOccurred.value = false
} else {
lockPatternUtils.reportFailedPasswordAttempt(selectedUserId)
}
- authenticationChallengeResult.emit(isSuccessful)
+ _failedAuthenticationAttempts.value = getFailedAuthenticationAttemptCount()
}
}
override suspend fun reportLockoutStarted(durationMs: Int) {
- return withContext(backgroundDispatcher) {
+ lockPatternUtils.setLockoutAttemptDeadline(selectedUserId, durationMs)
+ withContext(backgroundDispatcher) {
lockPatternUtils.reportPasswordLockout(durationMs, selectedUserId)
}
- }
-
- override suspend fun getFailedAuthenticationAttemptCount(): Int {
- return withContext(backgroundDispatcher) {
- lockPatternUtils.getCurrentFailedPasswordAttempts(selectedUserId)
- }
- }
-
- override suspend fun getLockoutEndTimestamp(): Long {
- return withContext(backgroundDispatcher) {
- lockPatternUtils.getLockoutAttemptDeadline(selectedUserId)
- }
- }
-
- override suspend fun setLockoutDuration(durationMs: Int) {
- withContext(backgroundDispatcher) {
- lockPatternUtils.setLockoutAttemptDeadline(selectedUserId, durationMs)
- }
+ _hasLockoutOccurred.value = true
}
override suspend fun checkCredential(
@@ -292,6 +285,12 @@
}
}
+ private suspend fun getFailedAuthenticationAttemptCount(): Int {
+ return withContext(backgroundDispatcher) {
+ lockPatternUtils.getCurrentFailedPasswordAttempts(selectedUserId)
+ }
+ }
+
private val selectedUserId: Int
get() = userRepository.getSelectedUserInfo().id
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index 797154e..c85ffe6 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -16,36 +16,25 @@
package com.android.systemui.authentication.domain.interactor
-import com.android.app.tracing.TraceUtils.Companion.withContext
import com.android.internal.widget.LockPatternView
import com.android.internal.widget.LockscreenCredential
import com.android.systemui.authentication.data.repository.AuthenticationRepository
-import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
-import kotlin.math.ceil
-import kotlin.math.max
-import kotlin.time.Duration.Companion.seconds
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.async
-import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
/**
* Hosts application business logic related to user authentication.
@@ -59,10 +48,7 @@
@Inject
constructor(
@Application private val applicationScope: CoroutineScope,
- @Background private val backgroundDispatcher: CoroutineDispatcher,
private val repository: AuthenticationRepository,
- private val userRepository: UserRepository,
- private val clock: SystemClock,
) {
/**
* The currently-configured authentication method. This determines how the authentication
@@ -85,13 +71,6 @@
val authenticationMethod: Flow<AuthenticationMethodModel> = repository.authenticationMethod
/**
- * The current authentication lockout (aka "throttling") state, set when the user has to wait
- * before being able to try another authentication attempt. `null` indicates lockout isn't
- * active.
- */
- val lockout: StateFlow<AuthenticationLockoutModel?> = repository.lockout
-
- /**
* Whether the auto confirm feature is enabled for the currently-selected user.
*
* Note that the length of the PIN is also important to take into consideration, please see
@@ -130,26 +109,35 @@
/** Whether the pattern should be visible for the currently-selected user. */
val isPatternVisible: StateFlow<Boolean> = repository.isPatternVisible
+ private val _onAuthenticationResult = MutableSharedFlow<Boolean>()
/**
* Emits the outcome (successful or unsuccessful) whenever a PIN/Pattern/Password security
* challenge is attempted by the user in order to unlock the device.
*/
- val authenticationChallengeResult: SharedFlow<Boolean> =
- repository.authenticationChallengeResult
+ val onAuthenticationResult: SharedFlow<Boolean> = _onAuthenticationResult.asSharedFlow()
/** Whether the "enhanced PIN privacy" setting is enabled for the current user. */
val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> = repository.isPinEnhancedPrivacyEnabled
- private var lockoutCountdownJob: Job? = null
+ /**
+ * The number of failed authentication attempts for the selected user since the last successful
+ * authentication.
+ */
+ val failedAuthenticationAttempts: StateFlow<Int> = repository.failedAuthenticationAttempts
- init {
- applicationScope.launch {
- userRepository.selectedUserInfo
- .map { it.id }
- .distinctUntilChanged()
- .collect { onSelectedUserChanged() }
- }
- }
+ /**
+ * Timestamp for when the current lockout (aka "throttling") will end, allowing the user to
+ * attempt authentication again. Returns `null` if no lockout is active.
+ *
+ * To be notified whenever a lockout is started, the caller should subscribe to
+ * [onAuthenticationResult].
+ *
+ * Note that the value is in milliseconds and matches [SystemClock.elapsedRealtime].
+ *
+ * Also note that the value may change when the selected user is changed.
+ */
+ val lockoutEndTimestamp: Long?
+ get() = repository.lockoutEndTimestamp
/**
* Returns the currently-configured authentication method. This determines how the
@@ -190,7 +178,7 @@
val skipCheck =
when {
// Lockout is active, the UI layer should not have called this; skip the attempt.
- lockout.value != null -> true
+ repository.lockoutEndTimestamp != null -> true
// The input is too short; skip the attempt.
input.isTooShort(authMethod) -> true
// Auto-confirm attempt when the feature is not enabled; skip the attempt.
@@ -211,27 +199,16 @@
credential.zeroize()
if (authenticationResult.isSuccessful || !tryAutoConfirm) {
- repository.reportAuthenticationAttempt(
- isSuccessful = authenticationResult.isSuccessful,
- )
+ repository.reportAuthenticationAttempt(authenticationResult.isSuccessful)
}
// Check if lockout should start and, if so, kick off the countdown:
if (!authenticationResult.isSuccessful && authenticationResult.lockoutDurationMs > 0) {
- repository.apply {
- setLockoutDuration(durationMs = authenticationResult.lockoutDurationMs)
- reportLockoutStarted(durationMs = authenticationResult.lockoutDurationMs)
- hasLockoutOccurred.value = true
- }
- startLockoutCountdown()
+ repository.reportLockoutStarted(authenticationResult.lockoutDurationMs)
}
- if (authenticationResult.isSuccessful) {
- // Since authentication succeeded, refresh lockout to make sure the state is completely
- // reflecting the upstream source of truth.
- refreshLockout()
-
- repository.hasLockoutOccurred.value = false
+ if (authenticationResult.isSuccessful || !tryAutoConfirm) {
+ _onAuthenticationResult.emit(authenticationResult.isSuccessful)
}
return if (authenticationResult.isSuccessful) {
@@ -249,54 +226,6 @@
}
}
- /** Starts refreshing the lockout state every second. */
- private suspend fun startLockoutCountdown() {
- cancelLockoutCountdown()
- lockoutCountdownJob =
- applicationScope.launch {
- while (refreshLockout()) {
- delay(1.seconds.inWholeMilliseconds)
- }
- }
- }
-
- /** Cancels any lockout state countdown started in [startLockoutCountdown]. */
- private fun cancelLockoutCountdown() {
- lockoutCountdownJob?.cancel()
- lockoutCountdownJob = null
- }
-
- /** Notifies that the currently-selected user has changed. */
- private suspend fun onSelectedUserChanged() {
- cancelLockoutCountdown()
- if (refreshLockout()) {
- startLockoutCountdown()
- }
- }
-
- /**
- * Refreshes the lockout state, hydrating the repository with the latest state.
- *
- * @return Whether lockout is active or not.
- */
- private suspend fun refreshLockout(): Boolean {
- withContext("$TAG#refreshLockout", backgroundDispatcher) {
- val failedAttemptCount = async { repository.getFailedAuthenticationAttemptCount() }
- val deadline = async { repository.getLockoutEndTimestamp() }
- val remainingMs = max(0, deadline.await() - clock.elapsedRealtime())
- repository.lockout.value =
- if (remainingMs > 0) {
- AuthenticationLockoutModel(
- failedAttemptCount = failedAttemptCount.await(),
- remainingSeconds = ceil(remainingMs / 1000f).toInt(),
- )
- } else {
- null // Lockout ended.
- }
- }
- return repository.lockout.value != null
- }
-
private fun AuthenticationMethodModel.createCredential(
input: List<Any>
): LockscreenCredential? {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index ab23564..83d415f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -403,6 +403,13 @@
final BiometricPromptLayout view = (BiometricPromptLayout) layoutInflater.inflate(
R.layout.biometric_prompt_layout, null, false);
+ /**
+ * View is only set visible in BiometricViewSizeBinder once PromptSize is determined
+ * that accounts for iconView size, to prevent prompt resizing being visible to the
+ * user.
+ * TODO(b/288175072): May be able to remove this once constraint layout is implemented
+ */
+ view.setVisibility(View.INVISIBLE);
mBiometricView = BiometricViewBinder.bind(view, viewModel, mPanelController,
// TODO(b/201510778): This uses the wrong timeout in some cases
getJankListener(view, TRANSIT,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
index 66fb8ca..7d9ec08 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
@@ -23,16 +23,14 @@
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.animation.Interpolators
import com.android.systemui.Dumpable
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.StatusBarState.SHADE
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.util.ViewController
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import java.io.PrintWriter
@@ -49,7 +47,7 @@
abstract class UdfpsAnimationViewController<T : UdfpsAnimationView>(
view: T,
protected val statusBarStateController: StatusBarStateController,
- protected val primaryBouncerInteractor: PrimaryBouncerInteractor,
+ protected val shadeInteractor: ShadeInteractor,
protected val dialogManager: SystemUIDialogManager,
private val dumpManager: DumpManager
) : ViewController<T>(view), Dumpable {
@@ -94,20 +92,18 @@
// can make the view not visible; and we still want to listen for events
// that may make the view visible again.
repeatOnLifecycle(Lifecycle.State.CREATED) {
- listenForBouncerExpansion(this)
+ listenForShadeExpansion(this)
}
}
}
@VisibleForTesting
- open suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
+ suspend fun listenForShadeExpansion(scope: CoroutineScope): Job {
return scope.launch {
- primaryBouncerInteractor.bouncerExpansion.map { 1f - it }.collect { expansion: Float ->
- if (statusBarStateController.state != SHADE) {
- notificationShadeVisible = expansion > 0f
- view.onExpansionChanged(expansion)
- updatePauseAuth()
- }
+ shadeInteractor.anyExpansion.collect { expansion ->
+ notificationShadeVisible = expansion > 0f
+ view.onExpansionChanged(expansion)
+ updatePauseAuth()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
index 03749a9..e7b0d9f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
@@ -15,9 +15,9 @@
*/
package com.android.systemui.biometrics
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.phone.SystemUIDialogManager
/**
@@ -26,13 +26,13 @@
class UdfpsBpViewController(
view: UdfpsBpView,
statusBarStateController: StatusBarStateController,
- primaryBouncerInteractor: PrimaryBouncerInteractor,
+ shadeInteractor: ShadeInteractor,
systemUIDialogManager: SystemUIDialogManager,
dumpManager: DumpManager
) : UdfpsAnimationViewController<UdfpsBpView>(
view,
statusBarStateController,
- primaryBouncerInteractor,
+ shadeInteractor,
systemUIDialogManager,
dumpManager
) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 65668b5..2fd13b3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -86,11 +86,10 @@
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter;
-import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.VibratorHelper;
@@ -115,7 +114,6 @@
import java.util.concurrent.Executor;
import javax.inject.Inject;
-import javax.inject.Provider;
import kotlinx.coroutines.ExperimentalCoroutinesApi;
@@ -152,7 +150,6 @@
@NonNull private final SystemUIDialogManager mDialogManager;
@NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@NonNull private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
- @NonNull private final Provider<UdfpsKeyguardViewModels> mUdfpsKeyguardViewModels;
@NonNull private final VibratorHelper mVibrator;
@NonNull private final FalsingManager mFalsingManager;
@NonNull private final PowerManager mPowerManager;
@@ -166,6 +163,7 @@
@VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener;
@NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator;
@NonNull private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
+ @NonNull private final ShadeInteractor mShadeInteractor;
@Nullable private final TouchProcessor mTouchProcessor;
@NonNull private final SessionTracker mSessionTracker;
@NonNull private final Lazy<DeviceEntryUdfpsTouchOverlayViewModel>
@@ -294,7 +292,8 @@
mKeyguardTransitionInteractor,
mSelectedUserInteractor,
mDeviceEntryUdfpsTouchOverlayViewModel,
- mDefaultUdfpsTouchOverlayViewModel
+ mDefaultUdfpsTouchOverlayViewModel,
+ mShadeInteractor
)));
}
@@ -623,7 +622,7 @@
} else {
onKeyguard = mOverlay != null
&& mOverlay.getAnimationViewController()
- instanceof UdfpsKeyguardViewControllerAdapter;
+ instanceof UdfpsKeyguardViewControllerLegacy;
}
return onKeyguard
&& mKeyguardStateController.canDismissLockScreen()
@@ -660,13 +659,13 @@
@NonNull ActivityLaunchAnimator activityLaunchAnimator,
@NonNull @BiometricsBackground Executor biometricsExecutor,
@NonNull PrimaryBouncerInteractor primaryBouncerInteractor,
+ @NonNull ShadeInteractor shadeInteractor,
@NonNull SinglePointerTouchProcessor singlePointerTouchProcessor,
@NonNull SessionTracker sessionTracker,
@NonNull AlternateBouncerInteractor alternateBouncerInteractor,
@NonNull InputManager inputManager,
@NonNull KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
@NonNull UdfpsKeyguardAccessibilityDelegate udfpsKeyguardAccessibilityDelegate,
- @NonNull Provider<UdfpsKeyguardViewModels> udfpsKeyguardViewModelsProvider,
@NonNull SelectedUserInteractor selectedUserInteractor,
@NonNull FpsUnlockTracker fpsUnlockTracker,
@NonNull KeyguardTransitionInteractor keyguardTransitionInteractor,
@@ -710,6 +709,7 @@
mBiometricExecutor = biometricsExecutor;
mPrimaryBouncerInteractor = primaryBouncerInteractor;
+ mShadeInteractor = shadeInteractor;
mAlternateBouncerInteractor = alternateBouncerInteractor;
mInputManager = inputManager;
mUdfpsKeyguardAccessibilityDelegate = udfpsKeyguardAccessibilityDelegate;
@@ -737,7 +737,6 @@
return Unit.INSTANCE;
});
mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
- mUdfpsKeyguardViewModels = udfpsKeyguardViewModelsProvider;
final UdfpsOverlayController mUdfpsOverlayController = new UdfpsOverlayController();
mFingerprintManager.setUdfpsOverlayController(mUdfpsOverlayController);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index dae6d08..b94a177 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -55,9 +55,9 @@
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
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.LockscreenShadeTransitionController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.phone.SystemUIDialogManager
@@ -108,6 +108,7 @@
private val selectedUserInteractor: SelectedUserInteractor,
private val deviceEntryUdfpsTouchOverlayViewModel: Lazy<DeviceEntryUdfpsTouchOverlayViewModel>,
private val defaultUdfpsTouchOverlayViewModel: Lazy<DefaultUdfpsTouchOverlayViewModel>,
+ private val shadeInteractor: ShadeInteractor,
) {
private var overlayViewLegacy: UdfpsView? = null
private set
@@ -278,7 +279,7 @@
updateAccessibilityViewLocation(sensorBounds)
},
statusBarStateController,
- primaryBouncerInteractor,
+ shadeInteractor,
dialogManager,
dumpManager
)
@@ -304,6 +305,7 @@
udfpsKeyguardAccessibilityDelegate,
selectedUserInteractor,
transitionInteractor,
+ shadeInteractor,
)
}
REASON_AUTH_BP -> {
@@ -311,7 +313,7 @@
UdfpsBpViewController(
view.addUdfpsView(R.layout.udfps_bp_view),
statusBarStateController,
- primaryBouncerInteractor,
+ shadeInteractor,
dialogManager,
dumpManager
)
@@ -321,7 +323,7 @@
UdfpsFpmEmptyViewController(
view.addUdfpsView(R.layout.udfps_fpm_empty_view),
statusBarStateController,
- primaryBouncerInteractor,
+ shadeInteractor,
dialogManager,
dumpManager
)
@@ -438,7 +440,7 @@
if (DeviceEntryUdfpsRefactor.isEnabled) {
!keyguardStateController.isShowing
} else {
- animation !is UdfpsKeyguardViewControllerAdapter
+ animation !is UdfpsKeyguardViewControllerLegacy
}
if (keyguardNotShowing) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt
index 88002e7..ab3fbb1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt
@@ -15,9 +15,9 @@
*/
package com.android.systemui.biometrics
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.phone.SystemUIDialogManager
/**
@@ -28,13 +28,13 @@
class UdfpsFpmEmptyViewController(
view: UdfpsFpmEmptyView,
statusBarStateController: StatusBarStateController,
- primaryBouncerInteractor: PrimaryBouncerInteractor,
+ shadeInteractor: ShadeInteractor,
systemUIDialogManager: SystemUIDialogManager,
dumpManager: DumpManager
) : UdfpsAnimationViewController<UdfpsFpmEmptyView>(
view,
statusBarStateController,
- primaryBouncerInteractor,
+ shadeInteractor,
systemUIDialogManager,
dumpManager
) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index 63fe26a..9f17024 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -33,10 +33,10 @@
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.adapter.UdfpsKeyguardViewControllerAdapter
import com.android.systemui.lifecycle.repeatWhenAttached
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.LockscreenShadeTransitionController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
@@ -69,20 +69,20 @@
systemUIDialogManager: SystemUIDialogManager,
private val udfpsController: UdfpsController,
private val activityLaunchAnimator: ActivityLaunchAnimator,
- primaryBouncerInteractor: PrimaryBouncerInteractor,
+ private val primaryBouncerInteractor: PrimaryBouncerInteractor,
private val alternateBouncerInteractor: AlternateBouncerInteractor,
private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
private val selectedUserInteractor: SelectedUserInteractor,
private val transitionInteractor: KeyguardTransitionInteractor,
+ shadeInteractor: ShadeInteractor,
) :
UdfpsAnimationViewController<UdfpsKeyguardViewLegacy>(
view,
statusBarStateController,
- primaryBouncerInteractor,
+ shadeInteractor,
systemUIDialogManager,
dumpManager,
- ),
- UdfpsKeyguardViewControllerAdapter {
+ ) {
private val uniqueIdentifier = this.toString()
private var showingUdfpsBouncer = false
private var udfpsRequested = false
@@ -199,11 +199,27 @@
listenForAodToOccludedTransitions(this)
listenForAlternateBouncerToAodTransitions(this)
listenForDreamingToAodTransitions(this)
+ listenForPrimaryBouncerToAodTransitions(this)
}
}
}
@VisibleForTesting
+ suspend fun listenForPrimaryBouncerToAodTransitions(scope: CoroutineScope): Job {
+ return scope.launch {
+ transitionInteractor
+ .transition(KeyguardState.PRIMARY_BOUNCER, KeyguardState.AOD)
+ .collect { transitionStep ->
+ view.onDozeAmountChanged(
+ transitionStep.value,
+ transitionStep.value,
+ ANIMATE_APPEAR_ON_SCREEN_OFF,
+ )
+ }
+ }
+ }
+
+ @VisibleForTesting
suspend fun listenForDreamingToAodTransitions(scope: CoroutineScope): Job {
return scope.launch {
transitionInteractor.transition(KeyguardState.DREAMING, KeyguardState.AOD).collect {
@@ -305,7 +321,7 @@
}
@VisibleForTesting
- override suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
+ suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
return scope.launch {
primaryBouncerInteractor.bouncerExpansion.collect { bouncerExpansion: Float ->
inputBouncerExpansion = bouncerExpansion
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 38043b4..f4a2811 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
@@ -16,6 +16,7 @@
package com.android.systemui.biometrics.domain.interactor
+import android.content.Context
import android.view.MotionEvent
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
@@ -23,12 +24,15 @@
import com.android.systemui.common.coroutine.ConflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.res.R
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
/** Encapsulates business logic for interacting with the UDFPS overlay. */
@@ -36,10 +40,12 @@
class UdfpsOverlayInteractor
@Inject
constructor(
+ @Application context: Context,
private val authController: AuthController,
private val selectedUserInteractor: SelectedUserInteractor,
@Application scope: CoroutineScope
) {
+ private val iconSize: Int = context.resources.getDimensionPixelSize(R.dimen.udfps_icon_size)
/** Whether a touch is within the under-display fingerprint sensor area */
fun isTouchWithinUdfpsArea(ev: MotionEvent): Boolean {
@@ -70,6 +76,14 @@
}
.stateIn(scope, started = SharingStarted.Eagerly, initialValue = UdfpsOverlayParams())
+ // Padding between the fingerprint icon and its bounding box in pixels.
+ val iconPadding: Flow<Int> =
+ udfpsOverlayParams.map { params ->
+ val sensorWidth = params.nativeSensorBounds.right - params.nativeSensorBounds.left
+ val nativePadding = (sensorWidth - iconSize) / 2
+ (nativePadding * params.scaleFactor).toInt()
+ }
+
companion object {
private const val TAG = "UdfpsOverlayInteractor"
}
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 90e4a38..a7fb6f7 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
@@ -97,7 +97,13 @@
val iconOverlayView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
val iconView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon)
-
+ /**
+ * View is only set visible in BiometricViewSizeBinder once PromptSize is determined that
+ * accounts for iconView size, to prevent prompt resizing being visible to the user.
+ *
+ * TODO(b/288175072): May be able to remove this once constraint layout is implemented
+ */
+ iconView.addLottieOnCompositionLoadedListener { viewModel.setIsIconViewLoaded(true) }
PromptIconViewBinder.bind(
iconView,
iconOverlayView,
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 7e16d1e..f340bd8 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
@@ -30,7 +30,6 @@
import androidx.core.view.doOnLayout
import androidx.core.view.isGone
import androidx.lifecycle.lifecycleScope
-import com.android.systemui.res.R
import com.android.systemui.biometrics.AuthPanelController
import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.ui.BiometricPromptLayout
@@ -41,6 +40,8 @@
import com.android.systemui.biometrics.ui.viewmodel.isNullOrNotSmall
import com.android.systemui.biometrics.ui.viewmodel.isSmall
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
/** Helper for [BiometricViewBinder] to handle resize transitions. */
@@ -92,8 +93,22 @@
// TODO(b/251476085): migrate the legacy panel controller and simplify this
view.repeatWhenAttached {
var currentSize: PromptSize? = null
+
lifecycleScope.launch {
- viewModel.size.collect { size ->
+ /**
+ * View is only set visible in BiometricViewSizeBinder once PromptSize is
+ * determined that accounts for iconView size, to prevent prompt resizing being
+ * visible to the user.
+ *
+ * TODO(b/288175072): May be able to remove isIconViewLoaded once constraint
+ * layout is implemented
+ */
+ combine(viewModel.isIconViewLoaded, viewModel.size, ::Pair).collect {
+ (isIconViewLoaded, size) ->
+ if (!isIconViewLoaded) {
+ return@collect
+ }
+
// prepare for animated size transitions
for (v in viewsToHideWhenSmall) {
v.showTextOrHide(forceHide = size.isSmall)
@@ -196,8 +211,9 @@
}
}
}
-
currentSize = size
+ view.visibility = View.VISIBLE
+ viewModel.setIsIconViewLoaded(false)
notifyAccessibilityChanged()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt
deleted file mode 100644
index 6f4e1a3..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt
+++ /dev/null
@@ -1,78 +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.biometrics.ui.controller
-
-import com.android.systemui.biometrics.UdfpsAnimationViewController
-import com.android.systemui.biometrics.UdfpsKeyguardView
-import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
-import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.phone.SystemUIDialogManager
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-
-/** Class that coordinates non-HBM animations during keyguard authentication. */
-@ExperimentalCoroutinesApi
-open class UdfpsKeyguardViewController(
- val view: UdfpsKeyguardView,
- statusBarStateController: StatusBarStateController,
- primaryBouncerInteractor: PrimaryBouncerInteractor,
- systemUIDialogManager: SystemUIDialogManager,
- dumpManager: DumpManager,
- private val alternateBouncerInteractor: AlternateBouncerInteractor,
- udfpsKeyguardViewModels: UdfpsKeyguardViewModels,
-) :
- UdfpsAnimationViewController<UdfpsKeyguardView>(
- view,
- statusBarStateController,
- primaryBouncerInteractor,
- systemUIDialogManager,
- dumpManager,
- ),
- UdfpsKeyguardViewControllerAdapter {
- private val uniqueIdentifier = this.toString()
- override val tag: String
- get() = TAG
-
- init {
- udfpsKeyguardViewModels.bindViews(view)
- }
-
- public override fun onViewAttached() {
- super.onViewAttached()
- alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, uniqueIdentifier)
- }
-
- public override fun onViewDetached() {
- super.onViewDetached()
- alternateBouncerInteractor.setAlternateBouncerUIAvailable(false, uniqueIdentifier)
- }
-
- override fun shouldPauseAuth(): Boolean {
- return !view.isVisible()
- }
-
- override fun listenForTouchesOutsideView(): Boolean {
- return true
- }
-
- companion object {
- private const val TAG = "UdfpsKeyguardViewController"
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModel.kt
index c19ea19..7106674 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.biometrics.ui.viewmodel
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.statusbar.phone.hideAffordancesRequest
@@ -26,22 +27,30 @@
/**
* View model for the UdfpsTouchOverlay for when UDFPS is being requested for device entry. Handles
- * touches as long as the device entry views are visible.
+ * touches as long as the device entry view is visible (the lockscreen or the alternate bouncer
+ * view).
*/
@ExperimentalCoroutinesApi
class DeviceEntryUdfpsTouchOverlayViewModel
@Inject
constructor(
deviceEntryIconViewModel: DeviceEntryIconViewModel,
+ alternateBouncerInteractor: AlternateBouncerInteractor,
systemUIDialogManager: SystemUIDialogManager,
) : UdfpsTouchOverlayViewModel {
- // TODO (b/305234447): AlternateBouncer showing overrides sysuiDialogHideAffordancesRequest
- override val shouldHandleTouches: Flow<Boolean> =
+ private val showingUdfpsAffordance: Flow<Boolean> =
combine(
deviceEntryIconViewModel.deviceEntryViewAlpha,
+ alternateBouncerInteractor.isVisible,
+ ) { deviceEntryViewAlpha, alternateBouncerVisible ->
+ deviceEntryViewAlpha > ALLOW_TOUCH_ALPHA_THRESHOLD || alternateBouncerVisible
+ }
+ override val shouldHandleTouches: Flow<Boolean> =
+ combine(
+ showingUdfpsAffordance,
systemUIDialogManager.hideAffordancesRequest,
- ) { deviceEntryViewAlpha, dialogRequestingHideAffordances ->
- deviceEntryViewAlpha > ALLOW_TOUCH_ALPHA_THRESHOLD && !dialogRequestingHideAffordances
+ ) { showingUdfpsAffordance, dialogRequestingHideAffordances ->
+ showingUdfpsAffordance && !dialogRequestingHideAffordances
}
companion object {
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 6d0a58e..d899827e 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
@@ -192,6 +192,28 @@
val iconViewModel: PromptIconViewModel =
PromptIconViewModel(this, displayStateInteractor, promptSelectorInteractor)
+ private val _isIconViewLoaded = MutableStateFlow(false)
+
+ /**
+ * For prompts with an iconView, false until the prompt's iconView animation has been loaded in
+ * the view, otherwise true by default. Used for BiometricViewSizeBinder to wait for the icon
+ * asset to be loaded before determining the prompt size.
+ */
+ val isIconViewLoaded: Flow<Boolean> =
+ combine(credentialKind, _isIconViewLoaded.asStateFlow()) { credentialKind, isIconViewLoaded
+ ->
+ if (credentialKind is PromptKind.Biometric) {
+ isIconViewLoaded
+ } else {
+ true
+ }
+ }
+
+ // Sets whether the prompt's iconView animation has been loaded in the view yet.
+ fun setIsIconViewLoaded(iconViewLoaded: Boolean) {
+ _isIconViewLoaded.value = iconViewLoaded
+ }
+
/** Padding for prompt UI elements */
val promptPadding: Flow<Rect> =
combine(size, displayStateInteractor.currentRotation) { size, rotation ->
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
index 84f7dd5..af32eb5 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
@@ -29,9 +29,10 @@
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
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
/** Encapsulates business logic for interacting with the lock-screen alternate bouncer. */
@SysUISingleton
@@ -52,19 +53,13 @@
private val alternateBouncerUiAvailableFromSource: HashSet<String> = HashSet()
private val alternateBouncerSupported: StateFlow<Boolean> =
if (DeviceEntryUdfpsRefactor.isEnabled) {
- // The device entry udfps refactor doesn't currently support the alternate bouncer.
- // TODO: Re-enable when b/287599719 is ready.
- MutableStateFlow(false).asStateFlow()
- // fingerprintPropertyRepository.sensorType
- // .map { sensorType ->
- // sensorType.isUdfps() || sensorType ==
- // FingerprintSensorType.POWER_BUTTON
- // }
- // .stateIn(
- // scope = scope,
- // started = SharingStarted.Eagerly,
- // initialValue = false,
- // )
+ fingerprintPropertyRepository.sensorType
+ .map { sensorType -> sensorType.isUdfps() || sensorType.isPowerButton() }
+ .stateIn(
+ scope = scope,
+ started = SharingStarted.Eagerly,
+ initialValue = false,
+ )
} else {
bouncerRepository.alternateBouncerUIAvailable
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index 724c0fe..1095abe 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -19,7 +19,6 @@
import android.content.Context
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.domain.interactor.AuthenticationResult
-import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.data.repository.BouncerRepository
import com.android.systemui.classifier.FalsingClassifier
@@ -29,17 +28,15 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
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.util.kotlin.pairwise
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
-import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
/** Encapsulates business logic and application state accessing use-cases. */
@@ -52,33 +49,12 @@
private val repository: BouncerRepository,
private val authenticationInteractor: AuthenticationInteractor,
private val keyguardFaceAuthInteractor: KeyguardFaceAuthInteractor,
- flags: SceneContainerFlags,
private val falsingInteractor: FalsingInteractor,
private val powerInteractor: PowerInteractor,
private val simBouncerInteractor: SimBouncerInteractor,
) {
-
- /** The user-facing message to show in the bouncer. */
- val message: StateFlow<String?> =
- combine(repository.message, authenticationInteractor.lockout) { message, lockout ->
- messageOrLockoutMessage(message, lockout)
- }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue =
- messageOrLockoutMessage(
- repository.message.value,
- authenticationInteractor.lockout.value,
- )
- )
-
- /**
- * The current authentication lockout (aka "throttling") state, set when the user has to wait
- * before being able to try another authentication attempt. `null` indicates lockout isn't
- * active.
- */
- val lockout: StateFlow<AuthenticationLockoutModel?> = authenticationInteractor.lockout
+ /** The user-facing message to show in the bouncer when lockout is not active. */
+ val message: StateFlow<String?> = repository.message
/** Whether the auto confirm feature is enabled for the currently-selected user. */
val isAutoConfirmEnabled: StateFlow<Boolean> = authenticationInteractor.isAutoConfirmEnabled
@@ -101,18 +77,13 @@
/** Emits a [Unit] each time the IME (keyboard) is hidden by the user. */
val onImeHiddenByUser: SharedFlow<Unit> = _onImeHiddenByUser
- init {
- if (flags.isEnabled()) {
- // Clear the message if moved from locked-out to no-longer locked-out.
- applicationScope.launch {
- lockout.pairwise().collect { (previous, current) ->
- if (previous != null && current == null) {
- clearMessage()
- }
- }
+ /** Emits a [Unit] each time a lockout is started for the selected user. */
+ val onLockoutStarted: Flow<Unit> =
+ authenticationInteractor.onAuthenticationResult
+ .filter { successfullyAuthenticated ->
+ !successfullyAuthenticated && authenticationInteractor.lockoutEndTimestamp != null
}
- }
- }
+ .map {}
/** Notifies that the user has places down a pointer, not necessarily dragging just yet. */
fun onDown() {
@@ -188,7 +159,7 @@
}
if (authenticationInteractor.getAuthenticationMethod() == AuthenticationMethodModel.Sim) {
- // We authenticate sim in SimInteractor
+ // SIM is authenticated in SimBouncerInteractor.
return AuthenticationResult.SKIPPED
}
@@ -196,18 +167,20 @@
// view-models, whose lifecycle (and thus scope) is shorter than this interactor.
// This allows the task to continue running properly even when the calling scope has been
// cancelled.
- return applicationScope
- .async {
- val authResult = authenticationInteractor.authenticate(input, tryAutoConfirm)
- if (
- authResult == AuthenticationResult.FAILED ||
- (authResult == AuthenticationResult.SKIPPED && !tryAutoConfirm)
- ) {
- showErrorMessage()
- }
- authResult
- }
- .await()
+ val authResult =
+ applicationScope
+ .async { authenticationInteractor.authenticate(input, tryAutoConfirm) }
+ .await()
+
+ if (authenticationInteractor.lockoutEndTimestamp != null) {
+ clearMessage()
+ } else if (
+ authResult == AuthenticationResult.FAILED ||
+ (authResult == AuthenticationResult.SKIPPED && !tryAutoConfirm)
+ ) {
+ showErrorMessage()
+ }
+ return authResult
}
/**
@@ -250,19 +223,4 @@
else -> ""
}
}
-
- private fun messageOrLockoutMessage(
- message: String?,
- lockoutModel: AuthenticationLockoutModel?,
- ): String {
- return when {
- lockoutModel != null ->
- applicationContext.getString(
- com.android.internal.R.string.lockscreen_too_many_failed_attempts_countdown,
- lockoutModel.remainingSeconds,
- )
- message != null -> message
- else -> ""
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
index 4b14343..be6cf85 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -19,6 +19,7 @@
import android.content.Context
import android.graphics.Bitmap
import androidx.core.graphics.drawable.toBitmap
+import com.android.internal.R
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
@@ -34,19 +35,24 @@
import com.android.systemui.user.ui.viewmodel.UserActionViewModel
import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
import com.android.systemui.user.ui.viewmodel.UserViewModel
+import com.android.systemui.util.time.SystemClock
import dagger.Module
import dagger.Provides
+import kotlin.math.ceil
+import kotlin.math.max
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.job
@@ -58,13 +64,14 @@
@Application private val applicationScope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
private val bouncerInteractor: BouncerInteractor,
- authenticationInteractor: AuthenticationInteractor,
+ private val authenticationInteractor: AuthenticationInteractor,
flags: SceneContainerFlags,
selectedUser: Flow<UserViewModel>,
users: Flow<List<UserViewModel>>,
userSwitcherMenu: Flow<List<UserActionViewModel>>,
actionButtonInteractor: BouncerActionButtonInteractor,
private val simBouncerInteractor: SimBouncerInteractor,
+ private val clock: SystemClock,
) {
val selectedUserImage: StateFlow<Bitmap?> =
selectedUser
@@ -104,15 +111,6 @@
val isUserSwitcherVisible: Boolean
get() = bouncerInteractor.isUserSwitcherVisible
- private val isInputEnabled: StateFlow<Boolean> =
- bouncerInteractor.lockout
- .map { it == null }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = bouncerInteractor.lockout.value == null,
- )
-
// Handle to the scope of the child ViewModel (stored in [authMethod]).
private var childViewModelScope: CoroutineScope? = null
private val _dialogMessage = MutableStateFlow<String?>(null)
@@ -138,19 +136,22 @@
*/
val dialogMessage: StateFlow<String?> = _dialogMessage.asStateFlow()
+ /**
+ * A message shown when the user has attempted the wrong credential too many times and now must
+ * wait a while before attempting to authenticate again.
+ *
+ * This is updated every second (countdown) during the lockout duration. When lockout is not
+ * active, this is `null` and no lockout message should be shown.
+ */
+ private val lockoutMessage = MutableStateFlow<String?>(null)
+
/** The user-facing message to show in the bouncer. */
val message: StateFlow<MessageViewModel> =
- combine(bouncerInteractor.message, bouncerInteractor.lockout) { message, lockout ->
- toMessageViewModel(message, isLockedOut = lockout != null)
- }
+ combine(bouncerInteractor.message, lockoutMessage) { _, _ -> createMessageViewModel() }
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue =
- toMessageViewModel(
- message = bouncerInteractor.message.value,
- isLockedOut = bouncerInteractor.lockout.value != null,
- ),
+ initialValue = createMessageViewModel(),
)
/**
@@ -194,24 +195,29 @@
initialValue = isFoldSplitRequired(authMethodViewModel.value),
)
+ private val isInputEnabled: StateFlow<Boolean> =
+ lockoutMessage
+ .map { it == null }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = authenticationInteractor.lockoutEndTimestamp == null,
+ )
+
+ private var lockoutCountdownJob: Job? = null
+
init {
if (flags.isEnabled()) {
applicationScope.launch {
- combine(bouncerInteractor.lockout, authMethodViewModel) {
- lockout,
- authMethodViewModel ->
- if (lockout != null && authMethodViewModel != null) {
- applicationContext.getString(
- authMethodViewModel.lockoutMessageId,
- lockout.failedAttemptCount,
- lockout.remainingSeconds,
- )
- } else {
- null
- }
- }
- .distinctUntilChanged()
- .collect { dialogMessage -> _dialogMessage.value = dialogMessage }
+ bouncerInteractor.onLockoutStarted.collect {
+ showLockoutDialog()
+ startLockoutCountdown()
+ }
+ }
+
+ applicationScope.launch {
+ // Update the lockout countdown whenever the selected user is switched.
+ selectedUser.collect { startLockoutCountdown() }
}
}
}
@@ -221,6 +227,48 @@
_dialogMessage.value = null
}
+ private fun showLockoutDialog() {
+ applicationScope.launch {
+ val failedAttempts = authenticationInteractor.failedAuthenticationAttempts.value
+ _dialogMessage.value =
+ authMethodViewModel.value?.lockoutMessageId?.let { messageId ->
+ applicationContext.getString(
+ messageId,
+ failedAttempts,
+ remainingLockoutSeconds()
+ )
+ }
+ }
+ }
+
+ /** Shows the countdown message and refreshes it every second. */
+ private fun startLockoutCountdown() {
+ lockoutCountdownJob?.cancel()
+ lockoutCountdownJob =
+ applicationScope.launch {
+ do {
+ val remainingSeconds = remainingLockoutSeconds()
+ lockoutMessage.value =
+ if (remainingSeconds > 0) {
+ applicationContext.getString(
+ R.string.lockscreen_too_many_failed_attempts_countdown,
+ remainingSeconds,
+ )
+ } else {
+ null
+ }
+ delay(1.seconds)
+ } while (remainingSeconds > 0)
+ lockoutCountdownJob = null
+ }
+ }
+
+ private fun remainingLockoutSeconds(): Int {
+ val endTimestampMs = authenticationInteractor.lockoutEndTimestamp ?: 0
+ val remainingMs = max(0, endTimestampMs - clock.elapsedRealtime())
+ return ceil(remainingMs / 1000f).toInt()
+ }
+
private fun isSideBySideSupported(authMethod: AuthMethodBouncerViewModel?): Boolean {
return isUserSwitcherVisible || authMethod !is PasswordBouncerViewModel
}
@@ -229,12 +277,11 @@
return authMethod !is PasswordBouncerViewModel
}
- private fun toMessageViewModel(
- message: String?,
- isLockedOut: Boolean,
- ): MessageViewModel {
+ private fun createMessageViewModel(): MessageViewModel {
+ val isLockedOut = lockoutMessage.value != null
return MessageViewModel(
- text = message ?: "",
+ // A lockout message takes precedence over the non-lockout message.
+ text = lockoutMessage.value ?: bouncerInteractor.message.value ?: "",
isUpdateAnimated = !isLockedOut,
)
}
@@ -328,6 +375,7 @@
userSwitcherViewModel: UserSwitcherViewModel,
actionButtonInteractor: BouncerActionButtonInteractor,
simBouncerInteractor: SimBouncerInteractor,
+ clock: SystemClock,
): BouncerViewModel {
return BouncerViewModel(
applicationContext = applicationContext,
@@ -341,6 +389,7 @@
userSwitcherMenu = userSwitcherViewModel.menu,
actionButtonInteractor = actionButtonInteractor,
simBouncerInteractor = simBouncerInteractor,
+ clock = clock,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index b682717..8e14778 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -56,13 +56,11 @@
/** Whether the UI should request focus on the text field element. */
val isTextFieldFocusRequested =
- combine(interactor.lockout, isTextFieldFocused) { throttling, hasFocus ->
- throttling == null && !hasFocus
- }
+ combine(isInputEnabled, isTextFieldFocused) { hasInput, hasFocus -> hasInput && !hasFocus }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = interactor.lockout.value == null && !isTextFieldFocused.value,
+ initialValue = isInputEnabled.value && !isTextFieldFocused.value,
)
override fun onHidden() {
@@ -104,7 +102,7 @@
* hidden.
*/
suspend fun onImeVisibilityChanged(isVisible: Boolean) {
- if (isImeVisible && !isVisible && interactor.lockout.value == null) {
+ if (isImeVisible && !isVisible && isInputEnabled.value) {
interactor.onImeHiddenByUser()
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/domain/CommonDomainLayerModule.kt b/packages/SystemUI/src/com/android/systemui/common/domain/CommonDomainLayerModule.kt
deleted file mode 100644
index 7be2eaf..0000000
--- a/packages/SystemUI/src/com/android/systemui/common/domain/CommonDomainLayerModule.kt
+++ /dev/null
@@ -1,27 +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.common.domain
-
-import com.android.systemui.common.domain.interactor.ConfigurationInteractor
-import com.android.systemui.common.domain.interactor.ConfigurationInteractorImpl
-import dagger.Binds
-import dagger.Module
-
-@Module
-abstract class CommonDomainLayerModule {
- @Binds abstract fun bindInteractor(impl: ConfigurationInteractorImpl): ConfigurationInteractor
-}
diff --git a/packages/SystemUI/src/com/android/systemui/common/domain/interactor/ConfigurationInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/domain/interactor/ConfigurationInteractor.kt
deleted file mode 100644
index 89053d1..0000000
--- a/packages/SystemUI/src/com/android/systemui/common/domain/interactor/ConfigurationInteractor.kt
+++ /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
- */
-package com.android.systemui.common.domain.interactor
-
-import android.content.res.Configuration
-import android.graphics.Rect
-import android.view.Surface
-import com.android.systemui.common.ui.data.repository.ConfigurationRepository
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.map
-
-interface ConfigurationInteractor {
- /**
- * Returns screen size adjusted to rotation, so returned screen sizes are stable across all
- * rotations, could be useful if you need to react to screen resize (e.g. fold/unfold on
- * foldable devices)
- */
- val naturalMaxBounds: Flow<Rect>
-}
-
-class ConfigurationInteractorImpl
-@Inject
-constructor(private val repository: ConfigurationRepository) : ConfigurationInteractor {
-
- override val naturalMaxBounds: Flow<Rect>
- get() = repository.configurationValues.map { it.naturalScreenBounds }.distinctUntilChanged()
-
- /**
- * Returns screen size adjusted to rotation, so returned screen size is stable across all
- * rotations
- */
- private val Configuration.naturalScreenBounds: Rect
- get() {
- val rotation = windowConfiguration.displayRotation
- val maxBounds = windowConfiguration.maxBounds
- return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
- Rect(0, 0, maxBounds.width(), maxBounds.height())
- } else {
- Rect(0, 0, maxBounds.height(), maxBounds.width())
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
index 3648f3b..1353985 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
@@ -17,17 +17,45 @@
package com.android.systemui.common.ui.domain.interactor
+import android.content.res.Configuration
+import android.graphics.Rect
+import android.view.Surface
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onStart
/** Business logic related to configuration changes. */
@SysUISingleton
class ConfigurationInteractor @Inject constructor(private val repository: ConfigurationRepository) {
+ /**
+ * Returns screen size adjusted to rotation, so returned screen size is stable across all
+ * rotations
+ */
+ private val Configuration.naturalScreenBounds: Rect
+ get() {
+ val rotation = windowConfiguration.displayRotation
+ val maxBounds = windowConfiguration.maxBounds
+ return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
+ Rect(0, 0, maxBounds.width(), maxBounds.height())
+ } else {
+ Rect(0, 0, maxBounds.height(), maxBounds.width())
+ }
+ }
+
+ /**
+ * Returns screen size adjusted to rotation, so returned screen sizes are stable across all
+ * rotations, could be useful if you need to react to screen resize (e.g. fold/unfold on
+ * foldable devices)
+ */
+ val naturalMaxBounds: Flow<Rect> =
+ repository.configurationValues.map { it.naturalScreenBounds }.distinctUntilChanged()
+
/** Given [resourceId], emit the dimension pixel size on config change */
fun dimensionPixelSize(resourceId: Int): Flow<Int> {
return onAnyConfigurationChange.mapLatest { repository.getDimensionPixelSize(resourceId) }
@@ -43,4 +71,7 @@
/** Emit an event on any config change */
val onAnyConfigurationChange: Flow<Unit> =
repository.onAnyConfigurationChange.onStart { emit(Unit) }
+
+ /** Emits the current resolution scaling factor */
+ val scaleForResolution: Flow<Float> = repository.scaleForResolution
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
index a12db6f..779446d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
@@ -117,10 +117,10 @@
fun updateItemRank(itemUid: Long, order: Int)
@Transaction
- fun updateWidgetOrder(ids: List<Int>) {
- ids.forEachIndexed { index, it ->
- val widget = getWidgetByIdNow(it)
- updateItemRank(widget.itemId, ids.size - index)
+ fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {
+ widgetIdToPriorityMap.forEach { (id, priority) ->
+ val widget = getWidgetByIdNow(id)
+ updateItemRank(widget.itemId, priority)
}
}
@@ -129,7 +129,7 @@
return insertWidget(
widgetId = widgetId,
componentName = provider.flattenToString(),
- insertItemRank(priority),
+ itemId = insertItemRank(priority),
)
}
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 ded5581..d1bbe57 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
@@ -62,8 +62,12 @@
/** Delete a widget by id from app widget service and the database. */
fun deleteWidget(widgetId: Int) {}
- /** Update the order of widgets in the database. */
- fun updateWidgetOrder(ids: List<Int>) {}
+ /**
+ * Update the order of widgets in the database.
+ *
+ * @param widgetIdToPriorityMap mapping of the widget ids to the priority of the widget.
+ */
+ fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {}
}
@OptIn(ExperimentalCoroutinesApi::class)
@@ -168,11 +172,11 @@
}
}
- override fun updateWidgetOrder(ids: List<Int>) {
+ override fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {
applicationScope.launch(bgDispatcher) {
- communalWidgetDao.updateWidgetOrder(ids)
+ communalWidgetDao.updateWidgetOrder(widgetIdToPriorityMap)
logger.i({ "Updated the order of widget list with ids: $str1." }) {
- str1 = ids.toString()
+ str1 = widgetIdToPriorityMap.toString()
}
}
}
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 e342c6b..0f4e583 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
@@ -102,8 +102,13 @@
/** Delete a widget by id. */
fun deleteWidget(id: Int) = widgetRepository.deleteWidget(id)
- /** Reorder widgets. The order in the list will be their display order in the hub. */
- fun updateWidgetOrder(ids: List<Int>) = widgetRepository.updateWidgetOrder(ids)
+ /**
+ * Reorder the widgets.
+ *
+ * @param widgetIdToPriorityMap mapping of the widget ids to their new priorities.
+ */
+ fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) =
+ widgetRepository.updateWidgetOrder(widgetIdToPriorityMap)
/** A list of widget content to be displayed in the communal hub. */
val widgetContent: Flow<List<CommunalContentModel.Widget>> =
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 bb9b4b5..3ae5229 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
@@ -20,6 +20,7 @@
import android.appwidget.AppWidgetProviderInfo
import android.widget.RemoteViews
import com.android.systemui.communal.shared.model.CommunalContentSize
+import java.util.UUID
/** Encapsulates data for a communal content. */
sealed interface CommunalContentModel {
@@ -39,6 +40,13 @@
override val size = CommunalContentSize.HALF
}
+ /** A placeholder item representing a new widget being added */
+ class WidgetPlaceholder : CommunalContentModel {
+ override val key: String = "widget_placeholder_${UUID.randomUUID()}"
+ // Same as widget size.
+ override val size = CommunalContentSize.HALF
+ }
+
class Tutorial(
id: Int,
override val size: CommunalContentSize,
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 708f137..577e404 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
@@ -16,6 +16,7 @@
package com.android.systemui.communal.ui.viewmodel
+import android.content.ComponentName
import android.os.PowerManager
import android.os.SystemClock
import android.view.MotionEvent
@@ -53,6 +54,13 @@
communalInteractor.setTransitionState(transitionState)
}
+ /**
+ * Called when a widget is added via drag and drop from the widget picker into the communal hub.
+ */
+ fun onAddWidget(componentName: ComponentName, priority: Int) {
+ communalInteractor.addWidget(componentName, priority)
+ }
+
// 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. */
@@ -82,8 +90,14 @@
/** Called as the UI requests deleting a widget. */
open fun onDeleteWidget(id: Int) {}
- /** Called as the UI requests reordering widgets. */
- open fun onReorderWidgets(ids: List<Int>) {}
+ /**
+ * Called as the UI requests reordering widgets.
+ *
+ * @param widgetIdToPriorityMap mapping of the widget ids to its priority. When re-ordering to
+ * add a new item in the middle, provide the priorities of existing widgets as if the new item
+ * existed, and then, call [onAddWidget] to add the new item at intended order.
+ */
+ open fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) {}
/** Called as the UI requests opening the widget editor. */
open fun onOpenWidgetEditor() {}
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 c82e000..368df9e 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
@@ -47,5 +47,6 @@
override fun onDeleteWidget(id: Int) = communalInteractor.deleteWidget(id)
- override fun onReorderWidgets(ids: List<Int>) = communalInteractor.updateWidgetOrder(ids)
+ override fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) =
+ communalInteractor.updateWidgetOrder(widgetIdToPriorityMap)
}
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 887b18c..c936c63 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -16,8 +16,9 @@
package com.android.systemui.communal.widgets
-import android.appwidget.AppWidgetProviderInfo
+import android.content.ComponentName
import android.content.Intent
+import android.content.pm.PackageManager
import android.os.Bundle
import android.os.RemoteException
import android.util.Log
@@ -39,10 +40,9 @@
private var windowManagerService: IWindowManager? = null,
) : ComponentActivity() {
companion object {
- /**
- * Intent extra name for the {@link AppWidgetProviderInfo} of a widget to add to hub mode.
- */
- const val ADD_WIDGET_INFO = "add_widget_info"
+ private const val EXTRA_IS_PENDING_WIDGET_DRAG = "is_pending_widget_drag"
+ private const val EXTRA_FILTER_STRATEGY = "filter_strategy"
+ private const val FILTER_STRATEGY_GLANCEABLE_HUB = 1
private const val TAG = "EditWidgetsActivity"
}
@@ -50,15 +50,23 @@
registerForActivityResult(StartActivityForResult()) { result ->
when (result.resultCode) {
RESULT_OK -> {
- result.data
- ?.let {
- it.getParcelableExtra(
- ADD_WIDGET_INFO,
- AppWidgetProviderInfo::class.java
- )
+ result.data?.let { intent ->
+ val isPendingWidgetDrag =
+ intent.getBooleanExtra(EXTRA_IS_PENDING_WIDGET_DRAG, false)
+ // Nothing to do when a widget is being dragged & dropped. The drop
+ // target in the communal grid will receive the widget to be added (if
+ // the user drops it over).
+ if (!isPendingWidgetDrag) {
+ intent
+ .getParcelableExtra(
+ Intent.EXTRA_COMPONENT_NAME,
+ ComponentName::class.java
+ )
+ ?.let { communalInteractor.addWidget(it, 0) }
+ ?: run { Log.w(TAG, "No AppWidgetProviderInfo found in result.") }
}
- ?.let { communalInteractor.addWidget(it.provider, 0) }
- ?: run { Log.w(TAG, "No AppWidgetProviderInfo found in result.") }
+ }
+ ?: run { Log.w(TAG, "No data in result.") }
}
else ->
Log.w(
@@ -75,9 +83,29 @@
activity = this,
viewModel = communalViewModel,
onOpenWidgetPicker = {
- addWidgetActivityLauncher.launch(
- Intent(applicationContext, WidgetPickerActivity::class.java)
- )
+ val localPackageManager: PackageManager = getPackageManager()
+ val intent =
+ Intent(Intent.ACTION_MAIN).also { it.addCategory(Intent.CATEGORY_HOME) }
+ localPackageManager
+ .resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
+ ?.activityInfo
+ ?.packageName
+ ?.let { packageName ->
+ try {
+ addWidgetActivityLauncher.launch(
+ Intent(Intent.ACTION_PICK).also {
+ it.setPackage(packageName)
+ it.putExtra(
+ EXTRA_FILTER_STRATEGY,
+ FILTER_STRATEGY_GLANCEABLE_HUB
+ )
+ }
+ )
+ } catch (e: Exception) {
+ Log.e(TAG, "Failed to launch widget picker activity", e)
+ }
+ }
+ ?: run { Log.e(TAG, "Couldn't resolve launcher package name") }
},
onEditDone = {
try {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetPickerActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetPickerActivity.kt
deleted file mode 100644
index a26afc8..0000000
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetPickerActivity.kt
+++ /dev/null
@@ -1,104 +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.widgets
-
-import android.appwidget.AppWidgetManager
-import android.appwidget.AppWidgetProviderInfo
-import android.content.Intent
-import android.graphics.Color
-import android.os.Bundle
-import android.util.Log
-import android.view.ViewGroup
-import android.widget.ImageView
-import android.widget.LinearLayout
-import androidx.activity.ComponentActivity
-import androidx.core.view.setMargins
-import androidx.core.view.setPadding
-import com.android.systemui.res.R
-import javax.inject.Inject
-
-/**
- * An Activity responsible for displaying a list of widgets to add to the hub mode grid. This is
- * essentially a placeholder until Launcher's widget picker can be used.
- */
-class WidgetPickerActivity
-@Inject
-constructor(
- private val appWidgetManager: AppWidgetManager,
-) : ComponentActivity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- setContentView(R.layout.widget_picker)
- loadWidgets()
- }
-
- private fun loadWidgets() {
- val containerView: ViewGroup? = findViewById(R.id.widgets_container)
- containerView?.apply {
- try {
- appWidgetManager
- .getInstalledProviders(AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD)
- ?.stream()
- ?.forEach { widgetInfo ->
- val activity = this@WidgetPickerActivity
- (widgetInfo.loadPreviewImage(activity, 0)
- ?: widgetInfo.loadIcon(activity, 0))
- ?.let {
- addView(
- ImageView(activity).also { v ->
- v.setImageDrawable(it)
- v.setBackgroundColor(WIDGET_PREVIEW_BACKGROUND_COLOR)
- v.setPadding(WIDGET_PREVIEW_PADDING)
- v.layoutParams =
- LinearLayout.LayoutParams(
- WIDGET_PREVIEW_SIZE,
- WIDGET_PREVIEW_SIZE
- )
- .also { lp ->
- lp.setMargins(WIDGET_PREVIEW_MARGINS)
- }
- v.setOnClickListener {
- setResult(
- RESULT_OK,
- Intent()
- .putExtra(
- EditWidgetsActivity.ADD_WIDGET_INFO,
- widgetInfo
- )
- )
- finish()
- }
- }
- )
- }
- }
- } catch (e: RuntimeException) {
- Log.e(TAG, "Exception fetching widget providers", e)
- }
- }
- }
-
- companion object {
- private const val WIDGET_PREVIEW_SIZE = 600
- private const val WIDGET_PREVIEW_MARGINS = 32
- private const val WIDGET_PREVIEW_PADDING = 32
- private val WIDGET_PREVIEW_BACKGROUND_COLOR = Color.rgb(216, 225, 220)
- private const val TAG = "WidgetPickerActivity"
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index 4b27af1..9afd5ed 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -20,7 +20,6 @@
import com.android.systemui.ForegroundServicesDialog;
import com.android.systemui.communal.widgets.EditWidgetsActivity;
-import com.android.systemui.communal.widgets.WidgetPickerActivity;
import com.android.systemui.contrast.ContrastDialogActivity;
import com.android.systemui.keyguard.WorkLockActivity;
import com.android.systemui.people.PeopleSpaceActivity;
@@ -158,12 +157,6 @@
@ClassKey(EditWidgetsActivity.class)
public abstract Activity bindEditWidgetsActivity(EditWidgetsActivity activity);
- /** Inject into WidgetPickerActivity. */
- @Binds
- @IntoMap
- @ClassKey(WidgetPickerActivity.class)
- public abstract Activity bindWidgetPickerActivity(WidgetPickerActivity activity);
-
/** Inject into SwitchToManagedProfileForCallActivity. */
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index ca8268d..a25c788 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -43,7 +43,6 @@
import com.android.systemui.classifier.FalsingModule;
import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
import com.android.systemui.common.data.CommonDataLayerModule;
-import com.android.systemui.common.domain.CommonDomainLayerModule;
import com.android.systemui.communal.dagger.CommunalModule;
import com.android.systemui.complication.dagger.ComplicationComponent;
import com.android.systemui.controls.dagger.ControlsModule;
@@ -179,7 +178,6 @@
ClockRegistryModule.class,
CommunalModule.class,
CommonDataLayerModule.class,
- CommonDomainLayerModule.class,
ConnectivityModule.class,
ControlsModule.class,
CoroutinesModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 47be8ab..f6a9570 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
@@ -159,7 +159,7 @@
fun attemptDeviceEntry() {
// TODO (b/307768356),
// 1. Check if the device is already authenticated by trust agent/passive biometrics
- // 2. show SPFS/UDFPS bouncer if it is available AlternateBouncerInteractor.show
+ // 2. Show SPFS/UDFPS bouncer if it is available AlternateBouncerInteractor.show
// 3. For face auth only setups trigger face auth, delay transitioning to bouncer for
// a small amount of time.
// 4. Transition to bouncer scene
@@ -197,8 +197,8 @@
init {
if (flags.isEnabled()) {
applicationScope.launch {
- authenticationInteractor.authenticationChallengeResult.collectLatest { successful ->
- if (successful) {
+ authenticationInteractor.onAuthenticationResult.collectLatest { isSuccessful ->
+ if (isSuccessful) {
repository.reportSuccessfulAuthentication()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/ui/binder/UdfpsAccessibilityOverlayBinder.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/binder/UdfpsAccessibilityOverlayBinder.kt
new file mode 100644
index 0000000..ef2537c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/binder/UdfpsAccessibilityOverlayBinder.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.deviceentry.ui.binder
+
+import android.annotation.SuppressLint
+import androidx.core.view.isInvisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.deviceentry.ui.view.UdfpsAccessibilityOverlay
+import com.android.systemui.deviceentry.ui.viewmodel.UdfpsAccessibilityOverlayViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+object UdfpsAccessibilityOverlayBinder {
+
+ /** Forwards hover events to the view model to make guided announcements for accessibility. */
+ @SuppressLint("ClickableViewAccessibility")
+ @JvmStatic
+ fun bind(
+ view: UdfpsAccessibilityOverlay,
+ viewModel: UdfpsAccessibilityOverlayViewModel,
+ ) {
+ view.setOnHoverListener { v, event -> viewModel.onHoverEvent(v, event) }
+ view.repeatWhenAttached {
+ // Repeat on CREATED because we update the visibility of the view
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ viewModel.visible.collect { visible -> view.isInvisible = !visible }
+ }
+ }
+ }
+}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/view/UdfpsAccessibilityOverlay.kt
similarity index 71%
copy from core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl
copy to packages/SystemUI/src/com/android/systemui/deviceentry/ui/view/UdfpsAccessibilityOverlay.kt
index 6c1f0fc..7be3230 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/view/UdfpsAccessibilityOverlay.kt
@@ -14,11 +14,10 @@
* limitations under the License.
*/
-package android.companion.virtual.camera;
+package com.android.systemui.deviceentry.ui.view
-/**
- * Data structure used to store {@link android.hardware.camera2.CameraMetadata} compatible with
- * VirtualCamera.
- * @hide
- */
-parcelable VirtualCameraMetadata;
+import android.content.Context
+import android.view.View
+
+/** Overlay to handle under-fingerprint sensor accessibility events. */
+class UdfpsAccessibilityOverlay(context: Context?) : View(context)
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/UdfpsAccessibilityOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/UdfpsAccessibilityOverlayViewModel.kt
new file mode 100644
index 0000000..80684b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/UdfpsAccessibilityOverlayViewModel.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.ui.viewmodel
+
+import android.graphics.Point
+import android.view.MotionEvent
+import android.view.View
+import com.android.systemui.accessibility.domain.interactor.AccessibilityInteractor
+import com.android.systemui.biometrics.UdfpsUtils
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
+import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+
+/** Models the UI state for the UDFPS accessibility overlay */
+@ExperimentalCoroutinesApi
+class UdfpsAccessibilityOverlayViewModel
+@Inject
+constructor(
+ udfpsOverlayInteractor: UdfpsOverlayInteractor,
+ accessibilityInteractor: AccessibilityInteractor,
+ deviceEntryIconViewModel: DeviceEntryIconViewModel,
+ deviceEntryFgIconViewModel: DeviceEntryForegroundViewModel,
+) {
+ private val udfpsUtils = UdfpsUtils()
+ private val udfpsOverlayParams: StateFlow<UdfpsOverlayParams> =
+ udfpsOverlayInteractor.udfpsOverlayParams
+
+ /** Overlay is only visible if touch exploration is enabled and UDFPS can be used. */
+ val visible: Flow<Boolean> =
+ accessibilityInteractor.isTouchExplorationEnabled.flatMapLatest { touchExplorationEnabled ->
+ if (touchExplorationEnabled) {
+ combine(
+ deviceEntryFgIconViewModel.viewModel,
+ deviceEntryIconViewModel.deviceEntryViewAlpha,
+ ) { iconViewModel, alpha ->
+ iconViewModel.type == DeviceEntryIconView.IconType.FINGERPRINT &&
+ !iconViewModel.useAodVariant &&
+ alpha == 1f
+ }
+ } else {
+ flowOf(false)
+ }
+ }
+
+ /** Give directional feedback to help the user authenticate with UDFPS. */
+ fun onHoverEvent(v: View, event: MotionEvent): Boolean {
+ val overlayParams = udfpsOverlayParams.value
+ val scaledTouch: Point =
+ udfpsUtils.getTouchInNativeCoordinates(event.getPointerId(0), event, overlayParams)
+
+ if (!udfpsUtils.isWithinSensorArea(event.getPointerId(0), event, overlayParams)) {
+ // view only receives motionEvents when [visible] which requires touchExplorationEnabled
+ val announceStr =
+ udfpsUtils.onTouchOutsideOfSensorArea(
+ /* touchExplorationEnabled */ true,
+ v.context,
+ scaledTouch.x,
+ scaledTouch.y,
+ overlayParams,
+ )
+ if (announceStr != null) {
+ v.announceForAccessibility(announceStr)
+ }
+ }
+ // always let the motion events go through to underlying views
+ return false
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index 5ec51f4..1540423 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -25,7 +25,9 @@
import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
@@ -36,20 +38,28 @@
class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, handler: Handler) :
FlagDependenciesBase(featureFlags, handler) {
override fun defineDependencies() {
+ // Internal notification backend dependencies
+ crossAppPoliteNotifications dependsOn politeNotifications
+ vibrateWhileUnlockedToken dependsOn politeNotifications
+
+ // Internal notification frontend dependencies
NotificationsLiveDataStoreRefactor.token dependsOn NotificationIconContainerRefactor.token
FooterViewRefactor.token dependsOn NotificationIconContainerRefactor.token
- val keyguardBottomAreaRefactor = FlagToken(
- FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor())
+ // Internal keyguard dependencies
KeyguardShadeMigrationNssl.token dependsOn keyguardBottomAreaRefactor
- val crossAppPoliteNotifToken =
- FlagToken(FLAG_CROSS_APP_POLITE_NOTIFICATIONS, crossAppPoliteNotifications())
- val politeNotifToken = FlagToken(FLAG_POLITE_NOTIFICATIONS, politeNotifications())
- crossAppPoliteNotifToken dependsOn politeNotifToken
-
- val vibrateWhileUnlockedToken =
- FlagToken(FLAG_VIBRATE_WHILE_UNLOCKED, vibrateWhileUnlocked())
- vibrateWhileUnlockedToken dependsOn politeNotifToken
+ // SceneContainer dependencies
+ SceneContainerFlag.getFlagDependencies().forEach { (alpha, beta) -> alpha dependsOn beta }
+ SceneContainerFlag.getMainStaticFlag() dependsOn MIGRATE_KEYGUARD_STATUS_BAR_VIEW
}
+
+ private inline val politeNotifications
+ get() = FlagToken(FLAG_POLITE_NOTIFICATIONS, politeNotifications())
+ private inline val crossAppPoliteNotifications
+ get() = FlagToken(FLAG_CROSS_APP_POLITE_NOTIFICATIONS, crossAppPoliteNotifications())
+ private inline val vibrateWhileUnlockedToken: FlagToken
+ get() = FlagToken(FLAG_VIBRATE_WHILE_UNLOCKED, vibrateWhileUnlocked())
+ private inline val keyguardBottomAreaRefactor
+ get() = FlagToken(FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor())
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt
index 6560ee3..14fda5e 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt
@@ -73,9 +73,19 @@
) {
val isMet = !alphaEnabled || betaEnabled
override fun toString(): String {
- val isMetBullet = if (isMet) "+" else "-"
- return "$isMetBullet $alphaName ($alphaEnabled) DEPENDS ON $betaName ($betaEnabled)"
+ val prefix =
+ when {
+ !isMet -> " [NOT MET]"
+ alphaEnabled -> " [met]"
+ betaEnabled -> " [ready]"
+ else -> "[not ready]"
+ }
+ val alphaState = if (alphaEnabled) "enabled" else "disabled"
+ val betaState = if (betaEnabled) "enabled" else "disabled"
+ return "$prefix $alphaName ($alphaState) DEPENDS ON $betaName ($betaState)"
}
+ /** Used whe posting a notification of unmet dependencies */
+ fun shortUnmetString(): String = "$alphaName DEPENDS ON $betaName"
}
protected infix fun UnreleasedFlag.dependsOn(other: UnreleasedFlag) =
@@ -124,7 +134,7 @@
unmet: List<FlagDependenciesBase.Dependency>
) {
val title = "Invalid flag dependencies: ${unmet.size} of ${all.size}"
- val details = unmet.joinToString("\n")
+ val details = unmet.joinToString("\n") { it.shortUnmetString() }
Log.e("FlagDependencies", "$title:\n$details")
val channel = NotificationChannel("FLAGS", "Flags", NotificationManager.IMPORTANCE_DEFAULT)
val notification =
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index b43f54d..f6db978 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -17,11 +17,11 @@
import android.provider.DeviceConfig
import com.android.internal.annotations.Keep
-import com.android.systemui.res.R
import com.android.systemui.flags.FlagsFactory.releasedFlag
import com.android.systemui.flags.FlagsFactory.resourceBooleanFlag
import com.android.systemui.flags.FlagsFactory.sysPropBooleanFlag
import com.android.systemui.flags.FlagsFactory.unreleasedFlag
+import com.android.systemui.res.R
/**
* List of [Flag] objects for use in SystemUI.
@@ -483,9 +483,6 @@
// TODO(b/264916608): Tracking Bug
@JvmField val SCREENSHOT_METADATA = unreleasedFlag("screenshot_metadata")
- // TODO(b/266955521): Tracking bug
- @JvmField val SCREENSHOT_DETECTION = releasedFlag("screenshot_detection")
-
// TODO(b/251205791): Tracking Bug
@JvmField val SCREENSHOT_APP_CLIPS = releasedFlag("screenshot_app_clips")
@@ -607,9 +604,6 @@
@JvmField
val LOCKSCREEN_WALLPAPER_DREAM_ENABLED = unreleasedFlag("enable_lockscreen_wallpaper_dream")
- // TODO(b/283084712): Tracking Bug
- @JvmField val IMPROVED_HUN_ANIMATIONS = unreleasedFlag("improved_hun_animations")
-
// TODO(b/283447257): Tracking bug
@JvmField
val BIGPICTURE_NOTIFICATION_LAZY_LOADING =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 20da00e..af5d48d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -124,7 +124,7 @@
indicationAreaHandle =
KeyguardIndicationAreaBinder.bind(
- notificationShadeWindowView,
+ notificationShadeWindowView.requireViewById(R.id.keyguard_indication_area),
keyguardIndicationAreaViewModel,
keyguardRootViewModel,
indicationController,
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 de15fd6..c98f637 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
@@ -22,6 +22,7 @@
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.res.R
import javax.inject.Inject
@@ -48,7 +49,9 @@
) {
init {
- alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, TAG)
+ if (!DeviceEntryUdfpsRefactor.isEnabled) {
+ alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, TAG)
+ }
}
private val showIndicatorForPrimaryBouncer: Flow<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 7e360cf..52f2759 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
@@ -141,5 +141,6 @@
companion object {
val TRANSITION_DURATION_MS = 300.milliseconds
val TO_GONE_DURATION = 500.milliseconds
+ val TO_AOD_DURATION = TRANSITION_DURATION_MS
}
}
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 64ff3b0c..1277585 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -32,11 +32,8 @@
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharedFlow
-import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.shareIn
/**
* Assists in creating sub-flows for a KeyguardTransition. Call [setup] once for a transition, and
@@ -68,8 +65,6 @@
* in the range of [0, 1]. View animations should begin and end within a subset of this
* range. This function maps the [startTime] and [duration] into [0, 1], when this subset is
* valid.
- *
- * Will produce a [SharedFlow], so that identical animations can use the same value.
*/
fun sharedFlow(
duration: Duration,
@@ -80,7 +75,7 @@
onFinish: (() -> Float)? = null,
interpolator: Interpolator = LINEAR,
name: String? = null
- ): SharedFlow<Float> {
+ ): Flow<Float> {
if (!duration.isPositive()) {
throw IllegalArgumentException("duration must be a positive number: $duration")
}
@@ -137,7 +132,6 @@
value
}
.filterNotNull()
- .shareIn(scope, SharingStarted.WhileSubscribed())
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt
deleted file mode 100644
index ebf1beb..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt
+++ /dev/null
@@ -1,25 +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.adapter
-
-/**
- * Temporary adapter class while
- * [com.android.systemui.biometrics.ui.controller.UdfpsKeyguardViewController] is being refactored
- * before [com.android.systemui.biometrics.UdfpsKeyguardViewControllerLegacy] is removed.
- *
- * TODO (b/278719514): Delete once udfps keyguard view is fully refactored.
- */
-interface UdfpsKeyguardViewControllerAdapter
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt
new file mode 100644
index 0000000..c749818
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.content.res.ColorStateList
+import android.view.View
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+
+@ExperimentalCoroutinesApi
+object AlternateBouncerUdfpsViewBinder {
+
+ /** Updates UI for the UDFPS icon on the alternate bouncer. */
+ @JvmStatic
+ fun bind(
+ view: DeviceEntryIconView,
+ viewModel: AlternateBouncerUdfpsIconViewModel,
+ ) {
+ if (DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()) {
+ return
+ }
+ val fgIconView = view.iconView
+ val bgView = view.bgView
+
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.accessibilityDelegateHint.collect { hint ->
+ view.accessibilityHintType = hint
+ }
+ }
+ }
+
+ fgIconView.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.fgViewModel.collect { fgViewModel ->
+ fgIconView.setImageState(
+ view.getIconState(fgViewModel.type, fgViewModel.useAodVariant),
+ /* merge */ false
+ )
+ fgIconView.imageTintList = ColorStateList.valueOf(fgViewModel.tint)
+ fgIconView.setPadding(
+ fgViewModel.padding,
+ fgViewModel.padding,
+ fgViewModel.padding,
+ fgViewModel.padding,
+ )
+ }
+ }
+ }
+
+ bgView.visibility = View.VISIBLE
+ bgView.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.bgColor.collect { color ->
+ bgView.imageTintList = ColorStateList.valueOf(color)
+ }
+ }
+ launch { viewModel.bgAlpha.collect { alpha -> bgView.alpha = alpha } }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
index d1d323f..78906ac 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
@@ -17,47 +17,49 @@
package com.android.systemui.keyguard.ui.binder
import android.view.View
-import android.view.ViewGroup
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.classifier.Classifier
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.scrim.ScrimView
-import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.gesture.TapGestureDetector
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
-/** Binds the alternate bouncer view to its view-model. */
+/**
+ * Binds the alternate bouncer view to its view-model.
+ *
+ * For devices that support UDFPS, this includes a UDFPS view.
+ */
@ExperimentalCoroutinesApi
object AlternateBouncerViewBinder {
/** Binds the view to the view-model, continuing to update the former based on the latter. */
@JvmStatic
fun bind(
- view: ViewGroup,
+ view: ConstraintLayout,
viewModel: AlternateBouncerViewModel,
- scope: CoroutineScope,
- notificationShadeWindowController: NotificationShadeWindowController,
falsingManager: FalsingManager,
swipeUpAnywhereGestureHandler: SwipeUpAnywhereGestureHandler,
tapGestureDetector: TapGestureDetector,
+ alternateBouncerUdfpsIconViewModel: AlternateBouncerUdfpsIconViewModel,
) {
- if (DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()) return
- scope.launch {
- // forcePluginOpen is necessary to show over occluded apps.
- // This cannot be tied to the view's lifecycle because setting this allows the view
- // to be started in the first place.
- viewModel.forcePluginOpen.collect {
- notificationShadeWindowController.setForcePluginOpen(it, this)
- }
+ if (DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()) {
+ return
}
+ optionallyAddUdfpsView(
+ view = view,
+ alternateBouncerUdfpsIconViewModel = alternateBouncerUdfpsIconViewModel,
+ )
val scrim = view.requireViewById(R.id.alternate_bouncer_scrim) as ScrimView
view.repeatWhenAttached { alternateBouncerViewContainer ->
@@ -99,6 +101,52 @@
}
}
}
+
+ private fun optionallyAddUdfpsView(
+ view: ConstraintLayout,
+ alternateBouncerUdfpsIconViewModel: AlternateBouncerUdfpsIconViewModel,
+ ) {
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ launch {
+ alternateBouncerUdfpsIconViewModel.iconLocation.collect { iconLocation ->
+ val viewId = R.id.alternate_bouncer_udfps_icon_view
+ var udfpsView = view.getViewById(viewId)
+ if (udfpsView == null) {
+ udfpsView =
+ DeviceEntryIconView(view.context, null).apply { id = viewId }
+ view.addView(udfpsView)
+ AlternateBouncerUdfpsViewBinder.bind(
+ udfpsView,
+ alternateBouncerUdfpsIconViewModel,
+ )
+ }
+
+ val constraintSet = ConstraintSet().apply { clone(view) }
+ constraintSet.apply {
+ constrainWidth(viewId, iconLocation.width)
+ constrainHeight(viewId, iconLocation.height)
+ connect(
+ viewId,
+ ConstraintSet.TOP,
+ ConstraintSet.PARENT_ID,
+ ConstraintSet.TOP,
+ iconLocation.top,
+ )
+ connect(
+ viewId,
+ ConstraintSet.START,
+ ConstraintSet.PARENT_ID,
+ ConstraintSet.START,
+ iconLocation.left
+ )
+ }
+ constraintSet.applyTo(view)
+ }
+ }
+ }
+ }
+ }
}
private const val swipeTag = "AlternateBouncer-SWIPE"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
index dcf4284..a02e8ac 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -131,10 +131,10 @@
bgView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
+ launch { bgViewModel.alpha.collect { alpha -> bgView.alpha = alpha } }
launch {
- bgViewModel.viewModel.collect { bgViewModel ->
- bgView.alpha = bgViewModel.alpha
- bgView.imageTintList = ColorStateList.valueOf(bgViewModel.tint)
+ bgViewModel.color.collect { color ->
+ bgView.imageTintList = ColorStateList.valueOf(color)
}
}
}
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 7d290c3..05fe0b2 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
@@ -32,8 +32,6 @@
import com.android.systemui.res.R
import kotlinx.coroutines.launch
-private val TAG = KeyguardClockViewBinder::class.simpleName
-
object KeyguardClockViewBinder {
@JvmStatic
fun bind(
@@ -74,12 +72,6 @@
applyConstraints(clockSection, keyguardRootView, true)
}
}
- launch {
- if (!migrateClocksToBlueprint()) return@launch
- viewModel.hasCustomWeatherDataDisplay.collect {
- applyConstraints(clockSection, keyguardRootView, true)
- }
- }
}
}
}
@@ -132,7 +124,7 @@
fun applyConstraints(
clockSection: ClockSection,
rootView: ConstraintLayout,
- animated: Boolean
+ animated: Boolean,
) {
val constraintSet = ConstraintSet().apply { clone(rootView) }
clockSection.applyConstraints(constraintSet)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
index 4efd9ef..4c33d90 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
@@ -54,12 +54,11 @@
keyguardRootViewModel: KeyguardRootViewModel,
indicationController: KeyguardIndicationController,
): DisposableHandle {
- val indicationArea: ViewGroup = view.requireViewById(R.id.keyguard_indication_area)
- indicationController.setIndicationArea(indicationArea)
+ indicationController.setIndicationArea(view)
- val indicationText: TextView = indicationArea.requireViewById(R.id.keyguard_indication_text)
+ val indicationText: TextView = view.requireViewById(R.id.keyguard_indication_text)
val indicationTextBottom: TextView =
- indicationArea.requireViewById(R.id.keyguard_indication_text_bottom)
+ view.requireViewById(R.id.keyguard_indication_text_bottom)
view.clipChildren = false
view.clipToPadding = false
@@ -71,7 +70,7 @@
launch {
if (keyguardBottomAreaRefactor()) {
keyguardRootViewModel.alpha.collect { alpha ->
- indicationArea.apply {
+ view.apply {
this.importantForAccessibility =
if (alpha == 0f) {
View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
@@ -83,7 +82,7 @@
}
} else {
viewModel.alpha.collect { alpha ->
- indicationArea.apply {
+ view.apply {
this.importantForAccessibility =
if (alpha == 0f) {
View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
@@ -98,7 +97,7 @@
launch {
viewModel.indicationAreaTranslationX.collect { translationX ->
- indicationArea.translationX = translationX
+ view.translationX = translationX
}
}
@@ -113,9 +112,7 @@
0
}
}
- .collect { paddingPx ->
- indicationArea.setPadding(paddingPx, 0, paddingPx, 0)
- }
+ .collect { paddingPx -> view.setPadding(paddingPx, 0, paddingPx, 0) }
}
launch {
@@ -124,7 +121,7 @@
.flatMapLatest { defaultBurnInOffsetY ->
viewModel.indicationAreaTranslationY(defaultBurnInOffsetY)
}
- .collect { translationY -> indicationArea.translationY = translationY }
+ .collect { translationY -> view.translationY = translationY }
}
launch {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 01a1ca3..362e7e6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -29,7 +29,6 @@
import android.view.ViewPropertyAnimator
import android.view.WindowInsets
import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.animation.Interpolators
import com.android.internal.jank.InteractionJankMonitor
@@ -67,6 +66,7 @@
import javax.inject.Provider
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -205,7 +205,6 @@
childViews[aodNotificationIconContainerId]
?.setAodNotifIconContainerIsVisible(
isVisible,
- featureFlags,
iconsAppearTranslationPx.value,
screenOffAnimationController,
)
@@ -359,41 +358,32 @@
}
}
- @JvmStatic
- fun bindAodIconVisibility(
+ suspend fun bindAodNotifIconVisibility(
view: View,
isVisible: Flow<AnimatedValue<Boolean>>,
configuration: ConfigurationState,
- featureFlags: FeatureFlagsClassic,
screenOffAnimationController: ScreenOffAnimationController,
- ): DisposableHandle? {
+ ) {
KeyguardShadeMigrationNssl.assertInLegacyMode()
- if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return null
- return view.repeatWhenAttached {
- lifecycleScope.launch {
- val iconAppearTranslationPx =
- configuration
- .getDimensionPixelSize(R.dimen.shelf_appear_translation)
- .stateIn(this)
- isVisible.collect { isVisible ->
- view.setAodNotifIconContainerIsVisible(
- isVisible,
- featureFlags,
- iconAppearTranslationPx.value,
- screenOffAnimationController,
- )
- }
+ if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return
+ coroutineScope {
+ val iconAppearTranslationPx =
+ configuration.getDimensionPixelSize(R.dimen.shelf_appear_translation).stateIn(this)
+ isVisible.collect { isVisible ->
+ view.setAodNotifIconContainerIsVisible(
+ isVisible = isVisible,
+ iconsAppearTranslationPx = iconAppearTranslationPx.value,
+ screenOffAnimationController = screenOffAnimationController,
+ )
}
}
}
private fun View.setAodNotifIconContainerIsVisible(
isVisible: AnimatedValue<Boolean>,
- featureFlags: FeatureFlagsClassic,
iconsAppearTranslationPx: Int,
screenOffAnimationController: ScreenOffAnimationController,
) {
- val statusViewMigrated = KeyguardShadeMigrationNssl.isEnabled
animate().cancel()
val animatorListener =
object : AnimatorListenerAdapter() {
@@ -404,13 +394,13 @@
when {
!isVisible.isAnimating -> {
alpha = 1f
- if (!statusViewMigrated) {
+ if (!KeyguardShadeMigrationNssl.isEnabled) {
translationY = 0f
}
visibility = if (isVisible.value) View.VISIBLE else View.INVISIBLE
}
newAodTransition() -> {
- animateInIconTranslation(statusViewMigrated)
+ animateInIconTranslation()
if (isVisible.value) {
CrossFadeHelper.fadeIn(this, animatorListener)
} else {
@@ -419,7 +409,7 @@
}
!isVisible.value -> {
// Let's make sure the icon are translated to 0, since we cancelled it above
- animateInIconTranslation(statusViewMigrated)
+ animateInIconTranslation()
CrossFadeHelper.fadeOut(this, animatorListener)
}
visibility != View.VISIBLE -> {
@@ -429,13 +419,12 @@
appearIcons(
animate = screenOffAnimationController.shouldAnimateAodIcons(),
iconsAppearTranslationPx,
- statusViewMigrated,
animatorListener,
)
}
else -> {
// Let's make sure the icons are translated to 0, since we cancelled it above
- animateInIconTranslation(statusViewMigrated)
+ animateInIconTranslation()
// We were fading out, let's fade in instead
CrossFadeHelper.fadeIn(this, animatorListener)
}
@@ -445,11 +434,10 @@
private fun View.appearIcons(
animate: Boolean,
iconAppearTranslation: Int,
- statusViewMigrated: Boolean,
animatorListener: Animator.AnimatorListener,
) {
if (animate) {
- if (!statusViewMigrated) {
+ if (!KeyguardShadeMigrationNssl.isEnabled) {
translationY = -iconAppearTranslation.toFloat()
}
alpha = 0f
@@ -457,19 +445,19 @@
.alpha(1f)
.setInterpolator(Interpolators.LINEAR)
.setDuration(AOD_ICONS_APPEAR_DURATION)
- .apply { if (statusViewMigrated) animateInIconTranslation() }
+ .apply { if (KeyguardShadeMigrationNssl.isEnabled) animateInIconTranslation() }
.setListener(animatorListener)
.start()
} else {
alpha = 1.0f
- if (!statusViewMigrated) {
+ if (!KeyguardShadeMigrationNssl.isEnabled) {
translationY = 0f
}
}
}
- private fun View.animateInIconTranslation(statusViewMigrated: Boolean) {
- if (!statusViewMigrated) {
+ private fun View.animateInIconTranslation() {
+ if (!KeyguardShadeMigrationNssl.isEnabled) {
animate().animateInIconTranslation().setDuration(AOD_ICONS_APPEAR_DURATION).start()
}
}
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 954d2cf..e36819b 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,13 +16,19 @@
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.keyguard.ui.view.layout.sections.SmartspaceSection
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
+import kotlinx.coroutines.launch
object KeyguardSmartspaceViewBinder {
@JvmStatic
@@ -30,15 +36,63 @@
smartspaceSection: SmartspaceSection,
keyguardRootView: ConstraintLayout,
clockViewModel: KeyguardClockViewModel,
+ smartspaceViewModel: KeyguardSmartspaceViewModel,
) {
keyguardRootView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
- clockViewModel.hasCustomWeatherDataDisplay.collect {
- val constraintSet = ConstraintSet().apply { clone(keyguardRootView) }
- smartspaceSection.applyConstraints(constraintSet)
- constraintSet.applyTo(keyguardRootView)
+ 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)
+ }
}
}
}
}
+
+ private fun addDateWeatherToBurnInLayer(
+ constraintLayout: ConstraintLayout,
+ smartspaceViewModel: KeyguardSmartspaceViewModel
+ ) {
+ val burnInLayer = constraintLayout.requireViewById<Layer>(R.id.burn_in_layer)
+ burnInLayer.apply {
+ if (
+ smartspaceViewModel.isSmartspaceEnabled &&
+ smartspaceViewModel.isDateWeatherDecoupled
+ ) {
+ val dateView = constraintLayout.requireViewById<View>(smartspaceViewModel.dateId)
+ val weatherView =
+ constraintLayout.requireViewById<View>(smartspaceViewModel.weatherId)
+ addView(weatherView)
+ addView(dateView)
+ }
+ }
+ }
+
+ private fun removeDateWeatherToBurnInLayer(
+ constraintLayout: ConstraintLayout,
+ smartspaceViewModel: KeyguardSmartspaceViewModel
+ ) {
+ val burnInLayer = constraintLayout.requireViewById<Layer>(R.id.burn_in_layer)
+ burnInLayer.apply {
+ if (
+ smartspaceViewModel.isSmartspaceEnabled &&
+ smartspaceViewModel.isDateWeatherDecoupled
+ ) {
+ val dateView = smartspaceViewModel.dateView
+ val weatherView = smartspaceViewModel.weatherView
+ removeView(weatherView)
+ removeView(dateView)
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt
deleted file mode 100644
index 52d87d3..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.keyguard.ui.binder
-
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
-import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel
-import com.android.systemui.lifecycle.repeatWhenAttached
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.launch
-
-@ExperimentalCoroutinesApi
-object UdfpsAodFingerprintViewBinder {
-
- /**
- * Drives UI for the UDFPS aod fingerprint view. See [UdfpsFingerprintViewBinder] and
- * [UdfpsBackgroundViewBinder].
- */
- @JvmStatic
- fun bind(
- view: LottieAnimationView,
- viewModel: UdfpsAodViewModel,
- ) {
- view.alpha = 0f
- view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch {
- viewModel.burnInOffsets.collect { burnInOffsets ->
- view.progress = burnInOffsets.progress
- view.translationX = burnInOffsets.x.toFloat()
- view.translationY = burnInOffsets.y.toFloat()
- }
- }
-
- launch { viewModel.alpha.collect { alpha -> view.alpha = alpha } }
-
- launch {
- viewModel.padding.collect { padding ->
- view.setPadding(padding, padding, padding, padding)
- }
- }
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt
deleted file mode 100644
index 0113628..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.keyguard.ui.binder
-
-import android.content.res.ColorStateList
-import android.widget.ImageView
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel
-import com.android.systemui.lifecycle.repeatWhenAttached
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.launch
-
-@ExperimentalCoroutinesApi
-object UdfpsBackgroundViewBinder {
-
- /**
- * Drives UI for the udfps background view. See [UdfpsAodFingerprintViewBinder] and
- * [UdfpsFingerprintViewBinder].
- */
- @JvmStatic
- fun bind(
- view: ImageView,
- viewModel: BackgroundViewModel,
- ) {
- view.alpha = 0f
- view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch {
- viewModel.transition.collect {
- view.alpha = it.alpha
- view.scaleX = it.scale
- view.scaleY = it.scale
- view.imageTintList = ColorStateList.valueOf(it.color)
- }
- }
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt
deleted file mode 100644
index d4621e6..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.keyguard.ui.binder
-
-import android.graphics.PorterDuff
-import android.graphics.PorterDuffColorFilter
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
-import com.airbnb.lottie.LottieAnimationView
-import com.airbnb.lottie.LottieProperty
-import com.airbnb.lottie.model.KeyPath
-import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel
-import com.android.systemui.lifecycle.repeatWhenAttached
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.launch
-
-@ExperimentalCoroutinesApi
-object UdfpsFingerprintViewBinder {
- private var udfpsIconColor = 0
-
- /**
- * Drives UI for the UDFPS fingerprint view when it's NOT on aod. See
- * [UdfpsAodFingerprintViewBinder] and [UdfpsBackgroundViewBinder].
- */
- @JvmStatic
- fun bind(
- view: LottieAnimationView,
- viewModel: FingerprintViewModel,
- ) {
- view.alpha = 0f
- view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch {
- viewModel.transition.collect {
- view.alpha = it.alpha
- view.scaleX = it.scale
- view.scaleY = it.scale
- if (udfpsIconColor != (it.color)) {
- udfpsIconColor = it.color
- view.invalidate()
- }
- }
- }
-
- launch {
- viewModel.burnInOffsets.collect { burnInOffsets ->
- view.translationX = burnInOffsets.x.toFloat()
- view.translationY = burnInOffsets.y.toFloat()
- }
- }
-
- launch {
- viewModel.dozeAmount.collect { dozeAmount ->
- // Lottie progress represents: aod=0 to lockscreen=1
- view.progress = 1f - dozeAmount
- }
- }
-
- launch {
- viewModel.padding.collect { padding ->
- view.setPadding(padding, padding, padding, padding)
- }
- }
- }
- }
-
- // Add a callback that updates the color to `udfpsIconColor` whenever invalidate is called
- view.addValueCallback(KeyPath("**"), LottieProperty.COLOR_FILTER) {
- PorterDuffColorFilter(udfpsIconColor, PorterDuff.Mode.SRC_ATOP)
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt
deleted file mode 100644
index aabb3f4..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt
+++ /dev/null
@@ -1,55 +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.biometrics.ui.binder
-
-import android.view.View
-import com.android.systemui.res.R
-import com.android.systemui.keyguard.ui.binder.UdfpsAodFingerprintViewBinder
-import com.android.systemui.keyguard.ui.binder.UdfpsBackgroundViewBinder
-import com.android.systemui.keyguard.ui.binder.UdfpsFingerprintViewBinder
-import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel
-import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel
-import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel
-import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardInternalViewModel
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-
-@ExperimentalCoroutinesApi
-object UdfpsKeyguardInternalViewBinder {
-
- @JvmStatic
- fun bind(
- view: View,
- viewModel: UdfpsKeyguardInternalViewModel,
- aodViewModel: UdfpsAodViewModel,
- fingerprintViewModel: FingerprintViewModel,
- backgroundViewModel: BackgroundViewModel,
- ) {
- view.accessibilityDelegate = viewModel.accessibilityDelegate
-
- // bind child views
- UdfpsAodFingerprintViewBinder.bind(view.requireViewById(R.id.udfps_aod_fp), aodViewModel)
- UdfpsFingerprintViewBinder.bind(
- view.requireViewById(R.id.udfps_lockscreen_fp),
- fingerprintViewModel
- )
- UdfpsBackgroundViewBinder.bind(
- view.requireViewById(R.id.udfps_keyguard_fp_bg),
- backgroundViewModel
- )
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt
deleted file mode 100644
index 475d26f..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.keyguard.ui.binder
-
-import android.graphics.RectF
-import android.view.View
-import android.widget.FrameLayout
-import androidx.asynclayoutinflater.view.AsyncLayoutInflater
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.biometrics.UdfpsKeyguardView
-import com.android.systemui.biometrics.ui.binder.UdfpsKeyguardInternalViewBinder
-import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel
-import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel
-import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel
-import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardInternalViewModel
-import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModel
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.res.R
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.launch
-
-@ExperimentalCoroutinesApi
-object UdfpsKeyguardViewBinder {
- /**
- * Drives UI for the keyguard UDFPS view. Inflates child views on a background thread. For view
- * binders for its child views, see [UdfpsFingerprintViewBinder], [UdfpsBackgroundViewBinder] &
- * [UdfpsAodFingerprintViewBinder].
- */
- @JvmStatic
- fun bind(
- view: UdfpsKeyguardView,
- viewModel: UdfpsKeyguardViewModel,
- udfpsKeyguardInternalViewModel: UdfpsKeyguardInternalViewModel,
- aodViewModel: UdfpsAodViewModel,
- fingerprintViewModel: FingerprintViewModel,
- backgroundViewModel: BackgroundViewModel,
- ) {
- val layoutInflaterFinishListener =
- AsyncLayoutInflater.OnInflateFinishedListener { inflatedInternalView, _, parent ->
- UdfpsKeyguardInternalViewBinder.bind(
- inflatedInternalView,
- udfpsKeyguardInternalViewModel,
- aodViewModel,
- fingerprintViewModel,
- backgroundViewModel,
- )
- val lp = inflatedInternalView.layoutParams as FrameLayout.LayoutParams
- lp.width = viewModel.sensorBounds.width()
- lp.height = viewModel.sensorBounds.height()
- val relativeToView =
- getBoundsRelativeToView(
- inflatedInternalView,
- RectF(viewModel.sensorBounds),
- )
- lp.setMarginsRelative(
- relativeToView.left.toInt(),
- relativeToView.top.toInt(),
- relativeToView.right.toInt(),
- relativeToView.bottom.toInt(),
- )
- parent!!.addView(inflatedInternalView, lp)
- }
- val inflater = AsyncLayoutInflater(view.context)
- inflater.inflate(R.layout.udfps_keyguard_view_internal, view, layoutInflaterFinishListener)
-
- view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- launch {
- combine(aodViewModel.isVisible, fingerprintViewModel.visible) {
- isAodVisible,
- isFingerprintVisible ->
- isAodVisible || isFingerprintVisible
- }
- .collect { view.setVisible(it) }
- }
- }
- }
- }
-
- /**
- * Converts coordinates of RectF relative to the screen to coordinates relative to this view.
- *
- * @param bounds RectF based off screen coordinates in current orientation
- */
- private fun getBoundsRelativeToView(view: View, bounds: RectF): RectF {
- val pos: IntArray = view.locationOnScreen
- return RectF(
- bounds.left - pos[0],
- bounds.top - pos[1],
- bounds.right - pos[0],
- bounds.bottom - pos[1]
- )
- }
-}
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 9d557bb..920fc04 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
@@ -15,6 +15,8 @@
*/
package com.android.systemui.keyguard.ui.transitions
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToAodTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel
@@ -39,6 +41,18 @@
abstract class DeviceEntryIconTransitionModule {
@Binds
@IntoSet
+ abstract fun alternateBouncerToAod(
+ impl: AlternateBouncerToAodTransitionViewModel
+ ): DeviceEntryIconTransition
+
+ @Binds
+ @IntoSet
+ abstract fun alternateBouncerToGone(
+ impl: AlternateBouncerToGoneTransitionViewModel
+ ): DeviceEntryIconTransition
+
+ @Binds
+ @IntoSet
abstract fun aodToLockscreen(
impl: AodToLockscreenTransitionViewModel
): DeviceEntryIconTransition
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
index 1c6a2ab..bc9671e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
@@ -31,18 +31,21 @@
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultUdfpsAccessibilityOverlaySection
import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule.Companion.KEYGUARD_AMBIENT_INDICATION_AREA_SECTION
import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
import java.util.Optional
import javax.inject.Inject
import javax.inject.Named
import kotlin.jvm.optionals.getOrNull
+import kotlinx.coroutines.ExperimentalCoroutinesApi
/**
* Positions elements of the lockscreen to the default position.
*
* This will be the most common use case for phones in portrait mode.
*/
+@ExperimentalCoroutinesApi
@SysUISingleton
@JvmSuppressWildcards
class DefaultKeyguardBlueprint
@@ -62,6 +65,7 @@
communalTutorialIndicatorSection: CommunalTutorialIndicatorSection,
clockSection: ClockSection,
smartspaceSection: SmartspaceSection,
+ udfpsAccessibilityOverlaySection: DefaultUdfpsAccessibilityOverlaySection,
) : KeyguardBlueprint {
override val id: String = DEFAULT
@@ -79,7 +83,8 @@
aodBurnInSection,
communalTutorialIndicatorSection,
clockSection,
- defaultDeviceEntrySection, // Add LAST: Intentionally has z-order above other views.
+ defaultDeviceEntrySection,
+ udfpsAccessibilityOverlaySection, // Add LAST: Intentionally has z-order above others
)
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
index bf70682..9b40433 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
@@ -29,14 +29,17 @@
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
+import com.android.systemui.keyguard.ui.view.layout.sections.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.util.kotlin.getOrNull
import java.util.Optional
import javax.inject.Inject
import javax.inject.Named
+import kotlinx.coroutines.ExperimentalCoroutinesApi
/** Vertically aligns the shortcuts with the udfps. */
+@ExperimentalCoroutinesApi
@SysUISingleton
class ShortcutsBesideUdfpsKeyguardBlueprint
@Inject
@@ -53,6 +56,7 @@
defaultNotificationStackScrollLayoutSection: DefaultNotificationStackScrollLayoutSection,
aodNotificationIconsSection: AodNotificationIconsSection,
aodBurnInSection: AodBurnInSection,
+ udfpsAccessibilityOverlaySection: DefaultUdfpsAccessibilityOverlaySection,
) : KeyguardBlueprint {
override val id: String = SHORTCUTS_BESIDE_UDFPS
@@ -68,7 +72,8 @@
splitShadeGuidelines,
aodNotificationIconsSection,
aodBurnInSection,
- defaultDeviceEntrySection, // Add LAST: Intentionally has z-order above other views.
+ defaultDeviceEntrySection,
+ udfpsAccessibilityOverlaySection, // Add LAST: Intentionally has z-order above others
)
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
index f890ae6..16539db 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
@@ -30,6 +30,8 @@
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.SmartspaceSection
+import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeClockSection
import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeNotificationStackScrollLayoutSection
import com.android.systemui.util.kotlin.getOrNull
@@ -59,6 +61,8 @@
aodNotificationIconsSection: AodNotificationIconsSection,
aodBurnInSection: AodBurnInSection,
communalTutorialIndicatorSection: CommunalTutorialIndicatorSection,
+ smartspaceSection: SmartspaceSection,
+ clockSection: SplitShadeClockSection,
) : KeyguardBlueprint {
override val id: String = ID
@@ -73,8 +77,10 @@
splitShadeNotificationStackScrollLayoutSection,
splitShadeGuidelines,
aodNotificationIconsSection,
+ smartspaceSection,
aodBurnInSection,
communalTutorialIndicatorSection,
+ clockSection,
defaultDeviceEntrySection, // Add LAST: Intentionally has z-order above other views.
)
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 8166b45..d89e1e4 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
@@ -23,7 +23,6 @@
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import com.android.systemui.Flags.migrateClocksToBlueprint
-import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
@@ -38,7 +37,6 @@
private val context: Context,
private val clockViewModel: KeyguardClockViewModel,
private val smartspaceViewModel: KeyguardSmartspaceViewModel,
- private val featureFlags: FeatureFlagsClassic,
) : KeyguardSection() {
lateinit var burnInLayer: Layer
@@ -59,6 +57,8 @@
}
}
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)
@@ -89,14 +89,6 @@
val smartspaceView =
constraintLayout.requireViewById<View>(smartspaceViewModel.smartspaceViewId)
addView(smartspaceView)
- if (smartspaceViewModel.isDateWeatherDecoupled) {
- val dateView =
- constraintLayout.requireViewById<View>(smartspaceViewModel.dateId)
- val weatherView =
- constraintLayout.requireViewById<View>(smartspaceViewModel.weatherId)
- addView(weatherView)
- addView(dateView)
- }
}
}
}
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 39a0547..b429ab4 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
@@ -28,7 +28,6 @@
import androidx.constraintlayout.widget.ConstraintSet.TOP
import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.common.ui.ConfigurationState
-import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
@@ -49,7 +48,6 @@
constructor(
private val context: Context,
private val configurationState: ConfigurationState,
- private val featureFlags: FeatureFlagsClassic,
private val iconBindingFailureTracker: StatusBarIconViewBindingFailureTracker,
private val nicAodViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore,
@@ -119,14 +117,8 @@
}
constraintSet.apply {
if (migrateClocksToBlueprint()) {
- connect(
- nicId,
- TOP,
- smartspaceViewModel.smartspaceViewId,
- topAlignment,
- bottomMargin
- )
- setGoneMargin(nicId, topAlignment, bottomMargin)
+ connect(nicId, TOP, smartspaceViewModel.smartspaceViewId, 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 1df920a..656c75c 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
@@ -27,7 +27,7 @@
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
-import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.binder.KeyguardClockViewBinder
@@ -50,19 +50,21 @@
alpha: Float,
) = views.forEach { view -> this.setAlpha(view.id, alpha) }
-class ClockSection
+open class ClockSection
@Inject
constructor(
private val clockInteractor: KeyguardClockInteractor,
- private val keyguardClockViewModel: KeyguardClockViewModel,
- val smartspaceViewModel: KeyguardSmartspaceViewModel,
+ protected val keyguardClockViewModel: KeyguardClockViewModel,
+ private val smartspaceViewModel: KeyguardSmartspaceViewModel,
private val context: Context,
private val splitShadeStateController: SplitShadeStateController,
- private val featureFlags: FeatureFlagsClassic,
) : KeyguardSection() {
override fun addViews(constraintLayout: ConstraintLayout) {}
override fun bindData(constraintLayout: ConstraintLayout) {
+ if (!Flags.migrateClocksToBlueprint()) {
+ return
+ }
KeyguardClockViewBinder.bind(
this,
constraintLayout,
@@ -72,6 +74,9 @@
}
override fun applyConstraints(constraintSet: ConstraintSet) {
+ if (!Flags.migrateClocksToBlueprint()) {
+ return
+ }
clockInteractor.clock?.let { clock ->
constraintSet.applyDeltaFrom(buildConstraints(clock, constraintSet))
}
@@ -94,16 +99,6 @@
}
}
- var largeClockEndGuideline = PARENT_ID
-
- // Return if largeClockEndGuideline changes,
- // and use it to decide whether to refresh blueprint
- fun setClockShouldBeCentered(shouldBeCentered: Boolean): Boolean {
- val previousValue = largeClockEndGuideline
- largeClockEndGuideline = if (shouldBeCentered) PARENT_ID else R.id.split_shade_guideline
- return previousValue != largeClockEndGuideline
- }
-
private fun getTargetClockFace(clock: ClockController): ClockFaceLayout =
if (keyguardClockViewModel.useLargeClock) getLargeClockFace(clock)
else getSmallClockFace(clock)
@@ -113,10 +108,10 @@
private fun getLargeClockFace(clock: ClockController): ClockFaceLayout = clock.largeClock.layout
private fun getSmallClockFace(clock: ClockController): ClockFaceLayout = clock.smallClock.layout
- fun applyDefaultConstraints(constraints: ConstraintSet) {
+ open fun applyDefaultConstraints(constraints: ConstraintSet) {
constraints.apply {
connect(R.id.lockscreen_clock_view_large, START, PARENT_ID, START)
- connect(R.id.lockscreen_clock_view_large, END, largeClockEndGuideline, END)
+ connect(R.id.lockscreen_clock_view_large, END, PARENT_ID, 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/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
index a693ec9..0bf9ad0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
@@ -23,7 +23,6 @@
import android.util.DisplayMetrics
import android.view.View
import android.view.WindowManager
-import android.widget.FrameLayout
import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
@@ -32,31 +31,24 @@
import com.android.keyguard.LockIconViewController
import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.biometrics.AuthController
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.shared.model.KeyguardSection
-import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler
-import com.android.systemui.keyguard.ui.binder.AlternateBouncerViewBinder
import com.android.systemui.keyguard.ui.binder.DeviceEntryIconViewBinder
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
-import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerViewModel
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationPanelView
-import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.VibratorHelper
-import com.android.systemui.statusbar.gesture.TapGestureDetector
import dagger.Lazy
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-/** Includes both the device entry icon and the alternate bouncer scrim. */
+/** Includes the device entry icon. */
@ExperimentalCoroutinesApi
class DefaultDeviceEntrySection
@Inject
@@ -72,27 +64,15 @@
private val deviceEntryForegroundViewModel: Lazy<DeviceEntryForegroundViewModel>,
private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>,
private val falsingManager: Lazy<FalsingManager>,
- private val alternateBouncerViewModel: Lazy<AlternateBouncerViewModel>,
- private val notificationShadeWindowController: Lazy<NotificationShadeWindowController>,
- @Application private val scope: CoroutineScope,
- private val swipeUpAnywhereGestureHandler: Lazy<SwipeUpAnywhereGestureHandler>,
- private val tapGestureDetector: Lazy<TapGestureDetector>,
private val vibratorHelper: Lazy<VibratorHelper>,
) : KeyguardSection() {
private val deviceEntryIconViewId = R.id.device_entry_icon_view
- private val alternateBouncerViewId = R.id.alternate_bouncer
override fun addViews(constraintLayout: ConstraintLayout) {
if (!keyguardBottomAreaRefactor() && !DeviceEntryUdfpsRefactor.isEnabled) {
return
}
- if (DeviceEntryUdfpsRefactor.isEnabled) {
- // The alternate bouncer scrim needs to be below the device entry icon view, so
- // we add the view here before adding the device entry icon view.
- View.inflate(context, R.layout.alternate_bouncer, constraintLayout)
- }
-
notificationPanelView.findViewById<View>(R.id.lock_icon_view).let {
notificationPanelView.removeView(it)
}
@@ -119,17 +99,6 @@
vibratorHelper.get(),
)
}
- constraintLayout.findViewById<FrameLayout?>(alternateBouncerViewId)?.let {
- AlternateBouncerViewBinder.bind(
- it,
- alternateBouncerViewModel.get(),
- scope,
- notificationShadeWindowController.get(),
- falsingManager.get(),
- swipeUpAnywhereGestureHandler.get(),
- tapGestureDetector.get(),
- )
- }
} else {
constraintLayout.findViewById<LockIconView?>(R.id.lock_icon_view)?.let {
lockIconViewController.get().setLockIconView(it)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
index 56f717d..66c137f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
@@ -54,7 +54,7 @@
if (keyguardBottomAreaRefactor()) {
indicationAreaHandle =
KeyguardIndicationAreaBinder.bind(
- constraintLayout,
+ constraintLayout.requireViewById(R.id.keyguard_indication_area),
keyguardIndicationAreaViewModel,
keyguardRootViewModel,
indicationController,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultUdfpsAccessibilityOverlaySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultUdfpsAccessibilityOverlaySection.kt
new file mode 100644
index 0000000..e1a33de
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultUdfpsAccessibilityOverlaySection.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.sections
+
+import android.content.Context
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.Flags
+import com.android.systemui.deviceentry.ui.binder.UdfpsAccessibilityOverlayBinder
+import com.android.systemui.deviceentry.ui.view.UdfpsAccessibilityOverlay
+import com.android.systemui.deviceentry.ui.viewmodel.UdfpsAccessibilityOverlayViewModel
+import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** Positions the UDFPS accessibility overlay on the bottom half of the keyguard. */
+@ExperimentalCoroutinesApi
+class DefaultUdfpsAccessibilityOverlaySection
+@Inject
+constructor(
+ private val context: Context,
+ private val viewModel: UdfpsAccessibilityOverlayViewModel,
+) : KeyguardSection() {
+ private val viewId = R.id.udfps_accessibility_overlay
+ private var cachedConstraintLayout: ConstraintLayout? = null
+
+ override fun addViews(constraintLayout: ConstraintLayout) {
+ cachedConstraintLayout = constraintLayout
+ constraintLayout.addView(UdfpsAccessibilityOverlay(context).apply { id = viewId })
+ }
+
+ override fun bindData(constraintLayout: ConstraintLayout) {
+ UdfpsAccessibilityOverlayBinder.bind(
+ constraintLayout.findViewById(viewId)!!,
+ viewModel,
+ )
+ }
+
+ override fun applyConstraints(constraintSet: ConstraintSet) {
+ constraintSet.apply {
+ connect(viewId, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START)
+ connect(viewId, ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END)
+
+ create(R.id.udfps_accessibility_overlay_top_guideline, ConstraintSet.HORIZONTAL)
+ setGuidelinePercent(R.id.udfps_accessibility_overlay_top_guideline, .5f)
+ connect(
+ viewId,
+ ConstraintSet.TOP,
+ R.id.udfps_accessibility_overlay_top_guideline,
+ ConstraintSet.BOTTOM,
+ )
+
+ if (Flags.keyguardBottomAreaRefactor()) {
+ connect(
+ viewId,
+ ConstraintSet.BOTTOM,
+ R.id.keyguard_indication_area,
+ ConstraintSet.TOP,
+ )
+ } else {
+ connect(viewId, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM)
+ }
+ }
+ }
+
+ override fun removeViews(constraintLayout: ConstraintLayout) {
+ constraintLayout.removeView(viewId)
+ }
+}
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 368b388..8cd51cd 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
@@ -17,7 +17,6 @@
package com.android.systemui.keyguard.ui.view.layout.sections
import android.content.Context
-import android.view.View
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
@@ -36,7 +35,7 @@
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
import javax.inject.Inject
-class SmartspaceSection
+open class SmartspaceSection
@Inject
constructor(
val keyguardClockViewModel: KeyguardClockViewModel,
@@ -45,9 +44,13 @@
val smartspaceController: LockscreenSmartspaceController,
val keyguardUnlockAnimationController: KeyguardUnlockAnimationController,
) : KeyguardSection() {
- private var smartspaceView: View? = null
- private var weatherView: View? = null
- private var dateView: View? = null
+ var smartspaceView by keyguardSmartspaceViewModel::smartspaceView
+ var weatherView by keyguardSmartspaceViewModel::weatherView
+ var dateView by keyguardSmartspaceViewModel::dateView
+
+ val smartspaceViewId = keyguardSmartspaceViewModel.smartspaceViewId
+ val weatherViewId = keyguardSmartspaceViewModel.weatherId
+ val dateViewId = keyguardSmartspaceViewModel.dateId
override fun addViews(constraintLayout: ConstraintLayout) {
if (!migrateClocksToBlueprint()) {
@@ -67,10 +70,21 @@
}
override fun bindData(constraintLayout: ConstraintLayout) {
- KeyguardSmartspaceViewBinder.bind(this, constraintLayout, keyguardClockViewModel)
+ if (!migrateClocksToBlueprint()) {
+ return
+ }
+ KeyguardSmartspaceViewBinder.bind(
+ this,
+ constraintLayout,
+ keyguardClockViewModel,
+ keyguardSmartspaceViewModel,
+ )
}
override fun applyConstraints(constraintSet: ConstraintSet) {
+ if (!migrateClocksToBlueprint()) {
+ return
+ }
// Generally, weather should be next to dateView
// smartspace should be below date & weather views
constraintSet.apply {
@@ -130,7 +144,20 @@
}
}
}
- updateVisibility(constraintSet)
+ }
+ updateVisibility(constraintSet)
+ }
+
+ override fun removeViews(constraintLayout: ConstraintLayout) {
+ if (!migrateClocksToBlueprint()) {
+ return
+ }
+ listOf(smartspaceView, dateView, weatherView).forEach {
+ it?.let {
+ if (it.parent == constraintLayout) {
+ constraintLayout.removeView(it)
+ }
+ }
}
}
@@ -158,14 +185,4 @@
}
}
}
-
- override fun removeViews(constraintLayout: ConstraintLayout) {
- listOf(smartspaceView, dateView, weatherView).forEach {
- it?.let {
- if (it.parent == constraintLayout) {
- constraintLayout.removeView(it)
- }
- }
- }
- }
}
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
new file mode 100644
index 0000000..1302bfa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.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.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.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
+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,
+ smartspaceViewModel: KeyguardSmartspaceViewModel,
+ context: Context,
+ splitShadeStateController: SplitShadeStateController,
+) :
+ ClockSection(
+ clockInteractor,
+ keyguardClockViewModel,
+ smartspaceViewModel,
+ 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/SplitShadeNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
index b0b5c81..0f8e673 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
@@ -23,7 +23,6 @@
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
-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
@@ -72,25 +71,9 @@
return
}
constraintSet.apply {
- val bottomMargin =
- context.resources.getDimensionPixelSize(R.dimen.keyguard_status_view_bottom_margin)
-
- if (migrateClocksToBlueprint()) {
- connect(
- R.id.nssl_placeholder,
- TOP,
- smartspaceViewModel.smartspaceViewId,
- TOP,
- bottomMargin
- )
- setGoneMargin(R.id.nssl_placeholder, TOP, bottomMargin)
- } else {
- val splitShadeTopMargin =
- context.resources.getDimensionPixelSize(
- R.dimen.large_screen_shade_header_height
- )
- connect(R.id.nssl_placeholder, TOP, PARENT_ID, TOP, splitShadeTopMargin)
- }
+ val splitShadeTopMargin =
+ context.resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height)
+ connect(R.id.nssl_placeholder, TOP, PARENT_ID, TOP, splitShadeTopMargin)
connect(R.id.nssl_placeholder, START, PARENT_ID, START)
connect(R.id.nssl_placeholder, END, PARENT_ID, END)
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
new file mode 100644
index 0000000..a8e3be7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.domain.interactor.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
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+
+/**
+ * Breaks down ALTERNATE BOUNCER->AOD transition into discrete steps for corresponding views to
+ * consume.
+ */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+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),
+ )
+
+ val deviceEntryBackgroundViewAlpha: Flow<Float> =
+ transitionAnimation.sharedFlow(
+ duration = FromAlternateBouncerTransitionInteractor.TO_AOD_DURATION,
+ onStep = { 1 - it },
+ onFinish = { 0f },
+ )
+
+ override val deviceEntryParentViewAlpha: Flow<Float> =
+ deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { udfpsEnrolledAndEnabled
+ ->
+ if (udfpsEnrolledAndEnabled) {
+ transitionAnimation.immediatelyTransitionTo(1f)
+ } else {
+ emptyFlow()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
index 023d16ca..5d6b0ce 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,8 +18,12 @@
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
+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
@@ -33,10 +37,20 @@
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),
+ )
/** Scrim alpha values */
val scrimAlpha: Flow<ScrimAlpha> =
bouncerToGoneFlows.scrimAlpha(TO_GONE_DURATION, ALTERNATE_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
new file mode 100644
index 0000000..f4ae365
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.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.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 kotlin.math.roundToInt
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+
+/** Models the UI state for the UDFPS icon view in the alternate bouncer view. */
+@ExperimentalCoroutinesApi
+class AlternateBouncerUdfpsIconViewModel
+@Inject
+constructor(
+ val context: Context,
+ configurationInteractor: ConfigurationInteractor,
+ deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
+ deviceEntryBackgroundViewModel: DeviceEntryBackgroundViewModel,
+ fingerprintPropertyRepository: FingerprintPropertyRepository,
+) {
+ private val isSupported: Flow<Boolean> = deviceEntryUdfpsInteractor.isUdfpsSupported
+ val iconLocation: Flow<IconLocation> =
+ isSupported.flatMapLatest { supportsUI ->
+ if (supportsUI) {
+ fingerprintPropertyRepository.sensorLocations.map { sensorLocations ->
+ val sensorLocation =
+ sensorLocations.getOrDefault("", SensorLocationInternal.DEFAULT)
+ IconLocation(
+ left = sensorLocation.sensorLocationX - sensorLocation.sensorRadius,
+ top = sensorLocation.sensorLocationY - sensorLocation.sensorRadius,
+ right = sensorLocation.sensorLocationX + sensorLocation.sensorRadius,
+ bottom = sensorLocation.sensorLocationY + sensorLocation.sensorRadius,
+ )
+ }
+ } else {
+ emptyFlow()
+ }
+ }
+ val accessibilityDelegateHint: Flow<DeviceEntryIconView.AccessibilityHintType> =
+ flowOf(DeviceEntryIconView.AccessibilityHintType.ENTER)
+
+ private val fgIconColor: Flow<Int> =
+ configurationInteractor.onAnyConfigurationChange
+ .map { Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary) }
+ .onStart {
+ emit(Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary))
+ }
+ private val fgIconPadding: Flow<Int> =
+ configurationInteractor.scaleForResolution.map { scale ->
+ (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
+ .roundToInt()
+ }
+ val fgViewModel: Flow<DeviceEntryForegroundViewModel.ForegroundIconViewModel> =
+ combine(
+ fgIconColor,
+ fgIconPadding,
+ ) { color, padding ->
+ DeviceEntryForegroundViewModel.ForegroundIconViewModel(
+ type = DeviceEntryIconView.IconType.FINGERPRINT,
+ useAodVariant = false,
+ tint = color,
+ padding = padding,
+ )
+ }
+
+ val bgColor: Flow<Int> = deviceEntryBackgroundViewModel.color
+ val bgAlpha: Flow<Float> = flowOf(1f)
+
+ data class IconLocation(
+ val left: Int,
+ val top: Int,
+ val right: Int,
+ val bottom: Int,
+ ) {
+ val width = right - left
+ val height = bottom - top
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
index 3e8bbb3..be9ae1d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
@@ -19,11 +19,10 @@
import android.content.Context
import com.android.settingslib.Utils
-import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
@@ -34,7 +33,7 @@
@Inject
constructor(
val context: Context,
- configurationRepository: ConfigurationRepository, // TODO (b/309655554): create & use interactor
+ configurationInteractor: ConfigurationInteractor,
lockscreenToAodTransitionViewModel: LockscreenToAodTransitionViewModel,
aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
goneToAodTransitionViewModel: GoneToAodTransitionViewModel,
@@ -42,9 +41,10 @@
occludedToAodTransitionViewModel: OccludedToAodTransitionViewModel,
occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
+ alternateBouncerToAodTransitionViewModel: AlternateBouncerToAodTransitionViewModel,
) {
- private val color: Flow<Int> =
- configurationRepository.onAnyConfigurationChange
+ val color: Flow<Int> =
+ configurationInteractor.onAnyConfigurationChange
.map {
Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorSurface)
}
@@ -56,7 +56,7 @@
)
)
}
- private val alpha: Flow<Float> =
+ val alpha: Flow<Float> =
setOf(
lockscreenToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
aodToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
@@ -65,19 +65,7 @@
occludedToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
occludedToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
dreamingToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ alternateBouncerToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
)
.merge()
-
- val viewModel: Flow<BackgroundViewModel> =
- combine(color, alpha) { color, alpha ->
- BackgroundViewModel(
- alpha = alpha,
- tint = color,
- )
- }
-
- data class BackgroundViewModel(
- val alpha: Float,
- val tint: Int,
- )
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
index 9a50d83..fe0b365 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
@@ -19,6 +19,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.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -26,7 +27,6 @@
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
import com.android.systemui.res.R
import javax.inject.Inject
-import kotlin.math.roundToInt
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -45,6 +45,7 @@
deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
transitionInteractor: KeyguardTransitionInteractor,
deviceEntryIconViewModel: DeviceEntryIconViewModel,
+ udfpsOverlayInteractor: UdfpsOverlayInteractor,
) {
private val isShowingAod: Flow<Boolean> =
transitionInteractor.startedKeyguardState.map { keyguardState ->
@@ -73,11 +74,7 @@
isTransitionToAod && isUdfps
}
.distinctUntilChanged()
- private val padding: Flow<Int> =
- configurationRepository.scaleForResolution.map { scale ->
- (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
- .roundToInt()
- }
+ private val padding: Flow<Int> = udfpsOverlayInteractor.iconPadding
val viewModel: Flow<ForegroundIconViewModel> =
combine(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index f95713b..eacaa40 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -29,6 +29,7 @@
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.util.kotlin.sample
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -52,15 +53,12 @@
transitionInteractor: KeyguardTransitionInteractor,
val keyguardInteractor: KeyguardInteractor,
val viewModel: AodToLockscreenTransitionViewModel,
- val shadeDependentFlows: ShadeDependentFlows,
private val sceneContainerFlags: SceneContainerFlags,
private val keyguardViewController: Lazy<KeyguardViewController>,
private val deviceEntryInteractor: DeviceEntryInteractor,
) {
private val intEvaluator = IntEvaluator()
private val floatEvaluator = FloatEvaluator()
- private val toAodFromState: Flow<KeyguardState> =
- transitionInteractor.transitionStepsToState(KeyguardState.AOD).map { it.from }
private val showingAlternateBouncer: Flow<Boolean> =
transitionInteractor.startedKeyguardState.map { keyguardState ->
keyguardState == KeyguardState.ALTERNATE_BOUNCER
@@ -95,13 +93,31 @@
fullyDozingBurnInProgress,
)
}
+
+ private val dozeAmount: Flow<Float> =
+ combine(
+ transitionInteractor.startedKeyguardTransitionStep,
+ merge(
+ transitionInteractor.transitionStepsFromState(KeyguardState.AOD).map {
+ 1f - it.value
+ },
+ transitionInteractor.transitionStepsToState(KeyguardState.AOD).map { it.value }
+ ),
+ ) { startedKeyguardTransitionStep, aodTransitionAmount ->
+ if (
+ startedKeyguardTransitionStep.to == KeyguardState.AOD ||
+ startedKeyguardTransitionStep.from == KeyguardState.AOD
+ ) {
+ aodTransitionAmount
+ } else {
+ // in case a new transition (ie: to occluded) cancels a transition to or from
+ // aod, then we want to make sure the doze amount is still updated to 0
+ 0f
+ }
+ }
// Burn-in offsets that animate based on the transition amount to AOD
private val animatedBurnInOffsets: Flow<BurnInOffsets> =
- combine(
- nonAnimatedBurnInOffsets,
- transitionInteractor.transitionStepsToState(KeyguardState.AOD)
- ) { burnInOffsets, transitionStepsToAod ->
- val dozeAmount = transitionStepsToAod.value
+ combine(nonAnimatedBurnInOffsets, dozeAmount) { burnInOffsets, dozeAmount ->
BurnInOffsets(
intEvaluator.evaluate(dozeAmount, 0, burnInOffsets.x),
intEvaluator.evaluate(dozeAmount, 0, burnInOffsets.y),
@@ -120,22 +136,35 @@
val burnInOffsets: Flow<BurnInOffsets> =
deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { udfpsEnrolled ->
if (udfpsEnrolled) {
- toAodFromState.flatMapLatest { fromState ->
- when (fromState) {
- KeyguardState.AOD,
- KeyguardState.GONE,
- KeyguardState.OCCLUDED,
- KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
- KeyguardState.OFF,
- KeyguardState.DOZING,
- KeyguardState.DREAMING,
- KeyguardState.PRIMARY_BOUNCER -> nonAnimatedBurnInOffsets
- KeyguardState.ALTERNATE_BOUNCER -> animatedBurnInOffsets
- KeyguardState.LOCKSCREEN ->
- shadeDependentFlows.transitionFlow(
- flowWhenShadeIsExpanded = nonAnimatedBurnInOffsets,
- flowWhenShadeIsNotExpanded = animatedBurnInOffsets,
- )
+ combine(
+ transitionInteractor.startedKeyguardTransitionStep.sample(
+ shadeInteractor.isAnyFullyExpanded,
+ ::Pair
+ ),
+ animatedBurnInOffsets,
+ nonAnimatedBurnInOffsets,
+ ) {
+ (startedTransitionStep, shadeExpanded),
+ animatedBurnInOffsets,
+ nonAnimatedBurnInOffsets ->
+ if (startedTransitionStep.to == KeyguardState.AOD) {
+ when (startedTransitionStep.from) {
+ KeyguardState.ALTERNATE_BOUNCER -> animatedBurnInOffsets
+ KeyguardState.LOCKSCREEN ->
+ if (shadeExpanded) {
+ nonAnimatedBurnInOffsets
+ } else {
+ animatedBurnInOffsets
+ }
+ else -> nonAnimatedBurnInOffsets
+ }
+ } else if (startedTransitionStep.from == KeyguardState.AOD) {
+ when (startedTransitionStep.to) {
+ KeyguardState.LOCKSCREEN -> animatedBurnInOffsets
+ else -> BurnInOffsets(x = 0, y = 0, progress = 0f)
+ }
+ } else {
+ BurnInOffsets(x = 0, y = 0, progress = 0f)
}
}
} else {
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 3aeff61..528a2ee 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
@@ -27,8 +27,8 @@
import com.android.systemui.plugins.clocks.ClockController
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
@@ -79,10 +79,10 @@
?: false
)
- val clockShouldBeCentered: Flow<Boolean> =
+ val clockShouldBeCentered: StateFlow<Boolean> =
keyguardInteractor.clockShouldBeCentered.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = true
+ initialValue = false
)
}
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 4541458..26e7ee8 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 android.content.Context
import android.util.Log
+import android.view.View
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
import javax.inject.Inject
@@ -25,7 +26,7 @@
@SysUISingleton
class KeyguardSmartspaceViewModel
@Inject
-constructor(val context: Context, smartspaceController: LockscreenSmartspaceController) {
+constructor(val context: Context, val smartspaceController: LockscreenSmartspaceController) {
val isSmartspaceEnabled: Boolean = smartspaceController.isEnabled()
val isWeatherEnabled: Boolean = smartspaceController.isWeatherEnabled()
val isDateWeatherDecoupled: Boolean = smartspaceController.isDateWeatherDecoupled()
@@ -38,6 +39,10 @@
val weatherId: Int
get() = getId("weather_smartspace_view")
+ var smartspaceView: View? = null
+ var weatherView: View? = null
+ var dateView: View? = null
+
private fun getId(name: String): Int {
return context.resources.getIdentifier(name, "id", context.packageName).also {
if (it == 0) {
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
new file mode 100644
index 0000000..d57e569
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.biometrics.AuthController
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+@SysUISingleton
+class LockscreenContentViewModel
+@Inject
+constructor(
+ private val interactor: KeyguardBlueprintInteractor,
+ private val authController: AuthController,
+) {
+ val isUdfpsVisible: Boolean
+ get() = authController.isUdfpsSupported
+
+ fun blueprintId(scope: CoroutineScope): StateFlow<String> {
+ return interactor.blueprint
+ .map { it.id }
+ .distinctUntilChanged()
+ .stateIn(
+ scope = scope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = interactor.getCurrentBlueprint().id,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt
deleted file mode 100644
index 6e77e13e..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.ui.viewmodel
-
-import android.content.Context
-import com.android.systemui.keyguard.domain.interactor.Offsets
-import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
-import com.android.systemui.res.R
-import javax.inject.Inject
-import kotlin.math.roundToInt
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
-
-/** View-model for UDFPS AOD view. */
-@ExperimentalCoroutinesApi
-class UdfpsAodViewModel
-@Inject
-constructor(
- val interactor: UdfpsKeyguardInteractor,
- val context: Context,
-) {
- val alpha: Flow<Float> = interactor.dozeAmount
- val burnInOffsets: Flow<Offsets> = interactor.burnInOffsets
- val isVisible: Flow<Boolean> = alpha.map { it != 0f }
-
- // Padding between the fingerprint icon and its bounding box in pixels.
- val padding: Flow<Int> =
- interactor.scaleForResolution.map { scale ->
- (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
- .roundToInt()
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt
deleted file mode 100644
index d894a11..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.ui.viewmodel
-
-import com.android.systemui.biometrics.UdfpsKeyguardAccessibilityDelegate
-import javax.inject.Inject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-
-@ExperimentalCoroutinesApi
-class UdfpsKeyguardInternalViewModel
-@Inject
-constructor(val accessibilityDelegate: UdfpsKeyguardAccessibilityDelegate)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt
deleted file mode 100644
index dca151d..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.ui.viewmodel
-
-import android.graphics.Rect
-import javax.inject.Inject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-
-@ExperimentalCoroutinesApi
-class UdfpsKeyguardViewModel @Inject constructor() {
- var sensorBounds: Rect = Rect()
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt
deleted file mode 100644
index 098b481..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.android.systemui.keyguard.ui.viewmodel
-
-import android.graphics.Rect
-import com.android.systemui.biometrics.UdfpsKeyguardView
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.ui.binder.UdfpsKeyguardViewBinder
-import javax.inject.Inject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-
-@ExperimentalCoroutinesApi
-@SysUISingleton
-class UdfpsKeyguardViewModels
-@Inject
-constructor(
- private val viewModel: UdfpsKeyguardViewModel,
- private val internalViewModel: UdfpsKeyguardInternalViewModel,
- private val aodViewModel: UdfpsAodViewModel,
- private val lockscreenFingerprintViewModel: FingerprintViewModel,
- private val lockscreenBackgroundViewModel: BackgroundViewModel,
-) {
-
- fun setSensorBounds(sensorBounds: Rect) {
- viewModel.sensorBounds = sensorBounds
- }
-
- fun bindViews(view: UdfpsKeyguardView) {
- UdfpsKeyguardViewBinder.bind(
- view,
- viewModel,
- internalViewModel,
- aodViewModel,
- lockscreenFingerprintViewModel,
- lockscreenBackgroundViewModel
- )
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt
deleted file mode 100644
index 642904d..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.ui.viewmodel
-
-import android.content.Context
-import androidx.annotation.ColorInt
-import com.android.settingslib.Utils.getColorAttrDefaultColor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.Offsets
-import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.StatusBarState
-import com.android.systemui.res.R
-import com.android.wm.shell.animation.Interpolators
-import javax.inject.Inject
-import kotlin.math.roundToInt
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
-
-/** View-model for UDFPS lockscreen views. */
-@ExperimentalCoroutinesApi
-open class UdfpsLockscreenViewModel(
- context: Context,
- lockscreenColorResId: Int,
- alternateBouncerColorResId: Int,
- transitionInteractor: KeyguardTransitionInteractor,
- udfpsKeyguardInteractor: UdfpsKeyguardInteractor,
- keyguardInteractor: KeyguardInteractor,
-) {
- private val toLockscreen: Flow<TransitionViewModel> =
- transitionInteractor.anyStateToLockscreenTransition.map {
- TransitionViewModel(
- alpha =
- if (it.from == KeyguardState.AOD) {
- it.value // animate
- } else {
- 1f
- },
- scale = 1f,
- color = getColorAttrDefaultColor(context, lockscreenColorResId),
- )
- }
-
- private val toAlternateBouncer: Flow<TransitionViewModel> =
- keyguardInteractor.statusBarState.flatMapLatest { statusBarState ->
- transitionInteractor.transitionStepsToState(KeyguardState.ALTERNATE_BOUNCER).map {
- TransitionViewModel(
- alpha = 1f,
- scale =
- if (visibleInKeyguardState(it.from, statusBarState)) {
- 1f
- } else {
- Interpolators.FAST_OUT_SLOW_IN.getInterpolation(it.value)
- },
- color = getColorAttrDefaultColor(context, alternateBouncerColorResId),
- )
- }
- }
-
- private val fadeOut: Flow<TransitionViewModel> =
- keyguardInteractor.statusBarState.flatMapLatest { statusBarState ->
- merge(
- transitionInteractor.anyStateToGoneTransition,
- transitionInteractor.anyStateToAodTransition,
- transitionInteractor.anyStateToOccludedTransition,
- transitionInteractor.anyStateToPrimaryBouncerTransition,
- transitionInteractor.anyStateToDreamingTransition,
- )
- .map {
- TransitionViewModel(
- alpha =
- if (visibleInKeyguardState(it.from, statusBarState)) {
- 1f - it.value
- } else {
- 0f
- },
- scale = 1f,
- color =
- if (it.from == KeyguardState.ALTERNATE_BOUNCER) {
- getColorAttrDefaultColor(context, alternateBouncerColorResId)
- } else {
- getColorAttrDefaultColor(context, lockscreenColorResId)
- },
- )
- }
- }
-
- private fun visibleInKeyguardState(
- state: KeyguardState,
- statusBarState: StatusBarState
- ): Boolean {
- return when (state) {
- KeyguardState.OFF,
- KeyguardState.DOZING,
- KeyguardState.DREAMING,
- KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
- KeyguardState.AOD,
- KeyguardState.PRIMARY_BOUNCER,
- KeyguardState.GONE,
- KeyguardState.OCCLUDED -> false
- KeyguardState.LOCKSCREEN -> statusBarState == StatusBarState.KEYGUARD
- KeyguardState.ALTERNATE_BOUNCER -> true
- }
- }
-
- private val keyguardStateTransition =
- merge(
- toAlternateBouncer,
- toLockscreen,
- fadeOut,
- )
-
- private val dialogHideAffordancesAlphaMultiplier: Flow<Float> =
- udfpsKeyguardInteractor.dialogHideAffordancesRequest.map { hideAffordances ->
- if (hideAffordances) {
- 0f
- } else {
- 1f
- }
- }
-
- private val alphaMultiplier: Flow<Float> =
- combine(
- transitionInteractor.startedKeyguardState,
- dialogHideAffordancesAlphaMultiplier,
- udfpsKeyguardInteractor.shadeExpansion,
- udfpsKeyguardInteractor.qsProgress,
- ) { startedKeyguardState, dialogHideAffordancesAlphaMultiplier, shadeExpansion, qsProgress
- ->
- if (startedKeyguardState == KeyguardState.ALTERNATE_BOUNCER) {
- 1f
- } else {
- dialogHideAffordancesAlphaMultiplier * (1f - shadeExpansion) * (1f - qsProgress)
- }
- }
-
- val transition: Flow<TransitionViewModel> =
- combine(
- alphaMultiplier,
- keyguardStateTransition,
- ) { alphaMultiplier, keyguardStateTransition ->
- TransitionViewModel(
- alpha = keyguardStateTransition.alpha * alphaMultiplier,
- scale = keyguardStateTransition.scale,
- color = keyguardStateTransition.color,
- )
- }
- val visible: Flow<Boolean> = transition.map { it.alpha != 0f }
-}
-
-@ExperimentalCoroutinesApi
-class FingerprintViewModel
-@Inject
-constructor(
- val context: Context,
- transitionInteractor: KeyguardTransitionInteractor,
- interactor: UdfpsKeyguardInteractor,
- keyguardInteractor: KeyguardInteractor,
-) :
- UdfpsLockscreenViewModel(
- context,
- android.R.attr.textColorPrimary,
- com.android.internal.R.attr.materialColorOnPrimaryFixed,
- transitionInteractor,
- interactor,
- keyguardInteractor,
- ) {
- val dozeAmount: Flow<Float> = interactor.dozeAmount
- val burnInOffsets: Flow<Offsets> = interactor.burnInOffsets
-
- // Padding between the fingerprint icon and its bounding box in pixels.
- val padding: Flow<Int> =
- interactor.scaleForResolution.map { scale ->
- (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
- .roundToInt()
- }
-}
-
-@ExperimentalCoroutinesApi
-class BackgroundViewModel
-@Inject
-constructor(
- val context: Context,
- transitionInteractor: KeyguardTransitionInteractor,
- interactor: UdfpsKeyguardInteractor,
- keyguardInteractor: KeyguardInteractor,
-) :
- UdfpsLockscreenViewModel(
- context,
- com.android.internal.R.attr.colorSurface,
- com.android.internal.R.attr.materialColorPrimaryFixed,
- transitionInteractor,
- interactor,
- keyguardInteractor,
- )
-
-data class TransitionViewModel(
- val alpha: Float,
- val scale: Float,
- @ColorInt val color: Int,
-)
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 d8bb3e6..0d5ba64 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -26,6 +26,7 @@
import com.android.systemui.log.echo.LogcatEchoTrackerProd;
import com.android.systemui.log.table.TableLogBuffer;
import com.android.systemui.log.table.TableLogBufferFactory;
+import com.android.systemui.plugins.clocks.ClockMessageBuffers;
import com.android.systemui.qs.QSFragmentLegacy;
import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository;
import com.android.systemui.qs.pipeline.shared.TileSpec;
@@ -417,6 +418,18 @@
}
/**
+ * Provides a {@link ClockMessageBuffers} which contains the keyguard clock message buffers.
+ */
+ @Provides
+ public static ClockMessageBuffers provideKeyguardClockMessageBuffers(
+ @KeyguardClockLog LogBuffer infraClockLog,
+ @KeyguardSmallClockLog LogBuffer smallClockLog,
+ @KeyguardLargeClockLog LogBuffer largeClockLog
+ ) {
+ return new ClockMessageBuffers(infraClockLog, smallClockLog, largeClockLog);
+ }
+
+ /**
* Provides a {@link LogBuffer} for use by {@link com.android.keyguard.KeyguardUpdateMonitor}.
*/
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index a99c51c2..be93936 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -405,12 +405,15 @@
}
widgetGroupIds.forEach { id ->
squishedViewState.widgetStates.get(id)?.let { state ->
- state.alpha =
- calculateAlpha(
- squishFraction,
- startPosition / nonsquishedHeight,
- endPosition / nonsquishedHeight
- )
+ // Don't modify alpha for elements that should be invisible (e.g. disabled seekbar)
+ if (state.alpha != 0f) {
+ state.alpha =
+ calculateAlpha(
+ squishFraction,
+ startPosition / nonsquishedHeight,
+ endPosition / nonsquishedHeight
+ )
+ }
}
}
return groupTop // used for the widget group above this group
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaInSceneContainerFlag.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaInSceneContainerFlag.kt
index 898298c..77279f2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaInSceneContainerFlag.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaInSceneContainerFlag.kt
@@ -17,6 +17,7 @@
package com.android.systemui.media.controls.util
import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
/** Helper for reading or using the media_in_scene_container flag state. */
@@ -25,6 +26,10 @@
/** The aconfig flag name */
const val FLAG_NAME = Flags.FLAG_MEDIA_IN_SCENE_CONTAINER
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
/** Is the flag enabled? */
@JvmStatic
inline val isEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index 0e7e69b..270bfbe 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -263,11 +263,7 @@
* If the shortcut entry `android:enabled` is set to `true`, the shortcut will be visible in the
* Widget Picker to all users.
*/
- // TODO(b/316332684)
- @Suppress("UNREACHABLE_CODE")
fun setNoteTaskShortcutEnabled(value: Boolean, user: UserHandle) {
- return // shortcut should not be enabled until additional features are implemented.
-
if (!userManager.isUserUnlocked(user)) {
debugLog { "setNoteTaskShortcutEnabled call but user locked: user=$user" }
return
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index 1ab64b7..ba3357c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -17,12 +17,10 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
-import android.os.Build;
import android.provider.Settings;
-import com.android.systemui.res.R;
import com.android.systemui.plugins.qs.QSTile;
-import com.android.systemui.util.leak.GarbageMonitor;
+import com.android.systemui.res.R;
import java.util.ArrayList;
import java.util.Arrays;
@@ -44,10 +42,6 @@
final String defaultTileList = res.getString(R.string.quick_settings_tiles_default);
tiles.addAll(Arrays.asList(defaultTileList.split(",")));
- if (Build.IS_DEBUGGABLE
- && GarbageMonitor.ADD_MEMORY_TILE_TO_DEFAULT_ON_DEBUGGABLE_BUILDS) {
- tiles.add(GarbageMonitor.MemoryTile.TILE_SPEC);
- }
return tiles;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index 2af7ae0..47b0624 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -23,7 +23,6 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
-import android.os.Build;
import android.provider.Settings;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
@@ -33,7 +32,6 @@
import androidx.annotation.Nullable;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.qs.QSTile;
@@ -42,8 +40,8 @@
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.util.leak.GarbageMonitor;
import java.util.ArrayList;
import java.util.Arrays;
@@ -114,9 +112,6 @@
possibleTiles.add(spec);
}
}
- if (Build.IS_DEBUGGABLE && !current.contains(GarbageMonitor.MemoryTile.TILE_SPEC)) {
- possibleTiles.add(GarbageMonitor.MemoryTile.TILE_SPEC);
- }
final ArrayList<QSTile> tilesToAdd = new ArrayList<>();
possibleTiles.remove("cell");
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/QSTileServiceWrapper.java b/packages/SystemUI/src/com/android/systemui/qs/external/QSTileServiceWrapper.java
index 2345667..83b6f0d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/QSTileServiceWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/QSTileServiceWrapper.java
@@ -19,16 +19,21 @@
import android.service.quicksettings.IQSTileService;
import android.util.Log;
+import androidx.annotation.NonNull;
+
public class QSTileServiceWrapper {
private static final String TAG = "IQSTileServiceWrapper";
+ @NonNull
private final IQSTileService mService;
- public QSTileServiceWrapper(IQSTileService service) {
+ public QSTileServiceWrapper(@NonNull IQSTileService service) {
mService = service;
}
+ // This can never be null, as it's the constructor parameter and it's final
+ @NonNull
public IBinder asBinder() {
return mService.asBinder();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index e08eb37..880289e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -40,6 +40,7 @@
import android.util.ArraySet;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
@@ -54,8 +55,10 @@
import java.util.NoSuchElementException;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Predicate;
/**
* Manages the lifecycle of a TileService.
@@ -101,8 +104,8 @@
private final ActivityManager mActivityManager;
private Set<Integer> mQueuedMessages = new ArraySet<>();
- @Nullable
- private volatile QSTileServiceWrapper mWrapper;
+ @NonNull
+ private volatile Optional<QSTileServiceWrapper> mOptionalWrapper = Optional.empty();
private boolean mListening;
private IBinder mClickBinder;
@@ -222,6 +225,7 @@
// Only try a new binding if we are not currently bound.
mIsBound.compareAndSet(false, bindServices());
if (!mIsBound.get()) {
+ Log.d(TAG, "Failed to bind to service");
mContext.unbindService(this);
}
} catch (SecurityException e) {
@@ -281,7 +285,7 @@
service.linkToDeath(this, 0);
} catch (RemoteException e) {
}
- mWrapper = wrapper;
+ mOptionalWrapper = Optional.of(wrapper);
handlePendingMessages();
}
@@ -368,6 +372,10 @@
* are supposed to be bound, we will try to bind after some amount of time.
*/
private void handleDeath() {
+ if (!mIsBound.get()) {
+ // If we are already not bound, don't do anything else.
+ return;
+ }
mExecutor.execute(() -> {
if (!mIsBound.get()) {
// If we are already not bound, don't do anything else.
@@ -522,7 +530,7 @@
@Override
public void onTileAdded() {
if (mDebug) Log.d(TAG, "onTileAdded " + getComponent());
- if (mWrapper == null || !mWrapper.onTileAdded()) {
+ if (isNullOrFailedAction(mOptionalWrapper, QSTileServiceWrapper::onTileAdded)) {
queueMessage(MSG_ON_ADDED);
handleDeath();
}
@@ -531,7 +539,7 @@
@Override
public void onTileRemoved() {
if (mDebug) Log.d(TAG, "onTileRemoved " + getComponent());
- if (mWrapper == null || !mWrapper.onTileRemoved()) {
+ if (isNullOrFailedAction(mOptionalWrapper, QSTileServiceWrapper::onTileRemoved)) {
queueMessage(MSG_ON_REMOVED);
handleDeath();
}
@@ -541,7 +549,7 @@
public void onStartListening() {
if (mDebug) Log.d(TAG, "onStartListening " + getComponent());
mListening = true;
- if (mWrapper != null && !mWrapper.onStartListening()) {
+ if (isNotNullAndFailedAction(mOptionalWrapper, QSTileServiceWrapper::onStartListening)) {
handleDeath();
}
}
@@ -550,7 +558,7 @@
public void onStopListening() {
if (mDebug) Log.d(TAG, "onStopListening " + getComponent());
mListening = false;
- if (mWrapper != null && !mWrapper.onStopListening()) {
+ if (isNotNullAndFailedAction(mOptionalWrapper, QSTileServiceWrapper::onStopListening)) {
handleDeath();
}
}
@@ -558,7 +566,7 @@
@Override
public void onClick(IBinder iBinder) {
if (mDebug) Log.d(TAG, "onClick " + iBinder + " " + getComponent() + " " + mUser);
- if (mWrapper == null || !mWrapper.onClick(iBinder)) {
+ if (isNullOrFailedAction(mOptionalWrapper, (wrapper) -> wrapper.onClick(iBinder))) {
mClickBinder = iBinder;
queueMessage(MSG_ON_CLICK);
handleDeath();
@@ -568,7 +576,7 @@
@Override
public void onUnlockComplete() {
if (mDebug) Log.d(TAG, "onUnlockComplete " + getComponent());
- if (mWrapper == null || !mWrapper.onUnlockComplete()) {
+ if (isNullOrFailedAction(mOptionalWrapper, QSTileServiceWrapper::onUnlockComplete)) {
queueMessage(MSG_ON_UNLOCK_COMPLETE);
handleDeath();
}
@@ -577,7 +585,7 @@
@Nullable
@Override
public IBinder asBinder() {
- return mWrapper != null ? mWrapper.asBinder() : null;
+ return mOptionalWrapper.map(QSTileServiceWrapper::asBinder).orElse(null);
}
@Override
@@ -591,18 +599,42 @@
}
private void freeWrapper() {
- if (mWrapper != null) {
+ if (mOptionalWrapper.isPresent()) {
try {
- mWrapper.asBinder().unlinkToDeath(this, 0);
+ mOptionalWrapper.ifPresent(
+ (wrapper) -> wrapper.asBinder().unlinkToDeath(this, 0)
+ );
} catch (NoSuchElementException e) {
Log.w(TAG, "Trying to unlink not linked recipient for component"
+ mIntent.getComponent().flattenToShortString());
}
- mWrapper = null;
+ mOptionalWrapper = Optional.empty();
}
}
public interface TileChangeListener {
void onTileChanged(ComponentName tile);
}
+
+ /**
+ * Returns true if the Optional is empty OR performing the action on the content of the Optional
+ * (when not empty) fails.
+ */
+ private static boolean isNullOrFailedAction(
+ Optional<QSTileServiceWrapper> optionalWrapper,
+ Predicate<QSTileServiceWrapper> action
+ ) {
+ return !optionalWrapper.map(action::test).orElse(false);
+ }
+
+ /**
+ * Returns true if the Optional is not empty AND performing the action on the content of
+ * the Optional fails.
+ */
+ private static boolean isNotNullAndFailedAction(
+ Optional<QSTileServiceWrapper> optionalWrapper,
+ Predicate<QSTileServiceWrapper> action
+ ) {
+ return !optionalWrapper.map(action::test).orElse(true);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index 17e6375..bdcbac0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -14,7 +14,6 @@
package com.android.systemui.qs.tileimpl;
-import android.os.Build;
import android.util.Log;
import androidx.annotation.Nullable;
@@ -25,15 +24,14 @@
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.external.CustomTile;
-import com.android.systemui.util.leak.GarbageMonitor;
-
-import dagger.Lazy;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Provider;
+import dagger.Lazy;
+
/**
* A factory that creates Quick Settings tiles based on a tileSpec
*
@@ -79,9 +77,7 @@
@Nullable
protected QSTileImpl createTileInternal(String tileSpec) {
// Stock tiles.
- if (mTileMap.containsKey(tileSpec)
- // We should not return a Garbage Monitory Tile if the build is not Debuggable
- && (!tileSpec.equals(GarbageMonitor.MemoryTile.TILE_SPEC) || Build.IS_DEBUGGABLE)) {
+ if (mTileMap.containsKey(tileSpec)) {
return mTileMap.get(tileSpec).get();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
index 66da8bd..216d716 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
@@ -42,9 +42,7 @@
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.recordissue.RecordIssueDialogDelegate
import com.android.systemui.res.R
-import com.android.systemui.settings.UserContextProvider
import com.android.systemui.statusbar.phone.KeyguardDismissUtil
-import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
@@ -63,8 +61,7 @@
private val keyguardDismissUtil: KeyguardDismissUtil,
private val keyguardStateController: KeyguardStateController,
private val dialogLaunchAnimator: DialogLaunchAnimator,
- private val sysuiDialogFactory: SystemUIDialog.Factory,
- private val userContextProvider: UserContextProvider,
+ private val delegateFactory: RecordIssueDialogDelegate.Factory,
) :
QSTileImpl<QSTile.BooleanState>(
host,
@@ -102,7 +99,8 @@
private fun showPrompt(view: View?) {
val dialog: AlertDialog =
- RecordIssueDialogDelegate(sysuiDialogFactory, userContextProvider) {
+ delegateFactory
+ .create {
isRecording = true
refreshState()
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt
index ca5302e..c932cee 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt
@@ -16,11 +16,15 @@
package com.android.systemui.qs.tiles.impl.custom.data.repository
+import android.content.pm.PackageManager
+import android.content.pm.ServiceInfo
import android.graphics.drawable.Icon
import android.os.UserHandle
import android.service.quicksettings.Tile
+import android.service.quicksettings.TileService
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.qs.external.CustomTileStatePersister
+import com.android.systemui.qs.external.PackageManagerAdapter
import com.android.systemui.qs.external.TileServiceKey
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.impl.custom.commons.copy
@@ -63,6 +67,12 @@
*/
fun getTile(user: UserHandle): Tile?
+ /** @see [com.android.systemui.qs.external.TileLifecycleManager.isActiveTile] */
+ suspend fun isTileActive(): Boolean
+
+ /** @see [com.android.systemui.qs.external.TileLifecycleManager.isToggleableTile] */
+ suspend fun isTileToggleable(): Boolean
+
/**
* Updates tile with the non-null values from [newTile]. Overwrites the current cache when
* [user] differs from the cached one. [isPersistable] tile will be persisted to be possibly
@@ -92,6 +102,7 @@
constructor(
private val tileSpec: TileSpec.CustomTileSpec,
private val customTileStatePersister: CustomTileStatePersister,
+ private val packageManagerAdapter: PackageManagerAdapter,
@Background private val backgroundContext: CoroutineContext,
) : CustomTileRepository {
@@ -149,6 +160,34 @@
}
}
+ override suspend fun isTileActive(): Boolean =
+ withContext(backgroundContext) {
+ try {
+ val info: ServiceInfo =
+ packageManagerAdapter.getServiceInfo(
+ tileSpec.componentName,
+ META_DATA_QUERY_FLAGS
+ )
+ info.metaData?.getBoolean(TileService.META_DATA_ACTIVE_TILE, false) == true
+ } catch (e: PackageManager.NameNotFoundException) {
+ false
+ }
+ }
+
+ override suspend fun isTileToggleable(): Boolean =
+ withContext(backgroundContext) {
+ try {
+ val info: ServiceInfo =
+ packageManagerAdapter.getServiceInfo(
+ tileSpec.componentName,
+ META_DATA_QUERY_FLAGS
+ )
+ info.metaData?.getBoolean(TileService.META_DATA_TOGGLEABLE_TILE, false) == true
+ } catch (e: PackageManager.NameNotFoundException) {
+ false
+ }
+ }
+
private suspend fun updateTile(
user: UserHandle,
isPersistable: Boolean,
@@ -193,4 +232,12 @@
private fun UserHandle.getKey() = TileServiceKey(tileSpec.componentName, this.identifier)
private data class TileWithUser(val user: UserHandle, val tile: Tile)
+
+ private companion object {
+ const val META_DATA_QUERY_FLAGS =
+ (PackageManager.GET_META_DATA or
+ PackageManager.MATCH_UNINSTALLED_PACKAGES or
+ PackageManager.MATCH_DIRECT_BOOT_UNAWARE or
+ PackageManager.MATCH_DIRECT_BOOT_AWARE)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt
index 351bba5..10b012d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt
@@ -19,10 +19,9 @@
import android.os.UserHandle
import android.service.quicksettings.Tile
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.qs.external.TileServiceManager
import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepository
import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileRepository
-import com.android.systemui.qs.tiles.impl.custom.di.bound.CustomTileBoundScope
+import com.android.systemui.qs.tiles.impl.di.QSTileScope
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
@@ -35,15 +34,13 @@
import kotlinx.coroutines.flow.onEach
/** Manages updates of the [Tile] assigned for the current custom tile. */
-@CustomTileBoundScope
+@QSTileScope
class CustomTileInteractor
@Inject
constructor(
- private val user: UserHandle,
private val defaultsRepository: CustomTileDefaultsRepository,
private val customTileRepository: CustomTileRepository,
- private val tileServiceManager: TileServiceManager,
- @CustomTileBoundScope private val boundScope: CoroutineScope,
+ @QSTileScope private val tileScope: CoroutineScope,
@Background private val backgroundContext: CoroutineContext,
) {
@@ -51,8 +48,7 @@
MutableSharedFlow<Tile>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
/** [Tile] updates. [updateTile] to emit a new one. */
- val tiles: Flow<Tile>
- get() = customTileRepository.getTiles(user)
+ fun getTiles(user: UserHandle): Flow<Tile> = customTileRepository.getTiles(user)
/**
* Current [Tile]
@@ -61,10 +57,14 @@
* the tile hasn't been updated for the current user. Can happen when this is accessed before
* [init] returns.
*/
- val tile: Tile
- get() =
- customTileRepository.getTile(user)
- ?: throw IllegalStateException("Attempt to get a tile for a wrong user")
+ fun getTile(user: UserHandle): Tile =
+ customTileRepository.getTile(user)
+ ?: throw IllegalStateException("Attempt to get a tile for a wrong user")
+
+ /**
+ * True if the tile is toggleable like a switch and false if it operates as a clickable button.
+ */
+ suspend fun isTileToggleable(): Boolean = customTileRepository.isTileToggleable()
/**
* Initializes the repository for the current user. Suspends until it's safe to call [tile]
@@ -73,36 +73,36 @@
* - receive tile update in [updateTile];
* - restoration happened for a persisted tile.
*/
- suspend fun init() {
- launchUpdates()
- customTileRepository.restoreForTheUserIfNeeded(user, tileServiceManager.isActiveTile)
+ suspend fun initForUser(user: UserHandle) {
+ launchUpdates(user)
+ customTileRepository.restoreForTheUserIfNeeded(user, customTileRepository.isTileActive())
// Suspend to make sure it gets the tile from one of the sources: restoration, defaults, or
// tile update.
customTileRepository.getTiles(user).firstOrNull()
}
- private fun launchUpdates() {
+ private fun launchUpdates(user: UserHandle) {
tileUpdates
.onEach {
customTileRepository.updateWithTile(
user,
it,
- tileServiceManager.isActiveTile,
+ customTileRepository.isTileActive(),
)
}
.flowOn(backgroundContext)
- .launchIn(boundScope)
+ .launchIn(tileScope)
defaultsRepository
.defaults(user)
.onEach {
customTileRepository.updateWithDefaults(
user,
it,
- tileServiceManager.isActiveTile,
+ customTileRepository.isTileActive(),
)
}
.flowOn(backgroundContext)
- .launchIn(boundScope)
+ .launchIn(tileScope)
}
/** Updates current [Tile]. Emits a new event in [tiles]. */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
index 580c421..ef3df48 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
@@ -22,8 +22,8 @@
/**
* Represents tiles behaviour logic. This ViewModel is a connection between tile view and data
- * layers. All direct inheritors must be added to the [QSTileViewModelInterfaceComplianceTest] class
- * to pass compliance tests.
+ * layers. All direct inheritors must be added to the
+ * [com.android.systemui.qs.tiles.viewmodel.QSTileViewModelTest] class to pass interface tests.
*
* All methods of this view model should be considered running on the main thread. This means no
* synchronous long running operations are permitted in any method.
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
index 5bf44fa..e051df4 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -24,27 +24,63 @@
import android.content.res.ColorStateList
import android.graphics.Color
import android.os.Bundle
+import android.os.UserHandle
import android.view.Gravity
import android.view.LayoutInflater
import android.view.WindowManager
import android.widget.Button
import android.widget.PopupMenu
import android.widget.Switch
+import androidx.annotation.AnyThread
+import androidx.annotation.MainThread
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
+import com.android.systemui.mediaprojection.SessionCreationSource
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog
+import com.android.systemui.qs.tiles.RecordIssueTile
import com.android.systemui.res.R
import com.android.systemui.screenrecord.RecordingService
import com.android.systemui.screenrecord.ScreenRecordingAudioSource
import com.android.systemui.settings.UserContextProvider
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.SystemUIDialog
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.concurrent.Executor
-class RecordIssueDialogDelegate(
+class RecordIssueDialogDelegate
+@AssistedInject
+constructor(
private val factory: SystemUIDialog.Factory,
private val userContextProvider: UserContextProvider,
- private val onStarted: Runnable
+ private val userTracker: UserTracker,
+ private val flags: FeatureFlagsClassic,
+ @Background private val bgExecutor: Executor,
+ @Main private val mainExecutor: Executor,
+ private val devicePolicyResolver: dagger.Lazy<ScreenCaptureDevicePolicyResolver>,
+ private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
+ private val userFileManager: UserFileManager,
+ @Assisted private val onStarted: Runnable,
) : SystemUIDialog.Delegate {
+ /** To inject dependencies and allow for easier testing */
+ @AssistedFactory
+ interface Factory {
+ /** Create a dialog object */
+ fun create(onStarted: Runnable): RecordIssueDialogDelegate
+ }
+
@SuppressLint("UseSwitchCompatOrMaterialCode") private lateinit var screenRecordSwitch: Switch
private lateinit var issueTypeButton: Button
+ @MainThread
override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
dialog.apply {
setView(LayoutInflater.from(context).inflate(R.layout.record_issue_dialog, null))
@@ -63,17 +99,64 @@
override fun createDialog(): SystemUIDialog = factory.create(this)
+ @MainThread
override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
dialog.apply {
window?.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
window?.setGravity(Gravity.CENTER)
screenRecordSwitch = requireViewById(R.id.screenrecord_switch)
+ screenRecordSwitch.setOnCheckedChangeListener { _, isEnabled ->
+ onScreenRecordSwitchClicked(context, isEnabled)
+ }
issueTypeButton = requireViewById(R.id.issue_type_button)
issueTypeButton.setOnClickListener { onIssueTypeClicked(context) }
}
}
+ @AnyThread
+ private fun onScreenRecordSwitchClicked(context: Context, isEnabled: Boolean) {
+ if (!isEnabled) return
+
+ bgExecutor.execute {
+ if (
+ flags.isEnabled(WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES) &&
+ devicePolicyResolver
+ .get()
+ .isScreenCaptureCompletelyDisabled(UserHandle.of(userTracker.userId))
+ ) {
+ mainExecutor.execute {
+ ScreenCaptureDisabledDialog(context).show()
+ screenRecordSwitch.isChecked = false
+ }
+ return@execute
+ }
+
+ mediaProjectionMetricsLogger.notifyProjectionInitiated(
+ userTracker.userId,
+ SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER
+ )
+
+ if (flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)) {
+ val prefs =
+ userFileManager.getSharedPreferences(
+ RecordIssueTile.TILE_SPEC,
+ Context.MODE_PRIVATE,
+ userTracker.userId
+ )
+ if (!prefs.getBoolean(HAS_APPROVED_SCREEN_RECORDING, false)) {
+ mainExecutor.execute {
+ ScreenCapturePermissionDialogDelegate(factory, prefs).createDialog().apply {
+ setOnCancelListener { screenRecordSwitch.isChecked = false }
+ show()
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @MainThread
private fun onIssueTypeClicked(context: Context) {
val selectedCategory = issueTypeButton.text.toString()
val popupMenu = PopupMenu(context, issueTypeButton)
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/ScreenCapturePermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/ScreenCapturePermissionDialogDelegate.kt
new file mode 100644
index 0000000..de6d3f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/ScreenCapturePermissionDialogDelegate.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recordissue
+
+import android.content.SharedPreferences
+import android.os.Bundle
+import android.view.Gravity
+import android.view.WindowManager
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+
+const val HAS_APPROVED_SCREEN_RECORDING = "HasApprovedScreenRecord"
+
+class ScreenCapturePermissionDialogDelegate(
+ private val dialogFactory: SystemUIDialog.Factory,
+ private val sharedPreferences: SharedPreferences,
+) : SystemUIDialog.Delegate {
+
+ override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
+ dialog.apply {
+ setIcon(R.drawable.ic_screenrecord)
+ setTitle(R.string.screenrecord_permission_dialog_title)
+ setMessage(R.string.screenrecord_permission_dialog_warning_entire_screen)
+ setNegativeButton(R.string.slice_permission_deny) { _, _ -> cancel() }
+ setPositiveButton(R.string.slice_permission_allow) { _, _ ->
+ sharedPreferences.edit().putBoolean(HAS_APPROVED_SCREEN_RECORDING, true).apply()
+ dismiss()
+ }
+ window?.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
+ window?.setGravity(Gravity.CENTER)
+ }
+ }
+
+ override fun createDialog(): SystemUIDialog = dialogFactory.create(this)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 47518bb..5abb4dd 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -48,6 +48,7 @@
import com.android.systemui.util.asIndenting
import com.android.systemui.util.printSection
import com.android.systemui.util.println
+import dagger.Lazy
import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -80,8 +81,8 @@
private val sceneLogger: SceneLogger,
@FalsingCollectorActual private val falsingCollector: FalsingCollector,
private val powerInteractor: PowerInteractor,
- private val simBouncerInteractor: SimBouncerInteractor,
- private val authenticationInteractor: AuthenticationInteractor,
+ private val simBouncerInteractor: Lazy<SimBouncerInteractor>,
+ private val authenticationInteractor: Lazy<AuthenticationInteractor>,
) : CoreStartable {
override fun start() {
@@ -152,7 +153,7 @@
}
}
applicationScope.launch {
- simBouncerInteractor.isAnySimSecure.collect { isAnySimLocked ->
+ simBouncerInteractor.get().isAnySimSecure.collect { isAnySimLocked ->
val canSwipeToEnter = deviceEntryInteractor.canSwipeToEnter.value
val isUnlocked = deviceEntryInteractor.isUnlocked.value
@@ -166,15 +167,17 @@
isUnlocked && canSwipeToEnter == false -> {
switchToScene(
targetSceneKey = SceneKey.Gone,
- loggingReason = "All SIM cards unlocked and device already" +
- " unlocked and lockscreen doesn't require a swipe to dismiss."
+ loggingReason =
+ "All SIM cards unlocked and device already" +
+ " unlocked and lockscreen doesn't require a swipe to dismiss."
)
}
else -> {
switchToScene(
targetSceneKey = SceneKey.Lockscreen,
- loggingReason = "All SIM cards unlocked and device still locked" +
- " or lockscreen still requires a swipe to dismiss."
+ loggingReason =
+ "All SIM cards unlocked and device still locked" +
+ " or lockscreen still requires a swipe to dismiss."
)
}
}
@@ -262,7 +265,7 @@
" to swipe up on lockscreen to enter.",
)
} else if (
- authenticationInteractor.getAuthenticationMethod() ==
+ authenticationInteractor.get().getAuthenticationMethod() ==
AuthenticationMethodModel.Sim
) {
switchToScene(
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 1156250..8c3e4a5 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
@@ -14,30 +14,94 @@
* limitations under the License.
*/
+@file:Suppress("NOTHING_TO_INLINE")
+
package com.android.systemui.scene.shared.flag
-import android.content.Context
-import androidx.annotation.VisibleForTesting
-import com.android.systemui.Flags as AConfigFlags
+import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
+import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.Flags.sceneContainer
import com.android.systemui.compose.ComposeFacade
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flag
-import com.android.systemui.flags.Flags
-import com.android.systemui.flags.ReleasedFlag
-import com.android.systemui.flags.ResourceBooleanFlag
-import com.android.systemui.flags.UnreleasedFlag
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.Flags.SCENE_CONTAINER_ENABLED
+import com.android.systemui.flags.RefactorFlagUtils
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.media.controls.util.MediaInSceneContainerFlag
-import com.android.systemui.res.R
import dagger.Module
import dagger.Provides
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
+
+/** Helper for reading or using the scene container flag state. */
+object SceneContainerFlag {
+ /** The flag description -- not an aconfig flag name */
+ const val DESCRIPTION = "SceneContainerFlag"
+
+ inline val isEnabled
+ get() =
+ SCENE_CONTAINER_ENABLED && // mainStaticFlag
+ sceneContainer() && // mainAconfigFlag
+ keyguardBottomAreaRefactor() &&
+ KeyguardShadeMigrationNssl.isEnabled &&
+ MediaInSceneContainerFlag.isEnabled &&
+ ComposeFacade.isComposeAvailable()
+
+ /**
+ * The main static flag, SCENE_CONTAINER_ENABLED. This is an explicit static flag check that
+ * helps with downstream optimizations (like unused code stripping) in builds where aconfig
+ * flags are still writable. Do not remove!
+ */
+ inline fun getMainStaticFlag() =
+ FlagToken("Flags.SCENE_CONTAINER_ENABLED", SCENE_CONTAINER_ENABLED)
+
+ /** The main aconfig flag. */
+ inline fun getMainAconfigFlag() = FlagToken(FLAG_SCENE_CONTAINER, sceneContainer())
+
+ /** The set of secondary flags which must be enabled for scene container to work properly */
+ inline fun getSecondaryFlags(): Sequence<FlagToken> =
+ sequenceOf(
+ FlagToken(FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor()),
+ KeyguardShadeMigrationNssl.token,
+ MediaInSceneContainerFlag.token,
+ )
+
+ /** The full set of requirements for SceneContainer */
+ inline fun getAllRequirements(): Sequence<FlagToken> {
+ val composeRequirement =
+ FlagToken("ComposeFacade.isComposeAvailable()", ComposeFacade.isComposeAvailable())
+ return sequenceOf(getMainStaticFlag(), getMainAconfigFlag()) +
+ getSecondaryFlags() +
+ composeRequirement
+ }
+
+ /** Return all dependencies of this flag in pairs where [Pair.first] depends on [Pair.second] */
+ inline fun getFlagDependencies(): Sequence<Pair<FlagToken, FlagToken>> {
+ val mainStaticFlag = getMainStaticFlag()
+ val mainAconfigFlag = getMainAconfigFlag()
+ return sequence {
+ // The static and aconfig flags should be equal; make them co-dependent
+ yield(mainAconfigFlag to mainStaticFlag)
+ yield(mainStaticFlag to mainAconfigFlag)
+ // all other flags depend on the static flag for brevity
+ } + getSecondaryFlags().map { mainStaticFlag to it }
+ }
+
+ /**
+ * 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, DESCRIPTION)
+
+ /**
+ * 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, DESCRIPTION)
+}
/**
* Defines interface for classes that can check whether the scene container framework feature is
@@ -52,133 +116,25 @@
fun requirementDescription(): String
}
-class SceneContainerFlagsImpl
-@AssistedInject
-constructor(
- @Application private val context: Context,
- private val featureFlagsClassic: FeatureFlagsClassic,
- @Assisted private val isComposeAvailable: Boolean,
-) : SceneContainerFlags {
-
- companion object {
- @VisibleForTesting
- val classicFlagTokens: List<Flag<Boolean>> =
- listOf(
- Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW,
- )
- }
-
- /** The list of requirements, all must be met for the feature to be enabled. */
- private val requirements =
- listOf(
- AconfigFlagMustBeEnabled(
- flagName = AConfigFlags.FLAG_SCENE_CONTAINER,
- flagValue = sceneContainer(),
- ),
- AconfigFlagMustBeEnabled(
- flagName = AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
- flagValue = keyguardBottomAreaRefactor(),
- ),
- AconfigFlagMustBeEnabled(
- flagName = KeyguardShadeMigrationNssl.FLAG_NAME,
- flagValue = KeyguardShadeMigrationNssl.isEnabled,
- ),
- AconfigFlagMustBeEnabled(
- flagName = MediaInSceneContainerFlag.FLAG_NAME,
- flagValue = MediaInSceneContainerFlag.isEnabled,
- ),
- ) +
- classicFlagTokens.map { flagToken -> FlagMustBeEnabled(flagToken) } +
- listOf(
- ComposeMustBeAvailable(),
- CompileTimeFlagMustBeEnabled(),
- ResourceConfigMustBeEnabled()
- )
+class SceneContainerFlagsImpl : SceneContainerFlags {
override fun isEnabled(): Boolean {
- // SCENE_CONTAINER_ENABLED is an explicit static flag check that helps with downstream
- // optimizations, e.g., unused code stripping. Do not remove!
- return Flags.SCENE_CONTAINER_ENABLED && requirements.all { it.isMet() }
+ return SceneContainerFlag.isEnabled
}
override fun requirementDescription(): String {
return buildString {
- requirements.forEach { requirement ->
+ SceneContainerFlag.getAllRequirements().forEach { requirement ->
append('\n')
- append(if (requirement.isMet()) " [MET]" else "[NOT MET]")
+ append(if (requirement.isEnabled) " [MET]" else "[NOT MET]")
append(" ${requirement.name}")
}
}
}
-
- private interface Requirement {
- val name: String
-
- fun isMet(): Boolean
- }
-
- private inner class ComposeMustBeAvailable : Requirement {
- override val name = "Jetpack Compose must be available"
-
- override fun isMet(): Boolean {
- return isComposeAvailable
- }
- }
-
- private inner class CompileTimeFlagMustBeEnabled : Requirement {
- override val name = "Flags.SCENE_CONTAINER_ENABLED must be enabled in code"
-
- override fun isMet(): Boolean {
- return Flags.SCENE_CONTAINER_ENABLED
- }
- }
-
- private inner class FlagMustBeEnabled<FlagType : Flag<*>>(
- private val flag: FlagType,
- ) : Requirement {
- override val name = "Flag ${flag.name} must be enabled"
-
- override fun isMet(): Boolean {
- return when (flag) {
- is ResourceBooleanFlag -> featureFlagsClassic.isEnabled(flag)
- is ReleasedFlag -> featureFlagsClassic.isEnabled(flag)
- is UnreleasedFlag -> featureFlagsClassic.isEnabled(flag)
- else -> error("Unsupported flag type ${flag.javaClass}")
- }
- }
- }
-
- private inner class AconfigFlagMustBeEnabled(
- flagName: String,
- private val flagValue: Boolean,
- ) : Requirement {
- override val name: String = "Aconfig flag $flagName must be enabled"
-
- override fun isMet(): Boolean {
- return flagValue
- }
- }
-
- private inner class ResourceConfigMustBeEnabled : Requirement {
- override val name: String = "R.bool.config_sceneContainerFrameworkEnabled must be true"
-
- override fun isMet(): Boolean {
- return context.resources.getBoolean(R.bool.config_sceneContainerFrameworkEnabled)
- }
- }
-
- @AssistedFactory
- interface Factory {
- fun create(isComposeAvailable: Boolean): SceneContainerFlagsImpl
- }
}
@Module
object SceneContainerFlagsModule {
- @Provides
- @SysUISingleton
- fun impl(factory: SceneContainerFlagsImpl.Factory): SceneContainerFlags {
- return factory.create(ComposeFacade.isComposeAvailable())
- }
+ @Provides @SysUISingleton fun impl(): SceneContainerFlags = SceneContainerFlagsImpl()
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
index 5cbea90..7130fa1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
@@ -11,8 +11,6 @@
import android.view.animation.AccelerateDecelerateInterpolator
import androidx.constraintlayout.widget.Guideline
import com.android.systemui.res.R
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import javax.inject.Inject
/**
@@ -23,7 +21,6 @@
constructor(
private val workProfileMessageController: WorkProfileMessageController,
private val screenshotDetectionController: ScreenshotDetectionController,
- private val featureFlags: FeatureFlags,
) {
private lateinit var container: ViewGroup
private lateinit var guideline: Guideline
@@ -63,10 +60,8 @@
fun onScreenshotTaken(screenshot: ScreenshotData) {
val workProfileData = workProfileMessageController.onScreenshotTaken(screenshot.userHandle)
- var notifiedApps: List<CharSequence> = listOf()
- if (featureFlags.isEnabled(Flags.SCREENSHOT_DETECTION)) {
- notifiedApps = screenshotDetectionController.maybeNotifyOfScreenshot(screenshot)
- }
+ var notifiedApps: List<CharSequence> =
+ screenshotDetectionController.maybeNotifyOfScreenshot(screenshot)
// If work profile first run needs to show, bias towards that, otherwise show screenshot
// detection notification if needed.
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 95f7c94a..17eb3c8 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1096,7 +1096,7 @@
}
@Override
- public void onPulseExpansionChanged(boolean expandingChanged) {
+ public void onPulseExpansionAmountChanged(boolean expandingChanged) {
if (mKeyguardBypassController.getBypassEnabled()) {
// Position the notifications while dragging down while pulsing
requestScrollerTopPaddingUpdate(false /* animate */);
@@ -1720,9 +1720,12 @@
}
// To prevent the weather clock from overlapping with the notification shelf on AOD, we use
// the small clock here
- if (mKeyguardStatusViewController.isLargeClockBlockingNotificationShelf()
- && hasVisibleNotifications() && isOnAod()) {
- return SMALL;
+ // With migrateClocksToBlueprint, weather clock will have behaviors similar to other clocks
+ if (!migrateClocksToBlueprint()) {
+ if (mKeyguardStatusViewController.isLargeClockBlockingNotificationShelf()
+ && hasVisibleNotifications() && isOnAod()) {
+ return SMALL;
+ }
}
return LARGE;
}
@@ -1742,8 +1745,9 @@
} else {
layout = mNotificationContainerParent;
}
+
if (migrateClocksToBlueprint()) {
- mKeyguardInteractor.setClockShouldBeCentered(shouldBeCentered);
+ mKeyguardInteractor.setClockShouldBeCentered(mSplitShadeEnabled && shouldBeCentered);
} else {
mKeyguardStatusViewController.updateAlignment(
layout, mSplitShadeEnabled, shouldBeCentered, animate);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 07ce577..3cf468f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -54,8 +54,13 @@
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
+import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler;
+import com.android.systemui.keyguard.ui.binder.AlternateBouncerViewBinder;
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerViewModel;
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
import com.android.systemui.log.BouncerLogger;
+import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.res.R;
import com.android.systemui.shared.animation.DisableSubpixelTextTransitionListener;
import com.android.systemui.statusbar.DragDownHelper;
@@ -64,6 +69,7 @@
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.gesture.TapGestureDetector;
import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
@@ -76,14 +82,19 @@
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;
+import dagger.Lazy;
+
import java.io.PrintWriter;
import java.util.Optional;
import java.util.function.Consumer;
import javax.inject.Inject;
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
+
/**
* Controller for {@link NotificationShadeWindowView}.
*/
@@ -149,6 +160,7 @@
};
private final SystemClock mClock;
+ @ExperimentalCoroutinesApi
@Inject
public NotificationShadeWindowViewController(
LockscreenShadeTransitionController transitionController,
@@ -190,7 +202,13 @@
QuickSettingsController quickSettingsController,
PrimaryBouncerInteractor primaryBouncerInteractor,
AlternateBouncerInteractor alternateBouncerInteractor,
- SelectedUserInteractor selectedUserInteractor) {
+ SelectedUserInteractor selectedUserInteractor,
+ Lazy<JavaAdapter> javaAdapter,
+ Lazy<AlternateBouncerViewModel> alternateBouncerViewModel,
+ Lazy<FalsingManager> falsingManager,
+ Lazy<SwipeUpAnywhereGestureHandler> swipeUpAnywhereGestureHandler,
+ Lazy<TapGestureDetector> tapGestureDetector,
+ Lazy<AlternateBouncerUdfpsIconViewModel> alternateBouncerUdfpsIconViewModel) {
mLockscreenShadeTransitionController = transitionController;
mFalsingCollector = falsingCollector;
mStatusBarStateController = statusBarStateController;
@@ -235,6 +253,25 @@
featureFlagsClassic,
selectedUserInteractor);
+ if (DeviceEntryUdfpsRefactor.isEnabled()) {
+ AlternateBouncerViewBinder.bind(
+ mView.findViewById(R.id.alternate_bouncer),
+ alternateBouncerViewModel.get(),
+ falsingManager.get(),
+ swipeUpAnywhereGestureHandler.get(),
+ tapGestureDetector.get(),
+ alternateBouncerUdfpsIconViewModel.get()
+ );
+ javaAdapter.get().alwaysCollectFlow(
+ alternateBouncerViewModel.get().getForcePluginOpen(),
+ forcePluginOpen ->
+ mNotificationShadeWindowController.setForcePluginOpen(
+ forcePluginOpen,
+ alternateBouncerViewModel.get()
+ )
+ );
+ }
+
collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(),
mLockscreenToDreamingTransition);
collectFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index 8d1e8d0..0c67279 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -20,9 +20,9 @@
import android.view.animation.Interpolator
import androidx.annotation.VisibleForTesting
import androidx.core.animation.ObjectAnimator
-import com.android.systemui.Dumpable
import com.android.app.animation.Interpolators
import com.android.app.animation.InterpolatorsAndroidX
+import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -31,6 +31,7 @@
import com.android.systemui.shade.ShadeViewController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
import com.android.systemui.statusbar.phone.DozeParameters
@@ -206,8 +207,15 @@
val nowExpanding = isPulseExpanding()
val changed = nowExpanding != pulseExpanding
pulseExpanding = nowExpanding
- for (listener in wakeUpListeners) {
- listener.onPulseExpansionChanged(changed)
+ if (!NotificationIconContainerRefactor.isEnabled) {
+ for (listener in wakeUpListeners) {
+ listener.onPulseExpansionAmountChanged(changed)
+ }
+ }
+ if (changed) {
+ for (listener in wakeUpListeners) {
+ listener.onPulseExpandingChanged(pulseExpanding)
+ }
}
}
}
@@ -620,13 +628,20 @@
*
* @param expandingChanged if the user has started or stopped expanding
*/
- fun onPulseExpansionChanged(expandingChanged: Boolean) {}
+ @Deprecated(
+ message = "Use onPulseExpandedChanged instead.",
+ replaceWith = ReplaceWith("onPulseExpandedChanged"),
+ )
+ fun onPulseExpansionAmountChanged(expandingChanged: Boolean) {}
/**
* Called when the animator started by [scheduleDelayedDozeAmountAnimation] begins running
* after the start delay, or after it ends/is cancelled.
*/
fun onDelayedDozeAmountAnimationRunning(running: Boolean) {}
+
+ /** Called whenever a pulse has started or stopped expanding. */
+ fun onPulseExpandingChanged(isPulseExpanding: Boolean) {}
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
index 3e9c6fb..3b48b39 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
@@ -3,10 +3,8 @@
import android.util.FloatProperty
import android.view.View
import androidx.annotation.FloatRange
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.flags.RefactorFlag
import com.android.systemui.res.R
+import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation
import com.android.systemui.statusbar.notification.stack.AnimationProperties
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
import kotlin.math.abs
@@ -42,13 +40,13 @@
/** Current top corner in pixel, based on [topRoundness] and [maxRadius] */
val topCornerRadius: Float
get() =
- if (roundableState.newHeadsUpAnim.isEnabled) roundableState.topCornerRadius
+ if (NotificationsImprovedHunAnimation.isEnabled) roundableState.topCornerRadius
else topRoundness * maxRadius
/** Current bottom corner in pixel, based on [bottomRoundness] and [maxRadius] */
val bottomCornerRadius: Float
get() =
- if (roundableState.newHeadsUpAnim.isEnabled) roundableState.bottomCornerRadius
+ if (NotificationsImprovedHunAnimation.isEnabled) roundableState.bottomCornerRadius
else bottomRoundness * maxRadius
/** Get and update the current radii */
@@ -318,13 +316,10 @@
internal val targetView: View,
private val roundable: Roundable,
maxRadius: Float,
- featureFlags: FeatureFlags? = null
) {
internal var maxRadius = maxRadius
private set
- internal val newHeadsUpAnim = RefactorFlag.forView(Flags.IMPROVED_HUN_ANIMATIONS, featureFlags)
-
/** Animatable for top roundness */
private val topAnimatable = topAnimatable(roundable)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt
index cf03d1c..2cc1403 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt
@@ -62,8 +62,8 @@
override val isPulseExpanding: Flow<Boolean> = conflatedCallbackFlow {
val listener =
object : NotificationWakeUpCoordinator.WakeUpListener {
- override fun onPulseExpansionChanged(expandingChanged: Boolean) {
- trySend(expandingChanged)
+ override fun onPulseExpandingChanged(isPulseExpanding: Boolean) {
+ trySend(isPulseExpanding)
}
}
trySend(wakeUpCoordinator.isPulseExpanding())
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index 61f9be5..76e5fd3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -112,15 +112,6 @@
aodIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
aodIcon.setIncreasedSize(true)
- // Construct the centered icon view.
- val centeredIcon = if (entry.sbn.notification.isMediaNotification) {
- iconBuilder.createIconView(entry).apply {
- scaleType = ImageView.ScaleType.CENTER_INSIDE
- }
- } else {
- null
- }
-
// Set the icon views' icons
val (normalIconDescriptor, sensitiveIconDescriptor) = getIconDescriptors(entry)
@@ -128,10 +119,7 @@
setIcon(entry, normalIconDescriptor, sbIcon)
setIcon(entry, sensitiveIconDescriptor, shelfIcon)
setIcon(entry, sensitiveIconDescriptor, aodIcon)
- if (centeredIcon != null) {
- setIcon(entry, normalIconDescriptor, centeredIcon)
- }
- entry.icons = IconPack.buildPack(sbIcon, shelfIcon, aodIcon, centeredIcon, entry.icons)
+ entry.icons = IconPack.buildPack(sbIcon, shelfIcon, aodIcon, entry.icons)
} catch (e: InflationException) {
entry.icons = IconPack.buildEmptyPack(entry.icons)
throw e
@@ -171,11 +159,6 @@
it.setNotification(entry.sbn, notificationContentDescription)
setIcon(entry, sensitiveIconDescriptor, it)
}
-
- entry.icons.centeredIcon?.let {
- it.setNotification(entry.sbn, notificationContentDescription)
- setIcon(entry, sensitiveIconDescriptor, it)
- }
}
private fun updateIconsSafe(entry: NotificationEntry) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java
index 054e381..442c097 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java
@@ -31,7 +31,6 @@
@Nullable private final StatusBarIconView mStatusBarIcon;
@Nullable private final StatusBarIconView mShelfIcon;
@Nullable private final StatusBarIconView mAodIcon;
- @Nullable private final StatusBarIconView mCenteredIcon;
@Nullable private StatusBarIcon mSmallIconDescriptor;
@Nullable private StatusBarIcon mPeopleAvatarDescriptor;
@@ -43,7 +42,7 @@
* haven't been inflated yet or there was an error while inflating them).
*/
public static IconPack buildEmptyPack(@Nullable IconPack fromSource) {
- return new IconPack(false, null, null, null, null, fromSource);
+ return new IconPack(false, null, null, null, fromSource);
}
/**
@@ -53,9 +52,8 @@
@NonNull StatusBarIconView statusBarIcon,
@NonNull StatusBarIconView shelfIcon,
@NonNull StatusBarIconView aodIcon,
- @Nullable StatusBarIconView centeredIcon,
@Nullable IconPack source) {
- return new IconPack(true, statusBarIcon, shelfIcon, aodIcon, centeredIcon, source);
+ return new IconPack(true, statusBarIcon, shelfIcon, aodIcon, source);
}
private IconPack(
@@ -63,12 +61,10 @@
@Nullable StatusBarIconView statusBarIcon,
@Nullable StatusBarIconView shelfIcon,
@Nullable StatusBarIconView aodIcon,
- @Nullable StatusBarIconView centeredIcon,
@Nullable IconPack source) {
mAreIconsAvailable = areIconsAvailable;
mStatusBarIcon = statusBarIcon;
mShelfIcon = shelfIcon;
- mCenteredIcon = centeredIcon;
mAodIcon = aodIcon;
if (source != null) {
mIsImportantConversation = source.mIsImportantConversation;
@@ -91,11 +87,6 @@
return mShelfIcon;
}
- @Nullable
- public StatusBarIconView getCenteredIcon() {
- return mCenteredIcon;
- }
-
/** The version of the icon that's shown when pulsing (in AOD). */
@Nullable
public StatusBarIconView getAodIcon() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt
index 30e2f0e0..9215568 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt
@@ -21,6 +21,7 @@
import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository
import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardViewStateRepository
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.wm.shell.bubbles.Bubbles
import java.util.Optional
@@ -37,10 +38,12 @@
constructor(
private val activeNotificationsInteractor: ActiveNotificationsInteractor,
private val bubbles: Optional<Bubbles>,
+ private val headsUpNotificationIconInteractor: HeadsUpNotificationIconInteractor,
private val keyguardViewStateRepository: NotificationsKeyguardViewStateRepository,
) {
/** Returns a subset of all active notifications based on the supplied filtration parameters. */
fun filteredNotifSet(
+ forceShowHeadsUp: Boolean = false,
showAmbient: Boolean = true,
showLowPriority: Boolean = true,
showDismissed: Boolean = true,
@@ -49,18 +52,21 @@
): Flow<Set<ActiveNotificationModel>> {
return combine(
activeNotificationsInteractor.topLevelRepresentativeNotifications,
+ headsUpNotificationIconInteractor.isolatedNotification,
keyguardViewStateRepository.areNotificationsFullyHidden,
- ) { notifications, notifsFullyHidden ->
+ ) { notifications, isolatedNotifKey, notifsFullyHidden ->
notifications
.asSequence()
.filter { model: ActiveNotificationModel ->
shouldShowNotificationIcon(
model = model,
+ forceShowHeadsUp = forceShowHeadsUp,
showAmbient = showAmbient,
showLowPriority = showLowPriority,
showDismissed = showDismissed,
showRepliedMessages = showRepliedMessages,
showPulsing = showPulsing,
+ isolatedNotifKey = isolatedNotifKey,
notifsFullyHidden = notifsFullyHidden,
)
}
@@ -70,14 +76,17 @@
private fun shouldShowNotificationIcon(
model: ActiveNotificationModel,
+ forceShowHeadsUp: Boolean,
showAmbient: Boolean,
showLowPriority: Boolean,
showDismissed: Boolean,
showRepliedMessages: Boolean,
showPulsing: Boolean,
+ isolatedNotifKey: String?,
notifsFullyHidden: Boolean,
): Boolean {
return when {
+ forceShowHeadsUp && model.key == isolatedNotifKey -> true
!showAmbient && model.isAmbient -> false
!showLowPriority && model.isSilent -> false
!showDismissed && model.isRowDismissed -> false
@@ -118,6 +127,7 @@
val statusBarNotifs: Flow<Set<ActiveNotificationModel>> =
settingsRepository.showSilentStatusIcons.flatMapLatest { showSilentIcons ->
iconsInteractor.filteredNotifSet(
+ forceShowHeadsUp = true,
showAmbient = false,
showLowPriority = showSilentIcons,
showDismissed = false,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerAlwaysOnDisplayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerAlwaysOnDisplayViewBinder.kt
new file mode 100644
index 0000000..d7c29f1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerAlwaysOnDisplayViewBinder.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.icon.ui.viewbinder
+
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.notification.collection.NotifCollection
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
+import com.android.systemui.statusbar.phone.NotificationIconContainer
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.statusbar.ui.SystemBarUtilsState
+import javax.inject.Inject
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.launch
+
+/** Binds a [NotificationIconContainer] to a [NotificationIconContainerAlwaysOnDisplayViewModel]. */
+class NotificationIconContainerAlwaysOnDisplayViewBinder
+@Inject
+constructor(
+ private val viewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
+ private val keyguardRootViewModel: KeyguardRootViewModel,
+ private val configuration: ConfigurationState,
+ private val failureTracker: StatusBarIconViewBindingFailureTracker,
+ private val screenOffAnimationController: ScreenOffAnimationController,
+ private val systemBarUtilsState: SystemBarUtilsState,
+ private val viewStore: AlwaysOnDisplayNotificationIconViewStore,
+) {
+ fun bindWhileAttached(view: NotificationIconContainer): DisposableHandle {
+ return view.repeatWhenAttached {
+ lifecycleScope.launch {
+ launch {
+ NotificationIconContainerViewBinder.bind(
+ view = view,
+ viewModel = viewModel,
+ configuration = configuration,
+ systemBarUtilsState = systemBarUtilsState,
+ failureTracker = failureTracker,
+ viewStore = viewStore,
+ )
+ }
+ launch {
+ KeyguardRootViewBinder.bindAodNotifIconVisibility(
+ view = view,
+ isVisible = keyguardRootViewModel.isNotifIconContainerVisible,
+ configuration = configuration,
+ screenOffAnimationController = screenOffAnimationController,
+ )
+ }
+ }
+ }
+ }
+}
+
+/** [IconViewStore] for the always-on display. */
+class AlwaysOnDisplayNotificationIconViewStore
+@Inject
+constructor(notifCollection: NotifCollection) :
+ IconViewStore by (notifCollection.iconViewStoreBy { it.aodIcon })
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerShelfViewBinder.kt
new file mode 100644
index 0000000..783488af
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerShelfViewBinder.kt
@@ -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.statusbar.notification.icon.ui.viewbinder
+
+import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.statusbar.notification.collection.NotifCollection
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.bindIcons
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel
+import com.android.systemui.statusbar.phone.NotificationIconContainer
+import com.android.systemui.statusbar.ui.SystemBarUtilsState
+import javax.inject.Inject
+
+/** Binds a [NotificationIconContainer] to a [NotificationIconContainerShelfViewModel]. */
+class NotificationIconContainerShelfViewBinder
+@Inject
+constructor(
+ private val viewModel: NotificationIconContainerShelfViewModel,
+ private val configuration: ConfigurationState,
+ private val systemBarUtilsState: SystemBarUtilsState,
+ private val failureTracker: StatusBarIconViewBindingFailureTracker,
+ private val viewStore: ShelfNotificationIconViewStore,
+) {
+ suspend fun bind(view: NotificationIconContainer) {
+ viewModel.icons.bindIcons(
+ view,
+ configuration,
+ systemBarUtilsState,
+ notifyBindingFailures = { failureTracker.shelfFailures = it },
+ viewStore,
+ )
+ }
+}
+
+/** [IconViewStore] for the [com.android.systemui.statusbar.NotificationShelf] */
+class ShelfNotificationIconViewStore @Inject constructor(notifCollection: NotifCollection) :
+ IconViewStore by (notifCollection.iconViewStoreBy { it.shelfIcon })
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt
new file mode 100644
index 0000000..8e089b1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.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.statusbar.notification.icon.ui.viewbinder
+
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.notification.collection.NotifCollection
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel
+import com.android.systemui.statusbar.phone.NotificationIconContainer
+import com.android.systemui.statusbar.ui.SystemBarUtilsState
+import javax.inject.Inject
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.launch
+
+/** Binds a [NotificationIconContainer] to a [NotificationIconContainerStatusBarViewModel]. */
+class NotificationIconContainerStatusBarViewBinder
+@Inject
+constructor(
+ private val viewModel: NotificationIconContainerStatusBarViewModel,
+ private val configuration: ConfigurationState,
+ private val systemBarUtilsState: SystemBarUtilsState,
+ private val failureTracker: StatusBarIconViewBindingFailureTracker,
+ private val viewStore: StatusBarNotificationIconViewStore,
+) {
+ fun bindWhileAttached(view: NotificationIconContainer): DisposableHandle {
+ return view.repeatWhenAttached {
+ lifecycleScope.launch {
+ NotificationIconContainerViewBinder.bind(
+ view = view,
+ viewModel = viewModel,
+ configuration = configuration,
+ systemBarUtilsState = systemBarUtilsState,
+ failureTracker = failureTracker,
+ viewStore = viewStore,
+ )
+ }
+ }
+ }
+}
+
+/** [IconViewStore] for the status bar. */
+class StatusBarNotificationIconViewStore @Inject constructor(notifCollection: NotifCollection) :
+ IconViewStore by (notifCollection.iconViewStoreBy { it.statusBarIcon })
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
index e1e30e1..8fe0022 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
@@ -35,7 +35,6 @@
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconColors
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconsViewData
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconsViewData.LimitType
@@ -45,7 +44,6 @@
import com.android.systemui.util.ui.isAnimating
import com.android.systemui.util.ui.stopAnimating
import com.android.systemui.util.ui.value
-import javax.inject.Inject
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
@@ -56,42 +54,6 @@
/** Binds a view-model to a [NotificationIconContainer]. */
object NotificationIconContainerViewBinder {
- @JvmStatic
- fun bindWhileAttached(
- view: NotificationIconContainer,
- viewModel: NotificationIconContainerShelfViewModel,
- configuration: ConfigurationState,
- systemBarUtilsState: SystemBarUtilsState,
- failureTracker: StatusBarIconViewBindingFailureTracker,
- viewStore: IconViewStore,
- ): DisposableHandle {
- return view.repeatWhenAttached {
- lifecycleScope.launch {
- viewModel.icons.bindIcons(
- view,
- configuration,
- systemBarUtilsState,
- notifyBindingFailures = { failureTracker.shelfFailures = it },
- viewStore,
- )
- }
- }
- }
-
- @JvmStatic
- fun bindWhileAttached(
- view: NotificationIconContainer,
- viewModel: NotificationIconContainerStatusBarViewModel,
- configuration: ConfigurationState,
- systemBarUtilsState: SystemBarUtilsState,
- failureTracker: StatusBarIconViewBindingFailureTracker,
- viewStore: IconViewStore,
- ): DisposableHandle =
- view.repeatWhenAttached {
- lifecycleScope.launch {
- bind(view, viewModel, configuration, systemBarUtilsState, failureTracker, viewStore)
- }
- }
suspend fun bind(
view: NotificationIconContainer,
@@ -215,7 +177,7 @@
* given `iconKey`. The parent [Job] of this coroutine will be cancelled automatically when the
* view is to be unbound.
*/
- private suspend fun Flow<NotificationIconsViewData>.bindIcons(
+ suspend fun Flow<NotificationIconsViewData>.bindIcons(
view: NotificationIconContainer,
configuration: ConfigurationState,
systemBarUtilsState: SystemBarUtilsState,
@@ -377,24 +339,14 @@
}
@ColorInt private const val DEFAULT_AOD_ICON_COLOR = Color.WHITE
- private const val TAG = "NotifIconContainerViewBinder"
+ private const val TAG = "NotifIconContainerViewBinder"
}
-/** [IconViewStore] for the [com.android.systemui.statusbar.NotificationShelf] */
-class ShelfNotificationIconViewStore @Inject constructor(notifCollection: NotifCollection) :
- IconViewStore by (notifCollection.iconViewStoreBy { it.shelfIcon })
-
-/** [IconViewStore] for the always-on display. */
-class AlwaysOnDisplayNotificationIconViewStore
-@Inject
-constructor(notifCollection: NotifCollection) :
- IconViewStore by (notifCollection.iconViewStoreBy { it.aodIcon })
-
-/** [IconViewStore] for the status bar. */
-class StatusBarNotificationIconViewStore @Inject constructor(notifCollection: NotifCollection) :
- IconViewStore by (notifCollection.iconViewStoreBy { it.statusBarIcon })
-
-private fun NotifCollection.iconViewStoreBy(block: (IconPack) -> StatusBarIconView?) =
+/**
+ * Convenience builder for [IconViewStore] that uses [block] to extract the relevant
+ * [StatusBarIconView] from an [IconPack] stored inside of the [NotifCollection].
+ */
+fun NotifCollection.iconViewStoreBy(block: (IconPack) -> StatusBarIconView?) =
IconViewStore { key ->
getEntry(key)?.icons?.let(block)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
index 6e5ac47..d00cd1f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
@@ -96,8 +96,8 @@
iconsViewData.visibleIcons.firstOrNull { it.notifKey == isolatedNotif }
}
}
- .pairwise(initialValue = null)
.distinctUntilChanged()
+ .pairwise(initialValue = null)
.sample(shadeInteractor.shadeExpansion) { (prev, iconInfo), shadeExpansion ->
val animate =
when {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionRefactor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionRefactor.kt
index 2624363..db33f92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionRefactor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionRefactor.kt
@@ -17,12 +17,17 @@
package com.android.systemui.statusbar.notification.interruption
import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
/** Helper for reading or using the visual interruptions refactor flag state. */
object VisualInterruptionRefactor {
const val FLAG_NAME = Flags.FLAG_VISUAL_INTERRUPTIONS_REFACTOR
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
/** Whether the refactor is enabled */
@JvmStatic
inline val isEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 4fe05ec..fca527f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -31,7 +31,6 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Interpolator;
-import android.view.animation.PathInterpolator;
import com.android.app.animation.Interpolators;
import com.android.internal.jank.InteractionJankMonitor;
@@ -44,6 +43,7 @@
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
+import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.util.DumpUtilsKt;
@@ -67,7 +67,8 @@
* The content of the view should start showing at animation progress value of
* #ALPHA_APPEAR_START_FRACTION.
*/
- private static final float ALPHA_APPEAR_START_FRACTION = .4f;
+
+ private static final float ALPHA_APPEAR_START_FRACTION = .7f;
/**
* The content should show fully with progress at #ALPHA_APPEAR_END_FRACTION
* The start of the animation is at #ALPHA_APPEAR_START_FRACTION
@@ -86,9 +87,7 @@
*/
private boolean mActivated;
- private final Interpolator mSlowOutFastInInterpolator;
private Interpolator mCurrentAppearInterpolator;
-
NotificationBackgroundView mBackgroundNormal;
private float mAnimationTranslationY;
private boolean mDrawingAppearAnimation;
@@ -116,7 +115,6 @@
public ActivatableNotificationView(Context context, AttributeSet attrs) {
super(context, attrs);
- mSlowOutFastInInterpolator = new PathInterpolator(0.8f, 0.0f, 0.6f, 1.0f);
setClipChildren(false);
setClipToPadding(false);
updateColors();
@@ -400,12 +398,16 @@
mCurrentAppearInterpolator = Interpolators.FAST_OUT_SLOW_IN;
targetValue = 1.0f;
} else {
- mCurrentAppearInterpolator = mSlowOutFastInInterpolator;
+ mCurrentAppearInterpolator = Interpolators.FAST_OUT_SLOW_IN_REVERSE;
targetValue = 0.0f;
}
mAppearAnimator = ValueAnimator.ofFloat(mAppearAnimationFraction,
targetValue);
- mAppearAnimator.setInterpolator(Interpolators.LINEAR);
+ if (NotificationsImprovedHunAnimation.isEnabled()) {
+ mAppearAnimator.setInterpolator(mCurrentAppearInterpolator);
+ } else {
+ mAppearAnimator.setInterpolator(Interpolators.LINEAR);
+ }
mAppearAnimator.setDuration(
(long) (duration * Math.abs(mAppearAnimationFraction - targetValue)));
mAppearAnimator.addUpdateListener(animation -> {
@@ -502,8 +504,9 @@
}
private void updateAppearRect() {
- float interpolatedFraction = mCurrentAppearInterpolator.getInterpolation(
- mAppearAnimationFraction);
+ float interpolatedFraction =
+ NotificationsImprovedHunAnimation.isEnabled() ? mAppearAnimationFraction
+ : mCurrentAppearInterpolator.getInterpolation(mAppearAnimationFraction);
mAppearAnimationTranslation = (1.0f - interpolatedFraction) * mAnimationTranslationY;
final int actualHeight = getActualHeight();
float bottom = actualHeight * interpolatedFraction;
@@ -524,6 +527,7 @@
}
private float getInterpolatedAppearAnimationFraction() {
+
if (mAppearAnimationFraction >= 0) {
return mCurrentAppearInterpolator.getInterpolation(mAppearAnimationFraction);
}
@@ -569,7 +573,7 @@
@Override
public float getTopCornerRadius() {
- if (mImprovedHunAnimation.isEnabled()) {
+ if (NotificationsImprovedHunAnimation.isEnabled()) {
return super.getTopCornerRadius();
}
@@ -579,7 +583,7 @@
@Override
public float getBottomCornerRadius() {
- if (mImprovedHunAnimation.isEnabled()) {
+ if (NotificationsImprovedHunAnimation.isEnabled()) {
return super.getBottomCornerRadius();
}
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 5872840..31ca106 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
@@ -513,15 +513,13 @@
private void setImageViewAnimationRunning(ImageView imageView, boolean running) {
if (imageView != null) {
Drawable drawable = imageView.getDrawable();
- if (drawable instanceof AnimationDrawable) {
- AnimationDrawable animationDrawable = (AnimationDrawable) drawable;
+ if (drawable instanceof AnimationDrawable animationDrawable) {
if (running) {
animationDrawable.start();
} else {
animationDrawable.stop();
}
- } else if (drawable instanceof AnimatedVectorDrawable) {
- AnimatedVectorDrawable animationDrawable = (AnimatedVectorDrawable) drawable;
+ } else if (drawable instanceof AnimatedVectorDrawable animationDrawable) {
if (running) {
animationDrawable.start();
} else {
@@ -3439,8 +3437,7 @@
@Override
protected boolean childNeedsClipping(View child) {
- if (child instanceof NotificationContentView) {
- NotificationContentView contentView = (NotificationContentView) child;
+ if (child instanceof NotificationContentView contentView) {
if (isClippingNeeded()) {
return true;
} else if (hasRoundedCorner()
@@ -3522,8 +3519,7 @@
@Override
public void applyToView(View view) {
- if (view instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ if (view instanceof ExpandableNotificationRow row) {
if (row.isExpandAnimationRunning()) {
return;
}
@@ -3543,8 +3539,7 @@
@Override
protected void onYTranslationAnimationFinished(View view) {
super.onYTranslationAnimationFinished(view);
- if (view instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ if (view instanceof ExpandableNotificationRow row) {
if (row.isHeadsUpAnimatingAway()) {
row.setHeadsUpAnimatingAway(false);
}
@@ -3553,8 +3548,7 @@
@Override
public void animateTo(View child, AnimationProperties properties) {
- if (child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ if (child instanceof ExpandableNotificationRow row) {
if (row.isExpandAnimationRunning()) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
index 2a3e69b..aefd348 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
@@ -28,10 +28,9 @@
import android.view.View;
import android.view.ViewOutlineProvider;
-import com.android.systemui.flags.Flags;
-import com.android.systemui.flags.RefactorFlag;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.RoundableState;
+import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
import com.android.systemui.util.DumpUtilsKt;
@@ -49,8 +48,6 @@
private float mOutlineAlpha = -1f;
private boolean mAlwaysRoundBothCorners;
private Path mTmpPath = new Path();
- protected final RefactorFlag mImprovedHunAnimation =
- RefactorFlag.forView(Flags.IMPROVED_HUN_ANIMATIONS);
/**
* {@code false} if the children views of the {@link ExpandableOutlineView} are translated when
@@ -126,7 +123,7 @@
return EMPTY_PATH;
}
float bottomRadius = mAlwaysRoundBothCorners ? getMaxRadius() : getBottomCornerRadius();
- if (!mImprovedHunAnimation.isEnabled() && (topRadius + bottomRadius > height)) {
+ if (!NotificationsImprovedHunAnimation.isEnabled() && (topRadius + bottomRadius > height)) {
float overShoot = topRadius + bottomRadius - height;
float currentTopRoundness = getTopRoundness();
float currentBottomRoundness = getBottomRoundness();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 49674d6..c4d266e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -676,8 +676,7 @@
mViewState.headsUpIsVisible = false;
// handling reset for child notifications
- if (this instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) this;
+ if (this instanceof ExpandableNotificationRow row) {
List<ExpandableNotificationRow> children = row.getAttachedChildren();
if (row.isSummaryWithChildren() && children != null) {
for (ExpandableNotificationRow childRow : children) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/AsyncHybridViewInflation.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/AsyncHybridViewInflation.kt
index 24e7f05..9556c2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/AsyncHybridViewInflation.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/AsyncHybridViewInflation.kt
@@ -17,6 +17,7 @@
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 hybrid view inflation flag state. */
@@ -24,6 +25,10 @@
object AsyncHybridViewInflation {
const val FLAG_NAME = Flags.FLAG_NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
/** Is async hybrid (single-line) view inflation enabled */
@JvmStatic
inline val isEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsImprovedHunAnimation.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsImprovedHunAnimation.kt
new file mode 100644
index 0000000..16d35fe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsImprovedHunAnimation.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.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the notifications improved hun animation flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object NotificationsImprovedHunAnimation {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_IMPROVED_HUN_ANIMATION
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.notificationsImprovedHunAnimation()
+
+ /**
+ * 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/shelf/ui/viewbinder/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
index 699e140..5ab4d4e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
@@ -16,60 +16,38 @@
package com.android.systemui.statusbar.notification.shelf.ui.viewbinder
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.common.ui.ConfigurationState
-import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.NotificationShelf
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ShelfNotificationIconViewStore
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerShelfViewBinder
import com.android.systemui.statusbar.notification.row.ui.viewbinder.ActivatableNotificationViewBinder
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
import com.android.systemui.statusbar.phone.NotificationIconAreaController
-import com.android.systemui.statusbar.ui.SystemBarUtilsState
import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
/** Binds a [NotificationShelf] to its [view model][NotificationShelfViewModel]. */
object NotificationShelfViewBinder {
- fun bind(
+ suspend fun bind(
shelf: NotificationShelf,
viewModel: NotificationShelfViewModel,
- configuration: ConfigurationState,
- systemBarUtilsState: SystemBarUtilsState,
falsingManager: FalsingManager,
- iconViewBindingFailureTracker: StatusBarIconViewBindingFailureTracker,
+ nicBinder: NotificationIconContainerShelfViewBinder,
notificationIconAreaController: NotificationIconAreaController,
- shelfIconViewStore: ShelfNotificationIconViewStore,
- ) {
+ ): Unit = coroutineScope {
ActivatableNotificationViewBinder.bind(viewModel, shelf, falsingManager)
shelf.apply {
if (NotificationIconContainerRefactor.isEnabled) {
- NotificationIconContainerViewBinder.bindWhileAttached(
- shelfIcons,
- viewModel.icons,
- configuration,
- systemBarUtilsState,
- iconViewBindingFailureTracker,
- shelfIconViewStore,
- )
+ launch { nicBinder.bind(shelfIcons) }
} else {
notificationIconAreaController.setShelfIcons(shelfIcons)
}
- repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch {
- viewModel.canModifyColorOfNotifications.collect(
- ::setCanModifyColorOfNotifications
- )
- }
- launch { viewModel.isClickable.collect(::setCanInteract) }
- registerViewListenersWhileAttached(shelf, viewModel)
- }
+ launch {
+ viewModel.canModifyColorOfNotifications.collect(::setCanModifyColorOfNotifications)
}
+ launch { viewModel.isClickable.collect(::setCanInteract) }
+ registerViewListenersWhileAttached(shelf, viewModel)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
index 64b5b62c..5ca8b53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
@@ -18,7 +18,6 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.NotificationShelf
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel
import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModel
import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
import javax.inject.Inject
@@ -32,7 +31,6 @@
constructor(
private val interactor: NotificationShelfInteractor,
activatableViewModel: ActivatableNotificationViewModel,
- val icons: NotificationIconContainerShelfViewModel,
) : ActivatableNotificationViewModel by activatableViewModel {
/** Is the shelf allowed to be clickable when it has content? */
val isClickable: Flow<Boolean>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 0236fc2..45b9c26 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -869,8 +869,7 @@
Path clipPath = mChildClipPath;
if (clipPath != null) {
final float translation;
- if (child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow notificationRow = (ExpandableNotificationRow) child;
+ if (child instanceof ExpandableNotificationRow notificationRow) {
translation = notificationRow.getTranslation();
} else {
translation = child.getTranslationX();
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 3bbdfd1..0f640c9 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,9 +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.shade.ShadeController;
import com.android.systemui.shade.TouchLogger;
-import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarState;
@@ -109,12 +107,12 @@
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
-import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
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.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -146,8 +144,6 @@
private static final String TAG = "StackScroller";
private static final boolean SPEW = Log.isLoggable(TAG, Log.VERBOSE);
- // Delay in milli-seconds before shade closes for clear all.
- private static final int DELAY_BEFORE_SHADE_CLOSE = 200;
private boolean mShadeNeedsToClose = false;
@VisibleForTesting
@@ -319,7 +315,7 @@
}
};
private NotificationStackScrollLogger mLogger;
- private NotificationsController mNotificationsController;
+ private Runnable mResetUserExpandedStatesRunnable;
private ActivityStarter mActivityStarter;
private final int[] mTempInt2 = new int[2];
private final HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
@@ -482,7 +478,7 @@
private final Rect mTmpRect = new Rect();
private ClearAllListener mClearAllListener;
private ClearAllAnimationListener mClearAllAnimationListener;
- private ShadeController mShadeController;
+ private Runnable mClearAllFinishedWhilePanelExpandedRunnable;
private Consumer<Boolean> mOnStackYChanged;
private Interpolator mHideXInterpolator = Interpolators.FAST_OUT_SLOW_IN;
@@ -997,8 +993,7 @@
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child.getVisibility() != View.GONE
- && child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ && child instanceof ExpandableNotificationRow row) {
if ((row.isPinned() || row.isHeadsUpAnimatingAway()) && row.getTranslation() < 0
&& row.getProvider().shouldShowGutsOnSnapOpen()) {
top = Math.min(top, row.getTranslationY());
@@ -1133,10 +1128,9 @@
for (int i = 0; i < n; i++) {
View view = getChildAt(i);
if (view.getVisibility() == View.GONE
- || !(view instanceof ExpandableNotificationRow)) {
+ || !(view instanceof ExpandableNotificationRow row)) {
continue;
}
- ExpandableNotificationRow row = (ExpandableNotificationRow) view;
currentIndex++;
boolean beforeSpeedBump;
if (mHighPriorityBeforeSpeedBump) {
@@ -1773,16 +1767,14 @@
}
public static boolean isPinnedHeadsUp(View v) {
- if (v instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ if (v instanceof ExpandableNotificationRow row) {
return row.isHeadsUp() && row.isPinned();
}
return false;
}
private boolean isHeadsUp(View v) {
- if (v instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ if (v instanceof ExpandableNotificationRow row) {
return row.isHeadsUp();
}
return false;
@@ -1824,8 +1816,7 @@
if ((bottom - top >= mMinInteractionHeight || !requireMinHeight)
&& touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
- if (slidingChild instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
+ if (slidingChild instanceof ExpandableNotificationRow row) {
NotificationEntry entry = row.getEntry();
if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
&& mTopHeadsUpEntry.getRow() != row
@@ -2368,8 +2359,7 @@
float rowTranslation = child.getTranslationY();
if (rowTranslation >= translationY) {
return child;
- } else if (!ignoreChildren && child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ } else if (!ignoreChildren && child instanceof ExpandableNotificationRow row) {
if (row.isSummaryWithChildren() && row.areChildrenExpanded()) {
List<ExpandableNotificationRow> notificationChildren =
row.getAttachedChildren();
@@ -2890,8 +2880,7 @@
}
private void focusNextViewIfFocused(View view) {
- if (view instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ if (view instanceof ExpandableNotificationRow row) {
if (row.shouldRefocusOnDismiss()) {
View nextView = row.getChildAfterViewWhenDismissed();
if (nextView == null) {
@@ -3039,8 +3028,7 @@
}
private int getIntrinsicHeight(View view) {
- if (view instanceof ExpandableView) {
- ExpandableView expandableView = (ExpandableView) view;
+ if (view instanceof ExpandableView expandableView) {
return expandableView.getIntrinsicHeight();
}
return view.getHeight();
@@ -3130,8 +3118,7 @@
generateAddAnimation(child, false /* fromMoreCard */);
updateAnimationState(child);
updateChronometerForChild(child);
- if (child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ if (child instanceof ExpandableNotificationRow row) {
row.setDismissUsingRowTranslationX(mDismissUsingRowTranslationX);
}
@@ -3200,8 +3187,7 @@
}
private void updateAnimationState(boolean running, View child) {
- if (child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ if (child instanceof ExpandableNotificationRow row) {
row.setAnimationRunning(running);
}
}
@@ -3328,8 +3314,10 @@
logHunAnimationSkipped(row, "row has no viewState");
continue;
}
+ boolean shouldHunAppearFromTheBottom =
+ mStackScrollAlgorithm.shouldHunAppearFromBottom(mAmbientState, viewState);
if (isHeadsUp && (mAddedHeadsUpChildren.contains(row) || pinnedAndClosed)) {
- if (pinnedAndClosed || shouldHunAppearFromBottom(viewState)) {
+ if (pinnedAndClosed || shouldHunAppearFromTheBottom) {
// Our custom add animation
type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
} else {
@@ -3341,6 +3329,11 @@
}
AnimationEvent event = new AnimationEvent(row, type);
event.headsUpFromBottom = onBottom;
+ if (NotificationsImprovedHunAnimation.isEnabled()) {
+ // TODO(b/283084712) remove this with the flag and update the HUN filters at
+ // creation
+ event.filter.animateHeight = false;
+ }
mAnimationEvents.add(event);
if (SPEW) {
Log.v(TAG, "Generating HUN animation event: "
@@ -3355,11 +3348,6 @@
mAddedHeadsUpChildren.clear();
}
- private boolean shouldHunAppearFromBottom(ExpandableViewState viewState) {
- return viewState.getYTranslation() + viewState.height
- >= mAmbientState.getMaxHeadsUpTranslation();
- }
-
private void generateGroupExpansionEvent() {
// Generate a group expansion/collapsing event if there is such a group at all
if (mExpandedGroupView != null) {
@@ -3396,8 +3384,7 @@
// we need to know the view after this one
float removedTranslation = child.getTranslationY();
boolean ignoreChildren = true;
- if (child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ if (child instanceof ExpandableNotificationRow row) {
if (row.isRemoved() && row.wasChildInGroupWhenRemoved()) {
removedTranslation = row.getTranslationWhenRemoved();
ignoreChildren = false;
@@ -3439,8 +3426,7 @@
private void generatePositionChangeEvents() {
for (ExpandableView child : mChildrenChangingPositions) {
Integer duration = null;
- if (child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ if (child instanceof ExpandableNotificationRow row) {
if (row.getEntry().isMarkedForUserTriggeredMovement()) {
duration = StackStateAnimator.ANIMATION_DURATION_PRIORITY_CHANGE;
row.getEntry().markForUserTriggeredMovement(false);
@@ -4114,7 +4100,7 @@
mAmbientState.setExpansionChanging(false);
if (!mIsExpanded) {
resetScrollPosition();
- mNotificationsController.resetUserExpandedStates();
+ mResetUserExpandedStatesRunnable.run();
clearTemporaryViews();
clearUserLockedViews();
resetAllSwipeState();
@@ -4124,8 +4110,7 @@
private void clearUserLockedViews() {
for (int i = 0; i < getChildCount(); i++) {
ExpandableView child = getChildAtIndex(i);
- if (child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ if (child instanceof ExpandableNotificationRow row) {
row.setUserLocked(false);
}
}
@@ -4139,8 +4124,7 @@
);
for (int i = 0; i < getChildCount(); i++) {
ExpandableView child = getChildAtIndex(i);
- if (child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ if (child instanceof ExpandableNotificationRow row) {
clearTemporaryViewsInGroup(
/* viewGroup = */ row.getChildrenContainer(),
/* reason = */ "clearTemporaryViewsInGroup(row.getChildrenContainer())"
@@ -4225,8 +4209,7 @@
}
void updateChronometerForChild(View child) {
- if (child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ if (child instanceof ExpandableNotificationRow row) {
row.setChronometerRunning(mIsExpanded);
}
}
@@ -4265,8 +4248,7 @@
}
private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
- if (view instanceof ExpandableNotificationRow && !onKeyguard()) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ if (view instanceof ExpandableNotificationRow row && !onKeyguard()) {
// TODO: once we're recycling this will need to check the adapter position of the child
if (row.isUserLocked() && row != getFirstChildNotGone()) {
if (row.isSummaryWithChildren()) {
@@ -4316,26 +4298,16 @@
if (mShadeNeedsToClose) {
mShadeNeedsToClose = false;
if (mIsExpanded) {
- collapseShadeDelayed();
+ mClearAllFinishedWhilePanelExpandedRunnable.run();
}
}
}
}
- private void collapseShadeDelayed() {
- postDelayed(
- () -> {
- mShadeController.animateCollapseShade(
- CommandQueue.FLAG_EXCLUDE_NONE);
- },
- DELAY_BEFORE_SHADE_CLOSE /* delayMillis */);
- }
-
private void clearHeadsUpDisappearRunning() {
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
- if (view instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ if (view instanceof ExpandableNotificationRow row) {
row.setHeadsUpAnimatingAway(false);
if (row.isSummaryWithChildren()) {
for (ExpandableNotificationRow child : row.getAttachedChildren()) {
@@ -4747,8 +4719,8 @@
return max + getStackTranslation();
}
- public void setNotificationsController(NotificationsController notificationsController) {
- this.mNotificationsController = notificationsController;
+ public void setResetUserExpandedStatesRunnable(Runnable runnable) {
+ this.mResetUserExpandedStatesRunnable = runnable;
}
public void setActivityStarter(ActivityStarter activityStarter) {
@@ -4932,7 +4904,9 @@
*/
public void setHeadsUpBoundaries(int height, int bottomBarHeight) {
mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight);
+ mStackScrollAlgorithm.setHeadsUpAppearHeightBottom(height);
mStateAnimator.setHeadsUpAppearHeightBottom(height);
+ mStateAnimator.setStackTopMargin(mAmbientState.getStackTopMargin());
requestChildrenUpdate();
}
@@ -5217,8 +5191,7 @@
}
View swipedView = mSwipeHelper.getSwipedView();
pw.println("Swiped view: " + swipedView);
- if (swipedView instanceof ExpandableView) {
- ExpandableView expandableView = (ExpandableView) swipedView;
+ if (swipedView instanceof ExpandableView expandableView) {
expandableView.dump(pw, args);
}
});
@@ -5301,8 +5274,7 @@
if (view instanceof SectionHeaderView && silentSectionWillBeGone) {
return true;
}
- if (view instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ if (view instanceof ExpandableNotificationRow row) {
if (isVisible(row) && includeChildInClearAll(row, selection)) {
return true;
}
@@ -5328,9 +5300,7 @@
if (shouldHideParent(view, selection)) {
viewsToHide.add(view);
}
- if (view instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
-
+ if (view instanceof ExpandableNotificationRow parent) {
if (isChildrenVisible(parent)) {
for (ExpandableNotificationRow child : parent.getAttachedChildren()) {
if (isVisible(child) && includeChildInClearAll(child, selection)) {
@@ -5350,10 +5320,9 @@
for (int i = 0; i < childCount; i++) {
final View view = getChildAt(i);
- if (!(view instanceof ExpandableNotificationRow)) {
+ if (!(view instanceof ExpandableNotificationRow parent)) {
continue;
}
- ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
if (includeChildInClearAll(parent, selection)) {
viewsToRemove.add(parent);
}
@@ -5681,8 +5650,8 @@
mFooterClearAllListener = listener;
}
- void setShadeController(ShadeController shadeController) {
- mShadeController = shadeController;
+ void setClearAllFinishedWhilePanelExpandedRunnable(Runnable runnable) {
+ mClearAllFinishedWhilePanelExpandedRunnable = runnable;
}
/**
@@ -5992,8 +5961,7 @@
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
mSwipeHelper.forceResetSwipeState(child);
- if (child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow childRow = (ExpandableNotificationRow) child;
+ if (child instanceof ExpandableNotificationRow childRow) {
List<ExpandableNotificationRow> grandchildren = childRow.getAttachedChildren();
if (grandchildren != null) {
for (ExpandableNotificationRow grandchild : grandchildren) {
@@ -6279,8 +6247,7 @@
}
static boolean canChildBeDismissed(View v) {
- if (v instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ if (v instanceof ExpandableNotificationRow row) {
if (row.areGutsExposed() || !row.getEntry().hasFinishedInitialization()) {
return false;
}
@@ -6290,8 +6257,7 @@
}
static boolean canChildBeCleared(View v) {
- if (v instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ if (v instanceof ExpandableNotificationRow row) {
if (row.areGutsExposed() || !row.getEntry().hasFinishedInitialization()) {
return false;
}
@@ -6386,8 +6352,7 @@
/* Only ever called as a consequence of an expansion gesture in the shade. */
@Override
public void setUserExpandedChild(View v, boolean userExpanded) {
- if (v instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ if (v instanceof ExpandableNotificationRow row) {
if (userExpanded && onKeyguard()) {
// Due to a race when locking the screen while touching, a notification may be
// expanded even after we went back to keyguard. An example of this happens if
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 e6315fd..6f5058c 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
@@ -81,6 +81,7 @@
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
@@ -152,6 +153,8 @@
private static final String TAG = "StackScrollerController";
private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
private static final String HIGH_PRIORITY = "high_priority";
+ /** Delay in milli-seconds before shade closes for clear all. */
+ private static final int DELAY_BEFORE_SHADE_CLOSE = 200;
private final boolean mAllowLongPress;
private final NotificationGutsManager mNotificationGutsManager;
@@ -402,8 +405,7 @@
if (!mAllowLongPress) {
return;
}
- if (view instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ if (view instanceof ExpandableNotificationRow row) {
mMetricsLogger.write(row.getEntry().getSbn().getLogMaker()
.setCategory(MetricsEvent.ACTION_TOUCH_GEAR)
.setType(MetricsEvent.TYPE_ACTION)
@@ -423,8 +425,7 @@
@Override
public void onMenuShown(View row) {
- if (row instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow notificationRow = (ExpandableNotificationRow) row;
+ if (row instanceof ExpandableNotificationRow notificationRow) {
mMetricsLogger.write(notificationRow.getEntry().getSbn().getLogMaker()
.setCategory(MetricsEvent.ACTION_REVEAL_GEAR)
.setType(MetricsEvent.TYPE_ACTION));
@@ -489,10 +490,9 @@
*/
@Override
public void onChildDismissed(View view) {
- if (!(view instanceof ActivatableNotificationView)) {
+ if (!(view instanceof ActivatableNotificationView row)) {
return;
}
- ActivatableNotificationView row = (ActivatableNotificationView) view;
if (!row.isDismissed()) {
handleChildViewDismissed(view);
}
@@ -516,8 +516,7 @@
if (mView.getClearAllInProgress()) {
return;
}
- if (view instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ if (view instanceof ExpandableNotificationRow row) {
if (row.isHeadsUp()) {
mHeadsUpManager.addSwipedOutNotification(
row.getEntry().getSbn().getKey());
@@ -548,8 +547,7 @@
ev.getY(),
true /* requireMinHeight */,
false /* ignoreDecors */);
- if (child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ if (child instanceof ExpandableNotificationRow row) {
ExpandableNotificationRow parent = row.getNotificationParent();
if (parent != null && parent.areChildrenExpanded()
&& (parent.areGutsExposed()
@@ -579,8 +577,7 @@
@Override
public void onChildSnappedBack(View animView, float targetLeft) {
mView.onSwipeEnd();
- if (animView instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) animView;
+ if (animView instanceof ExpandableNotificationRow row) {
if (row.isPinned() && !canChildBeDismissed(row)
&& row.getEntry().getSbn().getNotification().fullScreenIntent
== null) {
@@ -754,7 +751,7 @@
mView.setController(this);
mView.setLogger(mLogger);
mView.setTouchHandler(new TouchHandler());
- mView.setNotificationsController(mNotificationsController);
+ mView.setResetUserExpandedStatesRunnable(mNotificationsController::resetUserExpandedStates);
mView.setActivityStarter(mActivityStarter);
mView.setClearAllAnimationListener(this::onAnimationEnd);
mView.setClearAllListener((selection) -> mUiEventLogger.log(
@@ -770,7 +767,11 @@
mView.setIsRemoteInputActive(active);
}
});
- mView.setShadeController(mShadeController);
+ mView.setClearAllFinishedWhilePanelExpandedRunnable(()-> {
+ final Runnable doCollapseRunnable = () ->
+ mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
+ mView.postDelayed(doCollapseRunnable, /* delayMillis = */ DELAY_BEFORE_SHADE_CLOSE);
+ });
mDumpManager.registerDumpable(mView);
mKeyguardBypassController.registerOnBypassStateChangedListener(
@@ -852,7 +853,7 @@
mGroupExpansionManager.registerGroupExpansionChangeListener(
(changedRow, expanded) -> mView.onGroupExpandChanged(changedRow, expanded));
- mViewBinder.bind(mView, this);
+ mViewBinder.bindWhileAttached(mView, this);
if (!FooterViewRefactor.isEnabled()) {
collectFlow(mView, mKeyguardTransitionRepo.getTransitions(),
@@ -1973,8 +1974,7 @@
// Check if we need to clear any snooze leavebehinds
if (guts != null && !NotificationSwipeHelper.isTouchInView(ev, guts)
- && guts.getGutsContent() instanceof NotificationSnooze) {
- NotificationSnooze ns = (NotificationSnooze) guts.getGutsContent();
+ && guts.getGutsContent() instanceof NotificationSnooze ns) {
if ((ns.isExpanded() && isCancelOrUp)
|| (!horizontalSwipeWantsIt && scrollerWantsIt)) {
// If the leavebehind is expanded we clear it on the next up event, otherwise we
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 06ca9a5..664a6b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -37,6 +37,7 @@
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
+import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import java.util.ArrayList;
import java.util.List;
@@ -66,12 +67,15 @@
private boolean mClipNotificationScrollToTop;
@VisibleForTesting
float mHeadsUpInset;
+ @VisibleForTesting
+ float mHeadsUpAppearStartAboveScreen;
private int mPinnedZTranslationExtra;
private float mNotificationScrimPadding;
private int mMarginBottom;
private float mQuickQsOffsetHeight;
private float mSmallCornerRadius;
private float mLargeCornerRadius;
+ private int mHeadsUpAppearHeightBottom;
public StackScrollAlgorithm(
Context context,
@@ -94,6 +98,8 @@
int statusBarHeight = SystemBarUtils.getStatusBarHeight(context);
mHeadsUpInset = statusBarHeight + res.getDimensionPixelSize(
R.dimen.heads_up_status_bar_padding);
+ mHeadsUpAppearStartAboveScreen = res.getDimensionPixelSize(
+ R.dimen.heads_up_appear_y_above_screen);
mPinnedZTranslationExtra = res.getDimensionPixelSize(
R.dimen.heads_up_pinned_elevation);
mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height);
@@ -221,6 +227,25 @@
return getExpansionFractionWithoutShelf(mTempAlgorithmState, ambientState);
}
+ public void setHeadsUpAppearHeightBottom(int headsUpAppearHeightBottom) {
+ mHeadsUpAppearHeightBottom = headsUpAppearHeightBottom;
+ }
+
+ /**
+ * If the QuickSettings is showing full screen, we want to animate the HeadsUp Notifications
+ * from the bottom of the screen.
+ *
+ * @param ambientState Current ambient state.
+ * @param viewState The state of the HUN that is being queried to appear from the bottom.
+ *
+ * @return true if the HeadsUp Notifications should appear from the bottom
+ */
+ public boolean shouldHunAppearFromBottom(AmbientState ambientState,
+ ExpandableViewState viewState) {
+ return viewState.getYTranslation() + viewState.height
+ >= ambientState.getMaxHeadsUpTranslation();
+ }
+
public static void log(String s) {
if (DEBUG) {
android.util.Log.i(TAG, s);
@@ -229,8 +254,7 @@
public static void logView(View view, String s) {
String viewString = "";
- if (view instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = ((ExpandableNotificationRow) view);
+ if (view instanceof ExpandableNotificationRow row) {
if (row.getEntry() == null) {
viewString = "ExpandableNotificationRow has null NotificationEntry";
} else {
@@ -264,8 +288,7 @@
int childCount = algorithmState.visibleChildren.size();
for (int i = 0; i < childCount; i++) {
ExpandableView v = algorithmState.visibleChildren.get(i);
- if (v instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ if (v instanceof ExpandableNotificationRow row) {
row.updateChildrenStates();
}
}
@@ -376,8 +399,7 @@
continue;
}
notGoneIndex = updateNotGoneIndex(state, notGoneIndex, v);
- if (v instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ if (v instanceof ExpandableNotificationRow row) {
// handle the notGoneIndex for the children as well
List<ExpandableNotificationRow> children = row.getAttachedChildren();
@@ -508,10 +530,9 @@
private boolean hasNonClearableNotifs(StackScrollAlgorithmState algorithmState) {
for (int i = 0; i < algorithmState.visibleChildren.size(); i++) {
View child = algorithmState.visibleChildren.get(i);
- if (!(child instanceof ExpandableNotificationRow)) {
+ if (!(child instanceof ExpandableNotificationRow row)) {
continue;
}
- final ExpandableNotificationRow row = (ExpandableNotificationRow) child;
if (!row.canViewBeCleared()) {
return true;
}
@@ -715,10 +736,9 @@
ExpandableNotificationRow pulsingRow = null;
for (int i = 0; i < childCount; i++) {
View child = algorithmState.visibleChildren.get(i);
- if (!(child instanceof ExpandableNotificationRow)) {
+ if (!(child instanceof ExpandableNotificationRow row)) {
continue;
}
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
if (!row.showingPulsing() || (i == 0 && ambientState.isPulseExpanding())) {
continue;
}
@@ -760,10 +780,9 @@
ExpandableNotificationRow topHeadsUpEntry = null;
for (int i = 0; i < childCount; i++) {
View child = algorithmState.visibleChildren.get(i);
- if (!(child instanceof ExpandableNotificationRow)) {
+ if (!(child instanceof ExpandableNotificationRow row)) {
continue;
}
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
if (!(row.isHeadsUp() || row.isHeadsUpAnimatingAway())) {
continue;
}
@@ -793,10 +812,16 @@
}
}
if (row.isPinned()) {
- // Make sure row yTranslation is at maximum the HUN yTranslation,
- // which accounts for AmbientState.stackTopMargin in split-shade.
- childState.setYTranslation(
- Math.max(childState.getYTranslation(), headsUpTranslation));
+ if (NotificationsImprovedHunAnimation.isEnabled()) {
+ // Make sure row yTranslation is at the HUN yTranslation,
+ // which accounts for AmbientState.stackTopMargin in split-shade.
+ childState.setYTranslation(headsUpTranslation);
+ } else {
+ // Make sure row yTranslation is at maximum the HUN yTranslation,
+ // which accounts for AmbientState.stackTopMargin in split-shade.
+ childState.setYTranslation(
+ Math.max(childState.getYTranslation(), headsUpTranslation));
+ }
childState.height = Math.max(row.getIntrinsicHeight(), childState.height);
childState.hidden = false;
ExpandableViewState topState =
@@ -819,10 +844,22 @@
}
}
if (row.isHeadsUpAnimatingAway()) {
- // Make sure row yTranslation is at maximum the HUN yTranslation,
- // which accounts for AmbientState.stackTopMargin in split-shade.
- childState.setYTranslation(
- Math.max(childState.getYTranslation(), headsUpTranslation));
+ if (NotificationsImprovedHunAnimation.isEnabled()) {
+ if (shouldHunAppearFromBottom(ambientState, childState)) {
+ // move to the bottom of the screen
+ childState.setYTranslation(
+ mHeadsUpAppearHeightBottom + mHeadsUpAppearStartAboveScreen);
+ } else {
+ // move to the top of the screen
+ childState.setYTranslation(-ambientState.getStackTopMargin()
+ - mHeadsUpAppearStartAboveScreen);
+ }
+ } else {
+ // Make sure row yTranslation is at maximum the HUN yTranslation,
+ // which accounts for AmbientState.stackTopMargin in split-shade.
+ childState.setYTranslation(
+ Math.max(childState.getYTranslation(), headsUpTranslation));
+ }
// keep it visible for the animation
childState.hidden = false;
}
@@ -897,8 +934,7 @@
}
protected int getMaxAllowedChildHeight(View child) {
- if (child instanceof ExpandableView) {
- ExpandableView expandableView = (ExpandableView) child;
+ if (child instanceof ExpandableView expandableView) {
return expandableView.getIntrinsicHeight();
}
return child == null ? mCollapsedSize : child.getHeight();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index e94258f..a3e0941 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.stack;
+import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK;
@@ -26,6 +27,7 @@
import android.view.View;
import com.android.app.animation.Interpolators;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.KeyguardSliceView;
import com.android.systemui.res.R;
import com.android.systemui.shared.clocks.AnimatableClockView;
@@ -33,6 +35,7 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
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 java.util.ArrayList;
import java.util.HashSet;
@@ -68,6 +71,8 @@
private final int mGoToFullShadeAppearingTranslation;
private final int mPulsingAppearingTranslation;
+ @VisibleForTesting
+ float mHeadsUpAppearStartAboveScreen;
private final ExpandableViewState mTmpState = new ExpandableViewState();
private final AnimationProperties mAnimationProperties;
public NotificationStackScrollLayout mHostLayout;
@@ -85,21 +90,23 @@
private ValueAnimator mTopOverScrollAnimator;
private ValueAnimator mBottomOverScrollAnimator;
private int mHeadsUpAppearHeightBottom;
+ private int mStackTopMargin;
private boolean mShadeExpanded;
private ArrayList<ExpandableView> mTransientViewsToRemove = new ArrayList<>();
private NotificationShelf mShelf;
- private float mStatusBarIconLocation;
- private int[] mTmpLocation = new int[2];
private StackStateLogger mLogger;
public StackStateAnimator(NotificationStackScrollLayout hostLayout) {
mHostLayout = hostLayout;
+ // TODO(b/317061579) reload on configuration changes
mGoToFullShadeAppearingTranslation =
hostLayout.getContext().getResources().getDimensionPixelSize(
R.dimen.go_to_full_shade_appearing_translation);
mPulsingAppearingTranslation =
hostLayout.getContext().getResources().getDimensionPixelSize(
R.dimen.pulsing_notification_appear_translation);
+ mHeadsUpAppearStartAboveScreen = hostLayout.getContext().getResources()
+ .getDimensionPixelSize(R.dimen.heads_up_appear_y_above_screen);
mAnimationProperties = new AnimationProperties() {
@Override
public AnimationFilter getAnimationFilter() {
@@ -455,8 +462,37 @@
.AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED) {
ExpandableNotificationRow row = (ExpandableNotificationRow) event.mChangingView;
row.prepareExpansionChanged();
- } else if (event.animationType == NotificationStackScrollLayout
- .AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR) {
+ } else if (NotificationsImprovedHunAnimation.isEnabled()
+ && (event.animationType == ANIMATION_TYPE_HEADS_UP_APPEAR)) {
+ mHeadsUpAppearChildren.add(changingView);
+
+ mTmpState.copyFrom(changingView.getViewState());
+ if (event.headsUpFromBottom) {
+ // start from the bottom of the screen
+ mTmpState.setYTranslation(
+ mHeadsUpAppearHeightBottom + mHeadsUpAppearStartAboveScreen);
+ } else {
+ // start from the top of the screen
+ mTmpState.setYTranslation(
+ -mStackTopMargin - mHeadsUpAppearStartAboveScreen);
+ }
+ // set the height and the initial position
+ mTmpState.applyToView(changingView);
+ mAnimationProperties.setCustomInterpolator(View.TRANSLATION_Y,
+ Interpolators.FAST_OUT_SLOW_IN);
+
+ Runnable onAnimationEnd = null;
+ if (loggable) {
+ // This only captures HEADS_UP_APPEAR animations, but HUNs can appear with
+ // normal ADD animations, which would not be logged here.
+ String finalKey = key;
+ mLogger.logHUNViewAppearing(key);
+ onAnimationEnd = () -> mLogger.appearAnimationEnded(finalKey);
+ }
+ changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_APPEAR,
+ /* isHeadsUpAppear= */ true, onAnimationEnd);
+ } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_APPEAR) {
+ NotificationsImprovedHunAnimation.assertInLegacyMode();
// This item is added, initialize its properties.
ExpandableViewState viewState = changingView.getViewState();
mTmpState.copyFrom(viewState);
@@ -536,6 +572,10 @@
changingView.setInRemovalAnimation(true);
};
}
+ if (NotificationsImprovedHunAnimation.isEnabled()) {
+ mAnimationProperties.setCustomInterpolator(View.TRANSLATION_Y,
+ Interpolators.FAST_OUT_SLOW_IN_REVERSE);
+ }
long removeAnimationDelay = changingView.performRemoveAnimation(
ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
0, 0.0f, true /* isHeadsUpAppear */,
@@ -601,6 +641,10 @@
mHeadsUpAppearHeightBottom = headsUpAppearHeightBottom;
}
+ public void setStackTopMargin(int stackTopMargin) {
+ mStackTopMargin = stackTopMargin;
+ }
+
public void setShadeExpanded(boolean shadeExpanded) {
mShadeExpanded = shadeExpanded;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractor.kt
index 4de3a7f..3a650aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractor.kt
@@ -18,7 +18,7 @@
import android.graphics.Rect
import android.util.Log
import com.android.app.tracing.FlowTracing.traceEach
-import com.android.systemui.common.domain.interactor.ConfigurationInteractor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.shared.model.ScreenPowerState.SCREEN_ON
@@ -28,6 +28,7 @@
import com.android.systemui.util.kotlin.area
import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.kotlin.race
+import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.flow.Flow
@@ -38,7 +39,6 @@
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.withTimeout
-import javax.inject.Inject
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/DisplaySwitchNotificationsHiderFlag.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/DisplaySwitchNotificationsHiderFlag.kt
index 98c1734..3cd9a8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/DisplaySwitchNotificationsHiderFlag.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/DisplaySwitchNotificationsHiderFlag.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.stack.shared
import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
/** Helper for reading or using the DisplaySwitchNotificationsHider flag state. */
@@ -24,6 +25,10 @@
object DisplaySwitchNotificationsHiderFlag {
const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_HIDE_ON_DISPLAY_SWITCH
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
/** Is the hiding enabled? */
@JvmStatic
inline val isEnabled
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 274bf94..910b40f 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,29 +16,22 @@
package com.android.systemui.statusbar.notification.stack.ui.viewbinder
import androidx.core.view.doOnDetach
-import androidx.lifecycle.lifecycleScope
-import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel
-import kotlinx.coroutines.launch
/**
* Binds a [NotificationStackScrollLayoutController] to its [view model][NotificationListViewModel].
*/
object HideNotificationsBinder {
- fun bindHideList(
+ suspend fun bindHideList(
viewController: NotificationStackScrollLayoutController,
viewModel: NotificationListViewModel
) {
- viewController.view.repeatWhenAttached {
- lifecycleScope.launch {
- viewModel.hideListViewModel.shouldHideListForPerformance.collect { shouldHide ->
- viewController.bindHideState(shouldHide)
- }
- }
- }
-
viewController.view.doOnDetach { viewController.bindHideState(shouldHide = false) }
+
+ viewModel.hideListViewModel.shouldHideListForPerformance.collect { shouldHide ->
+ viewController.bindHideState(shouldHide)
+ }
}
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 9373d49..1b36660 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
@@ -32,15 +32,14 @@
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterViewBinder
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ShelfNotificationIconViewStore
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerShelfViewBinder
import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinder
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
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.statusbar.ui.SystemBarUtilsState
+import com.android.systemui.util.kotlin.getOrNull
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.combine
@@ -55,25 +54,27 @@
private val configuration: ConfigurationState,
private val falsingManager: FalsingManager,
private val iconAreaController: NotificationIconAreaController,
- private val iconViewBindingFailureTracker: StatusBarIconViewBindingFailureTracker,
private val metricsLogger: MetricsLogger,
- private val shelfIconViewStore: ShelfNotificationIconViewStore,
- private val systemBarUtilsState: SystemBarUtilsState,
+ private val nicBinder: NotificationIconContainerShelfViewBinder,
) {
- fun bind(
+ fun bindWhileAttached(
view: NotificationStackScrollLayout,
viewController: NotificationStackScrollLayoutController
) {
- bindShelf(view)
- bindHideList(viewController, viewModel)
+ val shelf =
+ LayoutInflater.from(view.context)
+ .inflate(R.layout.status_bar_notification_shelf, view, false) as NotificationShelf
+ view.setShelf(shelf)
- if (FooterViewRefactor.isEnabled) {
- bindFooter(view)
- bindEmptyShade(view)
+ view.repeatWhenAttached {
+ lifecycleScope.launch {
+ launch { bindShelf(shelf) }
+ launch { bindHideList(viewController, viewModel) }
- view.repeatWhenAttached {
- lifecycleScope.launch {
+ if (FooterViewRefactor.isEnabled) {
+ launch { bindFooter(view) }
+ launch { bindEmptyShade(view) }
viewModel.isImportantForAccessibility.collect { isImportantForAccessibility ->
view.setImportantForAccessibilityYesNo(isImportantForAccessibility)
}
@@ -82,73 +83,57 @@
}
}
- private fun bindShelf(parentView: NotificationStackScrollLayout) {
- val shelf =
- LayoutInflater.from(parentView.context)
- .inflate(R.layout.status_bar_notification_shelf, parentView, false)
- as NotificationShelf
+ private suspend fun bindShelf(shelf: NotificationShelf) {
NotificationShelfViewBinder.bind(
shelf,
viewModel.shelf,
- configuration,
- systemBarUtilsState,
falsingManager,
- iconViewBindingFailureTracker,
+ nicBinder,
iconAreaController,
- shelfIconViewStore,
)
- parentView.setShelf(shelf)
}
- private fun bindFooter(parentView: NotificationStackScrollLayout) {
- viewModel.footer.ifPresent { footerViewModel ->
+ private suspend fun bindFooter(parentView: NotificationStackScrollLayout) {
+ viewModel.footer.getOrNull()?.let { footerViewModel ->
// The footer needs to be re-inflated every time the theme or the font size changes.
- parentView.repeatWhenAttached {
- configuration.reinflateAndBindLatest(
- R.layout.status_bar_notification_footer,
- parentView,
- attachToRoot = false,
- backgroundDispatcher,
- ) { footerView: FooterView ->
- traceSection("bind FooterView") {
- val disposableHandle =
- FooterViewBinder.bind(
- footerView,
- footerViewModel,
- clearAllNotifications = {
- metricsLogger.action(
- MetricsProto.MetricsEvent.ACTION_DISMISS_ALL_NOTES
- )
- parentView.clearAllNotifications()
- },
- )
- parentView.setFooterView(footerView)
- return@reinflateAndBindLatest disposableHandle
- }
+ configuration.reinflateAndBindLatest(
+ R.layout.status_bar_notification_footer,
+ parentView,
+ attachToRoot = false,
+ backgroundDispatcher,
+ ) { footerView: FooterView ->
+ traceSection("bind FooterView") {
+ val disposableHandle =
+ FooterViewBinder.bind(
+ footerView,
+ footerViewModel,
+ clearAllNotifications = {
+ metricsLogger.action(
+ MetricsProto.MetricsEvent.ACTION_DISMISS_ALL_NOTES
+ )
+ parentView.clearAllNotifications()
+ },
+ )
+ parentView.setFooterView(footerView)
+ return@reinflateAndBindLatest disposableHandle
}
}
}
}
- private fun bindEmptyShade(
- parentView: NotificationStackScrollLayout,
- ) {
- parentView.repeatWhenAttached {
- lifecycleScope.launch {
- combine(
- viewModel.shouldShowEmptyShadeView,
- viewModel.areNotificationsHiddenInShade,
- viewModel.hasFilteredOutSeenNotifications,
- ::Triple
- )
- .collect { (shouldShow, areNotifsHidden, hasFilteredNotifs) ->
- parentView.updateEmptyShadeView(
- shouldShow,
- areNotifsHidden,
- hasFilteredNotifs,
- )
- }
+ private suspend fun bindEmptyShade(parentView: NotificationStackScrollLayout) {
+ combine(
+ viewModel.shouldShowEmptyShadeView,
+ viewModel.areNotificationsHiddenInShade,
+ viewModel.hasFilteredOutSeenNotifications,
+ ::Triple
+ )
+ .collect { (shouldShow, areNotifsHidden, hasFilteredNotifs) ->
+ parentView.updateEmptyShadeView(
+ shouldShow,
+ areNotifsHidden,
+ hasFilteredNotifs,
+ )
}
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 145dbff..7cc0888 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -196,7 +196,7 @@
}
private void updateKeyguardStatusBarHeight() {
- MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
+ ViewGroup.LayoutParams lp = (ViewGroup.LayoutParams) getLayoutParams();
lp.height = getStatusBarHeaderHeightKeyguard(mContext);
setLayoutParams(lp);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
index a62a1ed..e79f3ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
@@ -608,7 +608,7 @@
}
@Override
- public void onPulseExpansionChanged(boolean expandingChanged) {
+ public void onPulseExpansionAmountChanged(boolean expandingChanged) {
if (expandingChanged) {
updateAodIconsVisibility(true /* animate */, false /* force */);
}
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 0dabafb..f34a44a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -284,11 +284,22 @@
@Override
public String toString() {
- return "NotificationIconContainer("
- + "dozing=" + mDozing + " onLockScreen=" + mOnLockScreen
- + " overrideIconColor=" + mOverrideIconColor
- + " speedBumpIndex=" + mSpeedBumpIndex
- + " themedTextColorPrimary=#" + Integer.toHexString(mThemedTextColorPrimary) + ')';
+ if (NotificationIconContainerRefactor.isEnabled()) {
+ return super.toString()
+ + " {"
+ + " overrideIconColor=" + mOverrideIconColor
+ + ", maxIcons=" + mMaxIcons
+ + ", isStaticLayout=" + mIsStaticLayout
+ + ", themedTextColorPrimary=#" + Integer.toHexString(mThemedTextColorPrimary)
+ + " }";
+ } else {
+ return "NotificationIconContainer("
+ + "dozing=" + mDozing + " onLockScreen=" + mOnLockScreen
+ + " overrideIconColor=" + mOverrideIconColor
+ + " speedBumpIndex=" + mSpeedBumpIndex
+ + " themedTextColorPrimary=#" + Integer.toHexString(mThemedTextColorPrimary)
+ + ')';
+ }
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index cd99934..11456ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -38,7 +38,6 @@
import com.android.app.animation.InterpolatorsAndroidX;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dumpable;
-import com.android.systemui.common.ui.ConfigurationState;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeController;
@@ -54,10 +53,7 @@
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger.DisableState;
import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder;
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker;
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarNotificationIconViewStore;
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder;
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
@@ -75,7 +71,6 @@
import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarVisibilityChangeListener;
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.ui.SystemBarUtilsState;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.statusbar.window.StatusBarWindowStateListener;
import com.android.systemui.util.CarrierConfigTracker;
@@ -95,6 +90,8 @@
import kotlin.Unit;
+import kotlinx.coroutines.DisposableHandle;
+
/**
* Contains the collapsed status bar and handles hiding/showing based on disable flags
* and keyguard state. Also manages lifecycle to make sure the views it contains are being
@@ -151,10 +148,7 @@
private final DumpManager mDumpManager;
private final StatusBarWindowStateController mStatusBarWindowStateController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private final NotificationIconContainerStatusBarViewModel mStatusBarIconsViewModel;
- private final ConfigurationState mConfigurationState;
- private final SystemBarUtilsState mSystemBarUtilsState;
- private final StatusBarNotificationIconViewStore mStatusBarIconViewStore;
+ private final NotificationIconContainerStatusBarViewBinder mNicViewBinder;
private final DemoModeController mDemoModeController;
private List<String> mBlockedIcons = new ArrayList<>();
@@ -216,7 +210,7 @@
mWaitingForWindowStateChangeAfterCameraLaunch = false;
mTransitionFromLockscreenToDreamStarted = false;
};
- private final StatusBarIconViewBindingFailureTracker mIconViewBindingFailureTracker;
+ private DisposableHandle mNicBindingDisposable;
@Inject
public CollapsedStatusBarFragment(
@@ -234,7 +228,7 @@
KeyguardStateController keyguardStateController,
ShadeViewController shadeViewController,
StatusBarStateController statusBarStateController,
- StatusBarIconViewBindingFailureTracker iconViewBindingFailureTracker,
+ NotificationIconContainerStatusBarViewBinder nicViewBinder,
CommandQueue commandQueue,
CarrierConfigTracker carrierConfigTracker,
CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger,
@@ -244,10 +238,6 @@
DumpManager dumpManager,
StatusBarWindowStateController statusBarWindowStateController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
- NotificationIconContainerStatusBarViewModel statusBarIconsViewModel,
- ConfigurationState configurationState,
- SystemBarUtilsState systemBarUtilsState,
- StatusBarNotificationIconViewStore statusBarIconViewStore,
DemoModeController demoModeController) {
mStatusBarFragmentComponentFactory = statusBarFragmentComponentFactory;
mOngoingCallController = ongoingCallController;
@@ -263,7 +253,7 @@
mKeyguardStateController = keyguardStateController;
mShadeViewController = shadeViewController;
mStatusBarStateController = statusBarStateController;
- mIconViewBindingFailureTracker = iconViewBindingFailureTracker;
+ mNicViewBinder = nicViewBinder;
mCommandQueue = commandQueue;
mCarrierConfigTracker = carrierConfigTracker;
mCollapsedStatusBarFragmentLogger = collapsedStatusBarFragmentLogger;
@@ -273,10 +263,6 @@
mDumpManager = dumpManager;
mStatusBarWindowStateController = statusBarWindowStateController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mStatusBarIconsViewModel = statusBarIconsViewModel;
- mConfigurationState = configurationState;
- mSystemBarUtilsState = systemBarUtilsState;
- mStatusBarIconViewStore = statusBarIconViewStore;
mDemoModeController = demoModeController;
}
@@ -455,24 +441,24 @@
mStartableStates.put(startable, Startable.State.STOPPED);
}
mDumpManager.unregisterDumpable(getClass().getSimpleName());
+ if (NotificationIconContainerRefactor.isEnabled()) {
+ if (mNicBindingDisposable != null) {
+ mNicBindingDisposable.dispose();
+ mNicBindingDisposable = null;
+ }
+ }
}
/** Initializes views related to the notification icon area. */
public void initNotificationIconArea() {
ViewGroup notificationIconArea = mStatusBar.requireViewById(R.id.notification_icon_area);
if (NotificationIconContainerRefactor.isEnabled()) {
- mNotificationIconAreaInner =
- LayoutInflater.from(getContext())
- .inflate(R.layout.notification_icon_area, notificationIconArea, true);
+ LayoutInflater.from(getContext())
+ .inflate(R.layout.notification_icon_area, notificationIconArea, true);
NotificationIconContainer notificationIcons =
notificationIconArea.requireViewById(R.id.notificationIcons);
- NotificationIconContainerViewBinder.bindWhileAttached(
- notificationIcons,
- mStatusBarIconsViewModel,
- mConfigurationState,
- mSystemBarUtilsState,
- mIconViewBindingFailureTracker,
- mStatusBarIconViewStore);
+ mNotificationIconAreaInner = notificationIcons;
+ mNicBindingDisposable = mNicViewBinder.bindWhileAttached(notificationIcons);
} else {
mNotificationIconAreaInner =
mNotificationIconAreaController.getNotificationInnerAreaView();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
index 425da5f..48bf7ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
@@ -27,6 +27,7 @@
import javax.inject.Inject
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
@@ -48,7 +49,7 @@
override val subId: Int,
startingIsCarrierMerged: Boolean,
override val tableLogBuffer: TableLogBuffer,
- subscriptionModel: StateFlow<SubscriptionModel?>,
+ subscriptionModel: Flow<SubscriptionModel?>,
private val defaultNetworkName: NetworkNameModel,
private val networkNameSeparator: String,
@Application scope: CoroutineScope,
@@ -331,7 +332,7 @@
fun build(
subId: Int,
startingIsCarrierMerged: Boolean,
- subscriptionModel: StateFlow<SubscriptionModel?>,
+ subscriptionModel: Flow<SubscriptionModel?>,
defaultNetworkName: NetworkNameModel,
networkNameSeparator: String,
): FullMobileConnectionRepository {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index 4fb99c24..be2c21b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -96,7 +96,7 @@
class MobileConnectionRepositoryImpl(
override val subId: Int,
private val context: Context,
- subscriptionModel: StateFlow<SubscriptionModel?>,
+ subscriptionModel: Flow<SubscriptionModel?>,
defaultNetworkName: NetworkNameModel,
networkNameSeparator: String,
connectivityManager: ConnectivityManager,
@@ -448,7 +448,7 @@
fun build(
subId: Int,
mobileLogger: TableLogBuffer,
- subscriptionModel: StateFlow<SubscriptionModel?>,
+ subscriptionModel: Flow<SubscriptionModel?>,
defaultNetworkName: NetworkNameModel,
networkNameSeparator: String,
): MobileConnectionRepository {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index 2a510e4..a455db2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -357,10 +357,10 @@
@VisibleForTesting fun getSubIdRepoCache() = subIdRepositoryCache
- private fun subscriptionModelForSubId(subId: Int): StateFlow<SubscriptionModel?> {
- return subscriptions
- .map { list -> list.firstOrNull { model -> model.subscriptionId == subId } }
- .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+ private fun subscriptionModelForSubId(subId: Int): Flow<SubscriptionModel?> {
+ return subscriptions.map { list ->
+ list.firstOrNull { model -> model.subscriptionId == subId }
+ }
}
private fun createRepositoryForSubId(subId: Int): FullMobileConnectionRepository {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 4864fb8..5bced93 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -303,8 +303,7 @@
mEntry.mRemoteEditImeVisible = editTextRootWindowInsets != null
&& editTextRootWindowInsets.isVisible(WindowInsets.Type.ime());
if (!mEntry.mRemoteEditImeVisible && !mEditText.mShowImeOnInputConnection) {
- // Pass null to ensure all inputs are cleared for this entry b/227115380
- mController.removeRemoteInput(mEntry, null,
+ mController.removeRemoteInput(mEntry, mToken,
/* reason= */"RemoteInputView$WindowInsetAnimation#onEnd");
}
}
@@ -536,6 +535,11 @@
if (mEntry.getRow().isChangingPosition() || isTemporarilyDetached()) {
return;
}
+ // RemoteInputView can be detached from window before IME close event in some cases like
+ // remote input view removal with notification update. As a result of this, RemoteInputView
+ // will stop ime animation updates, which results in never removing remote input. That's why
+ // we have to set mRemoteEditImeAnimatingAway false on detach to remove remote input.
+ mEntry.mRemoteEditImeAnimatingAway = false;
mController.removeRemoteInput(mEntry, mToken,
/* reason= */"RemoteInputView#onDetachedFromWindow");
mController.removeSpinning(mEntry.getKey(), mToken);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index fa0cb5c..66bf527 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.policy;
import android.app.AlarmManager;
+import android.app.Flags;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -191,7 +192,11 @@
@Override
public void setZen(int zen, Uri conditionId, String reason) {
- mNoMan.setZenMode(zen, conditionId, reason);
+ if (Flags.modesApi()) {
+ mNoMan.setZenMode(zen, conditionId, reason, /* fromUser= */ true);
+ } else {
+ mNoMan.setZenMode(zen, conditionId, reason);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
index e0d205f..c170eb5 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
@@ -37,6 +37,7 @@
import com.android.internal.util.UserIcons
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.Flags.switchUserOnBg
import com.android.systemui.SystemUISecondaryUserService
import com.android.systemui.animation.Expandable
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -44,6 +45,7 @@
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.domain.interactor.KeyguardInteractor
@@ -100,6 +102,7 @@
broadcastDispatcher: BroadcastDispatcher,
keyguardUpdateMonitor: KeyguardUpdateMonitor,
@Background private val backgroundDispatcher: CoroutineDispatcher,
+ @Main private val mainDispatcher: CoroutineDispatcher,
private val activityManager: ActivityManager,
private val refreshUsersScheduler: RefreshUsersScheduler,
private val guestUserInteractor: GuestUserInteractor,
@@ -339,7 +342,11 @@
}
.launchIn(applicationScope)
restartSecondaryService(repository.getSelectedUserInfo().id)
- keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
+ applicationScope.launch {
+ withContext(mainDispatcher) {
+ keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
+ }
+ }
}
fun addCallback(callback: UserCallback) {
@@ -593,10 +600,18 @@
private fun switchUser(userId: Int) {
// TODO(b/246631653): track jank and latency like in the old impl.
refreshUsersScheduler.pause()
- try {
- activityManager.switchUser(userId)
- } catch (e: RemoteException) {
- Log.e(TAG, "Couldn't switch user.", e)
+ val runnable = Runnable {
+ try {
+ activityManager.switchUser(userId)
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Couldn't switch user.", e)
+ }
+ }
+
+ if (switchUserOnBg()) {
+ applicationScope.launch { withContext(backgroundDispatcher) { runnable.run() } }
+ } else {
+ runnable.run()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/DumpTruck.java b/packages/SystemUI/src/com/android/systemui/util/leak/DumpTruck.java
deleted file mode 100644
index 8215360..0000000
--- a/packages/SystemUI/src/com/android/systemui/util/leak/DumpTruck.java
+++ /dev/null
@@ -1,186 +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.systemui.util.leak;
-
-import android.content.ClipData;
-import android.content.ClipDescription;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Build;
-import android.util.Log;
-
-import androidx.core.content.FileProvider;
-
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
-
-/**
- * Utility class for dumping, compressing, sending, and serving heap dump files.
- *
- * <p>Unlike the Internet, this IS a big truck you can dump something on.
- */
-public class DumpTruck {
- private static final String FILEPROVIDER_AUTHORITY = "com.android.systemui.fileprovider";
- private static final String FILEPROVIDER_PATH = "leak";
-
- private static final String TAG = "DumpTruck";
- private static final int BUFSIZ = 1024 * 1024; // 1MB
-
- private final Context context;
- private final GarbageMonitor mGarbageMonitor;
- private Uri hprofUri;
- private long rss;
- final StringBuilder body = new StringBuilder();
-
- public DumpTruck(Context context, GarbageMonitor garbageMonitor) {
- this.context = context;
- mGarbageMonitor = garbageMonitor;
- }
-
- /**
- * Capture memory for the given processes and zip them up for sharing.
- *
- * @param pids
- * @return this, for chaining
- */
- public DumpTruck captureHeaps(List<Long> pids) {
- final File dumpDir = new File(context.getCacheDir(), FILEPROVIDER_PATH);
- dumpDir.mkdirs();
- hprofUri = null;
-
- body.setLength(0);
- body.append("Build: ").append(Build.DISPLAY).append("\n\nProcesses:\n");
-
- final ArrayList<String> paths = new ArrayList<String>();
- final int myPid = android.os.Process.myPid();
-
- for (Long pidL : pids) {
- final int pid = pidL.intValue();
- body.append(" pid ").append(pid);
- GarbageMonitor.ProcessMemInfo info = mGarbageMonitor.getMemInfo(pid);
- if (info != null) {
- body.append(":")
- .append(" up=")
- .append(info.getUptime())
- .append(" rss=")
- .append(info.currentRss);
- rss = info.currentRss;
- }
- if (pid == myPid) {
- final String path =
- new File(dumpDir, String.format("heap-%d.ahprof", pid)).getPath();
- Log.v(TAG, "Dumping memory info for process " + pid + " to " + path);
- try {
- android.os.Debug.dumpHprofData(path); // will block
- paths.add(path);
- body.append(" (hprof attached)");
- } catch (IOException e) {
- Log.e(TAG, "error dumping memory:", e);
- body.append("\n** Could not dump heap: \n").append(e.toString()).append("\n");
- }
- }
- body.append("\n");
- }
-
- try {
- final String zipfile =
- new File(dumpDir, String.format("hprof-%d.zip", System.currentTimeMillis()))
- .getCanonicalPath();
- if (DumpTruck.zipUp(zipfile, paths)) {
- final File pathFile = new File(zipfile);
- hprofUri = FileProvider.getUriForFile(context, FILEPROVIDER_AUTHORITY, pathFile);
- Log.v(TAG, "Heap dump accessible at URI: " + hprofUri);
- }
- } catch (IOException e) {
- Log.e(TAG, "unable to zip up heapdumps", e);
- body.append("\n** Could not zip up files: \n").append(e.toString()).append("\n");
- }
-
- return this;
- }
-
- /**
- * Get the Uri of the current heap dump. Be sure to call captureHeaps first.
- *
- * @return Uri to the dump served by the SystemUI file provider
- */
- public Uri getDumpUri() {
- return hprofUri;
- }
-
- /**
- * Get an ACTION_SEND intent suitable for startActivity() or attaching to a Notification.
- *
- * @return share intent
- */
- public Intent createShareIntent() {
- Intent shareIntent = new Intent(Intent.ACTION_SEND_MULTIPLE);
- shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- shareIntent.putExtra(Intent.EXTRA_SUBJECT,
- String.format("SystemUI memory dump (rss=%dM)", rss / 1024));
-
- shareIntent.putExtra(Intent.EXTRA_TEXT, body.toString());
-
- if (hprofUri != null) {
- final ArrayList<Uri> uriList = new ArrayList<>();
- uriList.add(hprofUri);
- shareIntent.setType("application/zip");
- shareIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uriList);
-
- // Include URI in ClipData also, so that grantPermission picks it up.
- // We don't use setData here because some apps interpret this as "to:".
- ClipData clipdata = new ClipData(new ClipDescription("content",
- new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}),
- new ClipData.Item(hprofUri));
- shareIntent.setClipData(clipdata);
- shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- }
- return shareIntent;
- }
-
- private static boolean zipUp(String zipfilePath, ArrayList<String> paths) {
- try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipfilePath))) {
- final byte[] buf = new byte[BUFSIZ];
-
- for (String filename : paths) {
- try (InputStream is = new BufferedInputStream(new FileInputStream(filename))) {
- ZipEntry entry = new ZipEntry(filename);
- zos.putNextEntry(entry);
- int len;
- while (0 < (len = is.read(buf, 0, BUFSIZ))) {
- zos.write(buf, 0, len);
- }
- zos.closeEntry();
- }
- }
- return true;
- } catch (IOException e) {
- Log.e(TAG, "error zipping up profile data", e);
- }
- return false;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
deleted file mode 100644
index de392d3..0000000
--- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
+++ /dev/null
@@ -1,617 +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.systemui.util.leak;
-
-import static android.service.quicksettings.Tile.STATE_ACTIVE;
-import static android.telephony.ims.feature.ImsFeature.STATE_UNAVAILABLE;
-
-import static com.android.internal.logging.MetricsLogger.VIEW_UNKNOWN;
-
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.ColorStateList;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Process;
-import android.os.SystemProperties;
-import android.provider.Settings;
-import android.text.format.DateUtils;
-import android.util.Log;
-import android.util.LongSparseArray;
-import android.view.View;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.CoreStartable;
-import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.qs.QSTile;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSHost;
-import com.android.systemui.qs.QsEventLogger;
-import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
-import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.util.concurrency.MessageRouter;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.inject.Inject;
-
-/**
- * Suite of tools to periodically inspect the System UI heap and possibly prompt the user to
- * capture heap dumps and report them. Includes the implementation of the "Dump SysUI Heap"
- * quick settings tile.
- */
-@SysUISingleton
-public class GarbageMonitor implements Dumpable {
- // Feature switches
- // ================
-
- // Whether to use TrackedGarbage to trigger LeakReporter. Off by default unless you set the
- // appropriate sysprop on a userdebug device.
- public static final boolean LEAK_REPORTING_ENABLED = Build.IS_DEBUGGABLE
- && SystemProperties.getBoolean("debug.enable_leak_reporting", false);
- public static final String FORCE_ENABLE_LEAK_REPORTING = "sysui_force_enable_leak_reporting";
-
- // Heap tracking: watch the current memory levels and update the MemoryTile if available.
- // On for all userdebug devices.
- public static final boolean HEAP_TRACKING_ENABLED = Build.IS_DEBUGGABLE;
-
- // Tell QSTileHost.java to toss this into the default tileset?
- public static final boolean ADD_MEMORY_TILE_TO_DEFAULT_ON_DEBUGGABLE_BUILDS = true;
-
- // whether to use ActivityManager.setHeapLimit (and post a notification to the user asking
- // to dump the heap). Off by default unless you set the appropriate sysprop on userdebug
- private static final boolean ENABLE_AM_HEAP_LIMIT = Build.IS_DEBUGGABLE
- && SystemProperties.getBoolean("debug.enable_sysui_heap_limit", false);
-
- // Tuning params
- // =============
-
- // threshold for setHeapLimit(), in KB (overrides R.integer.watch_heap_limit)
- private static final String SETTINGS_KEY_AM_HEAP_LIMIT = "systemui_am_heap_limit";
-
- private static final long GARBAGE_INSPECTION_INTERVAL =
- 15 * DateUtils.MINUTE_IN_MILLIS; // 15 min
- private static final long HEAP_TRACK_INTERVAL = 1 * DateUtils.MINUTE_IN_MILLIS; // 1 min
- private static final int HEAP_TRACK_HISTORY_LEN = 720; // 12 hours
-
- private static final int DO_GARBAGE_INSPECTION = 1000;
- private static final int DO_HEAP_TRACK = 3000;
-
- static final int GARBAGE_ALLOWANCE = 5;
-
- private static final String TAG = "GarbageMonitor";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- private final MessageRouter mMessageRouter;
- private final TrackedGarbage mTrackedGarbage;
- private final LeakReporter mLeakReporter;
- private final Context mContext;
- private final DelayableExecutor mDelayableExecutor;
- private MemoryTile mQSTile;
- private final DumpTruck mDumpTruck;
-
- private final LongSparseArray<ProcessMemInfo> mData = new LongSparseArray<>();
- private final ArrayList<Long> mPids = new ArrayList<>();
-
- private long mHeapLimit;
-
- /**
- */
- @Inject
- public GarbageMonitor(
- Context context,
- @Background DelayableExecutor delayableExecutor,
- @Background MessageRouter messageRouter,
- LeakDetector leakDetector,
- LeakReporter leakReporter,
- DumpManager dumpManager) {
- mContext = context.getApplicationContext();
-
- mDelayableExecutor = delayableExecutor;
- mMessageRouter = messageRouter;
- mMessageRouter.subscribeTo(DO_GARBAGE_INSPECTION, this::doGarbageInspection);
- mMessageRouter.subscribeTo(DO_HEAP_TRACK, this::doHeapTrack);
-
- mTrackedGarbage = leakDetector.getTrackedGarbage();
- mLeakReporter = leakReporter;
-
- mDumpTruck = new DumpTruck(mContext, this);
-
- dumpManager.registerDumpable(getClass().getSimpleName(), this);
-
- if (ENABLE_AM_HEAP_LIMIT) {
- mHeapLimit = Settings.Global.getInt(context.getContentResolver(),
- SETTINGS_KEY_AM_HEAP_LIMIT,
- mContext.getResources().getInteger(R.integer.watch_heap_limit));
- }
- }
-
- public void startLeakMonitor() {
- if (mTrackedGarbage == null) {
- return;
- }
-
- mMessageRouter.sendMessage(DO_GARBAGE_INSPECTION);
- }
-
- public void startHeapTracking() {
- startTrackingProcess(
- android.os.Process.myPid(), mContext.getPackageName(), System.currentTimeMillis());
- mMessageRouter.sendMessage(DO_HEAP_TRACK);
- }
-
- private boolean gcAndCheckGarbage() {
- if (mTrackedGarbage.countOldGarbage() > GARBAGE_ALLOWANCE) {
- Runtime.getRuntime().gc();
- return true;
- }
- return false;
- }
-
- void reinspectGarbageAfterGc() {
- int count = mTrackedGarbage.countOldGarbage();
- if (count > GARBAGE_ALLOWANCE) {
- mLeakReporter.dumpLeak(count);
- }
- }
-
- public ProcessMemInfo getMemInfo(int pid) {
- return mData.get(pid);
- }
-
- public List<Long> getTrackedProcesses() {
- return mPids;
- }
-
- public void startTrackingProcess(long pid, String name, long start) {
- synchronized (mPids) {
- if (mPids.contains(pid)) return;
-
- mPids.add(pid);
- logPids();
-
- mData.put(pid, new ProcessMemInfo(pid, name, start));
- }
- }
-
- private void logPids() {
- if (DEBUG) {
- StringBuffer sb = new StringBuffer("Now tracking processes: ");
- for (int i = 0; i < mPids.size(); i++) {
- final int p = mPids.get(i).intValue();
- sb.append(" ");
- }
- Log.v(TAG, sb.toString());
- }
- }
-
- private void update() {
- synchronized (mPids) {
- for (int i = 0; i < mPids.size(); i++) {
- final int pid = mPids.get(i).intValue();
- // rssValues contains [VmRSS, RssFile, RssAnon, VmSwap].
- long[] rssValues = Process.getRss(pid);
- if (rssValues == null && rssValues.length == 0) {
- if (DEBUG) Log.e(TAG, "update: Process.getRss() didn't provide any values.");
- break;
- }
- long rss = rssValues[0];
- final ProcessMemInfo info = mData.get(pid);
- info.rss[info.head] = info.currentRss = rss;
- info.head = (info.head + 1) % info.rss.length;
- if (info.currentRss > info.max) info.max = info.currentRss;
- if (info.currentRss == 0) {
- if (DEBUG) Log.v(TAG, "update: pid " + pid + " has rss=0, it probably died");
- mData.remove(pid);
- }
- }
- for (int i = mPids.size() - 1; i >= 0; i--) {
- final long pid = mPids.get(i).intValue();
- if (mData.get(pid) == null) {
- mPids.remove(i);
- logPids();
- }
- }
- }
- if (mQSTile != null) mQSTile.update();
- }
-
- private void setTile(MemoryTile tile) {
- mQSTile = tile;
- if (tile != null) tile.update();
- }
-
- private static String formatBytes(long b) {
- String[] SUFFIXES = {"B", "K", "M", "G", "T"};
- int i;
- for (i = 0; i < SUFFIXES.length; i++) {
- if (b < 1024) break;
- b /= 1024;
- }
- return b + SUFFIXES[i];
- }
-
- private Intent dumpHprofAndGetShareIntent() {
- return mDumpTruck.captureHeaps(getTrackedProcesses()).createShareIntent();
- }
-
- @Override
- public void dump(PrintWriter pw, @Nullable String[] args) {
- pw.println("GarbageMonitor params:");
- pw.println(String.format(" mHeapLimit=%d KB", mHeapLimit));
- pw.println(String.format(" GARBAGE_INSPECTION_INTERVAL=%d (%.1f mins)",
- GARBAGE_INSPECTION_INTERVAL,
- (float) GARBAGE_INSPECTION_INTERVAL / DateUtils.MINUTE_IN_MILLIS));
- final float htiMins = HEAP_TRACK_INTERVAL / DateUtils.MINUTE_IN_MILLIS;
- pw.println(String.format(" HEAP_TRACK_INTERVAL=%d (%.1f mins)",
- HEAP_TRACK_INTERVAL,
- htiMins));
- pw.println(String.format(" HEAP_TRACK_HISTORY_LEN=%d (%.1f hr total)",
- HEAP_TRACK_HISTORY_LEN,
- (float) HEAP_TRACK_HISTORY_LEN * htiMins / 60f));
-
- pw.println("GarbageMonitor tracked processes:");
-
- for (long pid : mPids) {
- final ProcessMemInfo pmi = mData.get(pid);
- if (pmi != null) {
- pmi.dump(pw, args);
- }
- }
- }
-
-
- private static class MemoryIconDrawable extends Drawable {
- long rss, limit;
- final Drawable baseIcon;
- final Paint paint = new Paint();
- final float dp;
-
- MemoryIconDrawable(Context context) {
- baseIcon = context.getDrawable(R.drawable.ic_memory).mutate();
- dp = context.getResources().getDisplayMetrics().density;
- paint.setColor(Color.WHITE);
- }
-
- public void setRss(long rss) {
- if (rss != this.rss) {
- this.rss = rss;
- invalidateSelf();
- }
- }
-
- public void setLimit(long limit) {
- if (limit != this.limit) {
- this.limit = limit;
- invalidateSelf();
- }
- }
-
- @Override
- public void draw(Canvas canvas) {
- baseIcon.draw(canvas);
-
- if (limit > 0 && rss > 0) {
- float frac = Math.min(1f, (float) rss / limit);
-
- final Rect bounds = getBounds();
- canvas.translate(bounds.left + 8 * dp, bounds.top + 5 * dp);
- //android:pathData="M16.0,5.0l-8.0,0.0l0.0,14.0l8.0,0.0z"
- canvas.drawRect(0, 14 * dp * (1 - frac), 8 * dp + 1, 14 * dp + 1, paint);
- }
- }
-
- @Override
- public void setBounds(int left, int top, int right, int bottom) {
- super.setBounds(left, top, right, bottom);
- baseIcon.setBounds(left, top, right, bottom);
- }
-
- @Override
- public int getIntrinsicHeight() {
- return baseIcon.getIntrinsicHeight();
- }
-
- @Override
- public int getIntrinsicWidth() {
- return baseIcon.getIntrinsicWidth();
- }
-
- @Override
- public void setAlpha(int i) {
- baseIcon.setAlpha(i);
- }
-
- @Override
- public void setColorFilter(ColorFilter colorFilter) {
- baseIcon.setColorFilter(colorFilter);
- paint.setColorFilter(colorFilter);
- }
-
- @Override
- public void setTint(int tint) {
- super.setTint(tint);
- baseIcon.setTint(tint);
- }
-
- @Override
- public void setTintList(ColorStateList tint) {
- super.setTintList(tint);
- baseIcon.setTintList(tint);
- }
-
- @Override
- public void setTintMode(PorterDuff.Mode tintMode) {
- super.setTintMode(tintMode);
- baseIcon.setTintMode(tintMode);
- }
-
- @Override
- public int getOpacity() {
- return PixelFormat.TRANSLUCENT;
- }
- }
-
- private static class MemoryGraphIcon extends QSTile.Icon {
- long rss, limit;
-
- public void setRss(long rss) {
- this.rss = rss;
- }
-
- public void setHeapLimit(long limit) {
- this.limit = limit;
- }
-
- @Override
- public Drawable getDrawable(Context context) {
- final MemoryIconDrawable drawable = new MemoryIconDrawable(context);
- drawable.setRss(rss);
- drawable.setLimit(limit);
- return drawable;
- }
- }
-
- public static class MemoryTile extends QSTileImpl<QSTile.State> {
- public static final String TILE_SPEC = "dbg:mem";
-
- private final GarbageMonitor gm;
- private ProcessMemInfo pmi;
- private boolean dumpInProgress;
- private final PanelInteractor mPanelInteractor;
-
- @Inject
- public MemoryTile(
- QSHost host,
- QsEventLogger uiEventLogger,
- @Background Looper backgroundLooper,
- @Main Handler mainHandler,
- FalsingManager falsingManager,
- MetricsLogger metricsLogger,
- StatusBarStateController statusBarStateController,
- ActivityStarter activityStarter,
- QSLogger qsLogger,
- GarbageMonitor monitor,
- PanelInteractor panelInteractor
- ) {
- super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
- statusBarStateController, activityStarter, qsLogger);
- gm = monitor;
- mPanelInteractor = panelInteractor;
- }
-
- @Override
- public State newTileState() {
- return new QSTile.State();
- }
-
- @Override
- public Intent getLongClickIntent() {
- return new Intent();
- }
-
- @Override
- protected void handleClick(@Nullable View view) {
- if (dumpInProgress) return;
-
- dumpInProgress = true;
- refreshState();
- new Thread("HeapDumpThread") {
- @Override
- public void run() {
- try {
- // wait for animations & state changes
- Thread.sleep(500);
- } catch (InterruptedException ignored) { }
- final Intent shareIntent = gm.dumpHprofAndGetShareIntent();
- mHandler.post(() -> {
- dumpInProgress = false;
- refreshState();
- mPanelInteractor.collapsePanels();
- mActivityStarter.postStartActivityDismissingKeyguard(shareIntent, 0);
- });
- }
- }.start();
- }
-
- @Override
- public int getMetricsCategory() {
- return VIEW_UNKNOWN;
- }
-
- @Override
- public void handleSetListening(boolean listening) {
- super.handleSetListening(listening);
- if (gm != null) gm.setTile(listening ? this : null);
-
- final ActivityManager am = mContext.getSystemService(ActivityManager.class);
- if (listening && gm.mHeapLimit > 0) {
- am.setWatchHeapLimit(1024 * gm.mHeapLimit); // why is this in bytes?
- } else {
- am.clearWatchHeapLimit();
- }
- }
-
- @Override
- public CharSequence getTileLabel() {
- return getState().label;
- }
-
- @Override
- protected void handleUpdateState(State state, Object arg) {
- pmi = gm.getMemInfo(Process.myPid());
- final MemoryGraphIcon icon = new MemoryGraphIcon();
- icon.setHeapLimit(gm.mHeapLimit);
- state.state = dumpInProgress ? STATE_UNAVAILABLE : STATE_ACTIVE;
- state.label = dumpInProgress
- ? "Dumping..."
- : mContext.getString(R.string.heap_dump_tile_name);
- if (pmi != null) {
- icon.setRss(pmi.currentRss);
- state.secondaryLabel =
- String.format(
- "rss: %s / %s",
- formatBytes(pmi.currentRss * 1024),
- formatBytes(gm.mHeapLimit * 1024));
- } else {
- icon.setRss(0);
- state.secondaryLabel = null;
- }
- state.icon = icon;
- }
-
- public void update() {
- refreshState();
- }
-
- public long getRss() {
- return pmi != null ? pmi.currentRss : 0;
- }
-
- public long getHeapLimit() {
- return gm != null ? gm.mHeapLimit : 0;
- }
- }
-
- /** */
- public static class ProcessMemInfo implements Dumpable {
- public long pid;
- public String name;
- public long startTime;
- public long currentRss;
- public long[] rss = new long[HEAP_TRACK_HISTORY_LEN];
- public long max = 1;
- public int head = 0;
-
- public ProcessMemInfo(long pid, String name, long start) {
- this.pid = pid;
- this.name = name;
- this.startTime = start;
- }
-
- public long getUptime() {
- return System.currentTimeMillis() - startTime;
- }
-
- @Override
- public void dump(PrintWriter pw, @Nullable String[] args) {
- pw.print("{ \"pid\": ");
- pw.print(pid);
- pw.print(", \"name\": \"");
- pw.print(name.replace('"', '-'));
- pw.print("\", \"start\": ");
- pw.print(startTime);
- pw.print(", \"rss\": [");
- // write rss values starting from the oldest, which is rss[head], wrapping around to
- // rss[(head-1) % rss.length]
- for (int i = 0; i < rss.length; i++) {
- if (i > 0) pw.print(",");
- pw.print(rss[(head + i) % rss.length]);
- }
- pw.println("] }");
- }
- }
-
- /** */
- @SysUISingleton
- public static class Service implements CoreStartable, Dumpable {
- private final Context mContext;
- private final GarbageMonitor mGarbageMonitor;
-
- @Inject
- public Service(Context context, GarbageMonitor garbageMonitor) {
- mContext = context;
- mGarbageMonitor = garbageMonitor;
- }
-
- @Override
- public void start() {
- boolean forceEnable =
- Settings.Secure.getInt(
- mContext.getContentResolver(), FORCE_ENABLE_LEAK_REPORTING, 0)
- != 0;
- if (LEAK_REPORTING_ENABLED || forceEnable) {
- mGarbageMonitor.startLeakMonitor();
- }
- if (HEAP_TRACKING_ENABLED || forceEnable) {
- mGarbageMonitor.startHeapTracking();
- }
- }
-
- @Override
- public void dump(PrintWriter pw, @Nullable String[] args) {
- if (mGarbageMonitor != null) mGarbageMonitor.dump(pw, args);
- }
- }
-
- private void doGarbageInspection(int id) {
- if (gcAndCheckGarbage()) {
- mDelayableExecutor.executeDelayed(this::reinspectGarbageAfterGc, 100);
- }
-
- mMessageRouter.cancelMessages(DO_GARBAGE_INSPECTION);
- mMessageRouter.sendMessageDelayed(DO_GARBAGE_INSPECTION, GARBAGE_INSPECTION_INTERVAL);
- }
-
- private void doHeapTrack(int id) {
- update();
- mMessageRouter.cancelMessages(DO_HEAP_TRACK);
- mMessageRouter.sendMessageDelayed(DO_HEAP_TRACK, HEAP_TRACK_INTERVAL);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitorModule.kt b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitorModule.kt
deleted file mode 100644
index e975200..0000000
--- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitorModule.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.util.leak
-
-import com.android.systemui.CoreStartable
-import com.android.systemui.qs.tileimpl.QSTileImpl
-import dagger.Binds
-import dagger.Module
-import dagger.multibindings.ClassKey
-import dagger.multibindings.IntoMap
-import dagger.multibindings.StringKey
-
-@Module
-interface GarbageMonitorModule {
- /** Inject into GarbageMonitor.Service. */
- @Binds
- @IntoMap
- @ClassKey(GarbageMonitor::class)
- fun bindGarbageMonitorService(sysui: GarbageMonitor.Service): CoreStartable
-
- @Binds
- @IntoMap
- @StringKey(GarbageMonitor.MemoryTile.TILE_SPEC)
- fun bindMemoryTile(memoryTile: GarbageMonitor.MemoryTile): QSTileImpl<*>
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 6f58bc2..e6637e6 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -30,13 +30,15 @@
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.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogcatOnlyMessageBuffer
import com.android.systemui.plugins.clocks.ClockAnimations
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockEvents
import com.android.systemui.plugins.clocks.ClockFaceConfig
import com.android.systemui.plugins.clocks.ClockFaceController
import com.android.systemui.plugins.clocks.ClockFaceEvents
+import com.android.systemui.plugins.clocks.ClockMessageBuffers
import com.android.systemui.plugins.clocks.ClockTickRate
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -94,9 +96,9 @@
@Mock private lateinit var largeClockEvents: ClockFaceEvents
@Mock private lateinit var parentView: View
private lateinit var repository: FakeKeyguardRepository
- @Mock private lateinit var smallLogBuffer: LogBuffer
- @Mock private lateinit var largeLogBuffer: LogBuffer
@Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+ private val messageBuffer = LogcatOnlyMessageBuffer(LogLevel.DEBUG)
+ private val clockBuffers = ClockMessageBuffers(messageBuffer, messageBuffer, messageBuffer)
private lateinit var underTest: ClockEventController
@Mock private lateinit var zenModeController: ZenModeController
@@ -140,8 +142,7 @@
context,
mainExecutor,
bgExecutor,
- smallLogBuffer,
- largeLogBuffer,
+ clockBuffers,
withDeps.featureFlags,
zenModeController
)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
index 88f63ad..a249961 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
@@ -34,14 +34,12 @@
import android.widget.RelativeLayout;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.common.ui.ConfigurationState;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager;
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel;
import com.android.systemui.log.LogBuffer;
import com.android.systemui.plugins.clocks.ClockAnimations;
import com.android.systemui.plugins.clocks.ClockController;
@@ -56,14 +54,9 @@
import com.android.systemui.shared.clocks.ClockRegistry;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore;
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker;
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel;
-import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerAlwaysOnDisplayViewBinder;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
-import com.android.systemui.statusbar.ui.SystemBarUtilsState;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.time.FakeSystemClock;
@@ -185,9 +178,7 @@
mKeyguardSliceViewController,
mNotificationIconAreaController,
mSmartspaceController,
- mock(SystemBarUtilsState.class),
- mock(ScreenOffAnimationController.class),
- mock(StatusBarIconViewBindingFailureTracker.class),
+ mock(NotificationIconContainerAlwaysOnDisplayViewBinder.class),
mKeyguardUnlockAnimationController,
mSecureSettings,
mExecutor,
@@ -195,11 +186,6 @@
mDumpManager,
mClockEventController,
mLogBuffer,
- mock(NotificationIconContainerAlwaysOnDisplayViewModel.class),
- mock(KeyguardRootViewModel.class),
- mock(ConfigurationState.class),
- mock(DozeParameters.class),
- mock(AlwaysOnDisplayNotificationIconViewStore.class),
KeyguardInteractorFactory.create(mFakeFeatureFlags).getKeyguardInteractor(),
mKeyguardClockInteractor,
mFakeFeatureFlags,
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 91219f0..0ee0939 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -151,6 +151,7 @@
)
udfpsOverlayInteractor =
UdfpsOverlayInteractor(
+ context,
authController,
selectedUserInteractor,
testScope.backgroundScope,
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 e2aa984..647dae6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
@@ -20,10 +20,9 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import org.junit.Assert.assertFalse
import org.junit.Before
@@ -42,8 +41,7 @@
@Mock lateinit var udfpsBpView: UdfpsBpView
@Mock lateinit var statusBarStateController: StatusBarStateController
- @Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
- @Mock lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
+ @Mock lateinit var shadeInteractor: ShadeInteractor
@Mock lateinit var systemUIDialogManager: SystemUIDialogManager
@Mock lateinit var dumpManager: DumpManager
@@ -55,7 +53,7 @@
UdfpsBpViewController(
udfpsBpView,
statusBarStateController,
- primaryBouncerInteractor,
+ shadeInteractor,
systemUIDialogManager,
dumpManager
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt
index 27d93eb..8f0e910 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt
@@ -24,7 +24,6 @@
import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS
import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING
import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_FIND_SENSOR
-import android.platform.test.annotations.RequiresFlagsEnabled
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.shared.model.AuthenticationReason
@@ -48,7 +47,6 @@
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
-@RequiresFlagsEnabled(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
@@ -62,6 +60,7 @@
@Before
fun setUp() {
+ mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
underTest = BiometricStatusRepositoryImpl(testScope.backgroundScope, biometricManager)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt
index 6978923..d7b7d79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt
@@ -19,7 +19,6 @@
import android.app.ActivityManager
import android.app.ActivityTaskManager
import android.content.ComponentName
-import android.platform.test.annotations.RequiresFlagsEnabled
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository
@@ -44,7 +43,6 @@
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
-@RequiresFlagsEnabled(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
@@ -59,6 +57,7 @@
@Before
fun setup() {
+ mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
biometricStatusRepository = FakeBiometricStatusRepository()
underTest = BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository)
}
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 bbf471c..6a68672 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
@@ -108,6 +108,7 @@
private fun createUdpfsOverlayInteractor() {
underTest =
UdfpsOverlayInteractor(
+ context,
authController,
selectedUserInteractor,
testScope.backgroundScope
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt
index 863d9eb..1b6aaab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt
@@ -18,20 +18,17 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
-import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
-import com.android.systemui.keyguard.ui.viewmodel.deviceEntryIconViewModelTransitionsMock
+import com.android.systemui.keyguard.ui.viewmodel.fakeDeviceEntryIconViewModelTransition
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.statusbar.phone.systemUIDialogManager
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -47,24 +44,14 @@
@SmallTest
@RunWith(JUnit4::class)
class DeviceEntryUdfpsTouchOverlayViewModelTest : SysuiTestCase() {
- val kosmos =
+ private val kosmos =
testKosmos().apply {
fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, true) }
}
- val testScope = kosmos.testScope
-
- private val testDeviceEntryIconTransitionAlpha = MutableStateFlow(0f)
- private val testDeviceEntryIconTransition: DeviceEntryIconTransition
- get() =
- object : DeviceEntryIconTransition {
- override val deviceEntryParentViewAlpha: Flow<Float> =
- testDeviceEntryIconTransitionAlpha.asStateFlow()
- }
-
- init {
- kosmos.deviceEntryIconViewModelTransitionsMock.add(testDeviceEntryIconTransition)
- }
- val systemUIDialogManager = kosmos.systemUIDialogManager
+ private val systemUIDialogManager = kosmos.systemUIDialogManager
+ private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
+ private val testScope = kosmos.testScope
+ private val deviceEntryIconViewModelTransition = kosmos.fakeDeviceEntryIconViewModelTransition
private val underTest = kosmos.deviceEntryUdfpsTouchOverlayViewModel
@Captor
@@ -80,7 +67,7 @@
testScope.runTest {
val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches)
- testDeviceEntryIconTransitionAlpha.value = 1f
+ deviceEntryIconViewModelTransition.setDeviceEntryParentViewAlpha(1f)
runCurrent()
verify(systemUIDialogManager).registerListener(sysuiDialogListenerCaptor.capture())
@@ -94,7 +81,7 @@
testScope.runTest {
val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches)
- testDeviceEntryIconTransitionAlpha.value = .3f
+ deviceEntryIconViewModelTransition.setDeviceEntryParentViewAlpha(.3f)
runCurrent()
verify(systemUIDialogManager).registerListener(sysuiDialogListenerCaptor.capture())
@@ -108,7 +95,7 @@
testScope.runTest {
val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches)
- testDeviceEntryIconTransitionAlpha.value = 1f
+ deviceEntryIconViewModelTransition.setDeviceEntryParentViewAlpha(1f)
runCurrent()
verify(systemUIDialogManager).registerListener(sysuiDialogListenerCaptor.capture())
@@ -116,4 +103,16 @@
assertThat(shouldHandleTouches).isTrue()
}
+
+ @Test
+ fun deviceEntryViewAlphaZero_alternateBouncerVisible_shouldHandleTouchesTrue() =
+ testScope.runTest {
+ val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches)
+
+ deviceEntryIconViewModelTransition.setDeviceEntryParentViewAlpha(0f)
+ runCurrent()
+
+ bouncerRepository.setAlternateVisible(true)
+ assertThat(shouldHandleTouches).isTrue()
+ }
}
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 7475235..6170e0c 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
@@ -128,6 +128,7 @@
)
udfpsOverlayInteractor =
UdfpsOverlayInteractor(
+ context,
authController,
selectedUserInteractor,
testScope.backgroundScope
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/domain/interactor/ConfigurationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/common/domain/interactor/ConfigurationInteractorTest.kt
deleted file mode 100644
index bfa3641..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/common/domain/interactor/ConfigurationInteractorTest.kt
+++ /dev/null
@@ -1,138 +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.common.domain.interactor
-
-import android.content.res.Configuration
-import android.graphics.Rect
-import android.testing.AndroidTestingRunner
-import android.view.Surface.ROTATION_0
-import android.view.Surface.ROTATION_90
-import android.view.Surface.Rotation
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
-import com.android.systemui.coroutines.collectValues
-import com.android.systemui.statusbar.policy.FakeConfigurationController
-import com.android.systemui.util.mockito.mock
-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
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-open class ConfigurationInteractorTest : SysuiTestCase() {
-
- private val testScope = TestScope()
-
- private val configurationController = FakeConfigurationController()
- private val configurationRepository =
- ConfigurationRepositoryImpl(
- configurationController,
- context,
- testScope.backgroundScope,
- mock()
- )
-
- private lateinit var configuration: Configuration
- private lateinit var underTest: ConfigurationInteractor
-
- @Before
- fun setUp() {
- configuration = context.resources.configuration
-
- val testableResources = context.getOrCreateTestableResources()
- testableResources.overrideConfiguration(configuration)
-
- underTest = ConfigurationInteractorImpl(configurationRepository)
- }
-
- @Test
- fun maxBoundsChange_emitsMaxBoundsChange() =
- testScope.runTest {
- val values by collectValues(underTest.naturalMaxBounds)
-
- updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
- runCurrent()
- updateDisplay(width = DISPLAY_WIDTH * 2, height = DISPLAY_HEIGHT * 3)
- runCurrent()
-
- assertThat(values)
- .containsExactly(
- Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT),
- Rect(0, 0, DISPLAY_WIDTH * 2, DISPLAY_HEIGHT * 3),
- )
- .inOrder()
- }
-
- @Test
- fun maxBoundsSameOnConfigChange_doesNotEmitMaxBoundsChange() =
- testScope.runTest {
- val values by collectValues(underTest.naturalMaxBounds)
-
- updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
- runCurrent()
- updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
- runCurrent()
-
- assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT))
- }
-
- @Test
- fun firstMaxBoundsChange_emitsMaxBoundsChange() =
- testScope.runTest {
- val values by collectValues(underTest.naturalMaxBounds)
-
- updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
- runCurrent()
-
- assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT))
- }
-
- @Test
- fun displayRotatedButMaxBoundsTheSame_doesNotEmitNewMaxBoundsChange() =
- testScope.runTest {
- val values by collectValues(underTest.naturalMaxBounds)
-
- updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
- runCurrent()
- updateDisplay(width = DISPLAY_HEIGHT, height = DISPLAY_WIDTH, rotation = ROTATION_90)
- runCurrent()
-
- assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT))
- }
-
- private fun updateDisplay(
- width: Int = DISPLAY_WIDTH,
- height: Int = DISPLAY_HEIGHT,
- @Rotation rotation: Int = ROTATION_0
- ) {
- configuration.windowConfiguration.maxBounds.set(Rect(0, 0, width, height))
- configuration.windowConfiguration.displayRotation = rotation
-
- configurationController.onConfigurationChanged(configuration)
- }
-
- private companion object {
- private const val DISPLAY_WIDTH = 100
- private const val DISPLAY_HEIGHT = 200
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
index c5c0208..9e007e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
@@ -16,14 +16,19 @@
package com.android.systemui.common.ui.domain.interactor
+import android.content.res.Configuration
+import android.graphics.Rect
+import android.view.Surface
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
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
@@ -35,13 +40,16 @@
@RunWith(AndroidJUnit4::class)
class ConfigurationInteractorTest : SysuiTestCase() {
private lateinit var testScope: TestScope
+ private lateinit var configuration: Configuration
private lateinit var underTest: ConfigurationInteractor
private lateinit var configurationRepository: FakeConfigurationRepository
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
-
+ configuration = context.resources.configuration
+ val testableResources = context.getOrCreateTestableResources()
+ testableResources.overrideConfiguration(configuration)
configurationRepository = FakeConfigurationRepository()
testScope = TestScope()
underTest = ConfigurationInteractor(configurationRepository)
@@ -79,4 +87,79 @@
assertThat(dimensionPixelSizes!![resourceId1]).isEqualTo(pixelSize1)
assertThat(dimensionPixelSizes!![resourceId2]).isEqualTo(pixelSize2)
}
+
+ @Test
+ fun maxBoundsChange_emitsMaxBoundsChange() =
+ testScope.runTest {
+ val values by collectValues(underTest.naturalMaxBounds)
+
+ updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
+ runCurrent()
+ updateDisplay(width = DISPLAY_WIDTH * 2, height = DISPLAY_HEIGHT * 3)
+ runCurrent()
+
+ assertThat(values)
+ .containsExactly(
+ Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT),
+ Rect(0, 0, DISPLAY_WIDTH * 2, DISPLAY_HEIGHT * 3),
+ )
+ .inOrder()
+ }
+
+ @Test
+ fun maxBoundsSameOnConfigChange_doesNotEmitMaxBoundsChange() =
+ testScope.runTest {
+ val values by collectValues(underTest.naturalMaxBounds)
+
+ updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
+ runCurrent()
+ updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
+ runCurrent()
+
+ assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT))
+ }
+
+ @Test
+ fun firstMaxBoundsChange_emitsMaxBoundsChange() =
+ testScope.runTest {
+ val values by collectValues(underTest.naturalMaxBounds)
+
+ updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
+ runCurrent()
+
+ assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT))
+ }
+
+ @Test
+ fun displayRotatedButMaxBoundsTheSame_doesNotEmitNewMaxBoundsChange() =
+ testScope.runTest {
+ val values by collectValues(underTest.naturalMaxBounds)
+
+ updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
+ runCurrent()
+ updateDisplay(
+ width = DISPLAY_HEIGHT,
+ height = DISPLAY_WIDTH,
+ rotation = Surface.ROTATION_90
+ )
+ runCurrent()
+
+ assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT))
+ }
+
+ private fun updateDisplay(
+ width: Int = DISPLAY_WIDTH,
+ height: Int = DISPLAY_HEIGHT,
+ @Surface.Rotation rotation: Int = Surface.ROTATION_0
+ ) {
+ configuration.windowConfiguration.maxBounds.set(Rect(0, 0, width, height))
+ configuration.windowConfiguration.displayRotation = rotation
+
+ configurationRepository.onConfigurationChange(configuration)
+ }
+
+ private companion object {
+ private const val DISPLAY_WIDTH = 100
+ private const val DISPLAY_HEIGHT = 200
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
index 16b2ed6..9c5cd71 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
@@ -140,21 +140,76 @@
}
assertThat(widgets())
.containsExactly(
+ communalItemRankEntry2,
+ communalWidgetItemEntry2,
communalItemRankEntry1,
communalWidgetItemEntry1,
- communalItemRankEntry2,
+ )
+ .inOrder()
+
+ // swapped priorities
+ val widgetIdsToPriorityMap = mapOf(widgetInfo1.widgetId to 2, widgetInfo2.widgetId to 1)
+ communalWidgetDao.updateWidgetOrder(widgetIdsToPriorityMap)
+ assertThat(widgets())
+ .containsExactly(
+ communalItemRankEntry1.copy(rank = 2),
+ communalWidgetItemEntry1,
+ communalItemRankEntry2.copy(rank = 1),
communalWidgetItemEntry2
)
+ .inOrder()
+ }
- val widgetIdsInNewOrder = listOf(widgetInfo2.widgetId, widgetInfo1.widgetId)
- communalWidgetDao.updateWidgetOrder(widgetIdsInNewOrder)
+ @Test
+ fun addNewWidgetWithReorder_emitsWidgetsInNewOrder() =
+ testScope.runTest {
+ val existingWidgets = listOf(widgetInfo1, widgetInfo2)
+ val widgets = collectLastValue(communalWidgetDao.getWidgets())
+
+ existingWidgets.forEach {
+ val (widgetId, provider, priority) = it
+ communalWidgetDao.addWidget(
+ widgetId = widgetId,
+ provider = provider,
+ priority = priority,
+ )
+ }
assertThat(widgets())
.containsExactly(
communalItemRankEntry2,
communalWidgetItemEntry2,
communalItemRankEntry1,
- communalWidgetItemEntry1
+ communalWidgetItemEntry1,
)
+ .inOrder()
+
+ // map with no item in the middle at index 1
+ val widgetIdsToIndexMap = mapOf(widgetInfo1.widgetId to 1, widgetInfo2.widgetId to 3)
+ communalWidgetDao.updateWidgetOrder(widgetIdsToIndexMap)
+ assertThat(widgets())
+ .containsExactly(
+ communalItemRankEntry2.copy(rank = 3),
+ communalWidgetItemEntry2,
+ communalItemRankEntry1.copy(rank = 1),
+ communalWidgetItemEntry1,
+ )
+ .inOrder()
+ // add the new middle item that we left space for.
+ communalWidgetDao.addWidget(
+ widgetId = widgetInfo3.widgetId,
+ provider = widgetInfo3.provider,
+ priority = 2,
+ )
+ assertThat(widgets())
+ .containsExactly(
+ communalItemRankEntry2.copy(rank = 3),
+ communalWidgetItemEntry2,
+ communalItemRankEntry3.copy(rank = 2),
+ communalWidgetItemEntry3,
+ communalItemRankEntry1.copy(rank = 1),
+ communalWidgetItemEntry1,
+ )
+ .inOrder()
}
data class FakeWidgetMetadata(
@@ -176,8 +231,15 @@
provider = ComponentName("pk_name", "cls_name_2"),
priority = 2
)
+ val widgetInfo3 =
+ FakeWidgetMetadata(
+ widgetId = 3,
+ provider = ComponentName("pk_name", "cls_name_3"),
+ priority = 3
+ )
val communalItemRankEntry1 = CommunalItemRank(uid = 1L, rank = widgetInfo1.priority)
val communalItemRankEntry2 = CommunalItemRank(uid = 2L, rank = widgetInfo2.priority)
+ val communalItemRankEntry3 = CommunalItemRank(uid = 3L, rank = widgetInfo3.priority)
val communalWidgetItemEntry1 =
CommunalWidgetItem(
uid = 1L,
@@ -192,5 +254,12 @@
componentName = widgetInfo2.provider.flattenToString(),
itemId = communalItemRankEntry2.uid,
)
+ val communalWidgetItemEntry3 =
+ CommunalWidgetItem(
+ uid = 3L,
+ widgetId = widgetInfo3.widgetId,
+ componentName = widgetInfo3.provider.flattenToString(),
+ itemId = communalItemRankEntry3.uid,
+ )
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
new file mode 100644
index 0000000..93ce86a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.data.repository.fakeAccessibilityRepository
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.data.ui.viewmodel.udfpsAccessibilityOverlayViewModel
+import com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
+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.viewmodel.fakeDeviceEntryIconViewModelTransition
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UdfpsAccessibilityOverlayViewModelTest : SysuiTestCase() {
+ private val kosmos =
+ testKosmos().apply {
+ fakeFeatureFlagsClassic.apply { set(FULL_SCREEN_USER_SWITCHER, false) }
+ }
+ private val deviceEntryIconTransition = kosmos.fakeDeviceEntryIconViewModelTransition
+ private val testScope = kosmos.testScope
+ private val accessibilityRepository = kosmos.fakeAccessibilityRepository
+ private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
+ private val deviceEntryFingerprintAuthRepository = kosmos.deviceEntryFingerprintAuthRepository
+ private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
+ private val shadeRepository = kosmos.fakeShadeRepository
+ private val underTest = kosmos.udfpsAccessibilityOverlayViewModel
+
+ @Test
+ fun visible() =
+ testScope.runTest {
+ val visible by collectLastValue(underTest.visible)
+ setupVisibleStateOnLockscreen()
+ assertThat(visible).isTrue()
+ }
+
+ @Test
+ fun touchExplorationNotEnabled_overlayNotVisible() =
+ testScope.runTest {
+ val visible by collectLastValue(underTest.visible)
+ setupVisibleStateOnLockscreen()
+ accessibilityRepository.isTouchExplorationEnabled.value = false
+ assertThat(visible).isFalse()
+ }
+
+ @Test
+ fun deviceEntryFgIconViewModelAod_overlayNotVisible() =
+ testScope.runTest {
+ val visible by collectLastValue(underTest.visible)
+ setupVisibleStateOnLockscreen()
+
+ // AOD
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ value = 0f,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ value = 1f,
+ transitionState = TransitionState.FINISHED,
+ )
+ )
+ assertThat(visible).isFalse()
+ }
+
+ @Test
+ fun deviceUnlocked_overlayNotVisible() =
+ testScope.runTest {
+ val visible by collectLastValue(underTest.visible)
+ setupVisibleStateOnLockscreen()
+ deviceEntryRepository.setUnlocked(true)
+ assertThat(visible).isFalse()
+ }
+
+ @Test
+ fun deviceEntryViewAlpha0_overlayNotVisible() =
+ testScope.runTest {
+ val visible by collectLastValue(underTest.visible)
+ setupVisibleStateOnLockscreen()
+ deviceEntryIconTransition.setDeviceEntryParentViewAlpha(0f)
+ assertThat(visible).isFalse()
+ }
+
+ private suspend fun setupVisibleStateOnLockscreen() {
+ // A11y enabled
+ accessibilityRepository.isTouchExplorationEnabled.value = true
+
+ // Transition alpha is 1f
+ deviceEntryIconTransition.setDeviceEntryParentViewAlpha(1f)
+
+ // Listening for UDFPS
+ fingerprintPropertyRepository.supportsUdfps()
+ deviceEntryFingerprintAuthRepository.setIsRunning(true)
+ deviceEntryRepository.setUnlocked(false)
+
+ // Lockscreen
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ value = 0f,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ value = 1f,
+ transitionState = TransitionState.FINISHED,
+ )
+ )
+
+ // Shade not expanded
+ shadeRepository.qsExpansion.value = 0f
+ shadeRepository.lockscreenShadeExpansion.value = 0f
+ }
+}
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 70d3f81..0616a34 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
@@ -17,7 +17,6 @@
package com.android.systemui.keyguard.domain.interactor
import android.os.Handler
-import android.platform.test.annotations.RequiresFlagsEnabled
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardUpdateMonitor
@@ -58,7 +57,6 @@
import org.mockito.junit.MockitoRule
@OptIn(ExperimentalCoroutinesApi::class)
-@RequiresFlagsEnabled(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
@SmallTest
@RunWith(JUnit4::class)
class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() {
@@ -80,6 +78,7 @@
@Before
fun setup() {
+ mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
primaryBouncerInteractor =
PrimaryBouncerInteractor(
bouncerRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
index 3109e76..ad86ee9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
@@ -37,6 +37,7 @@
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultUdfpsAccessibilityOverlaySection
import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
import com.android.systemui.util.mockito.whenever
@@ -70,7 +71,8 @@
@Mock private lateinit var communalTutorialIndicatorSection: CommunalTutorialIndicatorSection
@Mock private lateinit var clockSection: ClockSection
@Mock private lateinit var smartspaceSection: SmartspaceSection
-
+ @Mock
+ private lateinit var udfpsAccessibilityOverlaySection: DefaultUdfpsAccessibilityOverlaySection
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
@@ -90,6 +92,7 @@
communalTutorialIndicatorSection,
clockSection,
smartspaceSection,
+ udfpsAccessibilityOverlaySection,
)
}
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 e89b61f..dc0d9c5 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
@@ -20,7 +20,6 @@
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
@@ -43,7 +42,6 @@
@Mock private lateinit var keyguardClockViewModel: KeyguardClockViewModel
@Mock private lateinit var smartspaceViewModel: KeyguardSmartspaceViewModel
@Mock private lateinit var splitShadeStateController: SplitShadeStateController
- private var featureFlags: FakeFeatureFlagsClassic = FakeFeatureFlagsClassic()
private lateinit var underTest: ClockSection
@@ -88,7 +86,6 @@
smartspaceViewModel,
mContext,
splitShadeStateController,
- featureFlags
)
}
@@ -147,24 +144,6 @@
assetSmallClockTop(cs, expectedSmallClockTopMargin)
}
- @Test
- fun testLargeClockShouldBeCentered() {
- underTest.setClockShouldBeCentered(true)
- val cs = ConstraintSet()
- underTest.applyDefaultConstraints(cs)
- val constraint = cs.getConstraint(R.id.lockscreen_clock_view_large)
- assertThat(constraint.layout.endToEnd).isEqualTo(ConstraintSet.PARENT_ID)
- }
-
- @Test
- fun testLargeClockShouldNotBeCentered() {
- underTest.setClockShouldBeCentered(false)
- val cs = ConstraintSet()
- underTest.applyDefaultConstraints(cs)
- val constraint = cs.getConstraint(R.id.lockscreen_clock_view_large)
- assertThat(constraint.layout.endToEnd).isEqualTo(R.id.split_shade_guideline)
- }
-
private fun setLargeClock(useLargeClock: Boolean) {
whenever(keyguardClockViewModel.useLargeClock).thenReturn(useLargeClock)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
index 22569e2..c864704 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
@@ -30,20 +30,15 @@
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler
-import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerViewModel
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationPanelView
-import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.VibratorHelper
-import com.android.systemui.statusbar.gesture.TapGestureDetector
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -87,11 +82,6 @@
{ mock(DeviceEntryForegroundViewModel::class.java) },
{ mock(DeviceEntryBackgroundViewModel::class.java) },
{ falsingManager },
- { mock(AlternateBouncerViewModel::class.java) },
- { mock(NotificationShadeWindowController::class.java) },
- TestScope().backgroundScope,
- { mock(SwipeUpAnywhereGestureHandler::class.java) },
- { mock(TapGestureDetector::class.java) },
{ mock(VibratorHelper::class.java) },
)
}
@@ -177,21 +167,4 @@
assertThat(constraint.layout.topMargin).isEqualTo(5)
assertThat(constraint.layout.startMargin).isEqualTo(4)
}
-
- @Test
- fun deviceEntryIconViewIsAboveAlternateBouncerView() {
- mSetFlagsRule.enableFlags(AConfigFlags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
-
- val constraintLayout = ConstraintLayout(context, null)
- underTest.addViews(constraintLayout)
- assertThat(constraintLayout.childCount).isGreaterThan(0)
- val deviceEntryIconView = constraintLayout.getViewById(R.id.device_entry_icon_view)
- val alternateBouncerView = constraintLayout.getViewById(R.id.alternate_bouncer)
- assertThat(deviceEntryIconView).isNotNull()
- assertThat(alternateBouncerView).isNotNull()
-
- // device entry icon is above the alternate bouncer
- assertThat(constraintLayout.indexOfChild(deviceEntryIconView))
- .isGreaterThan(constraintLayout.indexOfChild(alternateBouncerView))
- }
}
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 bff27f6..740110b4 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
@@ -69,14 +69,13 @@
keyguardUnlockAnimationController,
)
constraintLayout = ConstraintLayout(mContext)
- whenever(lockscreenSmartspaceController.buildAndConnectView(constraintLayout))
- .thenReturn(smartspaceView)
- whenever(lockscreenSmartspaceController.buildAndConnectWeatherView(constraintLayout))
- .thenReturn(weatherView)
- whenever(lockscreenSmartspaceController.buildAndConnectDateView(constraintLayout))
- .thenReturn(dateView)
+ whenever(keyguardSmartspaceViewModel.smartspaceView).thenReturn(smartspaceView)
+ whenever(keyguardSmartspaceViewModel.weatherView).thenReturn(weatherView)
+ whenever(keyguardSmartspaceViewModel.dateView).thenReturn(dateView)
whenever(keyguardClockViewModel.hasCustomWeatherDataDisplay)
.thenReturn(hasCustomWeatherDataDisplay)
+ whenever(keyguardSmartspaceViewModel.smartspaceController)
+ .thenReturn(lockscreenSmartspaceController)
constraintSet = ConstraintSet()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 459a74c..ee1be10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -24,7 +24,6 @@
import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION
import com.android.systemui.SysuiTestCase
-import com.android.systemui.collectLastValue
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.coroutines.collectLastValue
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt
deleted file mode 100644
index 6512290..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.ui.viewmodel
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.doze.util.BurnInHelperWrapper
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.statusbar.phone.SystemUIDialogManager
-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.MockitoAnnotations
-
-@ExperimentalCoroutinesApi
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class UdfpsAodViewModelTest : SysuiTestCase() {
- private val defaultPadding = 12
- private lateinit var underTest: UdfpsAodViewModel
-
- private lateinit var testScope: TestScope
- private lateinit var configRepository: FakeConfigurationRepository
- private lateinit var bouncerRepository: KeyguardBouncerRepository
- private lateinit var keyguardRepository: FakeKeyguardRepository
- private lateinit var shadeRepository: FakeShadeRepository
- private lateinit var keyguardInteractor: KeyguardInteractor
-
- @Mock private lateinit var dialogManager: SystemUIDialogManager
- @Mock private lateinit var burnInHelper: BurnInHelperWrapper
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- overrideResource(com.android.systemui.res.R.dimen.lock_icon_padding, defaultPadding)
- testScope = TestScope()
- shadeRepository = FakeShadeRepository()
- KeyguardInteractorFactory.create().also {
- keyguardInteractor = it.keyguardInteractor
- keyguardRepository = it.repository
- configRepository = it.configurationRepository
- bouncerRepository = it.bouncerRepository
- }
- val udfpsKeyguardInteractor =
- UdfpsKeyguardInteractor(
- configRepository,
- BurnInInteractor(
- context,
- burnInHelper,
- testScope.backgroundScope,
- configRepository,
- keyguardInteractor,
- ),
- keyguardInteractor,
- shadeRepository,
- dialogManager,
- )
-
- underTest =
- UdfpsAodViewModel(
- udfpsKeyguardInteractor,
- context,
- )
- }
-
- @Test
- fun alphaAndVisibleUpdates_onDozeAmountChanges() =
- testScope.runTest {
- val alpha by collectLastValue(underTest.alpha)
- val visible by collectLastValue(underTest.isVisible)
-
- keyguardRepository.setDozeAmount(0f)
- runCurrent()
- assertThat(alpha).isEqualTo(0f)
- assertThat(visible).isFalse()
-
- keyguardRepository.setDozeAmount(.65f)
- runCurrent()
- assertThat(alpha).isEqualTo(.65f)
- assertThat(visible).isTrue()
-
- keyguardRepository.setDozeAmount(.23f)
- runCurrent()
- assertThat(alpha).isEqualTo(.23f)
- assertThat(visible).isTrue()
-
- keyguardRepository.setDozeAmount(1f)
- runCurrent()
- assertThat(alpha).isEqualTo(1f)
- assertThat(visible).isTrue()
- }
-
- @Test
- fun paddingUpdates_onScaleForResolutionChanges() =
- testScope.runTest {
- val padding by collectLastValue(underTest.padding)
-
- configRepository.setScaleForResolution(1f)
- runCurrent()
- assertThat(padding).isEqualTo(defaultPadding)
-
- configRepository.setScaleForResolution(2f)
- runCurrent()
- assertThat(padding).isEqualTo(defaultPadding * 2)
-
- configRepository.setScaleForResolution(.5f)
- runCurrent()
- assertThat(padding).isEqualTo((defaultPadding * .5f).toInt())
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
deleted file mode 100644
index 95b2fe5..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.ui.viewmodel
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
-import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.doze.util.BurnInHelperWrapper
-import com.android.systemui.keyguard.data.repository.FakeCommandQueue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.statusbar.phone.SystemUIDialogManager
-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.MockitoAnnotations
-
-/** Tests UdfpsFingerprintViewModel specific flows. */
-@ExperimentalCoroutinesApi
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class UdfpsFingerprintViewModelTest : SysuiTestCase() {
- private val defaultPadding = 12
- private lateinit var underTest: FingerprintViewModel
-
- private lateinit var testScope: TestScope
- private lateinit var configRepository: FakeConfigurationRepository
- private lateinit var bouncerRepository: KeyguardBouncerRepository
- private lateinit var keyguardRepository: FakeKeyguardRepository
- private lateinit var fakeCommandQueue: FakeCommandQueue
- private lateinit var transitionRepository: FakeKeyguardTransitionRepository
- private lateinit var shadeRepository: FakeShadeRepository
-
- @Mock private lateinit var burnInHelper: BurnInHelperWrapper
- @Mock private lateinit var dialogManager: SystemUIDialogManager
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- overrideResource(com.android.systemui.res.R.dimen.lock_icon_padding, defaultPadding)
- testScope = TestScope()
- configRepository = FakeConfigurationRepository()
- keyguardRepository = FakeKeyguardRepository()
- bouncerRepository = FakeKeyguardBouncerRepository()
- fakeCommandQueue = FakeCommandQueue()
- bouncerRepository = FakeKeyguardBouncerRepository()
- transitionRepository = FakeKeyguardTransitionRepository()
- shadeRepository = FakeShadeRepository()
- val keyguardInteractor =
- KeyguardInteractorFactory.create(
- repository = keyguardRepository,
- )
- .keyguardInteractor
-
- val transitionInteractor =
- KeyguardTransitionInteractorFactory.create(
- scope = testScope.backgroundScope,
- repository = transitionRepository,
- keyguardInteractor = keyguardInteractor,
- )
- .keyguardTransitionInteractor
-
- underTest =
- FingerprintViewModel(
- context,
- transitionInteractor,
- UdfpsKeyguardInteractor(
- configRepository,
- BurnInInteractor(
- context,
- burnInHelper,
- testScope.backgroundScope,
- configRepository,
- keyguardInteractor,
- ),
- keyguardInteractor,
- shadeRepository,
- dialogManager,
- ),
- keyguardInteractor,
- )
- }
-
- @Test
- fun paddingUpdates_onScaleForResolutionChanges() =
- testScope.runTest {
- val padding by collectLastValue(underTest.padding)
-
- configRepository.setScaleForResolution(1f)
- runCurrent()
- assertThat(padding).isEqualTo(defaultPadding)
-
- configRepository.setScaleForResolution(2f)
- runCurrent()
- assertThat(padding).isEqualTo(defaultPadding * 2)
-
- configRepository.setScaleForResolution(.5f)
- runCurrent()
- assertThat(padding).isEqualTo((defaultPadding * .5).toInt())
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
deleted file mode 100644
index 848a94b..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
+++ /dev/null
@@ -1,749 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.ui.viewmodel
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.settingslib.Utils
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.StatusBarState
-import com.android.systemui.keyguard.shared.model.TransitionState
-import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.statusbar.phone.SystemUIDialogManager
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.mock
-import com.android.wm.shell.animation.Interpolators
-import com.google.common.collect.Range
-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
-import org.mockito.MockitoAnnotations
-
-/** Tests UDFPS lockscreen view model transitions. */
-@ExperimentalCoroutinesApi
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class UdfpsLockscreenViewModelTest : SysuiTestCase() {
- private val lockscreenColorResId = android.R.attr.textColorPrimary
- private val alternateBouncerResId = com.android.internal.R.attr.materialColorOnPrimaryFixed
- private val lockscreenColor = Utils.getColorAttrDefaultColor(context, lockscreenColorResId)
- private val alternateBouncerColor =
- Utils.getColorAttrDefaultColor(context, alternateBouncerResId)
-
- @Mock private lateinit var dialogManager: SystemUIDialogManager
-
- private lateinit var underTest: UdfpsLockscreenViewModel
- private lateinit var testScope: TestScope
- private lateinit var transitionRepository: FakeKeyguardTransitionRepository
- private lateinit var configRepository: FakeConfigurationRepository
- private lateinit var keyguardRepository: FakeKeyguardRepository
- private lateinit var keyguardInteractor: KeyguardInteractor
- private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
- private lateinit var shadeRepository: FakeShadeRepository
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- testScope = TestScope()
- transitionRepository = FakeKeyguardTransitionRepository()
- shadeRepository = FakeShadeRepository()
- KeyguardInteractorFactory.create().also {
- keyguardInteractor = it.keyguardInteractor
- keyguardRepository = it.repository
- configRepository = it.configurationRepository
- bouncerRepository = it.bouncerRepository
- }
-
- val transitionInteractor =
- KeyguardTransitionInteractorFactory.create(
- scope = testScope.backgroundScope,
- repository = transitionRepository,
- keyguardInteractor = keyguardInteractor,
- )
- .keyguardTransitionInteractor
-
- underTest =
- UdfpsLockscreenViewModel(
- context,
- lockscreenColorResId,
- alternateBouncerResId,
- transitionInteractor,
- UdfpsKeyguardInteractor(
- configRepository,
- BurnInInteractor(
- context,
- burnInHelperWrapper = mock(),
- testScope.backgroundScope,
- configRepository,
- keyguardInteractor,
- ),
- keyguardInteractor,
- shadeRepository,
- dialogManager,
- ),
- keyguardInteractor,
- )
- }
-
- @Test
- fun goneToAodTransition() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
-
- // TransitionState.STARTED: gone -> AOD
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.GONE,
- to = KeyguardState.AOD,
- value = 0f,
- transitionState = TransitionState.STARTED,
- ownerName = "goneToAodTransition",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(visible).isFalse()
-
- // TransitionState.RUNNING: gone -> AOD
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.GONE,
- to = KeyguardState.AOD,
- value = .6f,
- transitionState = TransitionState.RUNNING,
- ownerName = "goneToAodTransition",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(visible).isFalse()
-
- // TransitionState.FINISHED: gone -> AOD
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.GONE,
- to = KeyguardState.AOD,
- value = 1f,
- transitionState = TransitionState.FINISHED,
- ownerName = "goneToAodTransition",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(visible).isFalse()
- }
-
- @Test
- fun lockscreenToAod() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
- keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
-
- // TransitionState.STARTED: lockscreen -> AOD
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.AOD,
- value = 0f,
- transitionState = TransitionState.STARTED,
- ownerName = "lockscreenToAod",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isTrue()
-
- // TransitionState.RUNNING: lockscreen -> AOD
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.AOD,
- value = .6f,
- transitionState = TransitionState.RUNNING,
- ownerName = "lockscreenToAod",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f))
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isTrue()
-
- // TransitionState.FINISHED: lockscreen -> AOD
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.AOD,
- value = 1f,
- transitionState = TransitionState.FINISHED,
- ownerName = "lockscreenToAod",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isFalse()
- }
-
- @Test
- fun lockscreenShadeLockedToAod() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
- keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
-
- // TransitionState.STARTED: lockscreen -> AOD
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.AOD,
- value = 0f,
- transitionState = TransitionState.STARTED,
- ownerName = "lockscreenToAod",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(visible).isFalse()
-
- // TransitionState.RUNNING: lockscreen -> AOD
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.AOD,
- value = .6f,
- transitionState = TransitionState.RUNNING,
- ownerName = "lockscreenToAod",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(visible).isFalse()
-
- // TransitionState.FINISHED: lockscreen -> AOD
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.AOD,
- value = 1f,
- transitionState = TransitionState.FINISHED,
- ownerName = "lockscreenToAod",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(visible).isFalse()
- }
-
- @Test
- fun aodToLockscreen() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
-
- // TransitionState.STARTED: AOD -> lockscreen
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.AOD,
- to = KeyguardState.LOCKSCREEN,
- value = 0f,
- transitionState = TransitionState.STARTED,
- ownerName = "aodToLockscreen",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isFalse()
-
- // TransitionState.RUNNING: AOD -> lockscreen
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.AOD,
- to = KeyguardState.LOCKSCREEN,
- value = .6f,
- transitionState = TransitionState.RUNNING,
- ownerName = "aodToLockscreen",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isIn(Range.closed(.59f, .61f))
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isTrue()
-
- // TransitionState.FINISHED: AOD -> lockscreen
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.AOD,
- to = KeyguardState.LOCKSCREEN,
- value = 1f,
- transitionState = TransitionState.FINISHED,
- ownerName = "aodToLockscreen",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isTrue()
- }
-
- @Test
- fun lockscreenToAlternateBouncer() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
- keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
-
- // TransitionState.STARTED: lockscreen -> alternate bouncer
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.ALTERNATE_BOUNCER,
- value = 0f,
- transitionState = TransitionState.STARTED,
- ownerName = "lockscreenToAlternateBouncer",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isTrue()
-
- // TransitionState.RUNNING: lockscreen -> alternate bouncer
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.ALTERNATE_BOUNCER,
- value = .6f,
- transitionState = TransitionState.RUNNING,
- ownerName = "lockscreenToAlternateBouncer",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isTrue()
-
- // TransitionState.FINISHED: lockscreen -> alternate bouncer
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.ALTERNATE_BOUNCER,
- value = 1f,
- transitionState = TransitionState.FINISHED,
- ownerName = "lockscreenToAlternateBouncer",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isTrue()
- }
-
- fun alternateBouncerToPrimaryBouncer() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
-
- // TransitionState.STARTED: alternate bouncer -> primary bouncer
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.ALTERNATE_BOUNCER,
- to = KeyguardState.PRIMARY_BOUNCER,
- value = 0f,
- transitionState = TransitionState.STARTED,
- ownerName = "alternateBouncerToPrimaryBouncer",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isTrue()
-
- // TransitionState.RUNNING: alternate bouncer -> primary bouncer
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.ALTERNATE_BOUNCER,
- to = KeyguardState.PRIMARY_BOUNCER,
- value = .6f,
- transitionState = TransitionState.RUNNING,
- ownerName = "alternateBouncerToPrimaryBouncer",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isIn(Range.closed(.59f, .61f))
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isTrue()
-
- // TransitionState.FINISHED: alternate bouncer -> primary bouncer
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.ALTERNATE_BOUNCER,
- to = KeyguardState.PRIMARY_BOUNCER,
- value = 1f,
- transitionState = TransitionState.FINISHED,
- ownerName = "alternateBouncerToPrimaryBouncer",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isFalse()
- }
-
- fun alternateBouncerToAod() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
-
- // TransitionState.STARTED: alternate bouncer -> aod
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.ALTERNATE_BOUNCER,
- to = KeyguardState.AOD,
- value = 0f,
- transitionState = TransitionState.STARTED,
- ownerName = "alternateBouncerToAod",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isTrue()
-
- // TransitionState.RUNNING: alternate bouncer -> aod
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.ALTERNATE_BOUNCER,
- to = KeyguardState.AOD,
- value = .6f,
- transitionState = TransitionState.RUNNING,
- ownerName = "alternateBouncerToAod",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f))
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isTrue()
-
- // TransitionState.FINISHED: alternate bouncer -> aod
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.ALTERNATE_BOUNCER,
- to = KeyguardState.AOD,
- value = 1f,
- transitionState = TransitionState.FINISHED,
- ownerName = "alternateBouncerToAod",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isFalse()
- }
-
- @Test
- fun lockscreenToOccluded() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
- keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
-
- // TransitionState.STARTED: lockscreen -> occluded
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.OCCLUDED,
- value = 0f,
- transitionState = TransitionState.STARTED,
- ownerName = "lockscreenToOccluded",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isTrue()
-
- // TransitionState.RUNNING: lockscreen -> occluded
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.OCCLUDED,
- value = .6f,
- transitionState = TransitionState.RUNNING,
- ownerName = "lockscreenToOccluded",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f))
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isTrue()
-
- // TransitionState.FINISHED: lockscreen -> occluded
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.OCCLUDED,
- value = 1f,
- transitionState = TransitionState.FINISHED,
- ownerName = "lockscreenToOccluded",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isFalse()
- }
-
- @Test
- fun occludedToLockscreen() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
-
- // TransitionState.STARTED: occluded -> lockscreen
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.OCCLUDED,
- to = KeyguardState.LOCKSCREEN,
- value = 0f,
- transitionState = TransitionState.STARTED,
- ownerName = "occludedToLockscreen",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isTrue()
-
- // TransitionState.RUNNING: occluded -> lockscreen
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.OCCLUDED,
- to = KeyguardState.LOCKSCREEN,
- value = .6f,
- transitionState = TransitionState.RUNNING,
- ownerName = "occludedToLockscreen",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isTrue()
-
- // TransitionState.FINISHED: occluded -> lockscreen
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.OCCLUDED,
- to = KeyguardState.LOCKSCREEN,
- value = 1f,
- transitionState = TransitionState.FINISHED,
- ownerName = "occludedToLockscreen",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isTrue()
- }
-
- @Test
- fun qsProgressChange() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
- givenTransitionToLockscreenFinished()
-
- // qsExpansion = 0f
- shadeRepository.setQsExpansion(0f)
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(visible).isEqualTo(true)
-
- // qsExpansion = .25
- shadeRepository.setQsExpansion(.2f)
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(.6f)
- assertThat(visible).isEqualTo(true)
-
- // qsExpansion = .5
- shadeRepository.setQsExpansion(.5f)
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(visible).isEqualTo(false)
-
- // qsExpansion = 1
- shadeRepository.setQsExpansion(1f)
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(visible).isEqualTo(false)
- }
-
- @Test
- fun shadeExpansionChanged() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
- givenTransitionToLockscreenFinished()
-
- // shadeExpansion = 0f
- shadeRepository.setUdfpsTransitionToFullShadeProgress(0f)
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(visible).isEqualTo(true)
-
- // shadeExpansion = .2
- shadeRepository.setUdfpsTransitionToFullShadeProgress(.2f)
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(.8f)
- assertThat(visible).isEqualTo(true)
-
- // shadeExpansion = .5
- shadeRepository.setUdfpsTransitionToFullShadeProgress(.5f)
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(.5f)
- assertThat(visible).isEqualTo(true)
-
- // shadeExpansion = 1
- shadeRepository.setUdfpsTransitionToFullShadeProgress(1f)
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(visible).isEqualTo(false)
- }
-
- @Test
- fun dialogHideAffordancesRequestChanged() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- givenTransitionToLockscreenFinished()
- runCurrent()
- val captor = argumentCaptor<SystemUIDialogManager.Listener>()
- Mockito.verify(dialogManager).registerListener(captor.capture())
-
- captor.value.shouldHideAffordances(true)
- assertThat(transition?.alpha).isEqualTo(0f)
-
- captor.value.shouldHideAffordances(false)
- assertThat(transition?.alpha).isEqualTo(1f)
- }
-
- @Test
- fun occludedToAlternateBouncer() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
-
- // TransitionState.STARTED: occluded -> alternate bouncer
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.OCCLUDED,
- to = KeyguardState.ALTERNATE_BOUNCER,
- value = 0f,
- transitionState = TransitionState.STARTED,
- ownerName = "occludedToAlternateBouncer",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(0f)
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isTrue()
-
- // TransitionState.RUNNING: occluded -> alternate bouncer
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.OCCLUDED,
- to = KeyguardState.ALTERNATE_BOUNCER,
- value = .6f,
- transitionState = TransitionState.RUNNING,
- ownerName = "occludedToAlternateBouncer",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale)
- .isEqualTo(Interpolators.FAST_OUT_SLOW_IN.getInterpolation(.6f))
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isTrue()
-
- // TransitionState.FINISHED: occluded -> alternate bouncer
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.OCCLUDED,
- to = KeyguardState.ALTERNATE_BOUNCER,
- value = 1f,
- transitionState = TransitionState.FINISHED,
- ownerName = "occludedToAlternateBouncer",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isTrue()
- }
-
- private suspend fun givenTransitionToLockscreenFinished() {
- transitionRepository.sendTransitionSteps(
- from = KeyguardState.AOD,
- to = KeyguardState.LOCKSCREEN,
- testScope
- )
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
index 1f99303..0a464e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
@@ -22,11 +22,11 @@
import android.testing.TestableLooper
import android.view.View
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.controls.models.player.MediaViewHolder
import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder
import com.android.systemui.media.controls.util.MediaFlags
+import com.android.systemui.res.R
import com.android.systemui.util.animation.MeasurementInput
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.animation.TransitionViewState
@@ -37,6 +37,7 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.floatThat
import org.mockito.Mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
@@ -183,10 +184,12 @@
// detail widgets occupy [90, 100]
whenever(detailWidgetState.y).thenReturn(90F)
whenever(detailWidgetState.height).thenReturn(10)
+ whenever(detailWidgetState.alpha).thenReturn(1F)
// control widgets occupy [150, 170]
whenever(controlWidgetState.y).thenReturn(150F)
whenever(controlWidgetState.height).thenReturn(20)
- // in current beizer, when the progress reach 0.38, the result will be 0.5
+ whenever(controlWidgetState.alpha).thenReturn(1F)
+ // in current bezier, when the progress reach 0.38, the result will be 0.5
mediaViewController.squishViewState(mockViewState, 181.4F / 200F)
verify(controlWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta }
verify(detailWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
@@ -196,6 +199,34 @@
}
@Test
+ fun testSquishViewState_applySquishFraction_toTransitionViewState_alpha_invisibleElements() {
+ whenever(mockViewState.copy()).thenReturn(mockCopiedState)
+ whenever(mockCopiedState.widgetStates)
+ .thenReturn(
+ mutableMapOf(
+ R.id.media_progress_bar to controlWidgetState,
+ R.id.header_artist to detailWidgetState
+ )
+ )
+ whenever(mockCopiedState.measureHeight).thenReturn(200)
+ // detail widgets occupy [90, 100]
+ whenever(detailWidgetState.y).thenReturn(90F)
+ whenever(detailWidgetState.height).thenReturn(10)
+ whenever(detailWidgetState.alpha).thenReturn(0F)
+ // control widgets occupy [150, 170]
+ whenever(controlWidgetState.y).thenReturn(150F)
+ whenever(controlWidgetState.height).thenReturn(20)
+ whenever(controlWidgetState.alpha).thenReturn(0F)
+ // Verify that alpha remains 0 throughout squishing
+ mediaViewController.squishViewState(mockViewState, 181.4F / 200F)
+ verify(controlWidgetState, never()).alpha = floatThat { it > 0 }
+ verify(detailWidgetState, never()).alpha = floatThat { it > 0 }
+ mediaViewController.squishViewState(mockViewState, 200F / 200F)
+ verify(controlWidgetState, never()).alpha = floatThat { it > 0 }
+ verify(detailWidgetState, never()).alpha = floatThat { it > 0 }
+ }
+
+ @Test
fun testSquishViewState_applySquishFraction_toTransitionViewState_alpha_forRecommendation() {
whenever(mockViewState.copy()).thenReturn(mockCopiedState)
whenever(mockCopiedState.widgetStates)
@@ -210,12 +241,15 @@
// media container widgets occupy [20, 300]
whenever(mediaContainerWidgetState.y).thenReturn(20F)
whenever(mediaContainerWidgetState.height).thenReturn(280)
+ whenever(mediaContainerWidgetState.alpha).thenReturn(1F)
// media title widgets occupy [320, 330]
whenever(mediaTitleWidgetState.y).thenReturn(320F)
whenever(mediaTitleWidgetState.height).thenReturn(10)
+ whenever(mediaTitleWidgetState.alpha).thenReturn(1F)
// media subtitle widgets occupy [340, 350]
whenever(mediaSubTitleWidgetState.y).thenReturn(340F)
whenever(mediaSubTitleWidgetState.height).thenReturn(10)
+ whenever(mediaSubTitleWidgetState.alpha).thenReturn(1F)
// in current beizer, when the progress reach 0.38, the result will be 0.5
mediaViewController.squishViewState(mockViewState, 307.6F / 360F)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 4d42324..b7618d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -75,7 +75,6 @@
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runCurrent
import org.junit.Before
-import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
@@ -463,7 +462,6 @@
// region setNoteTaskShortcutEnabled
@Test
- @Ignore("b/316332684")
fun setNoteTaskShortcutEnabled_setTrue() {
createNoteTaskController().setNoteTaskShortcutEnabled(value = true, userTracker.userHandle)
@@ -480,7 +478,6 @@
}
@Test
- @Ignore("b/316332684")
fun setNoteTaskShortcutEnabled_setFalse() {
createNoteTaskController().setNoteTaskShortcutEnabled(value = false, userTracker.userHandle)
@@ -497,7 +494,6 @@
}
@Test
- @Ignore("b/316332684")
fun setNoteTaskShortcutEnabled_workProfileUser_setTrue() {
whenever(context.createContextAsUser(eq(workUserInfo.userHandle), any()))
.thenReturn(workProfileContext)
@@ -519,7 +515,6 @@
}
@Test
- @Ignore("b/316332684")
fun setNoteTaskShortcutEnabled_workProfileUser_setFalse() {
whenever(context.createContextAsUser(eq(workUserInfo.userHandle), any()))
.thenReturn(workProfileContext)
@@ -738,7 +733,6 @@
// region internalUpdateNoteTaskAsUser
@Test
- @Ignore("b/316332684")
fun updateNoteTaskAsUserInternal_withNotesRole_withShortcuts_shouldUpdateShortcuts() {
createNoteTaskController(isEnabled = true)
.launchUpdateNoteTaskAsUser(userTracker.userHandle)
@@ -772,7 +766,6 @@
}
@Test
- @Ignore("b/316332684")
fun updateNoteTaskAsUserInternal_noNotesRole_shouldDisableShortcuts() {
whenever(roleManager.getRoleHoldersAsUser(ROLE_NOTES, userTracker.userHandle))
.thenReturn(emptyList())
@@ -796,7 +789,6 @@
}
@Test
- @Ignore("b/316332684")
fun updateNoteTaskAsUserInternal_flagDisabled_shouldDisableShortcuts() {
createNoteTaskController(isEnabled = false)
.launchUpdateNoteTaskAsUser(userTracker.userHandle)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index 5e2423a..ef7798e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -447,10 +447,6 @@
mContext.getOrCreateTestableResources()
.addOverride(R.string.quick_settings_tiles_default, "spec1,spec1");
List<String> specs = QSTileHost.loadTileSpecs(mContext, "default");
-
- // Remove spurious tiles, like dbg:mem
- specs.removeIf(spec -> !"spec1".equals(spec));
- assertEquals(1, specs.size());
}
@Test
@@ -458,10 +454,6 @@
mContext.getOrCreateTestableResources()
.addOverride(R.string.quick_settings_tiles_default, "spec1");
List<String> specs = QSTileHost.loadTileSpecs(mContext, "default,spec1");
-
- // Remove spurious tiles, like dbg:mem
- specs.removeIf(spec -> !"spec1".equals(spec));
- assertEquals(1, specs.size());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
index 067218a..5201e5d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
@@ -50,7 +50,6 @@
import com.android.systemui.qs.tiles.ScreenRecordTile
import com.android.systemui.qs.tiles.UiModeNightTile
import com.android.systemui.qs.tiles.WorkModeTile
-import com.android.systemui.util.leak.GarbageMonitor
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -117,7 +116,6 @@
@Mock private lateinit var dataSaverTile: DataSaverTile
@Mock private lateinit var nightDisplayTile: NightDisplayTile
@Mock private lateinit var nfcTile: NfcTile
- @Mock private lateinit var memoryTile: GarbageMonitor.MemoryTile
@Mock private lateinit var darkModeTile: UiModeNightTile
@Mock private lateinit var screenRecordTile: ScreenRecordTile
@Mock private lateinit var reduceBrightColorsTile: ReduceBrightColorsTile
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
index 9b61447..c7479fd5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
@@ -30,8 +30,8 @@
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.recordissue.RecordIssueDialogDelegate
import com.android.systemui.res.R
-import com.android.systemui.settings.UserContextProvider
import com.android.systemui.statusbar.phone.KeyguardDismissUtil
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -65,9 +65,9 @@
@Mock private lateinit var keyguardDismissUtil: KeyguardDismissUtil
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var dialogLauncherAnimator: DialogLaunchAnimator
- @Mock private lateinit var dialogFactory: SystemUIDialog.Factory
+ @Mock private lateinit var delegateFactory: RecordIssueDialogDelegate.Factory
+ @Mock private lateinit var dialogDelegate: RecordIssueDialogDelegate
@Mock private lateinit var dialog: SystemUIDialog
- @Mock private lateinit var userContextProvider: UserContextProvider
private lateinit var testableLooper: TestableLooper
private lateinit var tile: RecordIssueTile
@@ -76,7 +76,8 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(host.context).thenReturn(mContext)
- whenever(dialogFactory.create(any())).thenReturn(dialog)
+ whenever(delegateFactory.create(any())).thenReturn(dialogDelegate)
+ whenever(dialogDelegate.createDialog()).thenReturn(dialog)
testableLooper = TestableLooper.get(this)
tile =
@@ -93,8 +94,7 @@
keyguardDismissUtil,
keyguardStateController,
dialogLauncherAnimator,
- dialogFactory,
- userContextProvider,
+ delegateFactory,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
index c5d3524..7ce51ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
@@ -17,6 +17,9 @@
package com.android.systemui.recordissue
import android.app.Dialog
+import android.content.Context
+import android.content.SharedPreferences
+import android.os.UserHandle
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.widget.Button
@@ -25,48 +28,107 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
+import com.android.systemui.mediaprojection.SessionCreationSource
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver
import com.android.systemui.model.SysUiState
+import com.android.systemui.qs.tiles.RecordIssueTile
import com.android.systemui.res.R
+import com.android.systemui.settings.UserContextProvider
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.SystemUIDialogManager
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Executor
import java.util.concurrent.TimeUnit
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class RecordIssueDialogDelegateTest : SysuiTestCase() {
+ @Mock private lateinit var flags: FeatureFlagsClassic
+ @Mock private lateinit var devicePolicyResolver: ScreenCaptureDevicePolicyResolver
+ @Mock private lateinit var dprLazy: dagger.Lazy<ScreenCaptureDevicePolicyResolver>
+ @Mock private lateinit var mediaProjectionMetricsLogger: MediaProjectionMetricsLogger
+ @Mock private lateinit var userContextProvider: UserContextProvider
+ @Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var userFileManager: UserFileManager
+ @Mock private lateinit var sharedPreferences: SharedPreferences
+
+ @Mock private lateinit var sysuiState: SysUiState
+ @Mock private lateinit var systemUIDialogManager: SystemUIDialogManager
+ @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock private lateinit var bgExecutor: Executor
+ @Mock private lateinit var mainExecutor: Executor
+ @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+
private lateinit var dialog: SystemUIDialog
+ private lateinit var factory: SystemUIDialog.Factory
private lateinit var latch: CountDownLatch
@Before
fun setup() {
- val dialogFactory =
- SystemUIDialog.Factory(
- context,
- mock<FeatureFlags>(),
- mock<SystemUIDialogManager>(),
- mock<SysUiState>().apply {
- whenever(setFlag(anyInt(), anyBoolean())).thenReturn(this)
- },
- mock<BroadcastDispatcher>(),
- mock<DialogLaunchAnimator>()
+ MockitoAnnotations.initMocks(this)
+ whenever(dprLazy.get()).thenReturn(devicePolicyResolver)
+ whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState)
+ whenever(userContextProvider.userContext).thenReturn(mContext)
+ whenever(
+ userFileManager.getSharedPreferences(
+ eq(RecordIssueTile.TILE_SPEC),
+ eq(Context.MODE_PRIVATE),
+ anyInt()
+ )
+ )
+ .thenReturn(sharedPreferences)
+
+ factory =
+ spy(
+ SystemUIDialog.Factory(
+ context,
+ flags,
+ systemUIDialogManager,
+ sysuiState,
+ broadcastDispatcher,
+ dialogLaunchAnimator
+ )
)
latch = CountDownLatch(1)
dialog =
- RecordIssueDialogDelegate(dialogFactory, mock()) { latch.countDown() }.createDialog()
+ RecordIssueDialogDelegate(
+ factory,
+ userContextProvider,
+ userTracker,
+ flags,
+ bgExecutor,
+ mainExecutor,
+ dprLazy,
+ mediaProjectionMetricsLogger,
+ userFileManager,
+ ) {
+ latch.countDown()
+ }
+ .createDialog()
dialog.show()
}
@@ -91,4 +153,82 @@
dialog.getButton(Dialog.BUTTON_POSITIVE).callOnClick()
latch.await(1L, TimeUnit.MILLISECONDS)
}
+
+ @Test
+ fun screenCaptureDisabledDialog_isShown_whenFunctionalityIsDisabled() {
+ whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES))
+ .thenReturn(true)
+ whenever(devicePolicyResolver.isScreenCaptureCompletelyDisabled(any<UserHandle>()))
+ .thenReturn(true)
+
+ val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
+ screenRecordSwitch.isChecked = true
+
+ val bgCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+ verify(bgExecutor).execute(bgCaptor.capture())
+ bgCaptor.value.run()
+
+ val mainCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+ verify(mainExecutor).execute(mainCaptor.capture())
+ mainCaptor.value.run()
+
+ verify(mediaProjectionMetricsLogger, never())
+ .notifyProjectionInitiated(
+ anyInt(),
+ eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER)
+ )
+ assertThat(screenRecordSwitch.isChecked).isFalse()
+ }
+
+ @Test
+ fun screenCapturePermissionDialog_isShown_correctly() {
+ whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES))
+ .thenReturn(false)
+ whenever(devicePolicyResolver.isScreenCaptureCompletelyDisabled(any<UserHandle>()))
+ .thenReturn(false)
+ whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
+ whenever(sharedPreferences.getBoolean(HAS_APPROVED_SCREEN_RECORDING, false))
+ .thenReturn(false)
+
+ val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
+ screenRecordSwitch.isChecked = true
+
+ val bgCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+ verify(bgExecutor).execute(bgCaptor.capture())
+ bgCaptor.value.run()
+
+ val mainCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+ verify(mainExecutor).execute(mainCaptor.capture())
+ mainCaptor.value.run()
+
+ verify(mediaProjectionMetricsLogger)
+ .notifyProjectionInitiated(
+ anyInt(),
+ eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER)
+ )
+ verify(factory).create(any<ScreenCapturePermissionDialogDelegate>())
+ }
+
+ @Test
+ fun noDialogsAreShown_forScreenRecord_whenApprovalIsAlreadyGiven() {
+ whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES))
+ .thenReturn(false)
+ whenever(devicePolicyResolver.isScreenCaptureCompletelyDisabled(any<UserHandle>()))
+ .thenReturn(false)
+ whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(false)
+
+ val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
+ screenRecordSwitch.isChecked = true
+
+ val bgCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+ verify(bgExecutor).execute(bgCaptor.capture())
+ bgCaptor.value.run()
+
+ verify(mediaProjectionMetricsLogger)
+ .notifyProjectionInitiated(
+ anyInt(),
+ eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER)
+ )
+ verify(factory, never()).create(any<ScreenCapturePermissionDialogDelegate>())
+ }
}
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 b90ccc0..9941661 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2023 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,35 +16,23 @@
package com.android.systemui.scene.shared.flag
-import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.FakeFeatureFlagsImpl
-import com.android.systemui.Flags as AconfigFlags
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.compose.ComposeFacade
import com.android.systemui.flags.Flags
-import com.android.systemui.flags.ReleasedFlag
-import com.android.systemui.flags.ResourceBooleanFlag
-import com.android.systemui.flags.UnreleasedFlag
+import com.android.systemui.flags.setFlagValue
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.media.controls.util.MediaInSceneContainerFlag
-import com.android.systemui.res.R
-import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth
+import org.junit.Assume
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
@SmallTest
-@RunWith(Parameterized::class)
-internal class SceneContainerFlagsTest(
- private val testCase: TestCase,
-) : SysuiTestCase() {
-
- @Rule @JvmField val setFlagsRule: SetFlagsRule = SetFlagsRule()
-
- private lateinit var underTest: SceneContainerFlags
+@RunWith(AndroidJUnit4::class)
+internal class SceneContainerFlagsTest : SysuiTestCase() {
@Before
fun setUp() {
@@ -52,83 +40,39 @@
// Flags.SCENE_CONTAINER_ENABLED is no longer needed.
val field = Flags::class.java.getField("SCENE_CONTAINER_ENABLED")
field.isAccessible = true
- field.set(null, true)
+ field.set(null, true) // note: this does not work with multivalent tests
+ }
- val featureFlags =
- FakeFeatureFlagsClassic().apply {
- SceneContainerFlagsImpl.classicFlagTokens.forEach { flagToken ->
- when (flagToken) {
- is ResourceBooleanFlag -> set(flagToken, testCase.areAllFlagsSet)
- is ReleasedFlag -> set(flagToken, testCase.areAllFlagsSet)
- is UnreleasedFlag -> set(flagToken, testCase.areAllFlagsSet)
- else -> error("Unsupported flag type ${flagToken.javaClass}")
- }
- }
- }
- // TODO(b/306421592): get the aconfig FeatureFlags from the SetFlagsRule.
- val aconfigFlags = FakeFeatureFlagsImpl()
-
+ private fun setAconfigFlagsEnabled(enabled: Boolean) {
listOf(
- AconfigFlags.FLAG_SCENE_CONTAINER,
- AconfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
+ com.android.systemui.Flags.FLAG_SCENE_CONTAINER,
+ com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
KeyguardShadeMigrationNssl.FLAG_NAME,
MediaInSceneContainerFlag.FLAG_NAME,
)
- .forEach { flagToken ->
- setFlagsRule.enableFlags(flagToken)
- aconfigFlags.setFlag(flagToken, testCase.areAllFlagsSet)
- overrideResource(
- R.bool.config_sceneContainerFrameworkEnabled,
- testCase.isResourceConfigEnabled
- )
- }
-
- underTest =
- SceneContainerFlagsImpl(
- context = context,
- featureFlagsClassic = featureFlags,
- isComposeAvailable = testCase.isComposeAvailable,
- )
+ .forEach { flagName -> mSetFlagsRule.setFlagValue(flagName, enabled) }
}
@Test
- fun isEnabled() {
- assertThat(underTest.isEnabled()).isEqualTo(testCase.expectedEnabled)
+ fun isNotEnabled_withoutAconfigFlags() {
+ setAconfigFlagsEnabled(false)
+ Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(false)
+ Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(false)
}
- internal data class TestCase(
- val isComposeAvailable: Boolean,
- val areAllFlagsSet: Boolean,
- val isResourceConfigEnabled: Boolean,
- val expectedEnabled: Boolean,
- ) {
- override fun toString(): String {
- return "(compose=$isComposeAvailable + flags=$areAllFlagsSet) + XML" +
- " config=$isResourceConfigEnabled -> expected=$expectedEnabled"
- }
+ @Test
+ fun isEnabled_withAconfigFlags_withCompose() {
+ Assume.assumeTrue(ComposeFacade.isComposeAvailable())
+ setAconfigFlagsEnabled(true)
+ Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(true)
+ Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(true)
}
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun testCases() = buildList {
- repeat(8) { combination ->
- val isComposeAvailable = combination and 0b100 != 0
- val areAllFlagsSet = combination and 0b010 != 0
- val isResourceConfigEnabled = combination and 0b001 != 0
-
- val expectedEnabled =
- isComposeAvailable && areAllFlagsSet && isResourceConfigEnabled
-
- add(
- TestCase(
- isComposeAvailable = isComposeAvailable,
- areAllFlagsSet = areAllFlagsSet,
- expectedEnabled = expectedEnabled,
- isResourceConfigEnabled = isResourceConfigEnabled,
- )
- )
- }
- }
+ @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/screenshot/MessageContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
index d4e8d37..72fc65b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
@@ -10,8 +10,6 @@
import androidx.constraintlayout.widget.Guideline
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
@@ -39,7 +37,6 @@
lateinit var detectionNoticeView: ViewGroup
lateinit var container: FrameLayout
- var featureFlags = FakeFeatureFlags()
lateinit var screenshotView: ViewGroup
val userHandle = UserHandle.of(5)
@@ -55,7 +52,6 @@
MessageContainerController(
workProfileMessageController,
screenshotDetectionController,
- featureFlags
)
screenshotView = ConstraintLayout(mContext)
workProfileData = WorkProfileMessageController.WorkProfileFirstRunData(appName, icon)
@@ -105,8 +101,6 @@
@Test
fun testOnScreenshotTakenScreenshotData_nothingToShow() {
- featureFlags.set(Flags.SCREENSHOT_DETECTION, true)
-
messageContainer.onScreenshotTaken(screenshotData)
verify(workProfileMessageController, never()).populateView(any(), any(), any())
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 9d997da..86d8d54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -33,7 +33,6 @@
import com.android.keyguard.dagger.KeyguardBouncerComponent
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
-import com.android.systemui.back.domain.interactor.BackActionInteractor
import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
import com.android.systemui.bouncer.data.repository.BouncerMessageRepositoryImpl
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
@@ -68,9 +67,12 @@
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.SwipeUpAnywhereGestureHandler
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
import com.android.systemui.log.BouncerLogger
-import com.android.systemui.power.domain.interactor.PowerInteractor
+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
@@ -79,6 +81,7 @@
import com.android.systemui.statusbar.NotificationShadeDepthController
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.gesture.TapGestureDetector
import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository
import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
import com.android.systemui.statusbar.notification.stack.AmbientState
@@ -93,6 +96,7 @@
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
@@ -126,8 +130,6 @@
@Mock private lateinit var centralSurfaces: CentralSurfaces
@Mock private lateinit var dozeServiceHost: DozeServiceHost
@Mock private lateinit var dozeScrimController: DozeScrimController
- @Mock private lateinit var backActionInteractor: BackActionInteractor
- @Mock private lateinit var powerInteractor: PowerInteractor
@Mock private lateinit var dockManager: DockManager
@Mock private lateinit var notificationPanelViewController: NotificationPanelViewController
@Mock private lateinit var notificationShadeDepthController: NotificationShadeDepthController
@@ -275,6 +277,12 @@
primaryBouncerInteractor,
alternateBouncerInteractor,
mSelectedUserInteractor,
+ { mock (JavaAdapter::class.java )},
+ { mock(AlternateBouncerViewModel::class.java) },
+ { mock(FalsingManager::class.java) },
+ { mock(SwipeUpAnywhereGestureHandler::class.java) },
+ { mock(TapGestureDetector::class.java) },
+ { mock(AlternateBouncerUdfpsIconViewModel::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 9750f60..d9ff892 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -55,8 +55,12 @@
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.SwipeUpAnywhereGestureHandler
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
import com.android.systemui.log.BouncerLogger
+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
@@ -65,6 +69,7 @@
import com.android.systemui.statusbar.NotificationShadeDepthController
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.gesture.TapGestureDetector
import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository
import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
import com.android.systemui.statusbar.notification.stack.AmbientState
@@ -79,12 +84,14 @@
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
import com.android.systemui.util.mockito.whenever
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
import kotlinx.coroutines.test.runTest
@@ -99,6 +106,7 @@
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+@ExperimentalCoroutinesApi
@RunWith(AndroidTestingRunner::class)
@RunWithLooper(setAsMainLooper = true)
@SmallTest
@@ -259,6 +267,12 @@
primaryBouncerInteractor,
alternateBouncerInteractor,
mSelectedUserInteractor,
+ { Mockito.mock(JavaAdapter::class.java) },
+ { Mockito.mock(AlternateBouncerViewModel::class.java) },
+ { Mockito.mock(FalsingManager::class.java) },
+ { Mockito.mock(SwipeUpAnywhereGestureHandler::class.java) },
+ { Mockito.mock(TapGestureDetector::class.java) },
+ { Mockito.mock(AlternateBouncerUdfpsIconViewModel::class.java) },
)
controller.setupExpandedStatusBar()
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 ee94cbb..ee27c5c 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
@@ -26,6 +26,7 @@
import com.android.systemui.flags.Flags.TRANSIT_CLOCK
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockId
+import com.android.systemui.plugins.clocks.ClockMessageBuffers
import com.android.systemui.plugins.clocks.ClockMetadata
import com.android.systemui.plugins.clocks.ClockProviderPlugin
import com.android.systemui.plugins.clocks.ClockSettings
@@ -128,6 +129,7 @@
override fun createClock(settings: ClockSettings): ClockController =
createCallbacks[settings.clockId!!]!!(settings.clockId!!)
override fun getClockThumbnail(id: ClockId): Drawable? = thumbnailCallbacks[id]!!(id)
+ override fun initialize(buffers: ClockMessageBuffers?) { }
fun addClock(
id: ClockId,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
index fef262f..e0e8d1f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
@@ -26,6 +26,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.customization.R
+import com.android.systemui.plugins.clocks.ClockId
import com.android.systemui.plugins.clocks.ClockSettings
import com.android.systemui.shared.clocks.DefaultClockController.Companion.DOZE_COLOR
import com.android.systemui.util.mockito.any
@@ -49,6 +50,9 @@
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
+private fun DefaultClockProvider.createClock(id: ClockId): DefaultClockController =
+ createClock(ClockSettings(id, null)) as DefaultClockController
+
@RunWith(AndroidTestingRunner::class)
@SmallTest
class DefaultClockProviderTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
new file mode 100644
index 0000000..80f8cf1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.notifications.data.repository
+
+import android.provider.Settings
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.shared.settings.data.repository.FakeSecureSettingsRepository
+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.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class NotificationSettingsRepositoryTest : SysuiTestCase() {
+
+ private lateinit var underTest: NotificationSettingsRepository
+
+ private lateinit var testScope: TestScope
+ private lateinit var secureSettingsRepository: FakeSecureSettingsRepository
+
+ @Before
+ fun setUp() {
+ val testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
+ secureSettingsRepository = FakeSecureSettingsRepository()
+
+ underTest =
+ NotificationSettingsRepository(
+ scope = testScope.backgroundScope,
+ backgroundDispatcher = testDispatcher,
+ secureSettingsRepository = secureSettingsRepository,
+ )
+ }
+
+ @Test
+ fun testGetIsShowNotificationsOnLockscreenEnabled() =
+ testScope.runTest {
+ val showNotifs by collectLastValue(underTest.isShowNotificationsOnLockScreenEnabled)
+
+ secureSettingsRepository.set(
+ name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
+ value = 1,
+ )
+ assertThat(showNotifs).isEqualTo(true)
+
+ secureSettingsRepository.set(
+ name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
+ value = 0,
+ )
+ assertThat(showNotifs).isEqualTo(false)
+ }
+
+ @Test
+ fun testSetIsShowNotificationsOnLockscreenEnabled() =
+ testScope.runTest {
+ val showNotifs by collectLastValue(underTest.isShowNotificationsOnLockScreenEnabled)
+
+ underTest.setShowNotificationsOnLockscreenEnabled(true)
+ assertThat(showNotifs).isEqualTo(true)
+
+ underTest.setShowNotificationsOnLockscreenEnabled(false)
+ assertThat(showNotifs).isEqualTo(false)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
index 2bee7b8..d3febf5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
@@ -41,6 +41,7 @@
import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository;
import com.android.systemui.statusbar.domain.interactor.SilentNotificationStatusIconsVisibilityInteractor;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -151,6 +152,7 @@
@Test
public void testOnConnectReadStatusBarSetting() {
+ mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME);
NotificationListener.NotificationSettingsListener settingsListener =
mock(NotificationListener.NotificationSettingsListener.class);
mListener.addNotificationSettingsListener(settingsListener);
@@ -164,6 +166,7 @@
@Test
public void testOnStatusBarIconsBehaviorChanged() {
+ mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME);
NotificationListener.NotificationSettingsListener settingsListener =
mock(NotificationListener.NotificationSettingsListener.class);
mListener.addNotificationSettingsListener(settingsListener);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt
index a56fb2c..7d8cf36 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt
@@ -1,11 +1,10 @@
package com.android.systemui.statusbar.notification
+import android.platform.test.annotations.EnableFlags
import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import org.junit.Assert.assertEquals
@@ -20,8 +19,7 @@
@RunWith(JUnit4::class)
class RoundableTest : SysuiTestCase() {
private val targetView: View = mock()
- private val featureFlags = FakeFeatureFlags()
- private val roundable = FakeRoundable(targetView = targetView, featureFlags = featureFlags)
+ private val roundable = FakeRoundable(targetView = targetView)
@Test
fun defaultConfig_shouldNotHaveRoundedCorner() {
@@ -150,36 +148,36 @@
}
@Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
fun getCornerRadii_radius_maxed_to_height() {
whenever(targetView.height).thenReturn(10)
- featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true)
roundable.requestRoundness(1f, 1f, SOURCE1)
assertCornerRadiiEquals(5f, 5f)
}
@Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
fun getCornerRadii_topRadius_maxed_to_height() {
whenever(targetView.height).thenReturn(5)
- featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true)
roundable.requestRoundness(1f, 0f, SOURCE1)
assertCornerRadiiEquals(5f, 0f)
}
@Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
fun getCornerRadii_bottomRadius_maxed_to_height() {
whenever(targetView.height).thenReturn(5)
- featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true)
roundable.requestRoundness(0f, 1f, SOURCE1)
assertCornerRadiiEquals(0f, 5f)
}
@Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
fun getCornerRadii_radii_kept() {
whenever(targetView.height).thenReturn(100)
- featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true)
roundable.requestRoundness(1f, 1f, SOURCE1)
assertCornerRadiiEquals(MAX_RADIUS, MAX_RADIUS)
@@ -193,14 +191,12 @@
class FakeRoundable(
targetView: View,
radius: Float = MAX_RADIUS,
- featureFlags: FeatureFlags
) : Roundable {
override val roundableState =
RoundableState(
targetView = targetView,
roundable = this,
maxRadius = radius,
- featureFlags = featureFlags
)
override val clipHeight: Int
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt
index f3094cd..170f651 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt
@@ -80,7 +80,7 @@
assertThat(isPulseExpanding).isFalse()
withArgCaptor { verify(mockWakeUpCoordinator).addListener(capture()) }
- .onPulseExpansionChanged(true)
+ .onPulseExpandingChanged(true)
runCurrent()
assertThat(isPulseExpanding).isTrue()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
index 360a373..47feccf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
@@ -30,6 +30,7 @@
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
import com.android.systemui.statusbar.notification.data.repository.FakeNotificationsKeyguardViewStateRepository
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor
import com.android.systemui.statusbar.notification.shared.byIsAmbient
import com.android.systemui.statusbar.notification.shared.byIsLastMessageFromReply
import com.android.systemui.statusbar.notification.shared.byIsPulsing
@@ -264,6 +265,7 @@
interface TestComponent : SysUITestComponent<StatusBarNotificationIconsInteractor> {
val activeNotificationListRepository: ActiveNotificationListRepository
+ val headsUpIconsInteractor: HeadsUpNotificationIconInteractor
val keyguardViewStateRepository: FakeNotificationsKeyguardViewStateRepository
val notificationListenerSettingsRepository: NotificationListenerSettingsRepository
@@ -336,6 +338,14 @@
.comparingElementsUsing(byIsLastMessageFromReply)
.doesNotContain(true)
}
+
+ @Test
+ fun filteredEntrySet_includesIsolatedIcon() =
+ testComponent.runTest {
+ val filteredSet by collectLastValue(underTest.statusBarNotifs)
+ headsUpIconsInteractor.setIsolatedIconNotificationKey("notif5")
+ assertThat(filteredSet).comparingElementsUsing(byKey).contains("notif5")
+ }
}
private val testIcons =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
index 349a35eb..c40401f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
@@ -399,4 +399,29 @@
assertThat(isolatedIcon?.value?.notifKey).isEqualTo("notif1")
}
+
+ @Test
+ fun isolatedIcon_lastMessageIsFromReply_notNull() =
+ testComponent.runTest {
+ val icon: Icon = mock()
+ headsUpViewStateRepository.isolatedNotification.value = "notif1"
+ activeNotificationsRepository.activeNotifications.value =
+ ActiveNotificationsStore.Builder()
+ .apply {
+ addIndividualNotif(
+ activeNotificationModel(
+ key = "notif1",
+ groupKey = "group",
+ statusBarIcon = icon,
+ isLastMessageFromReply = true,
+ )
+ )
+ }
+ .build()
+
+ val isolatedIcon by collectLastValue(underTest.isolatedIcon)
+ runCurrent()
+
+ assertThat(isolatedIcon?.value?.notifKey).isEqualTo("notif1")
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index cb73108..0cd834d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -57,7 +57,6 @@
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.media.controls.util.MediaFeatureFlag;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -568,9 +567,6 @@
NotificationEntry entry,
@InflationFlag int extraInflationFlags)
throws Exception {
- // NOTE: This flag is read when the ExpandableNotificationRow is inflated, so it needs to be
- // set, but we do not want to override an existing value that is needed by a specific test.
- mFeatureFlags.setDefault(Flags.IMPROVED_HUN_ANIMATIONS);
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
mContext.LAYOUT_INFLATER_SERVICE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index c8dbdc5..d4300f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -17,6 +17,7 @@
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.StackScrollAlgorithmState
import com.android.systemui.util.mockito.mock
import junit.framework.Assert.assertEquals
@@ -28,8 +29,8 @@
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.mock
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
/** Tests for {@link NotificationShelf}. */
@SmallTest
@@ -53,7 +54,6 @@
MockitoAnnotations.initMocks(this)
mDependency.injectTestDependency(FeatureFlags::class.java, flags)
flags.set(Flags.SENSITIVE_REVEAL_ANIM, useSensitiveReveal)
- flags.setDefault(Flags.IMPROVED_HUN_ANIMATIONS)
val root = FrameLayout(context)
shelf =
LayoutInflater.from(root.context)
@@ -72,6 +72,7 @@
@Test
fun testShadeWidth_BasedOnFractionToShade() {
+ mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME)
setFractionToShade(0f)
setOnLockscreen(true)
@@ -87,6 +88,7 @@
@Test
fun testShelfIsLong_WhenNotOnLockscreen() {
+ mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME)
setFractionToShade(0f)
setOnLockscreen(false)
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 ba5ba2c..ad7dee3 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
@@ -82,7 +82,6 @@
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
-import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -116,7 +115,6 @@
private AmbientState mAmbientState;
private TestableResources mTestableResources;
@Rule public MockitoRule mockito = MockitoJUnit.rule();
- @Mock private NotificationsController mNotificationsController;
@Mock private SysuiStatusBarStateController mBarState;
@Mock private GroupMembershipManager mGroupMembershipManger;
@Mock private GroupExpansionManager mGroupExpansionManager;
@@ -193,7 +191,7 @@
mStackScrollerInternal.initView(getContext(), mNotificationSwipeHelper,
mNotificationStackSizeCalculator);
mStackScroller = spy(mStackScrollerInternal);
- mStackScroller.setNotificationsController(mNotificationsController);
+ mStackScroller.setResetUserExpandedStatesRunnable(()->{});
mStackScroller.setEmptyShadeView(mEmptyShadeView);
when(mStackScrollLayoutController.isHistoryEnabled()).thenReturn(true);
when(mStackScrollLayoutController.getNotificationRoundnessManager())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 08ef477..f266f03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -2,21 +2,28 @@
import android.annotation.DimenRes
import android.content.pm.PackageManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ShadeInterpolation.getContentAlpha
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.res.R
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
import com.android.systemui.statusbar.EmptyShadeView
import com.android.systemui.statusbar.NotificationShelf
import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.RoundableState
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView.FooterViewState
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Expect
@@ -24,6 +31,7 @@
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.Assume
import org.junit.Before
import org.junit.Rule
@@ -37,22 +45,26 @@
@SmallTest
class StackScrollAlgorithmTest : SysuiTestCase() {
- @JvmField @Rule
- var expect: Expect = Expect.create()
+ @JvmField @Rule var expect: Expect = Expect.create()
private val largeScreenShadeInterpolator = mock<LargeScreenShadeInterpolator>()
private val hostView = FrameLayout(context)
private val stackScrollAlgorithm = StackScrollAlgorithm(context, hostView)
private val notificationRow = mock<ExpandableNotificationRow>()
+ private val notificationEntry = mock<NotificationEntry>()
private val dumpManager = mock<DumpManager>()
+ @OptIn(ExperimentalCoroutinesApi::class)
private val mStatusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>()
private val notificationShelf = mock<NotificationShelf>()
- private val emptyShadeView = EmptyShadeView(context, /* attrs= */ null).apply {
- layout(/* l= */ 0, /* t= */ 0, /* r= */ 100, /* b= */ 100)
- }
- private val footerView = FooterView(context, /*attrs=*/null)
- private val ambientState = AmbientState(
+ private val emptyShadeView =
+ EmptyShadeView(context, /* attrs= */ null).apply {
+ layout(/* l= */ 0, /* t= */ 0, /* r= */ 100, /* b= */ 100)
+ }
+ private val footerView = FooterView(context, /*attrs=*/ null)
+ @OptIn(ExperimentalCoroutinesApi::class)
+ private val ambientState =
+ AmbientState(
context,
dumpManager,
/* sectionProvider */ { _, _ -> false },
@@ -62,13 +74,14 @@
)
private val testableResources = mContext.getOrCreateTestableResources()
+ private val featureFlags = mock<FeatureFlagsClassic>()
private val maxPanelHeight =
mContext.resources.displayMetrics.heightPixels -
- px(R.dimen.notification_panel_margin_top) -
- px(R.dimen.notification_panel_margin_bottom)
+ px(R.dimen.notification_panel_margin_top) -
+ px(R.dimen.notification_panel_margin_bottom)
private fun px(@DimenRes id: Int): Float =
- testableResources.resources.getDimensionPixelSize(id).toFloat()
+ testableResources.resources.getDimensionPixelSize(id).toFloat()
private val bigGap = px(R.dimen.notification_section_divider_height)
private val smallGap = px(R.dimen.notification_section_divider_height_lockscreen)
@@ -76,9 +89,12 @@
@Before
fun setUp() {
Assume.assumeFalse(isTv())
-
+ mDependency.injectTestDependency(FeatureFlags::class.java, featureFlags)
whenever(notificationShelf.viewState).thenReturn(ExpandableViewState())
whenever(notificationRow.viewState).thenReturn(ExpandableViewState())
+ whenever(notificationRow.entry).thenReturn(notificationEntry)
+ whenever(notificationRow.roundableState)
+ .thenReturn(RoundableState(notificationRow, notificationRow, 0f))
ambientState.isSmallScreen = true
hostView.addView(notificationRow)
@@ -92,7 +108,7 @@
fun resetViewStates_defaultHun_yTranslationIsInset() {
whenever(notificationRow.isPinned).thenReturn(true)
whenever(notificationRow.isHeadsUp).thenReturn(true)
- resetViewStates_hunYTranslationIsInset()
+ resetViewStates_hunYTranslationIs(stackScrollAlgorithm.mHeadsUpInset)
}
@Test
@@ -103,18 +119,87 @@
}
@Test
+ @DisableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
fun resetViewStates_hunAnimatingAway_yTranslationIsInset() {
whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
- resetViewStates_hunYTranslationIsInset()
+ resetViewStates_hunYTranslationIs(stackScrollAlgorithm.mHeadsUpInset)
}
@Test
+ @DisableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
fun resetViewStates_hunAnimatingAway_StackMarginChangesHunYTranslation() {
whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
resetViewStates_stackMargin_changesHunYTranslation()
}
@Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
+ fun resetViewStates_defaultHun_newHeadsUpAnim_yTranslationIsInset() {
+ whenever(notificationRow.isPinned).thenReturn(true)
+ whenever(notificationRow.isHeadsUp).thenReturn(true)
+ resetViewStates_hunYTranslationIs(stackScrollAlgorithm.mHeadsUpInset)
+ }
+
+ @Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
+ fun resetViewStates_defaultHunWithStackMargin_newHeadsUpAnim_changesHunYTranslation() {
+ whenever(notificationRow.isPinned).thenReturn(true)
+ whenever(notificationRow.isHeadsUp).thenReturn(true)
+ resetViewStates_stackMargin_changesHunYTranslation()
+ }
+
+ @Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
+ fun resetViewStates_defaultHun_showingQS_newHeadsUpAnim_hunTranslatedToMax() {
+ // Given: the shade is open and scrolled to the bottom to show the QuickSettings
+ val maxHunTranslation = 2000f
+ ambientState.maxHeadsUpTranslation = maxHunTranslation
+ ambientState.setLayoutMinHeight(2500) // Mock the height of shade
+ ambientState.stackY = 2500f // Scroll over the max translation
+ stackScrollAlgorithm.setIsExpanded(true) // Mark the shade open
+ whenever(notificationRow.mustStayOnScreen()).thenReturn(true)
+ whenever(notificationRow.isHeadsUp).thenReturn(true)
+ whenever(notificationRow.isAboveShelf).thenReturn(true)
+
+ resetViewStates_hunYTranslationIs(maxHunTranslation)
+ }
+
+ @Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
+ fun resetViewStates_hunAnimatingAway_showingQS_newHeadsUpAnim_hunTranslatedToBottomOfScreen() {
+ // Given: the shade is open and scrolled to the bottom to show the QuickSettings
+ val bottomOfScreen = 2600f
+ val maxHunTranslation = 2000f
+ ambientState.maxHeadsUpTranslation = maxHunTranslation
+ ambientState.setLayoutMinHeight(2500) // Mock the height of shade
+ ambientState.stackY = 2500f // Scroll over the max translation
+ stackScrollAlgorithm.setIsExpanded(true) // Mark the shade open
+ stackScrollAlgorithm.setHeadsUpAppearHeightBottom(bottomOfScreen.toInt())
+ whenever(notificationRow.mustStayOnScreen()).thenReturn(true)
+ whenever(notificationRow.isHeadsUp).thenReturn(true)
+ whenever(notificationRow.isAboveShelf).thenReturn(true)
+ whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
+
+ resetViewStates_hunYTranslationIs(
+ expected = bottomOfScreen + stackScrollAlgorithm.mHeadsUpAppearStartAboveScreen
+ )
+ }
+
+ @Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
+ fun resetViewStates_hunAnimatingAway_newHeadsUpAnim_hunTranslatedToTopOfScreen() {
+ val topMargin = 100f
+ ambientState.maxHeadsUpTranslation = 2000f
+ ambientState.stackTopMargin = topMargin.toInt()
+ whenever(notificationRow.intrinsicHeight).thenReturn(100)
+ whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
+
+ resetViewStates_hunYTranslationIs(
+ expected = -topMargin - stackScrollAlgorithm.mHeadsUpAppearStartAboveScreen
+ )
+ }
+
+ @Test
fun resetViewStates_hunAnimatingAway_bottomNotClipped() {
whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
@@ -136,6 +221,7 @@
}
@Test
+ @DisableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
fun resetViewStates_hunsOverlappingAndBottomHunAnimatingAway_bottomHunClipped() {
val topHun = mockExpandableNotificationRow()
val bottomHun = mockExpandableNotificationRow()
@@ -156,7 +242,7 @@
stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
val marginBottom =
- context.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom)
+ context.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom)
val fullHeight = ambientState.layoutMaxHeight + marginBottom - ambientState.stackY
val centeredY = ambientState.stackY + fullHeight / 2f - emptyShadeView.height / 2f
assertThat(emptyShadeView.viewState.yTranslation).isEqualTo(centeredY)
@@ -174,33 +260,37 @@
assertThat(notificationRow.viewState.alpha).isEqualTo(1f)
}
+ @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun resetViewStates_expansionChanging_notificationBecomesTransparent() {
whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(false)
resetViewStates_expansionChanging_notificationAlphaUpdated(
- expansionFraction = 0.25f,
- expectedAlpha = 0.0f
+ expansionFraction = 0.25f,
+ expectedAlpha = 0.0f
)
}
+ @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun resetViewStates_expansionChangingWhileBouncerInTransit_viewBecomesTransparent() {
whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(true)
resetViewStates_expansionChanging_notificationAlphaUpdated(
- expansionFraction = 0.85f,
- expectedAlpha = 0.0f
+ expansionFraction = 0.85f,
+ expectedAlpha = 0.0f
)
}
+ @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun resetViewStates_expansionChanging_notificationAlphaUpdated() {
whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(false)
resetViewStates_expansionChanging_notificationAlphaUpdated(
- expansionFraction = 0.6f,
- expectedAlpha = getContentAlpha(0.6f)
+ expansionFraction = 0.6f,
+ expectedAlpha = getContentAlpha(0.6f)
)
}
+ @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun resetViewStates_largeScreen_expansionChanging_alphaUpdated_largeScreenValue() {
val expansionFraction = 0.6f
@@ -216,13 +306,14 @@
)
}
+ @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun expansionChanging_largeScreen_bouncerInTransit_alphaUpdated_bouncerValues() {
ambientState.isSmallScreen = false
whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(true)
resetViewStates_expansionChanging_notificationAlphaUpdated(
- expansionFraction = 0.95f,
- expectedAlpha = aboutToShowBouncerProgress(0.95f),
+ expansionFraction = 0.95f,
+ expectedAlpha = aboutToShowBouncerProgress(0.95f),
)
}
@@ -235,10 +326,8 @@
stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
- verify(notificationShelf).updateState(
- /* algorithmState= */any(),
- /* ambientState= */eq(ambientState)
- )
+ verify(notificationShelf)
+ .updateState(/* algorithmState= */ any(), /* ambientState= */ eq(ambientState))
}
@Test
@@ -397,22 +486,31 @@
@Test
fun getGapForLocation_onLockscreen_returnsSmallGap() {
- val gap = stackScrollAlgorithm.getGapForLocation(
- /* fractionToShade= */ 0f, /* onKeyguard= */ true)
+ val gap =
+ stackScrollAlgorithm.getGapForLocation(
+ /* fractionToShade= */ 0f,
+ /* onKeyguard= */ true
+ )
assertThat(gap).isEqualTo(smallGap)
}
@Test
fun getGapForLocation_goingToShade_interpolatesGap() {
- val gap = stackScrollAlgorithm.getGapForLocation(
- /* fractionToShade= */ 0.5f, /* onKeyguard= */ true)
+ val gap =
+ stackScrollAlgorithm.getGapForLocation(
+ /* fractionToShade= */ 0.5f,
+ /* onKeyguard= */ true
+ )
assertThat(gap).isEqualTo(smallGap * 0.5f + bigGap * 0.5f)
}
@Test
fun getGapForLocation_notOnLockscreen_returnsBigGap() {
- val gap = stackScrollAlgorithm.getGapForLocation(
- /* fractionToShade= */ 0f, /* onKeyguard= */ false)
+ val gap =
+ stackScrollAlgorithm.getGapForLocation(
+ /* fractionToShade= */ 0f,
+ /* onKeyguard= */ false
+ )
assertThat(gap).isEqualTo(bigGap)
}
@@ -469,12 +567,14 @@
val expandableViewState = ExpandableViewState()
expandableViewState.headsUpIsVisible = false
- stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
- /* isShadeExpanded= */ true,
- /* mustStayOnScreen= */ true,
- /* isViewEndVisible= */ true,
- /* viewEnd= */ 0f,
- /* maxHunY= */ 10f)
+ stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(
+ expandableViewState,
+ /* isShadeExpanded= */ true,
+ /* mustStayOnScreen= */ true,
+ /* isViewEndVisible= */ true,
+ /* viewEnd= */ 0f,
+ /* maxHunY= */ 10f
+ )
assertTrue(expandableViewState.headsUpIsVisible)
}
@@ -484,12 +584,14 @@
val expandableViewState = ExpandableViewState()
expandableViewState.headsUpIsVisible = true
- stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
- /* isShadeExpanded= */ true,
- /* mustStayOnScreen= */ true,
- /* isViewEndVisible= */ true,
- /* viewEnd= */ 10f,
- /* maxHunY= */ 0f)
+ stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(
+ expandableViewState,
+ /* isShadeExpanded= */ true,
+ /* mustStayOnScreen= */ true,
+ /* isViewEndVisible= */ true,
+ /* viewEnd= */ 10f,
+ /* maxHunY= */ 0f
+ )
assertFalse(expandableViewState.headsUpIsVisible)
}
@@ -499,12 +601,14 @@
val expandableViewState = ExpandableViewState()
expandableViewState.headsUpIsVisible = true
- stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
- /* isShadeExpanded= */ false,
- /* mustStayOnScreen= */ true,
- /* isViewEndVisible= */ true,
- /* viewEnd= */ 10f,
- /* maxHunY= */ 1f)
+ stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(
+ expandableViewState,
+ /* isShadeExpanded= */ false,
+ /* mustStayOnScreen= */ true,
+ /* isViewEndVisible= */ true,
+ /* viewEnd= */ 10f,
+ /* maxHunY= */ 1f
+ )
assertTrue(expandableViewState.headsUpIsVisible)
}
@@ -514,12 +618,14 @@
val expandableViewState = ExpandableViewState()
expandableViewState.headsUpIsVisible = true
- stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
- /* isShadeExpanded= */ true,
- /* mustStayOnScreen= */ false,
- /* isViewEndVisible= */ true,
- /* viewEnd= */ 10f,
- /* maxHunY= */ 1f)
+ stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(
+ expandableViewState,
+ /* isShadeExpanded= */ true,
+ /* mustStayOnScreen= */ false,
+ /* isViewEndVisible= */ true,
+ /* viewEnd= */ 10f,
+ /* maxHunY= */ 1f
+ )
assertTrue(expandableViewState.headsUpIsVisible)
}
@@ -529,12 +635,14 @@
val expandableViewState = ExpandableViewState()
expandableViewState.headsUpIsVisible = true
- stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
- /* isShadeExpanded= */ true,
- /* mustStayOnScreen= */ true,
- /* isViewEndVisible= */ false,
- /* viewEnd= */ 10f,
- /* maxHunY= */ 1f)
+ stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(
+ expandableViewState,
+ /* isShadeExpanded= */ true,
+ /* mustStayOnScreen= */ true,
+ /* isViewEndVisible= */ false,
+ /* viewEnd= */ 10f,
+ /* maxHunY= */ 1f
+ )
assertTrue(expandableViewState.headsUpIsVisible)
}
@@ -544,9 +652,12 @@
val expandableViewState = ExpandableViewState()
expandableViewState.yTranslation = 50f
- stackScrollAlgorithm.clampHunToTop(/* quickQsOffsetHeight= */ 10f,
- /* stackTranslation= */ 0f,
- /* collapsedHeight= */ 1f, expandableViewState)
+ stackScrollAlgorithm.clampHunToTop(
+ /* quickQsOffsetHeight= */ 10f,
+ /* stackTranslation= */ 0f,
+ /* collapsedHeight= */ 1f,
+ expandableViewState
+ )
// qqs (10 + 0) < viewY (50)
assertEquals(50f, expandableViewState.yTranslation)
@@ -557,9 +668,12 @@
val expandableViewState = ExpandableViewState()
expandableViewState.yTranslation = -10f
- stackScrollAlgorithm.clampHunToTop(/* quickQsOffsetHeight= */ 10f,
- /* stackTranslation= */ 0f,
- /* collapsedHeight= */ 1f, expandableViewState)
+ stackScrollAlgorithm.clampHunToTop(
+ /* quickQsOffsetHeight= */ 10f,
+ /* stackTranslation= */ 0f,
+ /* collapsedHeight= */ 1f,
+ expandableViewState
+ )
// qqs (10 + 0) > viewY (-10)
assertEquals(10f, expandableViewState.yTranslation)
@@ -571,9 +685,12 @@
expandableViewState.height = 20
expandableViewState.yTranslation = -100f
- stackScrollAlgorithm.clampHunToTop(/* quickQsOffsetHeight= */ 10f,
- /* stackTranslation= */ 0f,
- /* collapsedHeight= */ 10f, expandableViewState)
+ stackScrollAlgorithm.clampHunToTop(
+ /* quickQsOffsetHeight= */ 10f,
+ /* stackTranslation= */ 0f,
+ /* collapsedHeight= */ 10f,
+ expandableViewState
+ )
// newTranslation = max(10, -100) = 10
// distToRealY = 10 - (-100f) = 110
@@ -587,9 +704,12 @@
expandableViewState.height = 20
expandableViewState.yTranslation = 5f
- stackScrollAlgorithm.clampHunToTop(/* quickQsOffsetHeight= */ 10f,
- /* stackTranslation= */ 0f,
- /* collapsedHeight= */ 10f, expandableViewState)
+ stackScrollAlgorithm.clampHunToTop(
+ /* quickQsOffsetHeight= */ 10f,
+ /* stackTranslation= */ 0f,
+ /* collapsedHeight= */ 10f,
+ expandableViewState
+ )
// newTranslation = max(10, 5) = 10
// distToRealY = 10 - 5 = 5
@@ -599,41 +719,49 @@
@Test
fun computeCornerRoundnessForPinnedHun_stackBelowScreen_round() {
- val currentRoundness = stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
+ val currentRoundness =
+ stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
/* hostViewHeight= */ 100f,
/* stackY= */ 110f,
/* viewMaxHeight= */ 20f,
- /* originalCornerRoundness= */ 0f)
+ /* originalCornerRoundness= */ 0f
+ )
assertEquals(1f, currentRoundness)
}
@Test
fun computeCornerRoundnessForPinnedHun_stackAboveScreenBelowPinPoint_halfRound() {
- val currentRoundness = stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
+ val currentRoundness =
+ stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
/* hostViewHeight= */ 100f,
/* stackY= */ 90f,
/* viewMaxHeight= */ 20f,
- /* originalCornerRoundness= */ 0f)
+ /* originalCornerRoundness= */ 0f
+ )
assertEquals(0.5f, currentRoundness)
}
@Test
fun computeCornerRoundnessForPinnedHun_stackAbovePinPoint_notRound() {
- val currentRoundness = stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
+ val currentRoundness =
+ stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
/* hostViewHeight= */ 100f,
/* stackY= */ 0f,
/* viewMaxHeight= */ 20f,
- /* originalCornerRoundness= */ 0f)
+ /* originalCornerRoundness= */ 0f
+ )
assertEquals(0f, currentRoundness)
}
@Test
fun computeCornerRoundnessForPinnedHun_originallyRoundAndStackAbovePinPoint_round() {
- val currentRoundness = stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
+ val currentRoundness =
+ stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
/* hostViewHeight= */ 100f,
/* stackY= */ 0f,
/* viewMaxHeight= */ 20f,
- /* originalCornerRoundness= */ 1f)
+ /* originalCornerRoundness= */ 1f
+ )
assertEquals(1f, currentRoundness)
}
@@ -642,23 +770,20 @@
// Given: shade is opened, yTranslation of HUN is 0,
// the height of HUN equals to the height of QQS Panel,
// and HUN fully overlaps with QQS Panel
- ambientState.stackTranslation = px(R.dimen.qqs_layout_margin_top) +
- px(R.dimen.qqs_layout_padding_bottom)
- val childHunView = createHunViewMock(
- isShadeOpen = true,
- fullyVisible = false,
- headerVisibleAmount = 1f
- )
+ ambientState.stackTranslation =
+ px(R.dimen.qqs_layout_margin_top) + px(R.dimen.qqs_layout_padding_bottom)
+ val childHunView =
+ createHunViewMock(isShadeOpen = true, fullyVisible = false, headerVisibleAmount = 1f)
val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
algorithmState.visibleChildren.add(childHunView)
// When: updateChildZValue() is called for the top HUN
stackScrollAlgorithm.updateChildZValue(
- /* i= */ 0,
- /* childrenOnTop= */ 0.0f,
- /* StackScrollAlgorithmState= */ algorithmState,
- /* ambientState= */ ambientState,
- /* shouldElevateHun= */ true
+ /* i= */ 0,
+ /* childrenOnTop= */ 0.0f,
+ /* StackScrollAlgorithmState= */ algorithmState,
+ /* ambientState= */ ambientState,
+ /* shouldElevateHun= */ true
)
// Then: full shadow would be applied
@@ -670,13 +795,10 @@
// Given: shade is opened, yTranslation of HUN is greater than 0,
// the height of HUN is equal to the height of QQS Panel,
// and HUN partially overlaps with QQS Panel
- ambientState.stackTranslation = px(R.dimen.qqs_layout_margin_top) +
- px(R.dimen.qqs_layout_padding_bottom)
- val childHunView = createHunViewMock(
- isShadeOpen = true,
- fullyVisible = false,
- headerVisibleAmount = 1f
- )
+ ambientState.stackTranslation =
+ px(R.dimen.qqs_layout_margin_top) + px(R.dimen.qqs_layout_padding_bottom)
+ val childHunView =
+ createHunViewMock(isShadeOpen = true, fullyVisible = false, headerVisibleAmount = 1f)
// Use half of the HUN's height as overlap
childHunView.viewState.yTranslation = (childHunView.viewState.height + 1 shr 1).toFloat()
val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
@@ -684,17 +806,17 @@
// When: updateChildZValue() is called for the top HUN
stackScrollAlgorithm.updateChildZValue(
- /* i= */ 0,
- /* childrenOnTop= */ 0.0f,
- /* StackScrollAlgorithmState= */ algorithmState,
- /* ambientState= */ ambientState,
- /* shouldElevateHun= */ true
+ /* i= */ 0,
+ /* childrenOnTop= */ 0.0f,
+ /* StackScrollAlgorithmState= */ algorithmState,
+ /* ambientState= */ ambientState,
+ /* shouldElevateHun= */ true
)
// Then: HUN should have shadow, but not as full size
assertThat(childHunView.viewState.zTranslation).isGreaterThan(0.0f)
assertThat(childHunView.viewState.zTranslation)
- .isLessThan(px(R.dimen.heads_up_pinned_elevation))
+ .isLessThan(px(R.dimen.heads_up_pinned_elevation))
}
@Test
@@ -702,28 +824,25 @@
// Given: shade is opened, yTranslation of HUN is equal to QQS Panel's height,
// the height of HUN is equal to the height of QQS Panel,
// and HUN doesn't overlap with QQS Panel
- ambientState.stackTranslation = px(R.dimen.qqs_layout_margin_top) +
- px(R.dimen.qqs_layout_padding_bottom)
+ ambientState.stackTranslation =
+ px(R.dimen.qqs_layout_margin_top) + px(R.dimen.qqs_layout_padding_bottom)
// Mock the height of shade
ambientState.setLayoutMinHeight(1000)
- val childHunView = createHunViewMock(
- isShadeOpen = true,
- fullyVisible = true,
- headerVisibleAmount = 1f
- )
+ val childHunView =
+ createHunViewMock(isShadeOpen = true, fullyVisible = true, headerVisibleAmount = 1f)
// HUN doesn't overlap with QQS Panel
- childHunView.viewState.yTranslation = ambientState.topPadding +
- ambientState.stackTranslation
+ childHunView.viewState.yTranslation =
+ ambientState.topPadding + ambientState.stackTranslation
val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
algorithmState.visibleChildren.add(childHunView)
// When: updateChildZValue() is called for the top HUN
stackScrollAlgorithm.updateChildZValue(
- /* i= */ 0,
- /* childrenOnTop= */ 0.0f,
- /* StackScrollAlgorithmState= */ algorithmState,
- /* ambientState= */ ambientState,
- /* shouldElevateHun= */ true
+ /* i= */ 0,
+ /* childrenOnTop= */ 0.0f,
+ /* StackScrollAlgorithmState= */ algorithmState,
+ /* ambientState= */ ambientState,
+ /* shouldElevateHun= */ true
)
// Then: HUN should not have shadow
@@ -737,11 +856,8 @@
ambientState.stackTranslation = -ambientState.topPadding
// Mock the height of shade
ambientState.setLayoutMinHeight(1000)
- val childHunView = createHunViewMock(
- isShadeOpen = false,
- fullyVisible = false,
- headerVisibleAmount = 0f
- )
+ val childHunView =
+ createHunViewMock(isShadeOpen = false, fullyVisible = false, headerVisibleAmount = 0f)
childHunView.viewState.yTranslation = 0f
// Shade is closed, thus childHunView's headerVisibleAmount is 0
childHunView.headerVisibleAmount = 0f
@@ -750,11 +866,11 @@
// When: updateChildZValue() is called for the top HUN
stackScrollAlgorithm.updateChildZValue(
- /* i= */ 0,
- /* childrenOnTop= */ 0.0f,
- /* StackScrollAlgorithmState= */ algorithmState,
- /* ambientState= */ ambientState,
- /* shouldElevateHun= */ true
+ /* i= */ 0,
+ /* childrenOnTop= */ 0.0f,
+ /* StackScrollAlgorithmState= */ algorithmState,
+ /* ambientState= */ ambientState,
+ /* shouldElevateHun= */ true
)
// Then: HUN should have full shadow
@@ -768,11 +884,8 @@
ambientState.stackTranslation = -ambientState.topPadding
// Mock the height of shade
ambientState.setLayoutMinHeight(1000)
- val childHunView = createHunViewMock(
- isShadeOpen = false,
- fullyVisible = false,
- headerVisibleAmount = 0.5f
- )
+ val childHunView =
+ createHunViewMock(isShadeOpen = false, fullyVisible = false, headerVisibleAmount = 0.5f)
childHunView.viewState.yTranslation = 0f
// Shade is being opened, thus childHunView's headerVisibleAmount is between 0 and 1
// use 0.5 as headerVisibleAmount here
@@ -782,17 +895,17 @@
// When: updateChildZValue() is called for the top HUN
stackScrollAlgorithm.updateChildZValue(
- /* i= */ 0,
- /* childrenOnTop= */ 0.0f,
- /* StackScrollAlgorithmState= */ algorithmState,
- /* ambientState= */ ambientState,
- /* shouldElevateHun= */ true
+ /* i= */ 0,
+ /* childrenOnTop= */ 0.0f,
+ /* StackScrollAlgorithmState= */ algorithmState,
+ /* ambientState= */ ambientState,
+ /* shouldElevateHun= */ true
)
// Then: HUN should have shadow, but not as full size
assertThat(childHunView.viewState.zTranslation).isGreaterThan(0.0f)
assertThat(childHunView.viewState.zTranslation)
- .isLessThan(px(R.dimen.heads_up_pinned_elevation))
+ .isLessThan(px(R.dimen.heads_up_pinned_elevation))
}
@Test
@@ -862,134 +975,174 @@
// stackScrollAlgorithm.resetViewStates is called.
ambientState.dozeAmount = 0.5f
setExpansionFractionWithoutShelfDuringAodToLockScreen(
- ambientState,
- algorithmState,
- fraction = 0.5f
+ ambientState,
+ algorithmState,
+ fraction = 0.5f
)
stackScrollAlgorithm.resetViewStates(ambientState, 0)
// Then: pulsingNotificationView should show at full height
assertEquals(
- stackScrollAlgorithm.getMaxAllowedChildHeight(pulsingNotificationView),
- pulsingNotificationView.viewState.height
+ stackScrollAlgorithm.getMaxAllowedChildHeight(pulsingNotificationView),
+ pulsingNotificationView.viewState.height
)
// After: reset dozeAmount and expansionFraction
ambientState.dozeAmount = 0f
setExpansionFractionWithoutShelfDuringAodToLockScreen(
- ambientState,
- algorithmState,
- fraction = 1f
+ ambientState,
+ algorithmState,
+ fraction = 1f
)
}
// region shouldPinHunToBottomOfExpandedQs
@Test
fun shouldHunBeVisibleWhenScrolled_mustStayOnScreenFalse_false() {
- assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
- /* mustStayOnScreen= */false,
- /* headsUpIsVisible= */false,
- /* showingPulsing= */false,
- /* isOnKeyguard=*/false,
- /*headsUpOnKeyguard=*/false
- )).isFalse()
+ assertThat(
+ stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
+ /* mustStayOnScreen= */ false,
+ /* headsUpIsVisible= */ false,
+ /* showingPulsing= */ false,
+ /* isOnKeyguard=*/ false,
+ /*headsUpOnKeyguard=*/ false
+ )
+ )
+ .isFalse()
}
@Test
fun shouldPinHunToBottomOfExpandedQs_headsUpIsVisible_false() {
- assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
- /* mustStayOnScreen= */true,
- /* headsUpIsVisible= */true,
- /* showingPulsing= */false,
- /* isOnKeyguard=*/false,
- /*headsUpOnKeyguard=*/false
- )).isFalse()
+ assertThat(
+ stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
+ /* mustStayOnScreen= */ true,
+ /* headsUpIsVisible= */ true,
+ /* showingPulsing= */ false,
+ /* isOnKeyguard=*/ false,
+ /*headsUpOnKeyguard=*/ false
+ )
+ )
+ .isFalse()
}
@Test
fun shouldHunBeVisibleWhenScrolled_showingPulsing_false() {
- assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
- /* mustStayOnScreen= */true,
- /* headsUpIsVisible= */false,
- /* showingPulsing= */true,
- /* isOnKeyguard=*/false,
- /* headsUpOnKeyguard= */false
- )).isFalse()
+ assertThat(
+ stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
+ /* mustStayOnScreen= */ true,
+ /* headsUpIsVisible= */ false,
+ /* showingPulsing= */ true,
+ /* isOnKeyguard=*/ false,
+ /* headsUpOnKeyguard= */ false
+ )
+ )
+ .isFalse()
}
@Test
fun shouldHunBeVisibleWhenScrolled_isOnKeyguard_false() {
- assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
- /* mustStayOnScreen= */true,
- /* headsUpIsVisible= */false,
- /* showingPulsing= */false,
- /* isOnKeyguard=*/true,
- /* headsUpOnKeyguard= */false
- )).isFalse()
+ assertThat(
+ stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
+ /* mustStayOnScreen= */ true,
+ /* headsUpIsVisible= */ false,
+ /* showingPulsing= */ false,
+ /* isOnKeyguard=*/ true,
+ /* headsUpOnKeyguard= */ false
+ )
+ )
+ .isFalse()
}
@Test
fun shouldHunBeVisibleWhenScrolled_isNotOnKeyguard_true() {
- assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
- /* mustStayOnScreen= */true,
- /* headsUpIsVisible= */false,
- /* showingPulsing= */false,
- /* isOnKeyguard=*/false,
- /* headsUpOnKeyguard= */false
- )).isTrue()
+ assertThat(
+ stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
+ /* mustStayOnScreen= */ true,
+ /* headsUpIsVisible= */ false,
+ /* showingPulsing= */ false,
+ /* isOnKeyguard=*/ false,
+ /* headsUpOnKeyguard= */ false
+ )
+ )
+ .isTrue()
}
@Test
fun shouldHunBeVisibleWhenScrolled_headsUpOnKeyguard_true() {
- assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
- /* mustStayOnScreen= */true,
- /* headsUpIsVisible= */false,
- /* showingPulsing= */false,
- /* isOnKeyguard=*/true,
- /* headsUpOnKeyguard= */true
- )).isTrue()
+ assertThat(
+ stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
+ /* mustStayOnScreen= */ true,
+ /* headsUpIsVisible= */ false,
+ /* showingPulsing= */ false,
+ /* isOnKeyguard=*/ true,
+ /* headsUpOnKeyguard= */ true
+ )
+ )
+ .isTrue()
+ }
+
+ @Test
+ fun shouldHunAppearFromBottom_hunAtMaxHunTranslation() {
+ ambientState.maxHeadsUpTranslation = 400f
+ val viewState =
+ ExpandableViewState().apply {
+ height = 100
+ yTranslation = ambientState.maxHeadsUpTranslation - height // move it to the max
+ }
+
+ assertTrue(stackScrollAlgorithm.shouldHunAppearFromBottom(ambientState, viewState))
+ }
+
+ @Test
+ fun shouldHunAppearFromBottom_hunBelowMaxHunTranslation() {
+ ambientState.maxHeadsUpTranslation = 400f
+ val viewState =
+ ExpandableViewState().apply {
+ height = 100
+ yTranslation =
+ ambientState.maxHeadsUpTranslation - height - 1 // move it below the max
+ }
+
+ assertFalse(stackScrollAlgorithm.shouldHunAppearFromBottom(ambientState, viewState))
}
// endregion
private fun createHunViewMock(
- isShadeOpen: Boolean,
- fullyVisible: Boolean,
- headerVisibleAmount: Float
+ isShadeOpen: Boolean,
+ fullyVisible: Boolean,
+ headerVisibleAmount: Float
) =
- mock<ExpandableNotificationRow>().apply {
- val childViewStateMock = createHunChildViewState(isShadeOpen, fullyVisible)
- whenever(this.viewState).thenReturn(childViewStateMock)
+ mock<ExpandableNotificationRow>().apply {
+ val childViewStateMock = createHunChildViewState(isShadeOpen, fullyVisible)
+ whenever(this.viewState).thenReturn(childViewStateMock)
- whenever(this.mustStayOnScreen()).thenReturn(true)
- whenever(this.headerVisibleAmount).thenReturn(headerVisibleAmount)
- }
-
+ whenever(this.mustStayOnScreen()).thenReturn(true)
+ whenever(this.headerVisibleAmount).thenReturn(headerVisibleAmount)
+ }
private fun createHunChildViewState(isShadeOpen: Boolean, fullyVisible: Boolean) =
- ExpandableViewState().apply {
- // Mock the HUN's height with ambientState.topPadding +
- // ambientState.stackTranslation
- height = (ambientState.topPadding + ambientState.stackTranslation).toInt()
- if (isShadeOpen && fullyVisible) {
- yTranslation =
- ambientState.topPadding + ambientState.stackTranslation
- } else {
- yTranslation = 0f
- }
- headsUpIsVisible = fullyVisible
+ ExpandableViewState().apply {
+ // Mock the HUN's height with ambientState.topPadding +
+ // ambientState.stackTranslation
+ height = (ambientState.topPadding + ambientState.stackTranslation).toInt()
+ if (isShadeOpen && fullyVisible) {
+ yTranslation = ambientState.topPadding + ambientState.stackTranslation
+ } else {
+ yTranslation = 0f
}
+ headsUpIsVisible = fullyVisible
+ }
- private fun createPulsingViewMock(
- ) =
- mock<ExpandableNotificationRow>().apply {
- whenever(this.viewState).thenReturn(ExpandableViewState())
- whenever(this.showingPulsing()).thenReturn(true)
- }
+ private fun createPulsingViewMock() =
+ mock<ExpandableNotificationRow>().apply {
+ whenever(this.viewState).thenReturn(ExpandableViewState())
+ whenever(this.showingPulsing()).thenReturn(true)
+ }
private fun setExpansionFractionWithoutShelfDuringAodToLockScreen(
- ambientState: AmbientState,
- algorithmState: StackScrollAlgorithm.StackScrollAlgorithmState,
- fraction: Float
+ ambientState: AmbientState,
+ algorithmState: StackScrollAlgorithm.StackScrollAlgorithmState,
+ fraction: Float
) {
// showingShelf: false
algorithmState.firstViewInShelf = null
@@ -1002,11 +1155,10 @@
ambientState.stackHeight = ambientState.stackEndHeight * fraction
}
- private fun resetViewStates_hunYTranslationIsInset() {
+ private fun resetViewStates_hunYTranslationIs(expected: Float) {
stackScrollAlgorithm.resetViewStates(ambientState, 0)
- assertThat(notificationRow.viewState.yTranslation)
- .isEqualTo(stackScrollAlgorithm.mHeadsUpInset)
+ assertThat(notificationRow.viewState.yTranslation).isEqualTo(expected)
}
private fun resetViewStates_stackMargin_changesHunYTranslation() {
@@ -1025,13 +1177,13 @@
}
private fun resetViewStates_hunsOverlapping_bottomHunClipped(
- topHun: ExpandableNotificationRow,
- bottomHun: ExpandableNotificationRow
+ topHun: ExpandableNotificationRow,
+ bottomHun: ExpandableNotificationRow
) {
- val topHunHeight = mContext.resources.getDimensionPixelSize(
- R.dimen.notification_content_min_height)
- val bottomHunHeight = mContext.resources.getDimensionPixelSize(
- R.dimen.notification_max_heads_up_height)
+ val topHunHeight =
+ mContext.resources.getDimensionPixelSize(R.dimen.notification_content_min_height)
+ val bottomHunHeight =
+ mContext.resources.getDimensionPixelSize(R.dimen.notification_max_heads_up_height)
whenever(topHun.intrinsicHeight).thenReturn(topHunHeight)
whenever(bottomHun.intrinsicHeight).thenReturn(bottomHunHeight)
@@ -1054,8 +1206,8 @@
}
private fun resetViewStates_expansionChanging_notificationAlphaUpdated(
- expansionFraction: Float,
- expectedAlpha: Float,
+ expansionFraction: Float,
+ expectedAlpha: Float,
) {
ambientState.isExpansionChanging = true
ambientState.expansionFraction = expansionFraction
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
new file mode 100644
index 0000000..5a57035
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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 android.platform.test.annotations.EnableFlags
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR
+import com.android.systemui.util.mockito.argumentCaptor
+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.ArgumentCaptor
+import org.mockito.Mockito.any
+import org.mockito.Mockito.description
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.verify
+
+private const val VIEW_HEIGHT = 100
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class StackStateAnimatorTest : SysuiTestCase() {
+
+ private lateinit var stackStateAnimator: StackStateAnimator
+ private val stackScroller: NotificationStackScrollLayout = mock()
+ private val view: ExpandableView = mock()
+ private val viewState: ExpandableViewState =
+ ExpandableViewState().apply { height = VIEW_HEIGHT }
+ private val runnableCaptor: ArgumentCaptor<Runnable> = argumentCaptor()
+ @Before
+ fun setUp() {
+ whenever(stackScroller.context).thenReturn(context)
+ whenever(view.viewState).thenReturn(viewState)
+ stackStateAnimator = StackStateAnimator(stackScroller)
+ }
+
+ @Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
+ fun startAnimationForEvents_headsUpFromTop_startsHeadsUpAppearAnim() {
+ val topMargin = 50f
+ val expectedStartY = -topMargin - stackStateAnimator.mHeadsUpAppearStartAboveScreen
+ val event = AnimationEvent(view, AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR)
+ stackStateAnimator.setStackTopMargin(topMargin.toInt())
+
+ stackStateAnimator.startAnimationForEvents(arrayListOf(event), 0)
+
+ verify(view).setActualHeight(VIEW_HEIGHT, false)
+ verify(view, description("should animate from the top")).translationY = expectedStartY
+ verify(view)
+ .performAddAnimation(
+ /* delay= */ 0L,
+ /* duration= */ ANIMATION_DURATION_HEADS_UP_APPEAR.toLong(),
+ /* isHeadsUpAppear= */ true,
+ /* onEndRunnable= */ null
+ )
+ }
+
+ @Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
+ fun startAnimationForEvents_headsUpFromBottom_startsHeadsUpAppearAnim() {
+ val screenHeight = 2000f
+ val expectedStartY = screenHeight + stackStateAnimator.mHeadsUpAppearStartAboveScreen
+ val event =
+ AnimationEvent(view, AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR).apply {
+ headsUpFromBottom = true
+ }
+ stackStateAnimator.setHeadsUpAppearHeightBottom(screenHeight.toInt())
+
+ stackStateAnimator.startAnimationForEvents(arrayListOf(event), 0)
+
+ verify(view).setActualHeight(VIEW_HEIGHT, false)
+ verify(view, description("should animate from the bottom")).translationY = expectedStartY
+ verify(view)
+ .performAddAnimation(
+ /* delay= */ 0L,
+ /* duration= */ ANIMATION_DURATION_HEADS_UP_APPEAR.toLong(),
+ /* isHeadsUpAppear= */ true,
+ /* onEndRunnable= */ null
+ )
+ }
+
+ @Test
+ fun startAnimationForEvents_startsHeadsUpDisappearAnim() {
+ val event = AnimationEvent(view, AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR)
+ stackStateAnimator.startAnimationForEvents(arrayListOf(event), 0)
+
+ verify(view)
+ .performRemoveAnimation(
+ /* duration= */ eq(ANIMATION_DURATION_HEADS_UP_DISAPPEAR.toLong()),
+ /* delay= */ eq(0L),
+ /* translationDirection= */ eq(0f),
+ /* isHeadsUpAnimation= */ eq(true),
+ /* onStartedRunnable= */ any(),
+ /* onFinishedRunnable= */ runnableCaptor.capture(),
+ /* animationListener= */ any()
+ )
+
+ runnableCaptor.value.run() // execute the end runnable
+
+ verify(view, description("should be called at the end of the animation"))
+ .removeFromTransientContainer()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt
index da543d4..cd6bb5f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt
@@ -17,10 +17,10 @@
package com.android.systemui.statusbar.notification.stack
import android.testing.AndroidTestingRunner
-import android.util.Log
-import android.util.Log.TerribleFailureHandler
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.assertDoesNotLogWtf
+import com.android.systemui.log.assertLogsWtf
import kotlin.math.log2
import kotlin.math.sqrt
import org.junit.Assert
@@ -32,61 +32,36 @@
class ViewStateTest : SysuiTestCase() {
private val viewState = ViewState()
- private var wtfHandler: TerribleFailureHandler? = null
- private var wtfCount = 0
-
@Suppress("DIVISION_BY_ZERO")
@Test
fun testWtfs() {
- interceptWtfs()
-
// Setting valid values doesn't cause any wtfs.
- viewState.alpha = 0.1f
- viewState.xTranslation = 0f
- viewState.yTranslation = 10f
- viewState.zTranslation = 20f
- viewState.scaleX = 0.5f
- viewState.scaleY = 0.25f
-
- expectWtfs(0)
+ assertDoesNotLogWtf {
+ viewState.alpha = 0.1f
+ viewState.xTranslation = 0f
+ viewState.yTranslation = 10f
+ viewState.zTranslation = 20f
+ viewState.scaleX = 0.5f
+ viewState.scaleY = 0.25f
+ }
// Setting NaN values leads to wtfs being logged, and the value not being changed.
- viewState.alpha = 0.0f / 0.0f
- expectWtfs(1)
+ assertLogsWtf { viewState.alpha = 0.0f / 0.0f }
Assert.assertEquals(viewState.alpha, 0.1f)
- viewState.xTranslation = Float.NaN
- expectWtfs(2)
+ assertLogsWtf { viewState.xTranslation = Float.NaN }
Assert.assertEquals(viewState.xTranslation, 0f)
- viewState.yTranslation = log2(-10.0).toFloat()
- expectWtfs(3)
+ assertLogsWtf { viewState.yTranslation = log2(-10.0).toFloat() }
Assert.assertEquals(viewState.yTranslation, 10f)
- viewState.zTranslation = sqrt(-1.0).toFloat()
- expectWtfs(4)
+ assertLogsWtf { viewState.zTranslation = sqrt(-1.0).toFloat() }
Assert.assertEquals(viewState.zTranslation, 20f)
- viewState.scaleX = Float.POSITIVE_INFINITY + Float.NEGATIVE_INFINITY
- expectWtfs(5)
+ assertLogsWtf { viewState.scaleX = Float.POSITIVE_INFINITY + Float.NEGATIVE_INFINITY }
Assert.assertEquals(viewState.scaleX, 0.5f)
- viewState.scaleY = Float.POSITIVE_INFINITY * 0
- expectWtfs(6)
+ assertLogsWtf { viewState.scaleY = Float.POSITIVE_INFINITY * 0 }
Assert.assertEquals(viewState.scaleY, 0.25f)
}
-
- private fun interceptWtfs() {
- wtfCount = 0
- wtfHandler =
- Log.setWtfHandler { _: String?, e: Log.TerribleFailure, _: Boolean ->
- Log.e("ViewStateTest", "Observed WTF: $e")
- wtfCount++
- }
- }
-
- private fun expectWtfs(expectedWtfCount: Int) {
- Assert.assertNotNull(wtfHandler)
- Assert.assertEquals(expectedWtfCount, wtfCount)
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
index ac20683..4bfd7e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
@@ -23,8 +23,8 @@
import android.view.Surface.ROTATION_90
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.domain.interactor.ConfigurationInteractorImpl
import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.coroutines.collectValues
import com.android.systemui.power.data.repository.FakePowerRepository
import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -81,7 +81,7 @@
testScope.backgroundScope,
mock()
)
- private val configurationInteractor = ConfigurationInteractorImpl(configurationRepository)
+ private val configurationInteractor = ConfigurationInteractor(configurationRepository)
private lateinit var configuration: Configuration
private lateinit var underTest: HideNotificationsInteractor
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 21774aa..c17a8ef 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
@@ -28,7 +28,6 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.TestMocksModule
import com.android.systemui.collectLastValue
-import com.android.systemui.common.domain.CommonDomainLayerModule
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FakeFeatureFlagsClassicModule
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -69,7 +68,6 @@
[
SysUITestModule::class,
ActivatableNotificationViewModelModule::class,
- CommonDomainLayerModule::class,
FooterViewModelModule::class,
HeadlessSystemUserModeModule::class,
UnfoldTransitionModule.Bindings::class,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
index 62d8f7f..9f4e1dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
@@ -23,6 +23,7 @@
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
@@ -73,6 +74,7 @@
@Test
fun calculateWidthFor_fiveIcons_widthForFourIcons() {
+ mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME)
iconContainer.setActualPaddingStart(10f)
iconContainer.setActualPaddingEnd(10f)
iconContainer.setIconSize(10)
@@ -151,7 +153,7 @@
iconContainer.addView(iconFive)
assertEquals(5, iconContainer.childCount)
- val width = iconContainer.calculateWidthFor(/* numIcons= */ 5f)
+ val width = iconContainer.calculateWidthFor(/* numIcons= */ 4f)
iconContainer.setActualLayoutWidth(width.toInt())
iconContainer.calculateIconXTranslations()
@@ -212,6 +214,7 @@
@Test
fun shouldForceOverflow_appearingAboveSpeedBump_true() {
+ mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME)
val forceOverflow =
iconContainer.shouldForceOverflow(
/* i= */ 1,
@@ -228,7 +231,7 @@
iconContainer.shouldForceOverflow(
/* i= */ 10,
/* speedBumpIndex= */ 11,
- /* iconAppearAmount= */ 0f,
+ /* iconAppearAmount= */ 0.1f,
/* maxVisibleIcons= */ 5
)
assertTrue(forceOverflow)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 1cc611c..54d3607 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -36,6 +36,7 @@
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
+import android.view.LayoutInflater;
import android.view.View;
import androidx.test.filters.SmallTest;
@@ -43,7 +44,6 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiBaseFragmentTest;
import com.android.systemui.animation.AnimatorTestRule;
-import com.android.systemui.common.ui.ConfigurationState;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.log.LogBuffer;
@@ -57,9 +57,7 @@
import com.android.systemui.statusbar.OperatorNameViewController;
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker;
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarNotificationIconViewStore;
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
@@ -70,7 +68,6 @@
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeCollapsedStatusBarViewBinder;
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeCollapsedStatusBarViewModel;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.ui.SystemBarUtilsState;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.statusbar.window.StatusBarWindowStateListener;
import com.android.systemui.util.CarrierConfigTracker;
@@ -95,7 +92,6 @@
private NotificationIconAreaController mMockNotificationAreaController;
private ShadeExpansionStateManager mShadeExpansionStateManager;
- private View mNotificationAreaInner;
private OngoingCallController mOngoingCallController;
private SystemStatusAnimationScheduler mAnimationScheduler;
private StatusBarLocationPublisher mLocationPublisher;
@@ -274,15 +270,15 @@
fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false);
- assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
+ assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
- assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility());
+ assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility());
fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false);
- assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
+ assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
}
@Test
@@ -314,7 +310,7 @@
// THEN all views are hidden
assertEquals(View.INVISIBLE, getClockView().getVisibility());
- assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
+ assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
}
@@ -330,7 +326,7 @@
// THEN all views are shown
assertEquals(View.VISIBLE, getClockView().getVisibility());
- assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility());
+ assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility());
assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
}
@@ -347,7 +343,7 @@
// THEN all views are hidden
assertEquals(View.INVISIBLE, getClockView().getVisibility());
- assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
+ assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
// WHEN the shade is updated to no longer be open
@@ -358,7 +354,7 @@
// THEN all views are shown
assertEquals(View.VISIBLE, getClockView().getVisibility());
- assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility());
+ assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility());
assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
}
@@ -372,7 +368,7 @@
// THEN all views are shown
assertEquals(View.VISIBLE, getClockView().getVisibility());
- assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility());
+ assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility());
assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
}
@@ -386,7 +382,7 @@
// THEN all views are hidden
assertEquals(View.GONE, getClockView().getVisibility());
- assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
+ assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
}
@@ -400,7 +396,7 @@
// THEN all views are hidden
assertEquals(View.GONE, getClockView().getVisibility());
- assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
+ assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
// WHEN the transition has finished
@@ -409,7 +405,7 @@
// THEN all views are shown
assertEquals(View.VISIBLE, getClockView().getVisibility());
- assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility());
+ assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility());
assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
}
@@ -442,7 +438,7 @@
assertEquals(View.VISIBLE,
mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
- assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
+ assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
}
@Test
@@ -507,8 +503,8 @@
fragment.disable(DEFAULT_DISPLAY, 0, 0, true);
// Notification area is hidden without delay
- assertEquals(0f, mNotificationAreaInner.getAlpha(), 0.01);
- assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
+ assertEquals(0f, getNotificationAreaView().getAlpha(), 0.01);
+ assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
}
@Test
@@ -702,7 +698,7 @@
mKeyguardStateController,
mShadeViewController,
mStatusBarStateController,
- mock(StatusBarIconViewBindingFailureTracker.class),
+ mock(NotificationIconContainerStatusBarViewBinder.class),
mCommandQueue,
mCarrierConfigTracker,
new CollapsedStatusBarFragmentLogger(
@@ -715,10 +711,6 @@
mDumpManager,
mStatusBarWindowStateController,
mKeyguardUpdateMonitor,
- mock(NotificationIconContainerStatusBarViewModel.class),
- mock(ConfigurationState.class),
- mock(SystemBarUtilsState.class),
- mock(StatusBarNotificationIconViewStore.class),
mock(DemoModeController.class));
}
@@ -731,11 +723,10 @@
private void setUpNotificationIconAreaController() {
mMockNotificationAreaController = mock(NotificationIconAreaController.class);
-
- mNotificationAreaInner = new View(mContext);
-
- when(mMockNotificationAreaController.getNotificationInnerAreaView()).thenReturn(
- mNotificationAreaInner);
+ View notificationAreaInner =
+ LayoutInflater.from(mContext).inflate(R.layout.notification_icon_area, null);
+ when(mMockNotificationAreaController.getNotificationInnerAreaView())
+ .thenReturn(notificationAreaInner);
}
/**
@@ -790,4 +781,8 @@
private View getEndSideContentView() {
return mFragment.getView().findViewById(R.id.status_bar_end_side_content);
}
+
+ private View getNotificationAreaView() {
+ return mFragment.getView().findViewById(R.id.notificationIcons);
+ }
}
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 0f779d9..44fa132 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
@@ -16,7 +16,8 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
-import android.platform.test.flag.junit.SetFlagsRule
+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.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH
@@ -73,8 +74,6 @@
class MobileIconViewModelTest : SysuiTestCase() {
private var connectivityRepository = FakeConnectivityRepository()
- private val setFlagsRule = SetFlagsRule()
-
private lateinit var underTest: MobileIconViewModel
private lateinit var interactor: MobileIconInteractorImpl
private lateinit var iconsInteractor: MobileIconsInteractorImpl
@@ -561,11 +560,9 @@
}
@Test
+ @DisableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
fun dataActivity_configOn_testIndicators_staticFlagOff() =
testScope.runTest {
- // GIVEN STATUS_BAR_STATIC_NETWORK_INDICATORS flag is off
- setFlagsRule.disableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
-
// Create a new view model here so the constants are properly read
whenever(constants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
@@ -618,11 +615,9 @@
}
@Test
+ @EnableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
fun dataActivity_configOn_testIndicators_staticFlagOn() =
testScope.runTest {
- // GIVEN STATUS_BAR_STATIC_NETWORK_INDICATORS flag is on
- setFlagsRule.enableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
-
// Create a new view model here so the constants are properly read
whenever(constants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
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 bf851eb..6714c94 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
@@ -1115,6 +1115,7 @@
broadcastDispatcher = fakeBroadcastDispatcher,
keyguardUpdateMonitor = keyguardUpdateMonitor,
backgroundDispatcher = utils.testDispatcher,
+ mainDispatcher = utils.testDispatcher,
activityManager = activityManager,
refreshUsersScheduler = refreshUsersScheduler,
guestUserInteractor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
index d1870b1..21d4549 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
@@ -258,6 +258,7 @@
broadcastDispatcher = fakeBroadcastDispatcher,
keyguardUpdateMonitor = keyguardUpdateMonitor,
backgroundDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
activityManager = activityManager,
refreshUsersScheduler = refreshUsersScheduler,
guestUserInteractor = guestUserInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index b7b24f6..d0804be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -170,6 +170,7 @@
broadcastDispatcher = fakeBroadcastDispatcher,
keyguardUpdateMonitor = keyguardUpdateMonitor,
backgroundDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
activityManager = activityManager,
refreshUsersScheduler = refreshUsersScheduler,
guestUserInteractor = guestUserInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/leak/GarbageMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/leak/GarbageMonitorTest.java
deleted file mode 100644
index a2b016f..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/util/leak/GarbageMonitorTest.java
+++ /dev/null
@@ -1,113 +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.systemui.util.leak;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.concurrency.MessageRouterImpl;
-import com.android.systemui.util.time.FakeSystemClock;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class GarbageMonitorTest extends SysuiTestCase {
-
- @Mock private LeakReporter mLeakReporter;
- @Mock private TrackedGarbage mTrackedGarbage;
- @Mock private DumpManager mDumpManager;
- private GarbageMonitor mGarbageMonitor;
- private final FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- mGarbageMonitor =
- new GarbageMonitor(
- mContext,
- mFakeExecutor,
- new MessageRouterImpl(mFakeExecutor),
- new LeakDetector(null, mTrackedGarbage, null, mDumpManager),
- mLeakReporter,
- mDumpManager);
- }
-
- @Test
- public void testALittleGarbage_doesntDump() {
- when(mTrackedGarbage.countOldGarbage()).thenReturn(GarbageMonitor.GARBAGE_ALLOWANCE);
-
- mGarbageMonitor.reinspectGarbageAfterGc();
-
- verify(mLeakReporter, never()).dumpLeak(anyInt());
- }
-
- @Test
- public void testTransientGarbage_doesntDump() {
- when(mTrackedGarbage.countOldGarbage()).thenReturn(GarbageMonitor.GARBAGE_ALLOWANCE + 1);
-
- // Start the leak monitor. Nothing gets reported immediately.
- mGarbageMonitor.startLeakMonitor();
- mFakeExecutor.runAllReady();
- verify(mLeakReporter, never()).dumpLeak(anyInt());
-
- // Garbage gets reset to 0 before the leak reporte actually gets called.
- when(mTrackedGarbage.countOldGarbage()).thenReturn(0);
- mFakeExecutor.advanceClockToLast();
- mFakeExecutor.runAllReady();
-
- // Therefore nothing gets dumped.
- verify(mLeakReporter, never()).dumpLeak(anyInt());
- }
-
- @Test
- public void testLotsOfPersistentGarbage_dumps() {
- when(mTrackedGarbage.countOldGarbage()).thenReturn(GarbageMonitor.GARBAGE_ALLOWANCE + 1);
-
- mGarbageMonitor.reinspectGarbageAfterGc();
-
- verify(mLeakReporter).dumpLeak(GarbageMonitor.GARBAGE_ALLOWANCE + 1);
- }
-
- @Test
- public void testLotsOfPersistentGarbage_dumpsAfterAtime() {
- when(mTrackedGarbage.countOldGarbage()).thenReturn(GarbageMonitor.GARBAGE_ALLOWANCE + 1);
-
- // Start the leak monitor. Nothing gets reported immediately.
- mGarbageMonitor.startLeakMonitor();
- mFakeExecutor.runAllReady();
- verify(mLeakReporter, never()).dumpLeak(anyInt());
-
- mFakeExecutor.advanceClockToLast();
- mFakeExecutor.runAllReady();
-
- verify(mLeakReporter).dumpLeak(GarbageMonitor.GARBAGE_ALLOWANCE + 1);
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt
index e59e475..7801684 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt
@@ -28,8 +28,8 @@
import com.android.systemui.model.SysUiStateTest
import com.android.wm.shell.bubbles.Bubble
import com.android.wm.shell.bubbles.BubbleEducationController
-import com.android.wm.shell.bubbles.PREF_MANAGED_EDUCATION
-import com.android.wm.shell.bubbles.PREF_STACK_EDUCATION
+import com.android.wm.shell.bubbles.ManageEducationView.Companion.PREF_MANAGED_EDUCATION
+import com.android.wm.shell.bubbles.StackEducationView.Companion.PREF_STACK_EDUCATION
import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.MoreExecutors.directExecutor
import org.junit.Assert.assertEquals
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 b217195..814ea19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -29,8 +29,6 @@
import static com.google.common.truth.Truth.assertThat;
-import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -50,6 +48,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
import android.app.ActivityManager;
import android.app.IActivityManager;
import android.app.INotificationManager;
@@ -186,7 +186,7 @@
import com.android.wm.shell.bubbles.BubbleViewInfoTask;
import com.android.wm.shell.bubbles.BubbleViewProvider;
import com.android.wm.shell.bubbles.Bubbles;
-import com.android.wm.shell.bubbles.StackEducationViewKt;
+import com.android.wm.shell.bubbles.StackEducationView;
import com.android.wm.shell.bubbles.properties.BubbleProperties;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.FloatingContentCoordinator;
@@ -1930,7 +1930,7 @@
@Test
public void testShowStackEdu_isNotConversationBubble() {
// Setup
- setPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION, false);
+ setPrefBoolean(StackEducationView.PREF_STACK_EDUCATION, false);
BubbleEntry bubbleEntry = createBubbleEntry(false /* isConversation */);
mBubbleController.updateBubble(bubbleEntry);
assertTrue(mBubbleController.hasBubbles());
@@ -1948,7 +1948,7 @@
@Test
public void testShowStackEdu_isConversationBubble() {
// Setup
- setPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION, false);
+ setPrefBoolean(StackEducationView.PREF_STACK_EDUCATION, false);
BubbleEntry bubbleEntry = createBubbleEntry(true /* isConversation */);
mBubbleController.updateBubble(bubbleEntry);
assertTrue(mBubbleController.hasBubbles());
@@ -1966,7 +1966,7 @@
@Test
public void testShowStackEdu_isSeenConversationBubble() {
// Setup
- setPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION, true);
+ setPrefBoolean(StackEducationView.PREF_STACK_EDUCATION, true);
BubbleEntry bubbleEntry = createBubbleEntry(true /* isConversation */);
mBubbleController.updateBubble(bubbleEntry);
assertTrue(mBubbleController.hasBubbles());
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryKosmos.kt
index a464fa8..fa79580 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryKosmos.kt
@@ -16,10 +16,8 @@
package com.android.systemui.accessibility.data.repository
-import android.view.accessibility.accessibilityManager
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-val Kosmos.accessibilityRepository by Fixture {
- AccessibilityRepository.invoke(a11yManager = accessibilityManager)
-}
+val Kosmos.fakeAccessibilityRepository by Fixture { FakeAccessibilityRepository() }
+val Kosmos.accessibilityRepository by Fixture { fakeAccessibilityRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
index 7c5696c..8486508 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
@@ -20,7 +20,6 @@
import com.android.internal.widget.LockPatternView
import com.android.internal.widget.LockscreenCredential
import com.android.keyguard.KeyguardSecurityModel.SecurityMode
-import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
import com.android.systemui.authentication.shared.model.AuthenticationResultModel
@@ -29,7 +28,6 @@
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -40,15 +38,11 @@
private val currentTime: () -> Long,
) : AuthenticationRepository {
- override val authenticationChallengeResult = MutableSharedFlow<Boolean>()
-
override val hintedPinLength: Int = HINTING_PIN_LENGTH
private val _isPatternVisible = MutableStateFlow(true)
override val isPatternVisible: StateFlow<Boolean> = _isPatternVisible.asStateFlow()
- override val lockout: MutableStateFlow<AuthenticationLockoutModel?> = MutableStateFlow(null)
-
override val hasLockoutOccurred = MutableStateFlow(false)
private val _isAutoConfirmFeatureEnabled = MutableStateFlow(false)
@@ -68,8 +62,6 @@
override val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> =
_isPinEnhancedPrivacyEnabled.asStateFlow()
- private var failedAttemptCount = 0
- private var lockoutEndTimestamp = 0L
private var credentialOverride: List<Any>? = null
private var securityMode: SecurityMode = DEFAULT_AUTHENTICATION_METHOD.toSecurityMode()
@@ -89,11 +81,27 @@
}
override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) {
- failedAttemptCount = if (isSuccessful) 0 else failedAttemptCount + 1
- authenticationChallengeResult.emit(isSuccessful)
+ if (isSuccessful) {
+ _failedAuthenticationAttempts.value = 0
+ _lockoutEndTimestamp = null
+ hasLockoutOccurred.value = false
+ lockoutStartedReportCount = 0
+ } else {
+ _failedAuthenticationAttempts.value++
+ }
}
+ private var _failedAuthenticationAttempts = MutableStateFlow(0)
+ override val failedAuthenticationAttempts: StateFlow<Int> =
+ _failedAuthenticationAttempts.asStateFlow()
+
+ private var _lockoutEndTimestamp: Long? = null
+ override val lockoutEndTimestamp: Long?
+ get() = if (currentTime() < (_lockoutEndTimestamp ?: 0)) _lockoutEndTimestamp else null
+
override suspend fun reportLockoutStarted(durationMs: Int) {
+ _lockoutEndTimestamp = (currentTime() + durationMs).takeIf { durationMs > 0 }
+ hasLockoutOccurred.value = true
lockoutStartedReportCount++
}
@@ -101,25 +109,10 @@
return (credentialOverride ?: DEFAULT_PIN).size
}
- override suspend fun getFailedAuthenticationAttemptCount(): Int {
- return failedAttemptCount
- }
-
- override suspend fun getLockoutEndTimestamp(): Long {
- return lockoutEndTimestamp
- }
-
fun setAutoConfirmFeatureEnabled(isEnabled: Boolean) {
_isAutoConfirmFeatureEnabled.value = isEnabled
}
- override suspend fun setLockoutDuration(durationMs: Int) {
- lockoutEndTimestamp = if (durationMs > 0) currentTime() + durationMs else 0
- if (durationMs > 0) {
- hasLockoutOccurred.value = true
- }
- }
-
override suspend fun checkCredential(
credential: LockscreenCredential
): AuthenticationResultModel {
@@ -136,8 +129,8 @@
else -> error("Unexpected credential type ${credential.type}!")
}
- return if (isSuccessful || failedAttemptCount < MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
- hasLockoutOccurred.value = false
+ val failedAttempts = _failedAuthenticationAttempts.value
+ return if (isSuccessful || failedAttempts < MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
AuthenticationResultModel(
isSuccessful = isSuccessful,
lockoutDurationMs = 0,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt
index 060ca4c..05cb059 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt
@@ -19,17 +19,11 @@
import com.android.systemui.authentication.data.repository.authenticationRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.kosmos.testDispatcher
-import com.android.systemui.user.data.repository.userRepository
-import com.android.systemui.util.time.fakeSystemClock
val Kosmos.authenticationInteractor by
Kosmos.Fixture {
AuthenticationInteractor(
applicationScope = applicationCoroutineScope,
repository = authenticationRepository,
- backgroundDispatcher = testDispatcher,
- userRepository = userRepository,
- clock = fakeSystemClock,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/domain/interactor/ConfigurationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/AuthControllerKosmos.kt
similarity index 73%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/common/domain/interactor/ConfigurationInteractorKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/AuthControllerKosmos.kt
index 94d9a72..7f70785 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/domain/interactor/ConfigurationInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/AuthControllerKosmos.kt
@@ -14,12 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.common.domain.interactor
+package com.android.systemui.biometrics
-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.util.mockito.mock
-val Kosmos.configurationInteractor by Fixture {
- ConfigurationInteractorImpl(repository = configurationRepository)
-}
+var Kosmos.authController by Fixture { mock<AuthController>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt
new file mode 100644
index 0000000..cbfc768
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.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.biometrics.domain.interactor
+
+import android.content.applicationContext
+import com.android.systemui.biometrics.authController
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
+
+val Kosmos.udfpsOverlayInteractor by Fixture {
+ UdfpsOverlayInteractor(
+ context = applicationContext,
+ authController = authController,
+ selectedUserInteractor = selectedUserInteractor,
+ scope = applicationCoroutineScope,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelKosmos.kt
index 5475659..8b1a1d9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelKosmos.kt
@@ -16,14 +16,18 @@
package com.android.systemui.biometrics.ui.viewmodel
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
import com.android.systemui.keyguard.ui.viewmodel.deviceEntryIconViewModel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.statusbar.phone.systemUIDialogManager
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+@ExperimentalCoroutinesApi
val Kosmos.deviceEntryUdfpsTouchOverlayViewModel by Fixture {
DeviceEntryUdfpsTouchOverlayViewModel(
deviceEntryIconViewModel = deviceEntryIconViewModel,
+ alternateBouncerInteractor = alternateBouncerInteractor,
systemUIDialogManager = systemUIDialogManager,
)
}
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
new file mode 100644
index 0000000..86a4509
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.domain.interactor
+
+import com.android.keyguard.keyguardUpdateMonitor
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+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
+
+var Kosmos.alternateBouncerInteractor by
+ Kosmos.Fixture {
+ AlternateBouncerInteractor(
+ statusBarStateController = statusBarStateController,
+ keyguardStateController = mock<KeyguardStateControllerImpl>(),
+ bouncerRepository = keyguardBouncerRepository,
+ fingerprintPropertyRepository = fingerprintPropertyRepository,
+ biometricSettingsRepository = biometricSettingsRepository,
+ systemClock = fakeSystemClock,
+ keyguardUpdateMonitor = keyguardUpdateMonitor,
+ scope = testScope.backgroundScope,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
index 6b38d6e..050c2c9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
@@ -37,7 +37,8 @@
MutableSharedFlow<Unit>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
override val onConfigurationChange: Flow<Unit> = _onConfigurationChange.asSharedFlow()
- private val _configurationChangeValues = MutableSharedFlow<Configuration>()
+ private val _configurationChangeValues =
+ MutableSharedFlow<Configuration>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
override val configurationValues: Flow<Configuration> =
_configurationChangeValues.asSharedFlow()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/ui/viewmodel/UdfpsAccessibilityOverlayViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/ui/viewmodel/UdfpsAccessibilityOverlayViewModelKosmos.kt
new file mode 100644
index 0000000..9175f09
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/ui/viewmodel/UdfpsAccessibilityOverlayViewModelKosmos.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.deviceentry.data.ui.viewmodel
+
+import com.android.systemui.accessibility.domain.interactor.accessibilityInteractor
+import com.android.systemui.biometrics.domain.interactor.udfpsOverlayInteractor
+import com.android.systemui.deviceentry.ui.viewmodel.UdfpsAccessibilityOverlayViewModel
+import com.android.systemui.keyguard.ui.viewmodel.deviceEntryForegroundIconViewModel
+import com.android.systemui.keyguard.ui.viewmodel.deviceEntryIconViewModel
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.udfpsAccessibilityOverlayViewModel by
+ Kosmos.Fixture {
+ UdfpsAccessibilityOverlayViewModel(
+ udfpsOverlayInteractor = udfpsOverlayInteractor,
+ accessibilityInteractor = accessibilityInteractor,
+ deviceEntryIconViewModel = deviceEntryIconViewModel,
+ deviceEntryFgIconViewModel = deviceEntryForegroundIconViewModel,
+ )
+ }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt
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
new file mode 100644
index 0000000..d9c6e4f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.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
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+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
new file mode 100644
index 0000000..e4821b0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelKosmos.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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+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
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.alternateBouncerToGoneTransitionViewModel by Fixture {
+ AlternateBouncerToGoneTransitionViewModel(
+ interactor = keyguardTransitionInteractor,
+ bouncerToGoneFlows = bouncerToGoneFlows,
+ animationFlow = keyguardTransitionAnimationFlow,
+ )
+}
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
new file mode 100644
index 0000000..4bfe4f5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryFgIconViewModelKosmos.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.content.applicationContext
+import com.android.systemui.biometrics.domain.interactor.udfpsOverlayInteractor
+import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+val Kosmos.deviceEntryForegroundIconViewModel by Fixture {
+ DeviceEntryForegroundViewModel(
+ context = applicationContext,
+ configurationRepository = configurationRepository,
+ deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
+ transitionInteractor = keyguardTransitionInteractor,
+ deviceEntryIconViewModel = deviceEntryIconViewModel,
+ udfpsOverlayInteractor = udfpsOverlayInteractor,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
index 6557bcf..5ceefde 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
@@ -28,8 +28,10 @@
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager
+val Kosmos.fakeDeviceEntryIconViewModelTransition by Fixture { FakeDeviceEntryIconTransition() }
+
val Kosmos.deviceEntryIconViewModelTransitionsMock by Fixture {
- mutableSetOf<DeviceEntryIconTransition>()
+ setOf<DeviceEntryIconTransition>(fakeDeviceEntryIconViewModelTransition)
}
val Kosmos.deviceEntryIconViewModel by Fixture {
@@ -41,7 +43,6 @@
transitionInteractor = keyguardTransitionInteractor,
keyguardInteractor = keyguardInteractor,
viewModel = aodToLockscreenTransitionViewModel,
- shadeDependentFlows = shadeDependentFlows,
sceneContainerFlags = sceneContainerFlags,
keyguardViewController = { statusBarKeyguardViewManager },
deviceEntryInteractor = deviceEntryInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/FakeDeviceEntryIconTransition.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/FakeDeviceEntryIconTransition.kt
new file mode 100644
index 0000000..6d872a3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/FakeDeviceEntryIconTransition.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeDeviceEntryIconTransition : DeviceEntryIconTransition {
+ private val _deviceEntryParentViewAlpha: MutableStateFlow<Float> = MutableStateFlow(0f)
+ override val deviceEntryParentViewAlpha: Flow<Float> = _deviceEntryParentViewAlpha.asStateFlow()
+
+ fun setDeviceEntryParentViewAlpha(alpha: Float) {
+ _deviceEntryParentViewAlpha.value = alpha
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt
index 6ccb3bc..5e67182 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt
@@ -20,6 +20,29 @@
import android.util.Log.TerribleFailureHandler
import junit.framework.Assert
+/** Asserts that the given block does not make a call to Log.wtf */
+fun assertDoesNotLogWtf(
+ message: String = "Expected Log.wtf not to be called",
+ notLoggingBlock: () -> Unit,
+) {
+ var caught: TerribleFailureLog? = null
+ val newHandler = TerribleFailureHandler { tag, failure, system ->
+ caught = TerribleFailureLog(tag, failure, system)
+ }
+ val oldHandler = Log.setWtfHandler(newHandler)
+ try {
+ notLoggingBlock()
+ } finally {
+ Log.setWtfHandler(oldHandler)
+ }
+ caught?.let { throw AssertionError("$message: $it", it.failure) }
+}
+
+fun assertDoesNotLogWtf(
+ message: String = "Expected Log.wtf not to be called",
+ notLoggingRunnable: Runnable,
+) = assertDoesNotLogWtf(message = message) { notLoggingRunnable.run() }
+
/**
* Assert that the given block makes a call to Log.wtf
*
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
new file mode 100644
index 0000000..d705248
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.custom
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.external.FakeCustomTileStatePersister
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileDefaultsRepository
+import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTilePackageUpdatesRepository
+import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileRepository
+import com.android.systemui.qs.tiles.impl.custom.data.repository.FakePackageManagerAdapterFacade
+
+var Kosmos.tileSpec: TileSpec.CustomTileSpec by Kosmos.Fixture()
+
+val Kosmos.customTileStatePersister: FakeCustomTileStatePersister by
+ Kosmos.Fixture { FakeCustomTileStatePersister() }
+
+val Kosmos.customTileRepository: FakeCustomTileRepository by
+ Kosmos.Fixture {
+ FakeCustomTileRepository(
+ customTileStatePersister,
+ packageManagerAdapterFacade,
+ testScope.testScheduler,
+ )
+ }
+
+val Kosmos.customTileDefaultsRepository: FakeCustomTileDefaultsRepository by
+ Kosmos.Fixture { FakeCustomTileDefaultsRepository() }
+
+val Kosmos.customTilePackagesUpdatesRepository: FakeCustomTilePackageUpdatesRepository by
+ Kosmos.Fixture { FakeCustomTilePackageUpdatesRepository() }
+
+val Kosmos.packageManagerAdapterFacade: FakePackageManagerAdapterFacade by
+ Kosmos.Fixture { FakePackageManagerAdapterFacade(tileSpec) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.kt
index ccf0391..ba803d8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.kt
@@ -19,21 +19,21 @@
import android.os.UserHandle
import android.service.quicksettings.Tile
import com.android.systemui.qs.external.FakeCustomTileStatePersister
-import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.flow.Flow
class FakeCustomTileRepository(
- tileSpec: TileSpec.CustomTileSpec,
customTileStatePersister: FakeCustomTileStatePersister,
+ private val packageManagerAdapterFacade: FakePackageManagerAdapterFacade,
testBackgroundContext: CoroutineContext,
) : CustomTileRepository {
private val realDelegate: CustomTileRepository =
CustomTileRepositoryImpl(
- tileSpec,
+ packageManagerAdapterFacade.tileSpec,
customTileStatePersister,
+ packageManagerAdapterFacade.packageManagerAdapter,
testBackgroundContext,
)
@@ -44,6 +44,10 @@
override fun getTile(user: UserHandle): Tile? = realDelegate.getTile(user)
+ override suspend fun isTileActive(): Boolean = realDelegate.isTileActive()
+
+ override suspend fun isTileToggleable(): Boolean = realDelegate.isTileToggleable()
+
override suspend fun updateWithTile(
user: UserHandle,
newTile: Tile,
@@ -55,4 +59,8 @@
defaults: CustomTileDefaults,
isPersistable: Boolean,
) = realDelegate.updateWithDefaults(user, defaults, isPersistable)
+
+ fun setTileActive(isActive: Boolean) = packageManagerAdapterFacade.setIsActive(isActive)
+
+ fun setTileToggleable(isActive: Boolean) = packageManagerAdapterFacade.setIsToggleable(isActive)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt
new file mode 100644
index 0000000..c9a7655
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.custom.data.repository
+
+import android.content.pm.ServiceInfo
+import android.os.Bundle
+import com.android.systemui.qs.external.PackageManagerAdapter
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+
+class FakePackageManagerAdapterFacade(
+ val tileSpec: TileSpec.CustomTileSpec,
+ val packageManagerAdapter: PackageManagerAdapter = mock {},
+) {
+
+ private var isToggleable: Boolean = false
+ private var isActive: Boolean = false
+
+ init {
+ whenever(packageManagerAdapter.getServiceInfo(eq(tileSpec.componentName), any()))
+ .thenAnswer {
+ ServiceInfo().apply {
+ metaData =
+ Bundle().apply {
+ putBoolean(
+ android.service.quicksettings.TileService.META_DATA_TOGGLEABLE_TILE,
+ isToggleable
+ )
+ putBoolean(
+ android.service.quicksettings.TileService.META_DATA_ACTIVE_TILE,
+ isActive
+ )
+ }
+ }
+ }
+ }
+
+ fun setIsActive(isActive: Boolean) {
+ this.isActive = isActive
+ }
+
+ fun setIsToggleable(isToggleable: Boolean) {
+ this.isToggleable = isToggleable
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index 0b41926..25b97b3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -91,7 +91,6 @@
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.data.repository.UserRepository
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.user.ui.viewmodel.UserActionViewModel
import com.android.systemui.user.ui.viewmodel.UserViewModel
@@ -174,7 +173,7 @@
mobileConnectionsRepository = mobileConnectionsRepository,
)
- val userRepository: UserRepository by lazy {
+ val userRepository: FakeUserRepository by lazy {
FakeUserRepository().apply {
val users = listOf(UserInfo(/* id= */ 0, "name", /* flags= */ 0))
setUserInfos(users)
@@ -236,9 +235,6 @@
return AuthenticationInteractor(
applicationScope = applicationScope(),
repository = repository,
- backgroundDispatcher = testDispatcher,
- userRepository = userRepository,
- clock = mock { whenever(elapsedRealtime()).thenAnswer { testScope.currentTime } }
)
}
@@ -274,7 +270,6 @@
repository = bouncerRepository,
authenticationInteractor = authenticationInteractor,
keyguardFaceAuthInteractor = keyguardFaceAuthInteractor,
- flags = sceneContainerFlags,
falsingInteractor = falsingInteractor(),
powerInteractor = powerInteractor(),
simBouncerInteractor = simBouncerInteractor,
@@ -312,6 +307,7 @@
userSwitcherMenu = flowOf(createMenuActions()),
actionButtonInteractor = actionButtonInteractor,
simBouncerInteractor = simBouncerInteractor,
+ clock = mock { whenever(elapsedRealtime()).thenAnswer { testScope.currentTime } },
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt
index 5c8fe0d..2d2f546 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt
@@ -24,6 +24,7 @@
import com.android.systemui.statusbar.data.repository.notificationListenerSettingsRepository
import com.android.systemui.statusbar.notification.data.repository.notificationsKeyguardViewStateRepository
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.headsUpNotificationIconInteractor
import com.android.wm.shell.bubbles.bubblesOptional
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -43,6 +44,7 @@
NotificationIconsInteractor(
activeNotificationsInteractor = activeNotificationsInteractor,
bubbles = bubblesOptional,
+ headsUpNotificationIconInteractor = headsUpNotificationIconInteractor,
keyguardViewStateRepository = notificationsKeyguardViewStateRepository,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/ShelfNotificationIconViewStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinderKosmos.kt
similarity index 64%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/ShelfNotificationIconViewStoreKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinderKosmos.kt
index f7f16a4..67fecb4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/ShelfNotificationIconViewStoreKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinderKosmos.kt
@@ -16,9 +16,22 @@
package com.android.systemui.statusbar.notification.icon.ui.viewbinder
+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.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 {
+ NotificationIconContainerShelfViewBinder(
+ notificationIconContainerShelfViewModel,
+ configurationState,
+ systemBarUtilsState,
+ statusBarIconViewBindingFailureTracker,
+ shelfNotificationIconViewStore,
+ )
+}
val Kosmos.shelfNotificationIconViewStore by Fixture {
ShelfNotificationIconViewStore(notifCollection = notifCollection)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelKosmos.kt
index 988172c..b906b60 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelKosmos.kt
@@ -18,7 +18,6 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.notificationIconContainerShelfViewModel
import com.android.systemui.statusbar.notification.row.ui.viewmodel.activatableNotificationViewModel
import com.android.systemui.statusbar.notification.shelf.domain.interactor.notificationShelfInteractor
@@ -26,6 +25,5 @@
NotificationShelfViewModel(
interactor = notificationShelfInteractor,
activatableViewModel = activatableNotificationViewModel,
- icons = notificationIconContainerShelfViewModel,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorKosmos.kt
index baca8b2..4232b27 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorKosmos.kt
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.notification.stack.domain.interactor
-import com.android.systemui.common.domain.interactor.configurationInteractor
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.power.domain.interactor.powerInteractor
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 ca5b401..04716b9 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
@@ -22,11 +22,9 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testDispatcher
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.shelfNotificationIconViewStore
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.statusBarIconViewBindingFailureTracker
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.notificationIconContainerShelfViewBinder
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationListViewModel
import com.android.systemui.statusbar.phone.notificationIconAreaController
-import com.android.systemui.statusbar.ui.systemBarUtilsState
val Kosmos.notificationListViewBinder by Fixture {
NotificationListViewBinder(
@@ -35,9 +33,7 @@
configuration = configurationState,
falsingManager = falsingManager,
iconAreaController = notificationIconAreaController,
- iconViewBindingFailureTracker = statusBarIconViewBindingFailureTracker,
metricsLogger = metricsLogger,
- shelfIconViewStore = shelfNotificationIconViewStore,
- systemBarUtilsState = systemBarUtilsState,
+ nicBinder = notificationIconContainerShelfViewBinder,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt
index 42c77aa..4e2dc7a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt
@@ -47,6 +47,7 @@
broadcastDispatcher = broadcastDispatcher,
keyguardUpdateMonitor = keyguardUpdateMonitor,
backgroundDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
activityManager = activityManager,
refreshUsersScheduler = refreshUsersScheduler,
guestUserInteractor = guestUserInteractor,
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index a4b2896..cab2d74 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -16,6 +16,7 @@
package com.android.server.appwidget;
+import static android.appwidget.flags.Flags.removeAppWidgetServiceIoFromCriticalPath;
import static android.content.Context.KEYGUARD_SERVICE;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -144,6 +145,7 @@
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
@@ -277,7 +279,12 @@
mKeyguardManager = (KeyguardManager) mContext.getSystemService(KEYGUARD_SERVICE);
mDevicePolicyManagerInternal = LocalServices.getService(DevicePolicyManagerInternal.class);
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
- mSaveStateHandler = BackgroundThread.getHandler();
+ if (removeAppWidgetServiceIoFromCriticalPath()) {
+ mSaveStateHandler = new Handler(BackgroundThread.get().getLooper(),
+ this::handleSaveMessage);
+ } else {
+ mSaveStateHandler = BackgroundThread.getHandler();
+ }
final ServiceThread serviceThread = new ServiceThread(TAG,
android.os.Process.THREAD_PRIORITY_FOREGROUND, false /* allowIo */);
serviceThread.start();
@@ -314,6 +321,40 @@
mMaxWidgetBitmapMemory = 6 * size.x * size.y;
}
+ private boolean handleSaveMessage(Message msg) {
+ final int userId = msg.what;
+ SparseArray<byte[]> userIdToBytesMapping;
+ synchronized (mLock) {
+ // No need to enforce unlocked state when there is no caller. User can be in the
+ // stopping state or removed by the time the message is processed
+ ensureGroupStateLoadedLocked(userId, false /* enforceUserUnlockingOrUnlocked */);
+ userIdToBytesMapping = saveStateToByteArrayLocked(userId);
+ }
+
+ for (int i = 0; i < userIdToBytesMapping.size(); i++) {
+ int currentProfileId = userIdToBytesMapping.keyAt(i);
+ byte[] currentStateByteArray = userIdToBytesMapping.valueAt(i);
+ AtomicFile currentFile = getSavedStateFile(currentProfileId);
+ FileOutputStream fileStream;
+ try {
+ fileStream = currentFile.startWrite();
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to start writing stream", e);
+ continue;
+ }
+
+ try {
+ fileStream.write(currentStateByteArray);
+ currentFile.finishWrite(fileStream);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to write state byte stream to file", e);
+ currentFile.failWrite(fileStream);
+ }
+ }
+
+ return true;
+ }
+
private void registerBroadcastReceiver() {
// Register for broadcasts about package install, etc., so we can
// update the provider list.
@@ -1944,7 +1985,12 @@
}
private void saveGroupStateAsync(int groupId) {
- mSaveStateHandler.post(new SaveStateRunnable(groupId));
+ if (removeAppWidgetServiceIoFromCriticalPath()) {
+ mSaveStateHandler.removeMessages(groupId);
+ mSaveStateHandler.sendEmptyMessage(groupId);
+ } else {
+ mSaveStateHandler.post(new SaveStateRunnable(groupId));
+ }
}
private void updateAppWidgetInstanceLocked(Widget widget, RemoteViews views,
@@ -3104,6 +3150,23 @@
}
@GuardedBy("mLock")
+ private @NonNull SparseArray<byte[]> saveStateToByteArrayLocked(int userId) {
+ tagProvidersAndHosts();
+
+ final int[] profileIds = mSecurityPolicy.getEnabledGroupProfileIds(userId);
+ SparseArray<byte[]> userIdToBytesMapping = new SparseArray<>();
+
+ for (int profileId : profileIds) {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ if (writeProfileStateToStreamLocked(outputStream, profileId)) {
+ userIdToBytesMapping.put(profileId, outputStream.toByteArray());
+ }
+ }
+
+ return userIdToBytesMapping;
+ }
+
+ @GuardedBy("mLock")
private void saveStateLocked(int userId) {
tagProvidersAndHosts();
@@ -3117,7 +3180,7 @@
FileOutputStream stream;
try {
stream = file.startWrite();
- if (writeProfileStateToFileLocked(stream, profileId)) {
+ if (writeProfileStateToStreamLocked(stream, profileId)) {
file.finishWrite(stream);
} else {
file.failWrite(stream);
@@ -3158,7 +3221,7 @@
}
@GuardedBy("mLock")
- private boolean writeProfileStateToFileLocked(FileOutputStream stream, int userId) {
+ private boolean writeProfileStateToStreamLocked(OutputStream stream, int userId) {
int N;
try {
diff --git a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
index d9741c8..4a6d5c9b 100644
--- a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
+++ b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
@@ -25,9 +25,6 @@
import android.service.autofill.FillResponse;
import android.util.Slog;
-import java.util.Objects;
-
-
/**
* Requests autofill response from a Remote Autofill Service. This autofill service can be
* either a Credential Autofill Service or the user-opted autofill service.
@@ -51,7 +48,6 @@
private final RemoteFillService mRemoteFillService;
private final SecondaryProviderCallback mCallback;
- private FillRequest mLastFillRequest;
private int mLastFlag;
SecondaryProviderHandler(
@@ -97,17 +93,11 @@
}
/**
- * Requests a new fill response. If the fill request is same as the last requested fill request,
- * then the request is duped.
+ * Requests a new fill response.
*/
public void onFillRequest(FillRequest pendingFillRequest, int flag) {
- if (Objects.equals(pendingFillRequest, mLastFillRequest)) {
- Slog.v(TAG, "Deduping fill request to secondary provider.");
- return;
- }
Slog.v(TAG, "Requesting fill response to secondary provider.");
mLastFlag = flag;
- mLastFillRequest = pendingFillRequest;
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 d527ce0..c4e8f12 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -367,6 +367,9 @@
@GuardedBy("mLock")
private SparseArray<FillResponse> mResponses;
+ @GuardedBy("mLock")
+ private SparseArray<FillResponse> mSecondaryResponses;
+
/**
* Contexts read from the app; they will be updated (sanitized, change values for save) before
* sent to {@link AutofillService}. Ordered by the time they were read.
@@ -713,7 +716,14 @@
mPendingFillRequest.getDelayedFillIntentSender());
}
mLastFillRequest = mPendingFillRequest;
- mRemoteFillService.onFillRequest(mPendingFillRequest);
+ if (shouldRequestSecondaryProvider(mPendingFillRequest.getFlags())
+ && mSecondaryProviderHandler != null) {
+ Slog.v(TAG, "Requesting fill response to secondary provider.");
+ mSecondaryProviderHandler.onFillRequest(mPendingFillRequest,
+ mPendingFillRequest.getFlags());
+ } else if (mRemoteFillService != null) {
+ mRemoteFillService.onFillRequest(mPendingFillRequest);
+ }
mPendingInlineSuggestionsRequest = null;
mWaitForInlineRequest = false;
mPendingFillRequest = null;
@@ -1196,7 +1206,8 @@
@GuardedBy("mLock")
private void requestNewFillResponseLocked(@NonNull ViewState viewState, int newState,
int flags) {
- final FillResponse existingResponse = viewState.getResponse();
+ final FillResponse existingResponse = shouldRequestSecondaryProvider(flags)
+ ? viewState.getSecondaryResponse() : viewState.getResponse();
mFillRequestEventLogger.startLogForNewRequest();
mRequestCount++;
mFillRequestEventLogger.maybeSetAppPackageUid(uid);
@@ -1804,6 +1815,10 @@
return;
}
synchronized (mLock) {
+ if (mSecondaryResponses == null) {
+ mSecondaryResponses = new SparseArray<>(2);
+ }
+ mSecondaryResponses.put(fillResponse.getRequestId(), fillResponse);
setViewStatesLocked(fillResponse, ViewState.STATE_FILLABLE, /* clearResponse= */ false,
/* isPrimary= */ false);
@@ -3980,7 +3995,7 @@
}
// If it's not, then check if it should start a partition.
- if (shouldStartNewPartitionLocked(id)) {
+ if (shouldStartNewPartitionLocked(id, flags)) {
if (sDebug) {
Slog.d(TAG, "Starting partition or augmented request for view id " + id + ": "
+ viewState.getStateAsString());
@@ -4008,9 +4023,11 @@
* @return {@code true} if a new partition should be started
*/
@GuardedBy("mLock")
- private boolean shouldStartNewPartitionLocked(@NonNull AutofillId id) {
+ private boolean shouldStartNewPartitionLocked(@NonNull AutofillId id, int flags) {
final ViewState currentView = mViewStates.get(id);
- if (mResponses == null) {
+ SparseArray<FillResponse> responses = shouldRequestSecondaryProvider(flags)
+ ? mSecondaryResponses : mResponses;
+ if (responses == null) {
return currentView != null && (currentView.getState()
& ViewState.STATE_PENDING_CREATE_INLINE_REQUEST) == 0;
}
@@ -4022,7 +4039,7 @@
return true;
}
- final int numResponses = mResponses.size();
+ final int numResponses = responses.size();
if (numResponses >= AutofillManagerService.getPartitionMaxCount()) {
Slog.e(TAG, "Not starting a new partition on " + id + " because session " + this.id
+ " reached maximum of " + AutofillManagerService.getPartitionMaxCount());
@@ -4030,7 +4047,7 @@
}
for (int responseNum = 0; responseNum < numResponses; responseNum++) {
- final FillResponse response = mResponses.valueAt(responseNum);
+ final FillResponse response = responses.valueAt(responseNum);
if (ArrayUtils.contains(response.getIgnoredIds(), id)) {
return false;
@@ -4066,6 +4083,10 @@
}
boolean shouldRequestSecondaryProvider(int flags) {
+ if (!mService.isAutofillCredmanIntegrationEnabled()
+ || mSecondaryProviderHandler == null) {
+ return false;
+ }
if (mIsPrimaryCredential) {
return (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) == 0;
} else {
@@ -4205,12 +4226,6 @@
}
break;
case ACTION_VIEW_ENTERED:
- if (shouldRequestSecondaryProvider(flags)
- && mSecondaryProviderHandler != null
- && mAssistReceiver.mLastFillRequest != null) {
- mSecondaryProviderHandler.onFillRequest(mAssistReceiver.mLastFillRequest,
- flags);
- }
mLatencyBaseTime = SystemClock.elapsedRealtime();
boolean wasPreviouslyFillDialog = mPreviouslyFillDialogPotentiallyStarted;
mPreviouslyFillDialogPotentiallyStarted = false;
@@ -4225,6 +4240,19 @@
viewState.setCurrentValue(value);
}
+ if (shouldRequestSecondaryProvider(flags)) {
+ if (requestNewFillResponseOnViewEnteredIfNecessaryLocked(
+ id, viewState, flags)) {
+ Slog.v(TAG, "Started a new fill request for secondary provider.");
+ return;
+ }
+ // If the ViewState is ready to be displayed, onReady() will be called.
+ viewState.update(value, virtualBounds, flags);
+
+ // return here because primary provider logic is not applicable.
+ return;
+ }
+
if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) {
if (sDebug) Slog.d(TAG, "Ignoring VIEW_ENTERED on URL BAR (id=" + id + ")");
return;
diff --git a/services/autofill/java/com/android/server/autofill/ViewState.java b/services/autofill/java/com/android/server/autofill/ViewState.java
index b0bb9ec..fec5aa5 100644
--- a/services/autofill/java/com/android/server/autofill/ViewState.java
+++ b/services/autofill/java/com/android/server/autofill/ViewState.java
@@ -162,6 +162,11 @@
return mPrimaryFillResponse;
}
+ @Nullable
+ FillResponse getSecondaryResponse() {
+ return mSecondaryFillResponse;
+ }
+
void setResponse(FillResponse response) {
setResponse(response, /* isPrimary= */ true);
}
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 df74770..62c6703 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -130,7 +130,7 @@
synchronized (mTransports) {
for (int i = 0; i < associationIds.length; i++) {
if (mTransports.contains(associationIds[i])) {
- mTransports.get(associationIds[i]).requestForResponse(message, data);
+ mTransports.get(associationIds[i]).sendMessage(message, data);
}
}
}
@@ -220,7 +220,7 @@
if (transport == null) {
return CompletableFuture.failedFuture(new IOException("Missing transport"));
}
- return transport.requestForResponse(MESSAGE_REQUEST_PERMISSION_RESTORE, data);
+ return transport.sendMessage(MESSAGE_REQUEST_PERMISSION_RESTORE, data);
}
}
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 32d4061..22b18ac 100644
--- a/services/companion/java/com/android/server/companion/transport/Transport.java
+++ b/services/companion/java/com/android/server/companion/transport/Transport.java
@@ -16,6 +16,9 @@
package com.android.server.companion.transport;
+import static android.companion.CompanionDeviceManager.MESSAGE_ONEWAY_FROM_WEARABLE;
+import static android.companion.CompanionDeviceManager.MESSAGE_ONEWAY_PING;
+import static android.companion.CompanionDeviceManager.MESSAGE_ONEWAY_TO_WEARABLE;
import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_CONTEXT_SYNC;
import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_PERMISSION_RESTORE;
import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_PING;
@@ -80,6 +83,10 @@
return (message & 0xFF000000) == 0x33000000;
}
+ private static boolean isOneway(int message) {
+ return (message & 0xFF000000) == 0x43000000;
+ }
+
@GuardedBy("mPendingRequests")
protected final SparseArray<CompletableFuture<byte[]>> mPendingRequests =
new SparseArray<>();
@@ -134,6 +141,42 @@
protected abstract void sendMessage(int message, int sequence, @NonNull byte[] data)
throws IOException;
+ /**
+ * Send a message using this transport. If the message was a request, then the returned Future
+ * object will complete successfully only if the remote device both received and processed it
+ * as expected. If the message was a send-and-forget type message, then the Future object will
+ * resolve successfully immediately (with null) upon sending the message.
+ *
+ * @param message the message type
+ * @param data the message payload
+ * @return Future object containing the result of the sent message.
+ */
+ public Future<byte[]> sendMessage(int message, byte[] data) {
+ final CompletableFuture<byte[]> pending = new CompletableFuture<>();
+ if (isOneway(message)) {
+ return sendAndForget(message, data);
+ } else if (isRequest(message)) {
+ return requestForResponse(message, data);
+ } else {
+ Slog.w(TAG, "Failed to send message 0x" + Integer.toHexString(message));
+ pending.completeExceptionally(new IllegalArgumentException(
+ "The message being sent must be either a one-way or a request."
+ ));
+ }
+ return pending;
+ }
+
+ /**
+ * @deprecated Method was renamed to sendMessage(int, byte[]) to support both
+ * send-and-forget type messages as well as wait-for-response type messages.
+ *
+ * @param message request message type
+ * @param data the message payload
+ * @return future object containing the result of the request.
+ *
+ * @see #sendMessage(int, byte[])
+ */
+ @Deprecated
public Future<byte[]> requestForResponse(int message, byte[] data) {
if (DEBUG) Slog.d(TAG, "Requesting for response");
final int sequence = mNextSequence.incrementAndGet();
@@ -154,6 +197,20 @@
return pending;
}
+ private Future<byte[]> sendAndForget(int message, byte[]data) {
+ if (DEBUG) Slog.d(TAG, "Sending a one-way message");
+ final CompletableFuture<byte[]> pending = new CompletableFuture<>();
+
+ try {
+ sendMessage(message, -1, data);
+ pending.complete(null);
+ } catch (IOException e) {
+ pending.completeExceptionally(e);
+ }
+
+ return pending;
+ }
+
protected final void handleMessage(int message, int sequence, @NonNull byte[] data)
throws IOException {
if (DEBUG) {
@@ -162,7 +219,9 @@
+ " from association " + mAssociationId);
}
- if (isRequest(message)) {
+ if (isOneway(message)) {
+ processOneway(message, data);
+ } else if (isRequest(message)) {
try {
processRequest(message, sequence, data);
} catch (IOException e) {
@@ -175,6 +234,21 @@
}
}
+ private void processOneway(int message, byte[] data) {
+ switch (message) {
+ case MESSAGE_ONEWAY_PING:
+ case MESSAGE_ONEWAY_FROM_WEARABLE:
+ case MESSAGE_ONEWAY_TO_WEARABLE: {
+ callback(message, data);
+ break;
+ }
+ default: {
+ Slog.w(TAG, "Ignoring unknown message 0x" + Integer.toHexString(message));
+ break;
+ }
+ }
+ }
+
private void processRequest(int message, int sequence, byte[] data)
throws IOException {
switch (message) {
diff --git a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
index a159a5e..5a548fd 100644
--- a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
+++ b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
@@ -54,9 +54,7 @@
"exclude-annotation": "android.support.test.filters.FlakyTest"
}
]
- }
- ],
- "postsubmit": [
+ },
{
"name": "CtsVirtualDevicesCameraTestCases",
"options": [
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 6940ffe..f24c4cc 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
@@ -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.
@@ -70,7 +70,7 @@
@Override
public void onProcessCaptureRequest(int streamId, int frameId) throws RemoteException {
- camera.onProcessCaptureRequest(streamId, frameId, /*metadata=*/ null);
+ camera.onProcessCaptureRequest(streamId, frameId);
}
@Override
diff --git a/services/core/Android.bp b/services/core/Android.bp
index b5c9ffd..5111b08 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -103,7 +103,6 @@
"android.hardware.power-java_shared",
],
srcs: [
- ":android.hardware.biometrics.face-V4-java-source",
":android.hardware.tv.hdmi.connection-V1-java-source",
":android.hardware.tv.hdmi.earc-V1-java-source",
":statslog-art-java-gen",
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index c258370..e289a56 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -27,6 +27,7 @@
per-file **IpSec* = file:/services/core/java/com/android/server/vcn/OWNERS
per-file *Location* = file:/services/core/java/com/android/server/location/OWNERS
per-file *Network* = file:/services/core/java/com/android/server/net/OWNERS
+per-file *SecurityStateManager* = file:/SECURITY_STATE_OWNERS
per-file *SoundTrigger* = file:/media/java/android/media/soundtrigger/OWNERS
per-file *Storage* = file:/core/java/android/os/storage/OWNERS
per-file *TimeUpdate* = file:/services/core/java/com/android/server/timezonedetector/OWNERS
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index df8f17a..d461643 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -70,7 +70,6 @@
import static android.os.PowerExemptionManager.REASON_OPT_OUT_REQUESTED;
import static android.os.PowerExemptionManager.REASON_OP_ACTIVATE_PLATFORM_VPN;
import static android.os.PowerExemptionManager.REASON_OP_ACTIVATE_VPN;
-import static android.os.PowerExemptionManager.REASON_OTHER;
import static android.os.PowerExemptionManager.REASON_PACKAGE_INSTALLER;
import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT;
import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI;
@@ -127,7 +126,6 @@
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import android.Manifest;
-import android.Manifest.permission;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1085,7 +1083,7 @@
// Use that as a shortcut if possible to avoid having to recheck all the conditions.
final boolean whileInUseAllowsUiJobScheduling =
ActivityManagerService.doesReasonCodeAllowSchedulingUserInitiatedJobs(
- r.getFgsAllowWIU());
+ r.getFgsAllowWiu_forStart());
r.updateAllowUiJobScheduling(whileInUseAllowsUiJobScheduling
|| mAm.canScheduleUserInitiatedJobs(callingUid, callingPid, callingPackage));
} else {
@@ -2320,7 +2318,7 @@
// If the foreground service is not started from TOP process, do not allow it to
// have while-in-use location/camera/microphone access.
- if (!r.isFgsAllowedWIU()) {
+ if (!r.isFgsAllowedWiu_forCapabilities()) {
Slog.w(TAG,
"Foreground service started from background can not have "
+ "location/camera/microphone access: service "
@@ -2436,7 +2434,7 @@
// mAllowWhileInUsePermissionInFgs.
r.mAllowStartForegroundAtEntering = r.getFgsAllowStart();
r.mAllowWhileInUsePermissionInFgsAtEntering =
- r.isFgsAllowedWIU();
+ r.isFgsAllowedWiu_forCapabilities();
r.mStartForegroundCount++;
r.mFgsEnterTime = SystemClock.uptimeMillis();
if (!stopProcStatsOp) {
@@ -2632,7 +2630,7 @@
policy.getForegroundServiceTypePolicyInfo(type, defaultToType);
final @ForegroundServicePolicyCheckCode int code = policy.checkForegroundServiceTypePolicy(
mAm.mContext, r.packageName, r.app.uid, r.app.getPid(),
- r.isFgsAllowedWIU(), policyInfo);
+ r.isFgsAllowedWiu_forStart(), policyInfo);
RuntimeException exception = null;
switch (code) {
case FGS_TYPE_POLICY_CHECK_DEPRECATED: {
@@ -7580,78 +7578,76 @@
* @param callingUid caller app's uid.
* @param intent intent to start/bind service.
* @param r the service to start.
- * @param isBindService True if it's called from bindService().
+ * @param inBindService True if it's called from bindService().
* @param forBoundFgs set to true if it's called from Service.startForeground() for a
* service that's not started but bound.
- * @return true if allow, false otherwise.
*/
private void setFgsRestrictionLocked(String callingPackage,
int callingPid, int callingUid, Intent intent, ServiceRecord r, int userId,
- BackgroundStartPrivileges backgroundStartPrivileges, boolean isBindService,
+ BackgroundStartPrivileges backgroundStartPrivileges, boolean inBindService,
boolean forBoundFgs) {
- @ReasonCode int allowWIU;
+ @ReasonCode int allowWiu;
@ReasonCode int allowStart;
// If called from bindService(), do not update the actual fields, but instead
// keep it in a separate set of fields.
- if (isBindService) {
- allowWIU = r.mAllowWIUInBindService;
- allowStart = r.mAllowStartInBindService;
+ if (inBindService) {
+ allowWiu = r.mAllowWiu_inBindService;
+ allowStart = r.mAllowStart_inBindService;
} else {
- allowWIU = r.mAllowWhileInUsePermissionInFgsReasonNoBinding;
- allowStart = r.mAllowStartForegroundNoBinding;
+ allowWiu = r.mAllowWiu_noBinding;
+ allowStart = r.mAllowStart_noBinding;
}
- // Check DeviceConfig flag.
- if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) {
- if (allowWIU == REASON_DENIED) {
- // BGFGS start restrictions are disabled. We're allowing while-in-use permissions.
- // Note REASON_OTHER since there's no other suitable reason.
- allowWIU = REASON_OTHER;
- }
- }
-
- if ((allowWIU == REASON_DENIED)
- || (allowStart == REASON_DENIED)) {
+ if ((allowWiu == REASON_DENIED) || (allowStart == REASON_DENIED)) {
@ReasonCode final int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
callingPackage, callingPid, callingUid, r.app, backgroundStartPrivileges);
// We store them to compare the old and new while-in-use logics to each other.
// (They're not used for any other purposes.)
- if (allowWIU == REASON_DENIED) {
- allowWIU = allowWhileInUse;
+ if (allowWiu == REASON_DENIED) {
+ allowWiu = allowWhileInUse;
}
if (allowStart == REASON_DENIED) {
allowStart = shouldAllowFgsStartForegroundWithBindingCheckLocked(
allowWhileInUse, callingPackage, callingPid, callingUid, intent, r,
- backgroundStartPrivileges, isBindService);
+ backgroundStartPrivileges, inBindService);
}
}
- if (isBindService) {
- r.mAllowWIUInBindService = allowWIU;
- r.mAllowStartInBindService = allowStart;
+ if (inBindService) {
+ r.mAllowWiu_inBindService = allowWiu;
+ r.mAllowStart_inBindService = allowStart;
} else {
if (!forBoundFgs) {
- // This is for "normal" situation.
- r.mAllowWhileInUsePermissionInFgsReasonNoBinding = allowWIU;
- r.mAllowStartForegroundNoBinding = allowStart;
+ // This is for "normal" situation -- either:
+ // - in Context.start[Foreground]Service()
+ // - or, in Service.startForeground() on a started service.
+ r.mAllowWiu_noBinding = allowWiu;
+ r.mAllowStart_noBinding = allowStart;
} else {
- // This logic is only for logging, so we only update the "by-binding" fields.
- if (r.mAllowWIUByBindings == REASON_DENIED) {
- r.mAllowWIUByBindings = allowWIU;
+ // Service.startForeground() is called on a service that's not started, but bound.
+ // In this case, we set them to "byBindings", not "noBinding", because
+ // we don't want to use them when we calculate the "legacy" code.
+ //
+ // We don't want to set them to "no binding" codes, because on U-QPR1 and below,
+ // we didn't call setFgsRestrictionLocked() in the code path which sets
+ // forBoundFgs to true, and we wanted to preserve the original behavior in other
+ // places to compare the legacy and new logic.
+ if (r.mAllowWiu_byBindings == REASON_DENIED) {
+ r.mAllowWiu_byBindings = allowWiu;
}
- if (r.mAllowStartByBindings == REASON_DENIED) {
- r.mAllowStartByBindings = allowStart;
+ if (r.mAllowStart_byBindings == REASON_DENIED) {
+ r.mAllowStart_byBindings = allowStart;
}
}
// Also do a binding client check, unless called from bindService().
- if (r.mAllowWIUByBindings == REASON_DENIED) {
- r.mAllowWIUByBindings =
+ if (r.mAllowWiu_byBindings == REASON_DENIED) {
+ r.mAllowWiu_byBindings =
shouldAllowFgsWhileInUsePermissionByBindingsLocked(callingUid);
}
- if (r.mAllowStartByBindings == REASON_DENIED) {
- r.mAllowStartByBindings = r.mAllowWIUByBindings;
+ if (r.mAllowStart_byBindings == REASON_DENIED) {
+ r.mAllowStart_byBindings = r.mAllowWiu_byBindings;
}
}
}
@@ -7660,13 +7656,13 @@
* Reset various while-in-use and BFSL related information.
*/
void resetFgsRestrictionLocked(ServiceRecord r) {
- r.clearFgsAllowWIU();
+ r.clearFgsAllowWiu();
r.clearFgsAllowStart();
r.mInfoAllowStartForeground = null;
r.mInfoTempFgsAllowListReason = null;
r.mLoggedInfoAllowStartForeground = false;
- r.updateAllowUiJobScheduling(r.isFgsAllowedWIU());
+ r.updateAllowUiJobScheduling(r.isFgsAllowedWiu_forStart());
}
boolean canStartForegroundServiceLocked(int callingPid, int callingUid, String callingPackage) {
@@ -8284,7 +8280,8 @@
allowWhileInUsePermissionInFgs = r.mAllowWhileInUsePermissionInFgsAtEntering;
fgsStartReasonCode = r.mAllowStartForegroundAtEntering;
} else {
- allowWhileInUsePermissionInFgs = r.isFgsAllowedWIU();
+ // TODO: Also log "forStart"
+ allowWhileInUsePermissionInFgs = r.isFgsAllowedWiu_forCapabilities();
fgsStartReasonCode = r.getFgsAllowStart();
}
final int callerTargetSdkVersion = r.mRecentCallerApplicationInfo != null
@@ -8306,7 +8303,7 @@
r.mFgsNotificationShown,
durationMs,
r.mStartForegroundCount,
- ActivityManagerUtils.hashComponentNameForAtom(r.shortInstanceName),
+ 0, // Short instance name -- no longer logging it.
r.mFgsHasNotificationPermission,
r.foregroundServiceType,
fgsTypeCheckCode,
@@ -8323,12 +8320,12 @@
mAm.getUidProcessCapabilityLocked(r.mRecentCallingUid),
0,
0,
- r.mAllowWhileInUsePermissionInFgsReasonNoBinding,
- r.mAllowWIUInBindService,
- r.mAllowWIUByBindings,
- r.mAllowStartForegroundNoBinding,
- r.mAllowStartInBindService,
- r.mAllowStartByBindings,
+ r.mAllowWiu_noBinding,
+ r.mAllowWiu_inBindService,
+ r.mAllowWiu_byBindings,
+ r.mAllowStart_noBinding,
+ r.mAllowStart_inBindService,
+ r.mAllowStart_byBindings,
fgsStartApi,
fgsRestrictionRecalculated);
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 3ce91c8..1d69905 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -225,7 +225,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 a80d2fd..f8451fd 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -164,6 +164,7 @@
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.am.MemoryStatUtil.hasMemcg;
import static com.android.server.am.ProcessList.ProcStartHandler;
+import static com.android.server.flags.Flags.disableSystemCompaction;
import static com.android.server.net.NetworkPolicyManagerInternal.updateBlockedReasonsWithProcState;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND;
@@ -8564,8 +8565,10 @@
final long now = SystemClock.uptimeMillis();
final long timeSinceLastIdle = now - mLastIdleTime;
- // Compact all non-zygote processes to freshen up the page cache.
- mOomAdjuster.mCachedAppOptimizer.compactAllSystem();
+ if (!disableSystemCompaction()) {
+ // Compact all non-zygote processes to freshen up the page cache.
+ mOomAdjuster.mCachedAppOptimizer.compactAllSystem();
+ }
final long lowRamSinceLastIdle = mAppProfiler.getLowRamTimeSinceIdleLPr(now);
mLastIdleTime = now;
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index ae0cd65..00dd169 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -2238,6 +2238,7 @@
pw.println("Performing idle maintenance...");
mInterface.sendIdleJobTrigger();
+ mInternal.performIdleMaintenance();
return 0;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerUtils.java b/services/core/java/com/android/server/am/ActivityManagerUtils.java
index 01466b8..78a2ecb 100644
--- a/services/core/java/com/android/server/am/ActivityManagerUtils.java
+++ b/services/core/java/com/android/server/am/ActivityManagerUtils.java
@@ -129,14 +129,6 @@
}
/**
- * @param shortInstanceName {@link ServiceRecord#shortInstanceName}.
- * @return hash of the ServiceRecord's shortInstanceName, combined with ANDROID_ID.
- */
- public static int hashComponentNameForAtom(String shortInstanceName) {
- return getUnsignedHashUnCached(shortInstanceName) ^ getAndroidIdHash();
- }
-
- /**
* Helper method to log an unsafe intent event.
*/
public static void logUnsafeIntentEvent(int event, int callingUid,
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index d0ab287..626b70b 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -275,7 +275,7 @@
@VisibleForTesting static final String DEFAULT_COMPACT_PROC_STATE_THROTTLE =
String.valueOf(ActivityManager.PROCESS_STATE_RECEIVER);
@VisibleForTesting static final long DEFAULT_FREEZER_DEBOUNCE_TIMEOUT = 10_000L;
- @VisibleForTesting static final boolean DEFAULT_FREEZER_EXEMPT_INST_PKG = true;
+ @VisibleForTesting static final boolean DEFAULT_FREEZER_EXEMPT_INST_PKG = false;
@VisibleForTesting static final boolean DEFAULT_FREEZER_BINDER_ENABLED = true;
@VisibleForTesting static final long DEFAULT_FREEZER_BINDER_DIVISOR = 4;
@VisibleForTesting static final int DEFAULT_FREEZER_BINDER_OFFSET = 500;
diff --git a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
index fc8ad6bc..b68572f 100644
--- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
+++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
@@ -507,7 +507,8 @@
r.appInfo.uid,
r.shortInstanceName,
fgsState, // FGS State
- r.isFgsAllowedWIU(), // allowWhileInUsePermissionInFgs
+ // TODO: Also log "forStart"
+ r.isFgsAllowedWiu_forCapabilities(), // allowWhileInUsePermissionInFgs
r.getFgsAllowStart(), // fgsStartReasonCode
r.appInfo.targetSdkVersion,
r.mRecentCallingUid,
@@ -518,7 +519,7 @@
r.mFgsNotificationShown,
0, // durationMs
r.mStartForegroundCount,
- ActivityManagerUtils.hashComponentNameForAtom(r.shortInstanceName),
+ 0, // Short instance name -- no longer logging it.
r.mFgsHasNotificationPermission,
r.foregroundServiceType,
0,
@@ -535,12 +536,12 @@
ActivityManager.PROCESS_CAPABILITY_NONE,
apiDurationBeforeFgsStart,
apiDurationAfterFgsEnd,
- r.mAllowWhileInUsePermissionInFgsReasonNoBinding,
- r.mAllowWIUInBindService,
- r.mAllowWIUByBindings,
- r.mAllowStartForegroundNoBinding,
- r.mAllowStartInBindService,
- r.mAllowStartByBindings,
+ r.mAllowWiu_noBinding,
+ r.mAllowWiu_inBindService,
+ r.mAllowWiu_byBindings,
+ r.mAllowStart_noBinding,
+ r.mAllowStart_inBindService,
+ r.mAllowStart_byBindings,
FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_NA,
false
);
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 3424578a..b507a60 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -2254,7 +2254,7 @@
if (s.isForeground) {
final int fgsType = s.foregroundServiceType;
- if (s.isFgsAllowedWIU()) {
+ if (s.isFgsAllowedWiu_forCapabilities()) {
capabilityFromFGS |=
(fgsType & FOREGROUND_SERVICE_TYPE_LOCATION)
!= 0 ? PROCESS_CAPABILITY_FOREGROUND_LOCATION : 0;
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 0fba739..08b129e 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -37,6 +37,9 @@
import android.app.Notification;
import android.app.PendingIntent;
import android.app.RemoteServiceException.CannotPostForegroundServiceNotificationException;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -81,6 +84,37 @@
// Maximum number of times it can fail during execution before giving up.
static final int MAX_DONE_EXECUTING_COUNT = 6;
+
+ // Compat IDs for the new FGS logic. For now, we just disable all of them.
+ // TODO: Enable them at some point, but only for V+ builds.
+
+ /**
+ * Compat ID to enable the new FGS start logic, for permission calculation used for
+ * per-FGS-type eligibility calculation.
+ * (See also android.app.ForegroundServiceTypePolicy)
+ */
+ @ChangeId
+ // @EnabledAfter(targetSdkVersion = VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Disabled
+ static final long USE_NEW_WIU_LOGIC_FOR_START = 311208629L;
+
+ /**
+ * Compat ID to enable the new FGS start logic, for capability calculation.
+ */
+ @ChangeId
+ // Always enabled
+ @Disabled
+ static final long USE_NEW_WIU_LOGIC_FOR_CAPABILITIES = 313677553L;
+
+ /**
+ * Compat ID to enable the new FGS start logic, for deciding whether to allow FGS start from
+ * the background.
+ */
+ @ChangeId
+ // @EnabledAfter(targetSdkVersion = VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Disabled
+ static final long USE_NEW_BFSL_LOGIC = 311208749L;
+
final ActivityManagerService ams;
final ComponentName name; // service component.
final ComponentName instanceName; // service component's per-instance name.
@@ -178,7 +212,7 @@
// If it's not DENIED, while-in-use permissions are allowed.
// while-in-use permissions in FGS started from background might be restricted.
@PowerExemptionManager.ReasonCode
- int mAllowWhileInUsePermissionInFgsReasonNoBinding = REASON_DENIED;
+ int mAllowWiu_noBinding = REASON_DENIED;
// A copy of mAllowWhileInUsePermissionInFgs's value when the service is entering FGS state.
boolean mAllowWhileInUsePermissionInFgsAtEntering;
@@ -208,7 +242,7 @@
// allow the service becomes foreground service? Service started from background may not be
// allowed to become a foreground service.
@PowerExemptionManager.ReasonCode
- int mAllowStartForegroundNoBinding = REASON_DENIED;
+ int mAllowStart_noBinding = REASON_DENIED;
// A copy of mAllowStartForeground's value when the service is entering FGS state.
@PowerExemptionManager.ReasonCode
int mAllowStartForegroundAtEntering = REASON_DENIED;
@@ -220,72 +254,172 @@
boolean mLoggedInfoAllowStartForeground;
@PowerExemptionManager.ReasonCode
- int mAllowWIUInBindService = REASON_DENIED;
+ int mAllowWiu_inBindService = REASON_DENIED;
@PowerExemptionManager.ReasonCode
- int mAllowWIUByBindings = REASON_DENIED;
+ int mAllowWiu_byBindings = REASON_DENIED;
@PowerExemptionManager.ReasonCode
- int mAllowStartInBindService = REASON_DENIED;
+ int mAllowStart_inBindService = REASON_DENIED;
@PowerExemptionManager.ReasonCode
- int mAllowStartByBindings = REASON_DENIED;
+ int mAllowStart_byBindings = REASON_DENIED;
- @PowerExemptionManager.ReasonCode
- int getFgsAllowWIU() {
- return mAllowWhileInUsePermissionInFgsReasonNoBinding != REASON_DENIED
- ? mAllowWhileInUsePermissionInFgsReasonNoBinding
- : mAllowWIUInBindService;
+ /**
+ * Whether to use the new "while-in-use permission" logic for FGS start
+ */
+ private boolean useNewWiuLogic_forStart() {
+ return Flags.newFgsRestrictionLogic() // This flag should only be set on V+
+ && CompatChanges.isChangeEnabled(USE_NEW_WIU_LOGIC_FOR_START, appInfo.uid);
}
- boolean isFgsAllowedWIU() {
- return getFgsAllowWIU() != REASON_DENIED;
+ /**
+ * Whether to use the new "while-in-use permission" logic for capabilities
+ */
+ private boolean useNewWiuLogic_forCapabilities() {
+ return Flags.newFgsRestrictionLogic() // This flag should only be set on V+
+ && CompatChanges.isChangeEnabled(USE_NEW_WIU_LOGIC_FOR_CAPABILITIES, appInfo.uid);
}
+ /**
+ * Whether to use the new "FGS BG start exemption" logic.
+ */
+ private boolean useNewBfslLogic() {
+ return Flags.newFgsRestrictionLogic() // This flag should only be set on V+
+ && CompatChanges.isChangeEnabled(USE_NEW_BFSL_LOGIC, appInfo.uid);
+ }
+
+
+ @PowerExemptionManager.ReasonCode
+ private int getFgsAllowWiu_legacy() {
+ // In the legacy mode (U-), we use mAllowWiu_inBindService and mAllowWiu_noBinding.
+ return reasonOr(
+ mAllowWiu_noBinding,
+ mAllowWiu_inBindService
+ );
+ }
+
+ @PowerExemptionManager.ReasonCode
+ private int getFgsAllowWiu_new() {
+ // In the new mode, use by-bindings instead of in-bind-service
+ return reasonOr(
+ mAllowWiu_noBinding,
+ mAllowWiu_byBindings
+ );
+ }
+
+ /**
+ * We use this logic for ForegroundServiceTypePolicy and UIDT eligibility check.
+ */
+ @PowerExemptionManager.ReasonCode
+ int getFgsAllowWiu_forStart() {
+ if (useNewWiuLogic_forStart()) {
+ return getFgsAllowWiu_new();
+ } else {
+ return getFgsAllowWiu_legacy();
+ }
+ }
+
+ /**
+ * We use this logic for the capability calculation in oom-adjuster.
+ */
+ @PowerExemptionManager.ReasonCode
+ int getFgsAllowWiu_forCapabilities() {
+ if (useNewWiuLogic_forCapabilities()) {
+ return getFgsAllowWiu_new();
+ } else {
+ // If alwaysUseNewLogicForWiuCapabilities() isn't set, just use the same logic as
+ // getFgsAllowWiu_forStart().
+ return getFgsAllowWiu_forStart();
+ }
+ }
+
+ /**
+ * We use this logic for ForegroundServiceTypePolicy and UIDT eligibility check.
+ */
+ boolean isFgsAllowedWiu_forStart() {
+ return getFgsAllowWiu_forStart() != REASON_DENIED;
+ }
+
+ /**
+ * We use this logic for the capability calculation in oom-adjuster.
+ */
+ boolean isFgsAllowedWiu_forCapabilities() {
+ return getFgsAllowWiu_forCapabilities() != REASON_DENIED;
+ }
+
+ @PowerExemptionManager.ReasonCode
+ private int getFgsAllowStart_legacy() {
+ // This is used for BFSL (background FGS launch) exemption.
+ // Originally -- on U-QPR1 and before -- we only used [in-bind-service] + [no-binding].
+ // This would exclude certain "valid" situations, so in U-QPR2, we started
+ // using [by-bindings] too.
+ return reasonOr(
+ mAllowStart_noBinding,
+ mAllowStart_inBindService,
+ mAllowStart_byBindings
+ );
+ }
+
+ @PowerExemptionManager.ReasonCode
+ private int getFgsAllowStart_new() {
+ // In the new mode, we don't use [in-bind-service].
+ return reasonOr(
+ mAllowStart_noBinding,
+ mAllowStart_byBindings
+ );
+ }
+
+ /**
+ * Calculate a BFSL exemption code.
+ */
@PowerExemptionManager.ReasonCode
int getFgsAllowStart() {
- return mAllowStartForegroundNoBinding != REASON_DENIED
- ? mAllowStartForegroundNoBinding
- : (mAllowStartByBindings != REASON_DENIED
- ? mAllowStartByBindings
- : mAllowStartInBindService);
+ if (useNewBfslLogic()) {
+ return getFgsAllowStart_new();
+ } else {
+ return getFgsAllowStart_legacy();
+ }
}
+ /**
+ * Return whether BFSL is allowed or not.
+ */
boolean isFgsAllowedStart() {
return getFgsAllowStart() != REASON_DENIED;
}
- void clearFgsAllowWIU() {
- mAllowWhileInUsePermissionInFgsReasonNoBinding = REASON_DENIED;
- mAllowWIUInBindService = REASON_DENIED;
- mAllowWIUByBindings = REASON_DENIED;
+ void clearFgsAllowWiu() {
+ mAllowWiu_noBinding = REASON_DENIED;
+ mAllowWiu_inBindService = REASON_DENIED;
+ mAllowWiu_byBindings = REASON_DENIED;
}
void clearFgsAllowStart() {
- mAllowStartForegroundNoBinding = REASON_DENIED;
- mAllowStartInBindService = REASON_DENIED;
- mAllowStartByBindings = REASON_DENIED;
+ mAllowStart_noBinding = REASON_DENIED;
+ mAllowStart_inBindService = REASON_DENIED;
+ mAllowStart_byBindings = REASON_DENIED;
}
@PowerExemptionManager.ReasonCode
- int reasonOr(@PowerExemptionManager.ReasonCode int first,
+ static int reasonOr(
+ @PowerExemptionManager.ReasonCode int first,
@PowerExemptionManager.ReasonCode int second) {
return first != REASON_DENIED ? first : second;
}
- boolean allowedChanged(@PowerExemptionManager.ReasonCode int first,
- @PowerExemptionManager.ReasonCode int second) {
- return (first == REASON_DENIED) != (second == REASON_DENIED);
+ @PowerExemptionManager.ReasonCode
+ static int reasonOr(
+ @PowerExemptionManager.ReasonCode int first,
+ @PowerExemptionManager.ReasonCode int second,
+ @PowerExemptionManager.ReasonCode int third) {
+ return first != REASON_DENIED ? first : reasonOr(second, third);
}
- String changeMessage(@PowerExemptionManager.ReasonCode int first,
- @PowerExemptionManager.ReasonCode int second) {
- return reasonOr(first, second) == REASON_DENIED ? "DENIED"
- : ("ALLOWED ("
- + reasonCodeToString(first)
- + "+"
- + reasonCodeToString(second)
- + ")");
+ boolean allowedChanged(
+ @PowerExemptionManager.ReasonCode int legacyCode,
+ @PowerExemptionManager.ReasonCode int newCode) {
+ return (legacyCode == REASON_DENIED) != (newCode == REASON_DENIED);
}
private String getFgsInfoForWtf() {
@@ -295,15 +429,14 @@
}
void maybeLogFgsLogicChange() {
- final int origWiu = reasonOr(mAllowWhileInUsePermissionInFgsReasonNoBinding,
- mAllowWIUInBindService);
- final int newWiu = reasonOr(mAllowWhileInUsePermissionInFgsReasonNoBinding,
- mAllowWIUByBindings);
- final int origStart = reasonOr(mAllowStartForegroundNoBinding, mAllowStartInBindService);
- final int newStart = reasonOr(mAllowStartForegroundNoBinding, mAllowStartByBindings);
+ final int wiuLegacy = getFgsAllowWiu_legacy();
+ final int wiuNew = getFgsAllowWiu_new();
- final boolean wiuChanged = allowedChanged(origWiu, newWiu);
- final boolean startChanged = allowedChanged(origStart, newStart);
+ final int startLegacy = getFgsAllowStart_legacy();
+ final int startNew = getFgsAllowStart_new();
+
+ final boolean wiuChanged = allowedChanged(wiuLegacy, wiuNew);
+ final boolean startChanged = allowedChanged(startLegacy, startNew);
if (!wiuChanged && !startChanged) {
return;
@@ -311,15 +444,14 @@
final String message = "FGS logic changed:"
+ (wiuChanged ? " [WIU changed]" : "")
+ (startChanged ? " [BFSL changed]" : "")
- + " OW:" // Orig-WIU
- + changeMessage(mAllowWhileInUsePermissionInFgsReasonNoBinding,
- mAllowWIUInBindService)
- + " NW:" // New-WIU
- + changeMessage(mAllowWhileInUsePermissionInFgsReasonNoBinding, mAllowWIUByBindings)
- + " OS:" // Orig-start
- + changeMessage(mAllowStartForegroundNoBinding, mAllowStartInBindService)
- + " NS:" // New-start
- + changeMessage(mAllowStartForegroundNoBinding, mAllowStartByBindings)
+ + " Orig WIU:"
+ + reasonCodeToString(wiuLegacy)
+ + " New WIU:"
+ + reasonCodeToString(wiuNew)
+ + " Orig BFSL:"
+ + reasonCodeToString(startLegacy)
+ + " New BFSL:"
+ + reasonCodeToString(startNew)
+ getFgsInfoForWtf();
Slog.wtf(TAG_SERVICE, message);
}
@@ -587,6 +719,7 @@
proto.write(ServiceRecordProto.AppInfo.RES_DIR, appInfo.publicSourceDir);
}
proto.write(ServiceRecordProto.AppInfo.DATA_DIR, appInfo.dataDir);
+ proto.write(ServiceRecordProto.AppInfo.TARGET_SDK_VERSION, appInfo.targetSdkVersion);
proto.end(appInfoToken);
}
if (app != null) {
@@ -611,8 +744,10 @@
ProtoUtils.toDuration(proto, ServiceRecordProto.LAST_ACTIVITY_TIME, lastActivity, now);
ProtoUtils.toDuration(proto, ServiceRecordProto.RESTART_TIME, restartTime, now);
proto.write(ServiceRecordProto.CREATED_FROM_FG, createdFromFg);
+
+ // TODO: Log "forStart" too.
proto.write(ServiceRecordProto.ALLOW_WHILE_IN_USE_PERMISSION_IN_FGS,
- isFgsAllowedWIU());
+ isFgsAllowedWiu_forCapabilities());
if (startRequested || delayedStop || lastStartId != 0) {
long startToken = proto.start(ServiceRecordProto.START);
@@ -691,15 +826,25 @@
proto.end(shortFgsToken);
}
+ // TODO: Dump all the mAllowWiu* and mAllowStart* fields as needed.
+
proto.end(token);
}
+ void dumpReasonCode(PrintWriter pw, String prefix, String fieldName, int code) {
+ pw.print(prefix);
+ pw.print(fieldName);
+ pw.print("=");
+ pw.println(PowerExemptionManager.reasonCodeToString(code));
+ }
+
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("intent={");
pw.print(intent.getIntent().toShortString(false, true, false, false));
pw.println('}');
pw.print(prefix); pw.print("packageName="); pw.println(packageName);
pw.print(prefix); pw.print("processName="); pw.println(processName);
+ pw.print(prefix); pw.print("targetSdkVersion="); pw.println(appInfo.targetSdkVersion);
if (permission != null) {
pw.print(prefix); pw.print("permission="); pw.println(permission);
}
@@ -727,26 +872,38 @@
pw.print(prefix); pw.print("mIsAllowedBgActivityStartsByStart=");
pw.println(mBackgroundStartPrivilegesByStartMerged);
}
- pw.print(prefix); pw.print("mAllowWhileInUsePermissionInFgsReason=");
- pw.println(PowerExemptionManager.reasonCodeToString(
- mAllowWhileInUsePermissionInFgsReasonNoBinding));
- pw.print(prefix); pw.print("mAllowWIUInBindService=");
- pw.println(PowerExemptionManager.reasonCodeToString(mAllowWIUInBindService));
- pw.print(prefix); pw.print("mAllowWIUByBindings=");
- pw.println(PowerExemptionManager.reasonCodeToString(mAllowWIUByBindings));
+ pw.print(prefix); pw.print("useNewWiuLogic_forCapabilities()=");
+ pw.println(useNewWiuLogic_forCapabilities());
+ pw.print(prefix); pw.print("useNewWiuLogic_forStart()=");
+ pw.println(useNewWiuLogic_forStart());
+ pw.print(prefix); pw.print("useNewBfslLogic()=");
+ pw.println(useNewBfslLogic());
+
+ dumpReasonCode(pw, prefix, "mAllowWiu_noBinding", mAllowWiu_noBinding);
+ dumpReasonCode(pw, prefix, "mAllowWiu_inBindService", mAllowWiu_inBindService);
+ dumpReasonCode(pw, prefix, "mAllowWiu_byBindings", mAllowWiu_byBindings);
+
+ dumpReasonCode(pw, prefix, "getFgsAllowWiu_legacy", getFgsAllowWiu_legacy());
+ dumpReasonCode(pw, prefix, "getFgsAllowWiu_new", getFgsAllowWiu_new());
+
+ dumpReasonCode(pw, prefix, "getFgsAllowWiu_forStart", getFgsAllowWiu_forStart());
+ dumpReasonCode(pw, prefix, "getFgsAllowWiu_forCapabilities",
+ getFgsAllowWiu_forCapabilities());
pw.print(prefix); pw.print("allowUiJobScheduling="); pw.println(mAllowUiJobScheduling);
pw.print(prefix); pw.print("recentCallingPackage=");
pw.println(mRecentCallingPackage);
pw.print(prefix); pw.print("recentCallingUid=");
pw.println(mRecentCallingUid);
- pw.print(prefix); pw.print("allowStartForeground=");
- pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartForegroundNoBinding));
- pw.print(prefix); pw.print("mAllowStartInBindService=");
- pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartInBindService));
- pw.print(prefix); pw.print("mAllowStartByBindings=");
- pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartByBindings));
+
+ dumpReasonCode(pw, prefix, "mAllowStart_noBinding", mAllowStart_noBinding);
+ dumpReasonCode(pw, prefix, "mAllowStart_inBindService", mAllowStart_inBindService);
+ dumpReasonCode(pw, prefix, "mAllowStart_byBindings", mAllowStart_byBindings);
+
+ dumpReasonCode(pw, prefix, "getFgsAllowStart_legacy", getFgsAllowStart_legacy());
+ dumpReasonCode(pw, prefix, "getFgsAllowStart_new", getFgsAllowStart_new());
+ dumpReasonCode(pw, prefix, "getFgsAllowStart", getFgsAllowStart());
pw.print(prefix); pw.print("startForegroundCount=");
pw.println(mStartForegroundCount);
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index a6b532c..2b81dbc 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -2059,9 +2059,6 @@
mTargetUserId = targetUserId;
userSwitchUiEnabled = mUserSwitchUiEnabled;
}
- if (android.multiuser.Flags.useAllCpusDuringUserSwitch()) {
- mInjector.setHasTopUi(true);
- }
if (userSwitchUiEnabled) {
UserInfo currentUserInfo = getUserInfo(currentUserId);
Pair<UserInfo, UserInfo> userNames = new Pair<>(currentUserInfo, targetUserInfo);
@@ -2130,9 +2127,6 @@
}
private void endUserSwitch() {
- if (android.multiuser.Flags.useAllCpusDuringUserSwitch()) {
- mInjector.setHasTopUi(false);
- }
final int nextUserId;
synchronized (mLock) {
nextUserId = ObjectUtils.getOrElse(mPendingTargetUserIds.poll(), UserHandle.USER_NULL);
@@ -3050,8 +3044,8 @@
/**
* Returns whether the given user requires credential entry at this time. This is used to
- * intercept activity launches for locked work apps due to work challenge being triggered
- * or when the profile user is yet to be unlocked.
+ * intercept activity launches for apps corresponding to locked profiles due to separate
+ * challenge being triggered or when the profile user is yet to be unlocked.
*/
protected boolean shouldConfirmCredentials(@UserIdInt int userId) {
if (getStartedUserState(userId) == null) {
@@ -3816,15 +3810,6 @@
getSystemServiceManager().onUserStarting(TimingsTraceAndSlog.newAsyncLog(), userId);
}
- void setHasTopUi(boolean hasTopUi) {
- try {
- Slogf.i(TAG, "Setting hasTopUi to " + hasTopUi);
- mService.setHasTopUi(hasTopUi);
- } catch (RemoteException e) {
- Slogf.e(TAG, "Failed to allow using all CPU cores", e);
- }
- }
-
void onSystemUserVisibilityChanged(boolean visible) {
getUserManagerInternal().onSystemUserVisibilityChanged(visible);
}
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index d9e8ddd..654aebd 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -28,3 +28,10 @@
description: "Restrict network access for certain applications in BFGS process state"
bug: "304347838"
}
+# Whether to use the new while-in-use / BG-FGS-start logic
+flag {
+ namespace: "backstage_power"
+ name: "new_fgs_restriction_logic"
+ description: "Enable the new FGS restriction logic"
+ bug: "276963716"
+}
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index b3fb9c9..8b7e56e 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -20,6 +20,7 @@
import static android.content.Intent.ACTION_PACKAGE_REMOVED;
import static android.content.Intent.EXTRA_REPLACING;
import static android.server.app.Flags.gameDefaultFrameRate;
+import static android.server.app.Flags.disableGameModeWhenAppTop;
import static com.android.internal.R.styleable.GameModeConfig_allowGameAngleDriver;
import static com.android.internal.R.styleable.GameModeConfig_allowGameDownscaling;
@@ -181,7 +182,9 @@
@Nullable
final MyUidObserver mUidObserver;
@GuardedBy("mUidObserverLock")
- private final Set<Integer> mForegroundGameUids = new HashSet<>();
+ private final Set<Integer> mGameForegroundUids = new HashSet<>();
+ @GuardedBy("mUidObserverLock")
+ private final Set<Integer> mNonGameForegroundUids = new HashSet<>();
private final GameManagerServiceSystemPropertiesWrapper mSysProps;
private float mGameDefaultFrameRateValue;
@@ -238,12 +241,10 @@
FileUtils.S_IRUSR | FileUtils.S_IWUSR
| FileUtils.S_IRGRP | FileUtils.S_IWGRP,
-1, -1);
- if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_GAME_SERVICE)) {
+ if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_GAME_SERVICE)) {
mGameServiceController = new GameServiceController(
context, BackgroundThread.getExecutor(),
- new GameServiceProviderSelectorImpl(
- context.getResources(),
- context.getPackageManager()),
+ new GameServiceProviderSelectorImpl(context.getResources(), mPackageManager),
new GameServiceProviderInstanceFactoryImpl(context));
} else {
mGameServiceController = null;
@@ -2245,7 +2246,7 @@
// Update all foreground games' frame rate.
synchronized (mUidObserverLock) {
- for (int uid : mForegroundGameUids) {
+ for (int uid : mGameForegroundUids) {
setGameDefaultFrameRateOverride(uid, getGameDefaultFrameRate(isEnabled));
}
}
@@ -2261,31 +2262,44 @@
@Override
public void onUidGone(int uid, boolean disabled) {
synchronized (mUidObserverLock) {
- disableGameMode(uid);
+ handleUidMovedOffTop(uid);
}
}
@Override
public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
+ switch (procState) {
+ case ActivityManager.PROCESS_STATE_TOP:
+ handleUidMovedToTop(uid);
+ return;
+ default:
+ handleUidMovedOffTop(uid);
+ }
+ }
+
+ private void handleUidMovedToTop(int uid) {
+ final String[] packages = mPackageManager.getPackagesForUid(uid);
+ if (packages == null || packages.length == 0) {
+ return;
+ }
+
+ final int userId = mContext.getUserId();
+ final boolean isNotGame = Arrays.stream(packages).noneMatch(
+ p -> isPackageGame(p, userId));
synchronized (mUidObserverLock) {
-
- if (procState != ActivityManager.PROCESS_STATE_TOP) {
- disableGameMode(uid);
+ if (isNotGame) {
+ if (disableGameModeWhenAppTop()) {
+ if (!mGameForegroundUids.isEmpty() && mNonGameForegroundUids.isEmpty()) {
+ Slog.v(TAG, "Game power mode OFF (first non-game in foreground)");
+ mPowerManagerInternal.setPowerMode(Mode.GAME, false);
+ }
+ mNonGameForegroundUids.add(uid);
+ }
return;
}
-
- final String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
- if (packages == null || packages.length == 0) {
- return;
- }
-
- final int userId = mContext.getUserId();
- if (!Arrays.stream(packages).anyMatch(p -> isPackageGame(p, userId))) {
- return;
- }
-
- if (mForegroundGameUids.isEmpty()) {
- Slog.v(TAG, "Game power mode ON (process state was changed to foreground)");
+ if (mGameForegroundUids.isEmpty() && (!disableGameModeWhenAppTop()
+ || mNonGameForegroundUids.isEmpty())) {
+ Slog.v(TAG, "Game power mode ON (first game in foreground)");
mPowerManagerInternal.setPowerMode(Mode.GAME, true);
}
final boolean isGameDefaultFrameRateDisabled =
@@ -2293,22 +2307,26 @@
PROPERTY_DEBUG_GFX_GAME_DEFAULT_FRAME_RATE_DISABLED, false);
setGameDefaultFrameRateOverride(uid,
getGameDefaultFrameRate(!isGameDefaultFrameRateDisabled));
- mForegroundGameUids.add(uid);
+ mGameForegroundUids.add(uid);
}
}
- private void disableGameMode(int uid) {
+ private void handleUidMovedOffTop(int uid) {
synchronized (mUidObserverLock) {
- if (!mForegroundGameUids.contains(uid)) {
- return;
+ if (mGameForegroundUids.contains(uid)) {
+ mGameForegroundUids.remove(uid);
+ if (mGameForegroundUids.isEmpty() && (!disableGameModeWhenAppTop()
+ || mNonGameForegroundUids.isEmpty())) {
+ Slog.v(TAG, "Game power mode OFF (no games in foreground)");
+ mPowerManagerInternal.setPowerMode(Mode.GAME, false);
+ }
+ } else if (disableGameModeWhenAppTop() && mNonGameForegroundUids.contains(uid)) {
+ mNonGameForegroundUids.remove(uid);
+ if (mNonGameForegroundUids.isEmpty() && !mGameForegroundUids.isEmpty()) {
+ Slog.v(TAG, "Game power mode ON (only games in foreground)");
+ mPowerManagerInternal.setPowerMode(Mode.GAME, true);
+ }
}
- mForegroundGameUids.remove(uid);
- if (!mForegroundGameUids.isEmpty()) {
- return;
- }
- Slog.v(TAG,
- "Game power mode OFF (process remomved or state changed to background)");
- mPowerManagerInternal.setPowerMode(Mode.GAME, false);
}
}
}
diff --git a/services/core/java/com/android/server/app/flags.aconfig b/services/core/java/com/android/server/app/flags.aconfig
index f2e4783..0673013 100644
--- a/services/core/java/com/android/server/app/flags.aconfig
+++ b/services/core/java/com/android/server/app/flags.aconfig
@@ -6,4 +6,11 @@
description: "This flag guards the new behavior with the addition of Game Default Frame Rate feature."
bug: "286084594"
is_fixed_read_only: true
-}
\ No newline at end of file
+}
+
+flag {
+ name: "disable_game_mode_when_app_top"
+ namespace: "game"
+ description: "Disable game power mode when a non-game app is also top and visible"
+ bug: "299295925"
+}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 8091753..dada72e 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -56,6 +56,7 @@
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import android.util.PrintWriterPrinter;
import com.android.internal.annotations.GuardedBy;
@@ -1640,7 +1641,7 @@
return mBtHelper.getLeAudioDeviceGroupId(device);
}
- /*package*/ List<String> getLeAudioGroupAddresses(int groupId) {
+ /*package*/ List<Pair<String, String>> getLeAudioGroupAddresses(int groupId) {
return mBtHelper.getLeAudioGroupAddresses(groupId);
}
@@ -2651,9 +2652,9 @@
}
}
- List<String> getDeviceAddresses(AudioDeviceAttributes device) {
+ List<String> getDeviceIdentityAddresses(AudioDeviceAttributes device) {
synchronized (mDeviceStateLock) {
- return mDeviceInventory.getDeviceAddresses(device);
+ return mDeviceInventory.getDeviceIdentityAddresses(device);
}
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index d138f24..e05824a 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -566,23 +566,40 @@
final int mDeviceType;
final @NonNull String mDeviceName;
final @NonNull String mDeviceAddress;
+ @NonNull String mDeviceIdentityAddress;
int mDeviceCodecFormat;
- @NonNull String mPeerDeviceAddress;
final int mGroupId;
+ @NonNull String mPeerDeviceAddress;
+ @NonNull String mPeerIdentityDeviceAddress;
/** Disabled operating modes for this device. Use a negative logic so that by default
* an empty list means all modes are allowed.
* See BluetoothAdapter.AUDIO_MODE_DUPLEX and BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY */
@NonNull ArraySet<String> mDisabledModes = new ArraySet(0);
- DeviceInfo(int deviceType, String deviceName, String deviceAddress,
- int deviceCodecFormat, String peerDeviceAddress, int groupId) {
+ DeviceInfo(int deviceType, String deviceName, String address,
+ String identityAddress, int codecFormat,
+ int groupId, String peerAddress, String peerIdentityAddress) {
mDeviceType = deviceType;
- mDeviceName = deviceName == null ? "" : deviceName;
- mDeviceAddress = deviceAddress == null ? "" : deviceAddress;
- mDeviceCodecFormat = deviceCodecFormat;
- mPeerDeviceAddress = peerDeviceAddress == null ? "" : peerDeviceAddress;
+ mDeviceName = TextUtils.emptyIfNull(deviceName);
+ mDeviceAddress = TextUtils.emptyIfNull(address);
+ mDeviceIdentityAddress = TextUtils.emptyIfNull(identityAddress);
+ mDeviceCodecFormat = codecFormat;
mGroupId = groupId;
+ mPeerDeviceAddress = TextUtils.emptyIfNull(peerAddress);
+ mPeerIdentityDeviceAddress = TextUtils.emptyIfNull(peerIdentityAddress);
+ }
+
+ /** Constructor for all devices except A2DP sink and LE Audio */
+ DeviceInfo(int deviceType, String deviceName, String address) {
+ this(deviceType, deviceName, address, null, AudioSystem.AUDIO_FORMAT_DEFAULT);
+ }
+
+ /** Constructor for A2DP sink devices */
+ DeviceInfo(int deviceType, String deviceName, String address,
+ String identityAddress, int codecFormat) {
+ this(deviceType, deviceName, address, identityAddress, codecFormat,
+ BluetoothLeAudio.GROUP_ID_INVALID, null, null);
}
void setModeDisabled(String mode) {
@@ -601,25 +618,20 @@
return isModeEnabled(BluetoothAdapter.AUDIO_MODE_DUPLEX);
}
- DeviceInfo(int deviceType, String deviceName, String deviceAddress,
- int deviceCodecFormat) {
- this(deviceType, deviceName, deviceAddress, deviceCodecFormat,
- null, BluetoothLeAudio.GROUP_ID_INVALID);
- }
-
- DeviceInfo(int deviceType, String deviceName, String deviceAddress) {
- this(deviceType, deviceName, deviceAddress, AudioSystem.AUDIO_FORMAT_DEFAULT);
- }
-
@Override
public String toString() {
return "[DeviceInfo: type:0x" + Integer.toHexString(mDeviceType)
+ " (" + AudioSystem.getDeviceName(mDeviceType)
+ ") name:" + mDeviceName
+ " addr:" + Utils.anonymizeBluetoothAddress(mDeviceType, mDeviceAddress)
+ + " identity addr:"
+ + Utils.anonymizeBluetoothAddress(mDeviceType, mDeviceIdentityAddress)
+ " codec: " + Integer.toHexString(mDeviceCodecFormat)
- + " peer addr:" + mPeerDeviceAddress
+ " group:" + mGroupId
+ + " peer addr:"
+ + Utils.anonymizeBluetoothAddress(mDeviceType, mPeerDeviceAddress)
+ + " peer identity addr:"
+ + Utils.anonymizeBluetoothAddress(mDeviceType, mPeerIdentityDeviceAddress)
+ " disabled modes: " + mDisabledModes + "]";
}
@@ -947,20 +959,44 @@
}
+ /**
+ * Goes over all connected LE Audio devices in the provided group ID and
+ * update:
+ * - the peer address according to the addres of other device in the same
+ * group (can also clear the peer address is not anymore in the group)
+ * - The dentity address if not yet set.
+ * LE Audio buds in a pair are in the same group.
+ * @param groupId the LE Audio group to update
+ */
/*package*/ void onUpdateLeAudioGroupAddresses(int groupId) {
synchronized (mDevicesLock) {
+ // <address, identy address>
+ List<Pair<String, String>> addresses = new ArrayList<>();
for (DeviceInfo di : mConnectedDevices.values()) {
if (di.mGroupId == groupId) {
- List<String> addresses = mDeviceBroker.getLeAudioGroupAddresses(groupId);
+ if (addresses.isEmpty()) {
+ addresses = mDeviceBroker.getLeAudioGroupAddresses(groupId);
+ }
if (di.mPeerDeviceAddress.equals("")) {
- for (String addr : addresses) {
- if (!addr.equals(di.mDeviceAddress)) {
- di.mPeerDeviceAddress = addr;
+ for (Pair<String, String> addr : addresses) {
+ if (!addr.first.equals(di.mDeviceAddress)) {
+ di.mPeerDeviceAddress = addr.first;
+ di.mPeerIdentityDeviceAddress = addr.second;
break;
}
}
- } else if (!addresses.contains(di.mPeerDeviceAddress)) {
+ } else if (!addresses.contains(
+ new Pair(di.mPeerDeviceAddress, di.mPeerIdentityDeviceAddress))) {
di.mPeerDeviceAddress = "";
+ di.mPeerIdentityDeviceAddress = "";
+ }
+ if (di.mDeviceIdentityAddress.equals("")) {
+ for (Pair<String, String> addr : addresses) {
+ if (addr.first.equals(di.mDeviceAddress)) {
+ di.mDeviceIdentityAddress = addr.second;
+ break;
+ }
+ }
}
}
}
@@ -1964,7 +2000,7 @@
mDeviceBroker.clearA2dpSuspended(true /* internalOnly */);
final DeviceInfo di = new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
- address, codec);
+ address, btInfo.mDevice.getIdentityAddress(), codec);
final String diKey = di.getKey();
mConnectedDevices.put(diKey, di);
// on a connection always overwrite the device seen by AudioPolicy, see comment above when
@@ -2381,12 +2417,15 @@
// Find LE Group ID and peer headset address if available
final int groupId = mDeviceBroker.getLeAudioDeviceGroupId(btInfo.mDevice);
String peerAddress = "";
+ String peerIdentityAddress = "";
if (groupId != BluetoothLeAudio.GROUP_ID_INVALID) {
- List<String> addresses = mDeviceBroker.getLeAudioGroupAddresses(groupId);
+ List<Pair<String, String>> addresses =
+ mDeviceBroker.getLeAudioGroupAddresses(groupId);
if (addresses.size() > 1) {
- for (String addr : addresses) {
- if (!addr.equals(address)) {
- peerAddress = addr;
+ for (Pair<String, String> addr : addresses) {
+ if (!addr.first.equals(address)) {
+ peerAddress = addr.first;
+ peerIdentityAddress = addr.second;
break;
}
}
@@ -2420,8 +2459,9 @@
// Reset LEA suspend state each time a new sink is connected
mDeviceBroker.clearLeAudioSuspended(true /* internalOnly */);
mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address),
- new DeviceInfo(device, name, address, codec,
- peerAddress, groupId));
+ new DeviceInfo(device, name, address,
+ btInfo.mDevice.getIdentityAddress(), codec,
+ groupId, peerAddress, peerIdentityAddress));
mDeviceBroker.postAccessoryPlugMediaUnmute(device);
setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false);
addAudioDeviceInInventoryIfNeeded(device, address, peerAddress,
@@ -2806,18 +2846,19 @@
mDevRoleCapturePresetDispatchers.finishBroadcast();
}
- List<String> getDeviceAddresses(AudioDeviceAttributes device) {
+ List<String> getDeviceIdentityAddresses(AudioDeviceAttributes device) {
List<String> addresses = new ArrayList<String>();
final String key = DeviceInfo.makeDeviceListKey(device.getInternalType(),
device.getAddress());
synchronized (mDevicesLock) {
DeviceInfo di = mConnectedDevices.get(key);
if (di != null) {
- if (!di.mDeviceAddress.isEmpty()) {
- addresses.add(di.mDeviceAddress);
+ if (!di.mDeviceIdentityAddress.isEmpty()) {
+ addresses.add(di.mDeviceIdentityAddress);
}
- if (!di.mPeerDeviceAddress.isEmpty()) {
- addresses.add(di.mPeerDeviceAddress);
+ if (!di.mPeerIdentityDeviceAddress.isEmpty()
+ && !di.mPeerIdentityDeviceAddress.equals(di.mDeviceIdentityAddress)) {
+ addresses.add(di.mPeerIdentityDeviceAddress);
}
}
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0c78231..37fe389 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -33,6 +33,7 @@
import static android.media.AudioManager.RINGER_MODE_SILENT;
import static android.media.AudioManager.RINGER_MODE_VIBRATE;
import static android.media.AudioManager.STREAM_SYSTEM;
+import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration;
import static android.os.Process.FIRST_APPLICATION_UID;
import static android.os.Process.INVALID_UID;
import static android.provider.Settings.Secure.VOLUME_HUSH_MUTE;
@@ -43,6 +44,7 @@
import static com.android.media.audio.Flags.alarmMinVolumeZero;
import static com.android.media.audio.Flags.bluetoothMacAddressAnonymization;
import static com.android.media.audio.Flags.disablePrescaleAbsoluteVolume;
+import static com.android.media.audio.Flags.ringerModeAffectsAlarm;
import static com.android.server.audio.SoundDoseHelper.ACTION_CHECK_MUSIC_ACTIVE;
import static com.android.server.utils.EventLogger.Event.ALOGE;
import static com.android.server.utils.EventLogger.Event.ALOGI;
@@ -116,6 +118,7 @@
import android.media.AudioSystem;
import android.media.AudioTrack;
import android.media.BluetoothProfileConnectionInfo;
+import android.media.FadeManagerConfiguration;
import android.media.IAudioDeviceVolumeDispatcher;
import android.media.IAudioFocusDispatcher;
import android.media.IAudioModeDispatcher;
@@ -605,6 +608,7 @@
};
private final boolean mUseFixedVolume;
+ private final boolean mRingerModeAffectsAlarm;
private final boolean mUseVolumeGroupAliases;
// If absolute volume is supported in AVRCP device
@@ -1298,6 +1302,9 @@
mUseFixedVolume = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_useFixedVolume);
+ mRingerModeAffectsAlarm = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_audio_ringer_mode_affects_alarm_stream);
+
mRecordMonitor = new RecordingActivityMonitor(mContext);
mRecordMonitor.registerRecordingCallback(mVoiceRecordingActivityMonitor, true);
@@ -4513,6 +4520,8 @@
+ bluetoothMacAddressAnonymization());
pw.println("\tcom.android.media.audio.disablePrescaleAbsoluteVolume:"
+ disablePrescaleAbsoluteVolume());
+ pw.println("\tandroid.media.audiopolicy.enableFadeManagerConfiguration:"
+ + enableFadeManagerConfiguration());
}
private void dumpAudioMode(PrintWriter pw) {
@@ -7015,6 +7024,19 @@
ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_DTMF);
}
+ if (ringerModeAffectsAlarm()) {
+ if (mRingerModeAffectsAlarm) {
+ boolean muteAlarmWithRinger =
+ mSettings.getGlobalInt(mContentResolver,
+ Settings.Global.MUTE_ALARM_STREAM_WITH_RINGER_MODE,
+ /* def= */ 0) != 0;
+ if (muteAlarmWithRinger) {
+ ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_ALARM);
+ } else {
+ ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_ALARM);
+ }
+ }
+ }
if (ringerModeAffectedStreams != mRingerModeAffectedStreams) {
mSettings.putSystemIntForUser(mContentResolver,
Settings.System.MODE_RINGER_STREAMS_AFFECTED,
@@ -9674,6 +9696,8 @@
Settings.Global.ZEN_MODE), false, this);
mContentResolver.registerContentObserver(Settings.Global.getUriFor(
Settings.Global.ZEN_MODE_CONFIG_ETAG), false, this);
+ mContentResolver.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.MUTE_ALARM_STREAM_WITH_RINGER_MODE), false, this);
mContentResolver.registerContentObserver(Settings.System.getUriFor(
Settings.System.MODE_RINGER_STREAMS_AFFECTED), false, this);
mContentResolver.registerContentObserver(Settings.Global.getUriFor(
@@ -12614,6 +12638,47 @@
}
/**
+ * see {@link AudioPolicy#setFadeManagerConfigurationForFocusLoss(FadeManagerConfiguration)}
+ */
+ @android.annotation.EnforcePermission(
+ android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public int setFadeManagerConfigurationForFocusLoss(
+ @NonNull FadeManagerConfiguration fmcForFocusLoss) {
+ super.setFadeManagerConfigurationForFocusLoss_enforcePermission();
+ ensureFadeManagerConfigIsEnabled();
+ Objects.requireNonNull(fmcForFocusLoss,
+ "Fade manager config for focus loss cannot be null");
+ validateFadeManagerConfiguration(fmcForFocusLoss);
+
+ return mPlaybackMonitor.setFadeManagerConfiguration(AudioManager.AUDIOFOCUS_LOSS,
+ fmcForFocusLoss);
+ }
+
+ /**
+ * see {@link AudioPolicy#clearFadeManagerConfigurationForFocusLoss()}
+ */
+ @android.annotation.EnforcePermission(
+ android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public int clearFadeManagerConfigurationForFocusLoss() {
+ super.clearFadeManagerConfigurationForFocusLoss_enforcePermission();
+ ensureFadeManagerConfigIsEnabled();
+
+ return mPlaybackMonitor.clearFadeManagerConfiguration(AudioManager.AUDIOFOCUS_LOSS);
+ }
+
+ /**
+ * see {@link AudioPolicy#getFadeManagerConfigurationForFocusLoss()}
+ */
+ @android.annotation.EnforcePermission(
+ android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public FadeManagerConfiguration getFadeManagerConfigurationForFocusLoss() {
+ super.getFadeManagerConfigurationForFocusLoss_enforcePermission();
+ ensureFadeManagerConfigIsEnabled();
+
+ return mPlaybackMonitor.getFadeManagerConfiguration(AudioManager.AUDIOFOCUS_LOSS);
+ }
+
+ /**
* @see AudioManager#getHalVersion
*/
public @Nullable AudioHalVersionInfo getHalVersion() {
@@ -12814,6 +12879,19 @@
}
}
+ private void ensureFadeManagerConfigIsEnabled() {
+ Preconditions.checkState(enableFadeManagerConfiguration(),
+ "Fade manager configuration not supported");
+ }
+
+ private void validateFadeManagerConfiguration(FadeManagerConfiguration fmc) {
+ // validate permission of audio attributes
+ List<AudioAttributes> attrs = fmc.getAudioAttributesWithVolumeShaperConfigs();
+ for (int index = 0; index < attrs.size(); index++) {
+ validateAudioAttributesUsage(attrs.get(index));
+ }
+ }
+
//======================
// Audio policy callbacks from AudioSystem for dynamic policies
//======================
@@ -13114,6 +13192,7 @@
+ "could not link to " + projection + " binder death", e);
}
}
+
int status = connectMixes();
if (status != AudioSystem.SUCCESS) {
release();
@@ -13471,6 +13550,43 @@
}
}
+ /**
+ * see {@link AudioManager#dispatchAudioFocusChangeWithFade(AudioFocusInfo, int, AudioPolicy,
+ * List, FadeManagerConfiguration)}
+ */
+ @android.annotation.EnforcePermission(
+ android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public int dispatchFocusChangeWithFade(AudioFocusInfo afi, int focusChange,
+ IAudioPolicyCallback pcb, List<AudioFocusInfo> otherActiveAfis,
+ FadeManagerConfiguration transientFadeMgrConfig) {
+ super.dispatchFocusChangeWithFade_enforcePermission();
+ ensureFadeManagerConfigIsEnabled();
+ Objects.requireNonNull(afi, "AudioFocusInfo cannot be null");
+ Objects.requireNonNull(pcb, "AudioPolicy callback cannot be null");
+ Objects.requireNonNull(otherActiveAfis,
+ "Other active AudioFocusInfo list cannot be null");
+ if (transientFadeMgrConfig != null) {
+ validateFadeManagerConfiguration(transientFadeMgrConfig);
+ }
+
+ synchronized (mAudioPolicies) {
+ Preconditions.checkState(mAudioPolicies.containsKey(pcb.asBinder()),
+ "Unregistered AudioPolicy for focus dispatch with fade");
+
+ // set the transient fade manager config to be used for handling this focus change
+ if (transientFadeMgrConfig != null) {
+ mPlaybackMonitor.setTransientFadeManagerConfiguration(focusChange,
+ transientFadeMgrConfig);
+ }
+ int status = mMediaFocusControl.dispatchFocusChangeWithFade(afi, focusChange,
+ otherActiveAfis);
+
+ if (transientFadeMgrConfig != null) {
+ mPlaybackMonitor.clearTransientFadeManagerConfiguration(focusChange);
+ }
+ return status;
+ }
+ }
//======================
// Audioserver state dispatch
@@ -13775,8 +13891,8 @@
return activeAssistantUids;
}
- List<String> getDeviceAddresses(AudioDeviceAttributes device) {
- return mDeviceBroker.getDeviceAddresses(device);
+ List<String> getDeviceIdentityAddresses(AudioDeviceAttributes device) {
+ return mDeviceBroker.getDeviceIdentityAddresses(device);
}
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 8075618..a818c30 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -58,6 +58,7 @@
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import com.android.internal.annotations.GuardedBy;
import com.android.server.utils.EventLogger;
@@ -1077,8 +1078,8 @@
return mLeAudio.getGroupId(device);
}
- /*package*/ List<String> getLeAudioGroupAddresses(int groupId) {
- List<String> addresses = new ArrayList<String>();
+ /*package*/ List<Pair<String, String>> getLeAudioGroupAddresses(int groupId) {
+ List<Pair<String, String>> addresses = new ArrayList<>();
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter == null || mLeAudio == null) {
return addresses;
@@ -1086,7 +1087,7 @@
List<BluetoothDevice> activeDevices = adapter.getActiveDevices(BluetoothProfile.LE_AUDIO);
for (BluetoothDevice device : activeDevices) {
if (device != null && mLeAudio.getGroupId(device) == groupId) {
- addresses.add(device.getAddress());
+ addresses.add(new Pair(device.getAddress(), device.getIdentityAddress()));
}
}
return addresses;
diff --git a/services/core/java/com/android/server/audio/FadeConfigurations.java b/services/core/java/com/android/server/audio/FadeConfigurations.java
index 2e27c76..37ecf0b 100644
--- a/services/core/java/com/android/server/audio/FadeConfigurations.java
+++ b/services/core/java/com/android/server/audio/FadeConfigurations.java
@@ -16,13 +16,22 @@
package com.android.server.audio;
+import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration;
+
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.media.AudioAttributes;
+import android.media.AudioManager;
import android.media.AudioPlaybackConfiguration;
+import android.media.FadeManagerConfiguration;
import android.media.VolumeShaper;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Collections;
import java.util.List;
+import java.util.Objects;
/**
* Class to encapsulate configurations used for fading players
@@ -69,51 +78,229 @@
private static final int INVALID_UID = -1;
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private FadeManagerConfiguration mDefaultFadeManagerConfig;
+ @GuardedBy("mLock")
+ private FadeManagerConfiguration mUpdatedFadeManagerConfig;
+ @GuardedBy("mLock")
+ private FadeManagerConfiguration mTransientFadeManagerConfig;
+ /** active fade manager is one of: transient > updated > default */
+ @GuardedBy("mLock")
+ private FadeManagerConfiguration mActiveFadeManagerConfig;
+
+ /**
+ * Sets the custom fade manager configuration
+ *
+ * @param fadeManagerConfig custom fade manager configuration
+ * @return {@link AudioManager#SUCCESS} if setting custom fade manager configuration succeeds
+ * or {@link AudioManager#ERROR} otherwise (example - when fade manager configuration
+ * feature is disabled)
+ */
+ public int setFadeManagerConfiguration(
+ @NonNull FadeManagerConfiguration fadeManagerConfig) {
+ if (!enableFadeManagerConfiguration()) {
+ return AudioManager.ERROR;
+ }
+
+ synchronized (mLock) {
+ mUpdatedFadeManagerConfig = Objects.requireNonNull(fadeManagerConfig,
+ "Fade manager configuration cannot be null");
+ mActiveFadeManagerConfig = getActiveFadeMgrConfigLocked();
+ }
+ return AudioManager.SUCCESS;
+ }
+
+ /**
+ * Clears the fade manager configuration that was previously set with
+ * {@link #setFadeManagerConfiguration(FadeManagerConfiguration)}
+ *
+ * @return {@link AudioManager#SUCCESS} if previously set fade manager configuration is cleared
+ * or {@link AudioManager#ERROR} otherwise (example, when fade manager configuration feature
+ * is disabled)
+ */
+ public int clearFadeManagerConfiguration() {
+ if (!enableFadeManagerConfiguration()) {
+ return AudioManager.ERROR;
+ }
+
+ synchronized (mLock) {
+ mUpdatedFadeManagerConfig = null;
+ mActiveFadeManagerConfig = getActiveFadeMgrConfigLocked();
+ }
+ return AudioManager.SUCCESS;
+ }
+
+ /**
+ * Returns the active fade manager configuration
+ *
+ * @return {@code null} if feature is disabled, or the custom fade manager configuration if set,
+ * or default fade manager configuration if not set.
+ */
+ @Nullable
+ public FadeManagerConfiguration getFadeManagerConfiguration() {
+ if (!enableFadeManagerConfiguration()) {
+ return null;
+ }
+
+ synchronized (mLock) {
+ return mActiveFadeManagerConfig;
+ }
+ }
+
+ /**
+ * Sets the transient fade manager configuration
+ *
+ * @param fadeManagerConfig custom fade manager configuration
+ * @return {@link AudioManager#SUCCESS} if setting custom fade manager configuration succeeds
+ * or {@link AudioManager#ERROR} otherwise (example - when fade manager configuration is
+ * disabled)
+ */
+ public int setTransientFadeManagerConfiguration(
+ @NonNull FadeManagerConfiguration fadeManagerConfig) {
+ if (!enableFadeManagerConfiguration()) {
+ return AudioManager.ERROR;
+ }
+
+ synchronized (mLock) {
+ mTransientFadeManagerConfig = Objects.requireNonNull(fadeManagerConfig,
+ "Transient FadeManagerConfiguration cannot be null");
+ mActiveFadeManagerConfig = getActiveFadeMgrConfigLocked();
+ }
+ return AudioManager.SUCCESS;
+ }
+
+ /**
+ * Clears the transient fade manager configuration that was previously set with
+ * {@link #setTransientFadeManagerConfiguration(FadeManagerConfiguration)}
+ *
+ * @return {@link AudioManager#SUCCESS} if previously set transient fade manager configuration
+ * is cleared or {@link AudioManager#ERROR} otherwise (example - when fade manager
+ * configuration is disabled)
+ */
+ public int clearTransientFadeManagerConfiguration() {
+ if (!enableFadeManagerConfiguration()) {
+ return AudioManager.ERROR;
+ }
+ synchronized (mLock) {
+ mTransientFadeManagerConfig = null;
+ mActiveFadeManagerConfig = getActiveFadeMgrConfigLocked();
+ }
+ return AudioManager.SUCCESS;
+ }
+
+ /**
+ * Query if fade should be enforecd on players
+ *
+ * @return {@code true} if fade is enabled or using default configurations, {@code false}
+ * otherwise.
+ */
+ public boolean isFadeEnabled() {
+ if (!enableFadeManagerConfiguration()) {
+ return true;
+ }
+
+ synchronized (mLock) {
+ return getUpdatedFadeManagerConfigLocked().isFadeEnabled();
+ }
+ }
+
/**
* Query {@link android.media.AudioAttributes.AttributeUsage usages} that are allowed to
* fade
+ *
* @return list of {@link android.media.AudioAttributes.AttributeUsage}
*/
@NonNull
public List<Integer> getFadeableUsages() {
- return DEFAULT_FADEABLE_USAGES;
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_FADEABLE_USAGES;
+ }
+
+ synchronized (mLock) {
+ FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
+ // when fade is not enabled, return an empty list instead
+ return fadeManagerConfig.isFadeEnabled() ? fadeManagerConfig.getFadeableUsages()
+ : Collections.EMPTY_LIST;
+ }
}
/**
* Query {@link android.media.AudioAttributes.AttributeContentType content types} that are
* exempted from fade enforcement
+ *
* @return list of {@link android.media.AudioAttributes.AttributeContentType}
*/
@NonNull
public List<Integer> getUnfadeableContentTypes() {
- return DEFAULT_UNFADEABLE_CONTENT_TYPES;
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_UNFADEABLE_CONTENT_TYPES;
+ }
+
+ synchronized (mLock) {
+ FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
+ // when fade is not enabled, return an empty list instead
+ return fadeManagerConfig.isFadeEnabled() ? fadeManagerConfig.getUnfadeableContentTypes()
+ : Collections.EMPTY_LIST;
+ }
}
/**
* Query {@link android.media.AudioPlaybackConfiguration.PlayerType player types} that are
* exempted from fade enforcement
+ *
* @return list of {@link android.media.AudioPlaybackConfiguration.PlayerType}
*/
@NonNull
public List<Integer> getUnfadeablePlayerTypes() {
- return DEFAULT_UNFADEABLE_PLAYER_TYPES;
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_UNFADEABLE_PLAYER_TYPES;
+ }
+
+ synchronized (mLock) {
+ FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
+ // when fade is not enabled, return an empty list instead
+ return fadeManagerConfig.isFadeEnabled() ? fadeManagerConfig.getUnfadeablePlayerTypes()
+ : Collections.EMPTY_LIST;
+ }
}
/**
* Get the {@link android.media.VolumeShaper.Configuration} configuration to be applied
* for the fade-out
+ *
* @param aa The {@link android.media.AudioAttributes}
* @return {@link android.media.VolumeShaper.Configuration} for the
- * {@link android.media.AudioAttributes.AttributeUsage} or default volume shaper if not
- * configured
+ * {@link android.media.AudioAttributes} or default volume shaper if not configured
*/
@NonNull
public VolumeShaper.Configuration getFadeOutVolumeShaperConfig(@NonNull AudioAttributes aa) {
- return DEFAULT_FADEOUT_VSHAPE;
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_FADEOUT_VSHAPE;
+ }
+ return getOptimalFadeOutVolShaperConfig(aa);
}
/**
+ * Get the {@link android.media.VolumeShaper.Configuration} configuration to be applied for the
+ * fade in
+ *
+ * @param aa The {@link android.media.AudioAttributes}
+ * @return {@link android.media.VolumeShaper.Configuration} for the
+ * {@link android.media.AudioAttributes} or {@code null} otherwise
+ */
+ @Nullable
+ public VolumeShaper.Configuration getFadeInVolumeShaperConfig(@NonNull AudioAttributes aa) {
+ if (!enableFadeManagerConfiguration()) {
+ return null;
+ }
+ return getOptimalFadeInVolShaperConfig(aa);
+ }
+
+
+ /**
* Get the duration to fade out a player of type usage
+ *
* @param aa The {@link android.media.AudioAttributes}
* @return duration in milliseconds for the
* {@link android.media.AudioAttributes} or default duration if not configured
@@ -122,22 +309,73 @@
if (!isFadeable(aa, INVALID_UID, AudioPlaybackConfiguration.PLAYER_TYPE_UNKNOWN)) {
return 0;
}
- return DEFAULT_FADE_OUT_DURATION_MS;
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_FADE_OUT_DURATION_MS;
+ }
+ return getOptimalFadeOutDuration(aa);
}
/**
- * Get the delay to fade in offending players that do not stop after losing audio focus.
+ * Get the delay to fade in offending players that do not stop after losing audio focus
+ *
* @param aa The {@link android.media.AudioAttributes}
* @return delay in milliseconds for the
* {@link android.media.AudioAttributes.Attribute} or default delay if not configured
*/
public long getDelayFadeInOffenders(@NonNull AudioAttributes aa) {
- return DEFAULT_DELAY_FADE_IN_OFFENDERS_MS;
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_DELAY_FADE_IN_OFFENDERS_MS;
+ }
+
+ synchronized (mLock) {
+ return getUpdatedFadeManagerConfigLocked().getFadeInDelayForOffenders();
+ }
+ }
+
+ /**
+ * Query {@link android.media.AudioAttributes} that are exempted from fade enforcement
+ *
+ * @return list of {@link android.media.AudioAttributes}
+ */
+ @NonNull
+ public List<AudioAttributes> getUnfadeableAudioAttributes() {
+ // unfadeable audio attributes is only supported with fade manager configurations
+ if (!enableFadeManagerConfiguration()) {
+ return Collections.EMPTY_LIST;
+ }
+
+ synchronized (mLock) {
+ FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
+ // when fade is not enabled, return empty list
+ return fadeManagerConfig.isFadeEnabled()
+ ? fadeManagerConfig.getUnfadeableAudioAttributes() : Collections.EMPTY_LIST;
+ }
+ }
+
+ /**
+ * Query uids that are exempted from fade enforcement
+ *
+ * @return list of uids
+ */
+ @NonNull
+ public List<Integer> getUnfadeableUids() {
+ // unfadeable uids is only supported with fade manager configurations
+ if (!enableFadeManagerConfiguration()) {
+ return Collections.EMPTY_LIST;
+ }
+
+ synchronized (mLock) {
+ FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
+ // when fade is not enabled, return empty list
+ return fadeManagerConfig.isFadeEnabled() ? fadeManagerConfig.getUnfadeableUids()
+ : Collections.EMPTY_LIST;
+ }
}
/**
* Check if it is allowed to fade for the given {@link android.media.AudioAttributes},
- * client uid and {@link android.media.AudioPlaybackConfiguration.PlayerType} config.
+ * client uid and {@link android.media.AudioPlaybackConfiguration.PlayerType} config
+ *
* @param aa The {@link android.media.AudioAttributes}
* @param uid The uid of the client owning the player
* @param playerType The {@link android.media.AudioPlaybackConfiguration.PlayerType}
@@ -145,36 +383,173 @@
*/
public boolean isFadeable(@NonNull AudioAttributes aa, int uid,
@AudioPlaybackConfiguration.PlayerType int playerType) {
- if (isPlayerTypeUnfadeable(playerType)) {
- if (DEBUG) {
- Slog.i(TAG, "not fadeable: player type:" + playerType);
+ synchronized (mLock) {
+ if (isPlayerTypeUnfadeableLocked(playerType)) {
+ if (DEBUG) {
+ Slog.i(TAG, "not fadeable: player type:" + playerType);
+ }
+ return false;
}
- return false;
- }
- if (isContentTypeUnfadeable(aa.getContentType())) {
- if (DEBUG) {
- Slog.i(TAG, "not fadeable: content type:" + aa.getContentType());
+ if (isContentTypeUnfadeableLocked(aa.getContentType())) {
+ if (DEBUG) {
+ Slog.i(TAG, "not fadeable: content type:" + aa.getContentType());
+ }
+ return false;
}
- return false;
- }
- if (!isUsageFadeable(aa.getUsage())) {
- if (DEBUG) {
- Slog.i(TAG, "not fadeable: usage:" + aa.getUsage());
+ if (!isUsageFadeableLocked(aa.getSystemUsage())) {
+ if (DEBUG) {
+ Slog.i(TAG, "not fadeable: usage:" + aa.getUsage());
+ }
+ return false;
}
- return false;
+ // new configs using fade manager configuration
+ if (isUnfadeableForFadeMgrConfigLocked(aa, uid)) {
+ return false;
+ }
+ return true;
}
- return true;
}
- private boolean isUsageFadeable(int usage) {
- return getFadeableUsages().contains(usage);
+ /** Tries to get the fade out volume shaper config closest to the audio attributes */
+ private VolumeShaper.Configuration getOptimalFadeOutVolShaperConfig(AudioAttributes aa) {
+ synchronized (mLock) {
+ FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
+ // check if the specific audio attributes has a volume shaper config defined
+ VolumeShaper.Configuration volShaperConfig =
+ fadeManagerConfig.getFadeOutVolumeShaperConfigForAudioAttributes(aa);
+ if (volShaperConfig != null) {
+ return volShaperConfig;
+ }
+
+ // get the volume shaper config for usage
+ // for fadeable usages, this should never return null
+ return fadeManagerConfig.getFadeOutVolumeShaperConfigForUsage(
+ aa.getSystemUsage());
+ }
}
- private boolean isContentTypeUnfadeable(int contentType) {
- return getUnfadeableContentTypes().contains(contentType);
+ /** Tries to get the fade in volume shaper config closest to the audio attributes */
+ private VolumeShaper.Configuration getOptimalFadeInVolShaperConfig(AudioAttributes aa) {
+ synchronized (mLock) {
+ FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
+ // check if the specific audio attributes has a volume shaper config defined
+ VolumeShaper.Configuration volShaperConfig =
+ fadeManagerConfig.getFadeInVolumeShaperConfigForAudioAttributes(aa);
+ if (volShaperConfig != null) {
+ return volShaperConfig;
+ }
+
+ // get the volume shaper config for usage
+ // for fadeable usages, this should never return null
+ return fadeManagerConfig.getFadeInVolumeShaperConfigForUsage(aa.getSystemUsage());
+ }
}
- private boolean isPlayerTypeUnfadeable(int playerType) {
- return getUnfadeablePlayerTypes().contains(playerType);
+ /** Tries to get the duration closest to the audio attributes */
+ private long getOptimalFadeOutDuration(AudioAttributes aa) {
+ synchronized (mLock) {
+ FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
+ // check if specific audio attributes has a duration defined
+ long duration = fadeManagerConfig.getFadeOutDurationForAudioAttributes(aa);
+ if (duration != FadeManagerConfiguration.DURATION_NOT_SET) {
+ return duration;
+ }
+
+ // get the duration for usage
+ // for fadeable usages, this should never return DURATION_NOT_SET
+ return fadeManagerConfig.getFadeOutDurationForUsage(aa.getSystemUsage());
+ }
+ }
+
+ @GuardedBy("mLock")
+ private boolean isUnfadeableForFadeMgrConfigLocked(AudioAttributes aa, int uid) {
+ if (isAudioAttributesUnfadeableLocked(aa)) {
+ if (DEBUG) {
+ Slog.i(TAG, "not fadeable: aa:" + aa);
+ }
+ return true;
+ }
+ if (isUidUnfadeableLocked(uid)) {
+ if (DEBUG) {
+ Slog.i(TAG, "not fadeable: uid:" + uid);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @GuardedBy("mLock")
+ private boolean isUsageFadeableLocked(int usage) {
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_FADEABLE_USAGES.contains(usage);
+ }
+ return getUpdatedFadeManagerConfigLocked().isUsageFadeable(usage);
+ }
+
+ @GuardedBy("mLock")
+ private boolean isContentTypeUnfadeableLocked(int contentType) {
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_UNFADEABLE_CONTENT_TYPES.contains(contentType);
+ }
+ return getUpdatedFadeManagerConfigLocked().isContentTypeUnfadeable(contentType);
+ }
+
+ @GuardedBy("mLock")
+ private boolean isPlayerTypeUnfadeableLocked(int playerType) {
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_UNFADEABLE_PLAYER_TYPES.contains(playerType);
+ }
+ return getUpdatedFadeManagerConfigLocked().isPlayerTypeUnfadeable(playerType);
+ }
+
+ @GuardedBy("mLock")
+ private boolean isAudioAttributesUnfadeableLocked(AudioAttributes aa) {
+ if (!enableFadeManagerConfiguration()) {
+ // default fade configs do not support unfadeable audio attributes, hence return false
+ return false;
+ }
+ return getUpdatedFadeManagerConfigLocked().isAudioAttributesUnfadeable(aa);
+ }
+
+ @GuardedBy("mLock")
+ private boolean isUidUnfadeableLocked(int uid) {
+ if (!enableFadeManagerConfiguration()) {
+ // default fade configs do not support unfadeable uids, hence return false
+ return false;
+ }
+ return getUpdatedFadeManagerConfigLocked().isUidUnfadeable(uid);
+ }
+
+ @GuardedBy("mLock")
+ private FadeManagerConfiguration getUpdatedFadeManagerConfigLocked() {
+ if (mActiveFadeManagerConfig == null) {
+ mActiveFadeManagerConfig = getActiveFadeMgrConfigLocked();
+ }
+ return mActiveFadeManagerConfig;
+ }
+
+ /** Priority between fade manager configs: Transient > Updated > Default */
+ @GuardedBy("mLock")
+ private FadeManagerConfiguration getActiveFadeMgrConfigLocked() {
+ // below configs are arranged in the order of priority
+ // configs placed higher have higher priority
+ if (mTransientFadeManagerConfig != null) {
+ return mTransientFadeManagerConfig;
+ }
+
+ if (mUpdatedFadeManagerConfig != null) {
+ return mUpdatedFadeManagerConfig;
+ }
+
+ // default - must be the lowest priority
+ return getDefaultFadeManagerConfigLocked();
+ }
+
+ @GuardedBy("mLock")
+ private FadeManagerConfiguration getDefaultFadeManagerConfigLocked() {
+ if (mDefaultFadeManagerConfig == null) {
+ mDefaultFadeManagerConfig = new FadeManagerConfiguration.Builder().build();
+ }
+ return mDefaultFadeManagerConfig;
}
}
diff --git a/services/core/java/com/android/server/audio/FadeOutManager.java b/services/core/java/com/android/server/audio/FadeOutManager.java
index 1171f97..2cceb5a 100644
--- a/services/core/java/com/android/server/audio/FadeOutManager.java
+++ b/services/core/java/com/android/server/audio/FadeOutManager.java
@@ -16,21 +16,24 @@
package com.android.server.audio;
+import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration;
+
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioPlaybackConfiguration;
+import android.media.FadeManagerConfiguration;
import android.media.VolumeShaper;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.server.utils.EventLogger;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Objects;
+import java.util.List;
+import java.util.Map;
/**
* Class to handle fading out players
@@ -40,14 +43,6 @@
public static final String TAG = "AS.FadeOutManager";
private static final boolean DEBUG = PlaybackActivityMonitor.DEBUG;
- private static final VolumeShaper.Operation PLAY_CREATE_IF_NEEDED =
- new VolumeShaper.Operation.Builder(VolumeShaper.Operation.PLAY)
- .createIfNeeded()
- .build();
-
- // like a PLAY_CREATE_IF_NEEDED operation but with a skip to the end of the ramp
- private static final VolumeShaper.Operation PLAY_SKIP_RAMP =
- new VolumeShaper.Operation.Builder(PLAY_CREATE_IF_NEEDED).setXOffset(1.0f).build();
private final Object mLock = new Object();
@@ -57,16 +52,81 @@
@GuardedBy("mLock")
private final SparseArray<FadedOutApp> mUidToFadedAppsMap = new SparseArray<>();
- @GuardedBy("mLock")
- private FadeConfigurations mFadeConfigurations;
+ private final FadeConfigurations mFadeConfigurations = new FadeConfigurations();
- public FadeOutManager() {
- mFadeConfigurations = new FadeConfigurations();
+ /**
+ * Sets the custom fade manager configuration to be used for player fade out and in
+ *
+ * @param fadeManagerConfig custom fade manager configuration
+ * @return {@link AudioManager#SUCCESS} if setting fade manager config succeeded,
+ * {@link AudioManager#ERROR} otherwise
+ */
+ int setFadeManagerConfiguration(FadeManagerConfiguration fadeManagerConfig) {
+ // locked to ensure the fade configs are not updated while faded app state is being updated
+ synchronized (mLock) {
+ return mFadeConfigurations.setFadeManagerConfiguration(fadeManagerConfig);
+ }
}
- public FadeOutManager(FadeConfigurations fadeConfigurations) {
- mFadeConfigurations = Objects.requireNonNull(fadeConfigurations,
- "Fade configurations can not be null");
+ /**
+ * Clears the fade manager configuration that was previously set with
+ * {@link #setFadeManagerConfiguration(FadeManagerConfiguration)}
+ *
+ * @return {@link AudioManager#SUCCESS} if clearing fade manager config succeeded,
+ * {@link AudioManager#ERROR} otherwise
+ */
+ int clearFadeManagerConfiguration() {
+ // locked to ensure the fade configs are not updated while faded app state is being updated
+ synchronized (mLock) {
+ return mFadeConfigurations.clearFadeManagerConfiguration();
+ }
+ }
+
+ /**
+ * Returns the active fade manager configuration
+ *
+ * @return the {@link FadeManagerConfiguration}
+ */
+ FadeManagerConfiguration getFadeManagerConfiguration() {
+ return mFadeConfigurations.getFadeManagerConfiguration();
+ }
+
+ /**
+ * Sets the transient fade manager configuration to be used for player fade out and in
+ *
+ * @param fadeManagerConfig fade manager config that has higher priority than the existing
+ * fade manager configuration. This is expected to be transient.
+ * @return {@link AudioManager#SUCCESS} if setting fade manager config succeeded,
+ * {@link AudioManager#ERROR} otherwise
+ */
+ int setTransientFadeManagerConfiguration(FadeManagerConfiguration fadeManagerConfig) {
+ // locked to ensure the fade configs are not updated while faded app state is being updated
+ synchronized (mLock) {
+ return mFadeConfigurations.setTransientFadeManagerConfiguration(fadeManagerConfig);
+ }
+ }
+
+ /**
+ * Clears the transient fade manager configuration that was previously set with
+ * {@link #setTransientFadeManagerConfiguration(FadeManagerConfiguration)}
+ *
+ * @return {@link AudioManager#SUCCESS} if clearing fade manager config succeeded,
+ * {@link AudioManager#ERROR} otherwise
+ */
+ int clearTransientFadeManagerConfiguration() {
+ // locked to ensure the fade configs are not updated while faded app state is being updated
+ synchronized (mLock) {
+ return mFadeConfigurations.clearTransientFadeManagerConfiguration();
+ }
+ }
+
+ /**
+ * Query if fade is enblead and can be enforced on players
+ *
+ * @return {@code true} if fade is enabled, {@code false} otherwise.
+ */
+ boolean isFadeEnabled() {
+ return mFadeConfigurations.isFadeEnabled();
}
// TODO explore whether a shorter fade out would be a better UX instead of not fading out at all
@@ -128,7 +188,7 @@
}
}
- void fadeOutUid(int uid, ArrayList<AudioPlaybackConfiguration> players) {
+ void fadeOutUid(int uid, List<AudioPlaybackConfiguration> players) {
Slog.i(TAG, "fadeOutUid() uid:" + uid);
synchronized (mLock) {
if (!mUidToFadedAppsMap.contains(uid)) {
@@ -148,15 +208,31 @@
* @param uid the uid for the app to unfade out
* @param players map of current available players (so we can get an APC from piid)
*/
- void unfadeOutUid(int uid, HashMap<Integer, AudioPlaybackConfiguration> players) {
+ void unfadeOutUid(int uid, Map<Integer, AudioPlaybackConfiguration> players) {
Slog.i(TAG, "unfadeOutUid() uid:" + uid);
synchronized (mLock) {
- final FadedOutApp fa = mUidToFadedAppsMap.get(uid);
+ FadedOutApp fa = mUidToFadedAppsMap.get(uid);
if (fa == null) {
return;
}
mUidToFadedAppsMap.remove(uid);
- fa.removeUnfadeAll(players);
+
+ if (!enableFadeManagerConfiguration()) {
+ fa.removeUnfadeAll(players);
+ return;
+ }
+
+ // since fade manager configs may have volume-shaper config per audio attributes,
+ // iterate through each palyer and gather respective configs for fade in
+ ArrayList<AudioPlaybackConfiguration> apcs = new ArrayList<>(players.values());
+ for (int index = 0; index < apcs.size(); index++) {
+ AudioPlaybackConfiguration apc = apcs.get(index);
+ VolumeShaper.Configuration config = mFadeConfigurations
+ .getFadeInVolumeShaperConfig(apc.getAudioAttributes());
+ fa.fadeInPlayer(apc, config);
+ }
+ // ideal case all players should be faded in
+ fa.clear();
}
}
@@ -209,16 +285,6 @@
}
}
- /**
- * Update fade configurations used for player fade operations
- * @param fadeConfigurations set of configs that define fade properties
- */
- void setFadeConfigurations(@NonNull FadeConfigurations fadeConfigurations) {
- synchronized (mLock) {
- mFadeConfigurations = fadeConfigurations;
- }
- }
-
void dump(PrintWriter pw) {
synchronized (mLock) {
for (int index = 0; index < mUidToFadedAppsMap.size(); index++) {
@@ -232,6 +298,15 @@
* Class to group players from a common app, that are faded out.
*/
private static final class FadedOutApp {
+ private static final VolumeShaper.Operation PLAY_CREATE_IF_NEEDED =
+ new VolumeShaper.Operation.Builder(VolumeShaper.Operation.PLAY)
+ .createIfNeeded()
+ .build();
+
+ // like a PLAY_CREATE_IF_NEEDED operation but with a skip to the end of the ramp
+ private static final VolumeShaper.Operation PLAY_SKIP_RAMP =
+ new VolumeShaper.Operation.Builder(PLAY_CREATE_IF_NEEDED).setXOffset(1.0f).build();
+
private final int mUid;
// key -> piid; value -> volume shaper config applied
private final SparseArray<VolumeShaper.Configuration> mFadedPlayers = new SparseArray<>();
@@ -269,17 +344,8 @@
return;
}
if (apc.getPlayerProxy() != null) {
- try {
- PlaybackActivityMonitor.sEventLogger.enqueue(
- (new PlaybackActivityMonitor.FadeOutEvent(apc, skipRamp)).printLog(
- TAG));
- apc.getPlayerProxy().applyVolumeShaper(volShaper,
- skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED);
- mFadedPlayers.put(piid, volShaper);
- } catch (Exception e) {
- Slog.e(TAG, "Error fading out player piid:" + piid
- + " uid:" + apc.getClientUid(), e);
- }
+ applyVolumeShaperInternal(apc, piid, volShaper,
+ skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED);
} else {
if (DEBUG) {
Slog.v(TAG, "Error fading out player piid:" + piid
@@ -288,21 +354,13 @@
}
}
- void removeUnfadeAll(HashMap<Integer, AudioPlaybackConfiguration> players) {
+ void removeUnfadeAll(Map<Integer, AudioPlaybackConfiguration> players) {
for (int index = 0; index < mFadedPlayers.size(); index++) {
int piid = mFadedPlayers.keyAt(index);
final AudioPlaybackConfiguration apc = players.get(piid);
if ((apc != null) && (apc.getPlayerProxy() != null)) {
- final VolumeShaper.Configuration volShaper = mFadedPlayers.valueAt(index);
- try {
- PlaybackActivityMonitor.sEventLogger.enqueue(
- (new EventLogger.StringEvent("unfading out piid:"
- + piid)).printLog(TAG));
- apc.getPlayerProxy().applyVolumeShaper(volShaper,
- VolumeShaper.Operation.REVERSE);
- } catch (Exception e) {
- Slog.e(TAG, "Error unfading out player piid:" + piid + " uid:" + mUid, e);
- }
+ applyVolumeShaperInternal(apc, piid, /* volShaperConfig= */ null,
+ VolumeShaper.Operation.REVERSE);
} else {
// this piid was in the list of faded players, but wasn't found
if (DEBUG) {
@@ -314,8 +372,61 @@
mFadedPlayers.clear();
}
+ void fadeInPlayer(@NonNull AudioPlaybackConfiguration apc,
+ @Nullable VolumeShaper.Configuration config) {
+ int piid = Integer.valueOf(apc.getPlayerInterfaceId());
+ // if not found, no need to fade in since it was never faded out
+ if (!mFadedPlayers.contains(piid)) {
+ if (DEBUG) {
+ Slog.v(TAG, "Player (piid: " + piid + ") for uid (" + mUid
+ + ") is not faded out, no need to fade in");
+ }
+ return;
+ }
+
+ mFadedPlayers.remove(piid);
+ if (apc.getPlayerProxy() != null) {
+ applyVolumeShaperInternal(apc, piid, config,
+ config != null ? PLAY_CREATE_IF_NEEDED : VolumeShaper.Operation.REVERSE);
+ } else {
+ if (DEBUG) {
+ Slog.v(TAG, "Error fading in player piid:" + piid
+ + ", player not found for uid " + mUid);
+ }
+ }
+ }
+
+ void clear() {
+ if (mFadedPlayers.size() > 0) {
+ if (DEBUG) {
+ Slog.v(TAG, "Non empty faded players list being cleared! Faded out players:"
+ + mFadedPlayers);
+ }
+ }
+ // should the players be faded in irrespective?
+ mFadedPlayers.clear();
+ }
+
void removeReleased(@NonNull AudioPlaybackConfiguration apc) {
mFadedPlayers.delete(Integer.valueOf(apc.getPlayerInterfaceId()));
}
+
+ private void applyVolumeShaperInternal(AudioPlaybackConfiguration apc, int piid,
+ VolumeShaper.Configuration volShaperConfig, VolumeShaper.Operation operation) {
+ VolumeShaper.Configuration config = volShaperConfig;
+ // when operation is reverse, use the fade out volume shaper config instead
+ if (operation.equals(VolumeShaper.Operation.REVERSE)) {
+ config = mFadedPlayers.get(piid);
+ }
+ try {
+ PlaybackActivityMonitor.sEventLogger.enqueue(
+ (new PlaybackActivityMonitor.FadeEvent(apc, config, operation))
+ .printLog(TAG));
+ apc.getPlayerProxy().applyVolumeShaper(config, operation);
+ } catch (Exception e) {
+ Slog.e(TAG, "Error fading player piid:" + piid + " uid:" + mUid
+ + " operation:" + operation, e);
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/audio/FocusRequester.java b/services/core/java/com/android/server/audio/FocusRequester.java
index 00c04ff..f462539 100644
--- a/services/core/java/com/android/server/audio/FocusRequester.java
+++ b/services/core/java/com/android/server/audio/FocusRequester.java
@@ -33,6 +33,7 @@
import com.android.server.pm.UserManagerInternal;
import java.io.PrintWriter;
+import java.util.List;
/**
* @hide
@@ -534,6 +535,33 @@
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
+ @GuardedBy("MediaFocusControl.mAudioFocusLock")
+ int dispatchFocusChangeWithFadeLocked(int focusChange, List<FocusRequester> otherActiveFrs) {
+ if (focusChange == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
+ || focusChange == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
+ || focusChange == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT
+ || focusChange == AudioManager.AUDIOFOCUS_GAIN) {
+ mFocusLossFadeLimbo = false;
+ mFocusController.restoreVShapedPlayers(this);
+ } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS
+ && mFocusController.shouldEnforceFade()) {
+ for (int index = 0; index < otherActiveFrs.size(); index++) {
+ // candidate for fade-out before a receiving a loss
+ if (mFocusController.fadeOutPlayers(otherActiveFrs.get(index), /* loser= */ this)) {
+ // active players are being faded out, delay the dispatch of focus loss
+ // mark this instance as being faded so it's not released yet as the focus loss
+ // will be dispatched later, it is now in limbo mode
+ mFocusLossFadeLimbo = true;
+ mFocusController.postDelayedLossAfterFade(this,
+ mFocusController.getFadeOutDurationOnFocusLossMillis(
+ this.getAudioAttributes()));
+ return AudioManager.AUDIOFOCUS_REQUEST_DELAYED;
+ }
+ }
+ }
+ return dispatchFocusChange(focusChange);
+ }
+
void dispatchFocusResultFromExtPolicy(int requestResult) {
final IAudioFocusDispatcher fd = mFocusDispatcher;
if (fd == null) {
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index 58f5d5e..0df0006 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -16,6 +16,8 @@
package com.android.server.audio;
+import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
@@ -195,6 +197,15 @@
}
return mFocusEnforcer.getFadeInDelayForOffendersMillis(aa);
}
+
+ @Override
+ public boolean shouldEnforceFade() {
+ if (!enableFadeManagerConfiguration()) {
+ return ENFORCE_FADEOUT_FOR_FOCUS_LOSS;
+ }
+
+ return mFocusEnforcer.shouldEnforceFade();
+ }
//==========================================================================================
// AudioFocus
//==========================================================================================
@@ -861,14 +872,17 @@
return;
}
}
- final FocusRequester fr;
- if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
- fr = mFocusOwnersForFocusPolicy.remove(afi.getClientId());
- } else {
- fr = mFocusOwnersForFocusPolicy.get(afi.getClientId());
- }
- if (fr != null) {
- fr.dispatchFocusResultFromExtPolicy(requestResult);
+ synchronized (mAudioFocusLock) {
+ FocusRequester fr = getFocusRequesterLocked(afi.getClientId(),
+ /* shouldRemove= */ requestResult == AudioManager.AUDIOFOCUS_REQUEST_FAILED);
+ if (fr != null) {
+ fr.dispatchFocusResultFromExtPolicy(requestResult);
+ // if fade is enabled for external focus policies, apply it when setting
+ // focus result as well
+ if (enableFadeManagerConfiguration()) {
+ fr.handleFocusGainFromRequest(requestResult);
+ }
+ }
}
}
@@ -902,24 +916,80 @@
+ afi.getClientId());
}
synchronized (mAudioFocusLock) {
- if (mFocusPolicy == null) {
- if (DEBUG) { Log.v(TAG, "> failed: no focus policy" ); }
- return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
- }
- final FocusRequester fr;
- if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
- fr = mFocusOwnersForFocusPolicy.remove(afi.getClientId());
- } else {
- fr = mFocusOwnersForFocusPolicy.get(afi.getClientId());
- }
+ FocusRequester fr = getFocusRequesterLocked(afi.getClientId(),
+ /* shouldRemove= */ focusChange == AudioManager.AUDIOFOCUS_LOSS);
if (fr == null) {
- if (DEBUG) { Log.v(TAG, "> failed: no such focus requester known" ); }
+ if (DEBUG) {
+ Log.v(TAG, "> failed: no such focus requester known");
+ }
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
return fr.dispatchFocusChange(focusChange);
}
}
+ int dispatchFocusChangeWithFade(AudioFocusInfo afi, int focusChange,
+ List<AudioFocusInfo> otherActiveAfis) {
+ if (DEBUG) {
+ Log.v(TAG, "dispatchFocusChangeWithFade " + AudioManager.audioFocusToString(focusChange)
+ + " to afi client=" + afi.getClientId()
+ + " other active afis=" + otherActiveAfis);
+ }
+
+ synchronized (mAudioFocusLock) {
+ String clientId = afi.getClientId();
+ // do not remove the entry since it can be posted for fade
+ FocusRequester fr = getFocusRequesterLocked(clientId, /* shouldRemove= */ false);
+ if (fr == null) {
+ if (DEBUG) {
+ Log.v(TAG, "> failed: no such focus requester known");
+ }
+ return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
+ }
+
+ // convert other AudioFocusInfo to corresponding FocusRequester
+ ArrayList<FocusRequester> otherActiveFrs = new ArrayList<>();
+ for (int index = 0; index < otherActiveAfis.size(); index++) {
+ FocusRequester otherFr = getFocusRequesterLocked(
+ otherActiveAfis.get(index).getClientId(), /* shouldRemove= */ false);
+ if (otherFr == null) {
+ continue;
+ }
+ otherActiveFrs.add(otherFr);
+ }
+
+ int status = fr.dispatchFocusChangeWithFadeLocked(focusChange, otherActiveFrs);
+ if (status != AudioManager.AUDIOFOCUS_REQUEST_DELAYED
+ && focusChange == AudioManager.AUDIOFOCUS_LOSS) {
+ mFocusOwnersForFocusPolicy.remove(clientId);
+ }
+
+ return status;
+ }
+ }
+
+ @GuardedBy("mAudioFocusLock")
+ private FocusRequester getFocusRequesterLocked(String clientId, boolean shouldRemove) {
+ if (mFocusPolicy == null) {
+ if (DEBUG) {
+ Log.v(TAG, "> failed: no focus policy");
+ }
+ return null;
+ }
+
+ FocusRequester fr;
+ if (shouldRemove) {
+ fr = mFocusOwnersForFocusPolicy.remove(clientId);
+ } else {
+ fr = mFocusOwnersForFocusPolicy.get(clientId);
+ }
+
+ if (fr == null && DEBUG) {
+ Log.v(TAG, "> failed: no such focus requester known");
+ }
+ return fr;
+ }
+
private void dumpExtFocusPolicyFocusOwners(PrintWriter pw) {
final Set<Entry<String, FocusRequester>> owners = mFocusOwnersForFocusPolicy.entrySet();
final Iterator<Entry<String, FocusRequester>> ownerIterator = owners.iterator();
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index bc9b9b4..e69fbbd 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -38,6 +38,7 @@
import android.media.AudioPlaybackConfiguration.FormatInfo;
import android.media.AudioPlaybackConfiguration.PlayerMuteEvent;
import android.media.AudioSystem;
+import android.media.FadeManagerConfiguration;
import android.media.IPlaybackConfigDispatcher;
import android.media.PlayerBase;
import android.media.VolumeShaper;
@@ -156,8 +157,7 @@
private final int mMaxAlarmVolume;
private int mPrivilegedAlarmActiveCount = 0;
private final Consumer<AudioDeviceAttributes> mMuteAwaitConnectionTimeoutCb;
- private final FadeOutManager mFadeOutManager;
-
+ private final FadeOutManager mFadeOutManager = new FadeOutManager();
PlaybackActivityMonitor(Context context, int maxAlarmVolume,
Consumer<AudioDeviceAttributes> muteTimeoutCallback) {
@@ -167,7 +167,6 @@
AudioPlaybackConfiguration.sPlayerDeathMonitor = this;
mMuteAwaitConnectionTimeoutCb = muteTimeoutCallback;
initEventHandler();
- mFadeOutManager = new FadeOutManager(new FadeConfigurations());
}
//=================================================================
@@ -971,6 +970,12 @@
return mFadeOutManager.getFadeInDelayForOffendersMillis(aa);
}
+ @Override
+ public boolean shouldEnforceFade() {
+ return mFadeOutManager.isFadeEnabled();
+ }
+
+
//=================================================================
// Track playback activity listeners
@@ -1010,6 +1015,27 @@
}
}
+ int setFadeManagerConfiguration(int focusType, FadeManagerConfiguration fadeMgrConfig) {
+ return mFadeOutManager.setFadeManagerConfiguration(fadeMgrConfig);
+ }
+
+ int clearFadeManagerConfiguration(int focusType) {
+ return mFadeOutManager.clearFadeManagerConfiguration();
+ }
+
+ FadeManagerConfiguration getFadeManagerConfiguration(int focusType) {
+ return mFadeOutManager.getFadeManagerConfiguration();
+ }
+
+ int setTransientFadeManagerConfiguration(int focusType,
+ FadeManagerConfiguration fadeMgrConfig) {
+ return mFadeOutManager.setTransientFadeManagerConfiguration(fadeMgrConfig);
+ }
+
+ int clearTransientFadeManagerConfiguration(int focusType) {
+ return mFadeOutManager.clearTransientFadeManagerConfiguration();
+ }
+
/**
* Inner class to track clients that want to be notified of playback updates
*/
@@ -1337,6 +1363,38 @@
}
}
+ static final class FadeEvent extends EventLogger.Event {
+ private final int mPlayerIId;
+ private final int mPlayerType;
+ private final int mClientUid;
+ private final int mClientPid;
+ private final AudioAttributes mPlayerAttr;
+ private final VolumeShaper.Configuration mVShaper;
+ private final VolumeShaper.Operation mVOperation;
+
+ FadeEvent(AudioPlaybackConfiguration apc, VolumeShaper.Configuration vShaper,
+ VolumeShaper.Operation vOperation) {
+ mPlayerIId = apc.getPlayerInterfaceId();
+ mClientUid = apc.getClientUid();
+ mClientPid = apc.getClientPid();
+ mPlayerAttr = apc.getAudioAttributes();
+ mPlayerType = apc.getPlayerType();
+ mVShaper = vShaper;
+ mVOperation = vOperation;
+ }
+
+ @Override
+ public String eventToString() {
+ return "Fade Event:" + " player piid:" + mPlayerIId
+ + " uid/pid:" + mClientUid + "/" + mClientPid
+ + " player type:"
+ + AudioPlaybackConfiguration.toLogFriendlyPlayerType(mPlayerType)
+ + " attr:" + mPlayerAttr
+ + " volume shaper:" + mVShaper
+ + " volume operation:" + mVOperation;
+ }
+ }
+
private abstract static class VolumeShaperEvent extends EventLogger.Event {
private final int mPlayerIId;
private final boolean mSkipRamp;
diff --git a/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java b/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java
index f1d42f3..4a29eca 100644
--- a/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java
+++ b/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java
@@ -79,4 +79,11 @@
* @return delay in milliseconds
*/
long getFadeInDelayForOffendersMillis(@NonNull AudioAttributes aa);
+
+ /**
+ * Check if the fade should be enforced
+ *
+ * @return {@code true} if fade should be enforced, {@code false} otherwise
+ */
+ boolean shouldEnforceFade();
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 4f7f31d..8428f12 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -1639,8 +1639,7 @@
return -1;
}
final AudioDeviceAttributes currentDevice = sRoutingDevices.get(0);
- List<String> deviceAddresses = mAudioService.getDeviceAddresses(currentDevice);
-
+ List<String> deviceAddresses = mAudioService.getDeviceIdentityAddresses(currentDevice);
// We limit only to Sensor.TYPE_HEAD_TRACKER here to avoid confusion
// with gaming sensors. (Note that Sensor.TYPE_ROTATION_VECTOR
// and Sensor.TYPE_GAME_ROTATION_VECTOR are supported internally by
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index dafea9a..d5d8fd2 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -51,9 +51,13 @@
import android.hardware.biometrics.PromptInfo;
import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.biometrics.SensorPropertiesInternal;
+import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.face.FaceSensorConfigurations;
import android.hardware.face.FaceSensorProperties;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.IFaceService;
+import android.hardware.fingerprint.FingerprintSensorConfigurations;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintService;
@@ -122,8 +126,6 @@
/**
* Allows to test with various device sensor configurations.
- * @param context
- * @return
*/
@VisibleForTesting
public String[] getConfiguration(Context context) {
@@ -131,6 +133,30 @@
}
/**
+ * Allows to test with various device sensor configurations.
+ */
+ @VisibleForTesting
+ public String[] getFingerprintConfiguration(Context context) {
+ return getConfiguration(context);
+ }
+
+ /**
+ * Allows to test with various device sensor configurations.
+ */
+ @VisibleForTesting
+ public String[] getFaceConfiguration(Context context) {
+ return getConfiguration(context);
+ }
+
+ /**
+ * Allows to test with various device sensor configurations.
+ */
+ @VisibleForTesting
+ public String[] getIrisConfiguration(Context context) {
+ return getConfiguration(context);
+ }
+
+ /**
* Allows us to mock FingerprintService for testing
*/
@VisibleForTesting
@@ -173,6 +199,22 @@
}
return false;
}
+
+ /**
+ * Allows to test with various fingerprint aidl instances.
+ */
+ @VisibleForTesting
+ public String[] getFingerprintAidlInstances() {
+ return ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR);
+ }
+
+ /**
+ * Allows to test with various face aidl instances.
+ */
+ @VisibleForTesting
+ public String[] getFaceAidlInstances() {
+ return ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR);
+ }
}
private final class AuthServiceImpl extends IAuthService.Stub {
@@ -717,12 +759,129 @@
hidlConfigs = null;
}
- // Registers HIDL and AIDL authenticators, but only HIDL configs need to be provided.
- registerAuthenticators(hidlConfigs);
+ if (com.android.server.biometrics.Flags.deHidl()) {
+ Slog.d(TAG, "deHidl flag is on.");
+ registerAuthenticators();
+ } else {
+ // Registers HIDL and AIDL authenticators, but only HIDL configs need to be provided.
+ registerAuthenticators(hidlConfigs);
+ }
mInjector.publishBinderService(this, mImpl);
}
+ private void registerAuthenticators() {
+ registerFingerprintSensors(mInjector.getFingerprintAidlInstances(),
+ mInjector.getFingerprintConfiguration(getContext()));
+ registerFaceSensors(mInjector.getFaceAidlInstances(),
+ mInjector.getFaceConfiguration(getContext()));
+ registerIrisSensors(mInjector.getIrisConfiguration(getContext()));
+ }
+
+ private void registerIrisSensors(String[] hidlConfigStrings) {
+ final SensorConfig[] hidlConfigs;
+ if (!mInjector.isHidlDisabled(getContext())) {
+ final int firstApiLevel = SystemProperties.getInt(SYSPROP_FIRST_API_LEVEL, 0);
+ final int apiLevel = SystemProperties.getInt(SYSPROP_API_LEVEL, firstApiLevel);
+ if (hidlConfigStrings.length == 0 && apiLevel == Build.VERSION_CODES.R) {
+ // For backwards compatibility with R where biometrics could work without being
+ // configured in config_biometric_sensors. In the absence of a vendor provided
+ // configuration, we assume the weakest biometric strength (i.e. convenience).
+ Slog.w(TAG, "Found R vendor partition without config_biometric_sensors");
+ hidlConfigStrings = generateRSdkCompatibleConfiguration();
+ }
+ hidlConfigs = new SensorConfig[hidlConfigStrings.length];
+ for (int i = 0; i < hidlConfigStrings.length; ++i) {
+ hidlConfigs[i] = new SensorConfig(hidlConfigStrings[i]);
+ }
+ } else {
+ hidlConfigs = null;
+ }
+
+ final List<SensorPropertiesInternal> hidlIrisSensors = new ArrayList<>();
+
+ if (hidlConfigs != null) {
+ for (SensorConfig sensor : hidlConfigs) {
+ switch (sensor.modality) {
+ case TYPE_IRIS:
+ hidlIrisSensors.add(getHidlIrisSensorProps(sensor.id, sensor.strength));
+ break;
+
+ default:
+ Slog.e(TAG, "Unknown modality: " + sensor.modality);
+ }
+ }
+ }
+
+ final IIrisService irisService = mInjector.getIrisService();
+ if (irisService != null) {
+ try {
+ irisService.registerAuthenticators(hidlIrisSensors);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException when registering iris authenticators", e);
+ }
+ } else if (hidlIrisSensors.size() > 0) {
+ Slog.e(TAG, "HIDL iris configuration exists, but IrisService is null.");
+ }
+ }
+
+ private void registerFaceSensors(final String[] faceAidlInstances,
+ final String[] hidlConfigStrings) {
+ final FaceSensorConfigurations mFaceSensorConfigurations =
+ new FaceSensorConfigurations(hidlConfigStrings != null
+ && hidlConfigStrings.length > 0);
+
+ if (hidlConfigStrings != null && hidlConfigStrings.length > 0) {
+ mFaceSensorConfigurations.addHidlConfigs(
+ hidlConfigStrings, getContext());
+ }
+
+ if (faceAidlInstances != null && faceAidlInstances.length > 0) {
+ mFaceSensorConfigurations.addAidlConfigs(faceAidlInstances,
+ name -> IFace.Stub.asInterface(Binder.allowBlocking(
+ ServiceManager.waitForDeclaredService(name))));
+ }
+
+ final IFaceService faceService = mInjector.getFaceService();
+ if (faceService != null) {
+ try {
+ faceService.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException when registering face authenticators", e);
+ }
+ } else if (mFaceSensorConfigurations.hasSensorConfigurations()) {
+ Slog.e(TAG, "Face configuration exists, but FaceService is null.");
+ }
+ }
+
+ private void registerFingerprintSensors(final String[] fingerprintAidlInstances,
+ final String[] hidlConfigStrings) {
+ final FingerprintSensorConfigurations mFingerprintSensorConfigurations =
+ new FingerprintSensorConfigurations(!(hidlConfigStrings != null
+ && hidlConfigStrings.length > 0));
+
+ if (hidlConfigStrings != null && hidlConfigStrings.length > 0) {
+ mFingerprintSensorConfigurations.addHidlSensors(hidlConfigStrings, getContext());
+ }
+
+ if (fingerprintAidlInstances != null && fingerprintAidlInstances.length > 0) {
+ mFingerprintSensorConfigurations.addAidlSensors(fingerprintAidlInstances,
+ name -> IFingerprint.Stub.asInterface(Binder.allowBlocking(
+ ServiceManager.waitForDeclaredService(name))));
+ }
+
+ final IFingerprintService fingerprintService = mInjector.getFingerprintService();
+ if (fingerprintService != null) {
+ try {
+ fingerprintService.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException when registering fingerprint authenticators", e);
+ }
+ } else if (mFingerprintSensorConfigurations.hasSensorConfigurations()) {
+ Slog.e(TAG, "Fingerprint configuration exists, but FingerprintService is null.");
+ }
+ }
+
/**
* Generates an array of string configs with entries that correspond to the biometric features
* declared on the device. Returns an empty array if no biometric features are declared.
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index 037ea38a..89b638b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -202,7 +202,7 @@
};
@VisibleForTesting
- BiometricScheduler(@NonNull String tag,
+ public BiometricScheduler(@NonNull String tag,
@NonNull Handler handler,
@SensorType int sensorType,
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
index 57feedc..0c3dfa7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
@@ -387,6 +387,11 @@
return isAuthentication || isDetection;
}
+ /** If this operation is {@link StartUserClient}. */
+ public boolean isStartUserOperation() {
+ return mClientMonitor instanceof StartUserClient<?, ?>;
+ }
+
/** If this operation performs acquisition {@link AcquisitionClient}. */
public boolean isAcquisitionOperation() {
return mClientMonitor instanceof AcquisitionClient;
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
index 7f8f38f..6daaad1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
@@ -54,8 +54,6 @@
// is all done internally.
super(context, lazyDaemon, token, null /* ClientMonitorCallbackConverter */, userId, owner,
0 /* cookie */, sensorId, logger, biometricContext);
- //, BiometricsProtoEnums.ACTION_ENUMERATE,
- // BiometricsProtoEnums.CLIENT_UNKNOWN);
mEnrolledList = enrolledList;
mUtils = utils;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
index 8075470..3753bbd 100644
--- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
@@ -135,7 +135,7 @@
final int currentUserId = mCurrentUserRetriever.getCurrentUserId();
final int nextUserId = mPendingOperations.getFirst().getTargetUserId();
- if (nextUserId == currentUserId) {
+ if (nextUserId == currentUserId || mPendingOperations.getFirst().isStartUserOperation()) {
super.startNextOperationIfIdle();
} else if (currentUserId == UserHandle.USER_NULL) {
final BaseClientMonitor startClient =
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 578d9dc..6af223b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -36,6 +36,7 @@
import android.hardware.biometrics.face.SensorProps;
import android.hardware.face.Face;
import android.hardware.face.FaceAuthenticateOptions;
+import android.hardware.face.FaceSensorConfigurations;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.FaceServiceReceiver;
import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
@@ -55,6 +56,7 @@
import android.util.proto.ProtoOutputStream;
import android.view.Surface;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.SystemService;
@@ -76,6 +78,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.function.Supplier;
/**
* A service to manage multiple clients that want to access the face HAL API.
@@ -86,7 +89,7 @@
protected static final String TAG = "FaceService";
- private final FaceServiceWrapper mServiceWrapper;
+ @VisibleForTesting final FaceServiceWrapper mServiceWrapper;
private final LockoutResetDispatcher mLockoutResetDispatcher;
private final LockPatternUtils mLockPatternUtils;
@NonNull
@@ -94,11 +97,18 @@
@NonNull
private final BiometricStateCallback<ServiceProvider, FaceSensorPropertiesInternal>
mBiometricStateCallback;
+ @NonNull
+ private final FaceProviderFunction mFaceProviderFunction;
+
+ interface FaceProviderFunction {
+ FaceProvider getFaceProvider(Pair<String, SensorProps[]> filteredSensorProps,
+ boolean resetLockoutRequiresChallenge);
+ }
/**
* Receives the incoming binder calls from FaceManager.
*/
- private final class FaceServiceWrapper extends IFaceService.Stub {
+ @VisibleForTesting final class FaceServiceWrapper extends IFaceService.Stub {
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
@@ -672,7 +682,8 @@
final SensorProps[] props = face.getSensorProps();
final FaceProvider provider = new FaceProvider(getContext(),
mBiometricStateCallback, props, instance, mLockoutResetDispatcher,
- BiometricContext.getInstance(getContext()));
+ BiometricContext.getInstance(getContext()),
+ false /* resetLockoutRequiresChallenge */);
providers.add(provider);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
@@ -704,6 +715,55 @@
});
}
+ @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+ public void registerAuthenticatorsLegacy(
+ FaceSensorConfigurations faceSensorConfigurations) {
+ super.registerAuthenticatorsLegacy_enforcePermission();
+
+ if (!faceSensorConfigurations.hasSensorConfigurations()) {
+ Slog.d(TAG, "No face sensors to register.");
+ return;
+ }
+ mRegistry.registerAll(() -> getProviders(faceSensorConfigurations));
+ }
+
+ private List<ServiceProvider> getProviders(
+ FaceSensorConfigurations faceSensorConfigurations) {
+ final List<ServiceProvider> providers = new ArrayList<>();
+ final Pair<String, SensorProps[]> filteredSensorProps =
+ filterAvailableHalInstances(faceSensorConfigurations);
+ providers.add(mFaceProviderFunction.getFaceProvider(filteredSensorProps,
+ faceSensorConfigurations.getResetLockoutRequiresChallenge()));
+ return providers;
+ }
+
+ @NonNull
+ private Pair<String, SensorProps[]> filterAvailableHalInstances(
+ FaceSensorConfigurations faceSensorConfigurations) {
+ Pair<String, SensorProps[]> finalSensorPair = faceSensorConfigurations.getSensorPair();
+
+ if (faceSensorConfigurations.isSingleSensorConfigurationPresent()) {
+ return finalSensorPair;
+ }
+
+ final Pair<String, SensorProps[]> virtualSensorProps = faceSensorConfigurations
+ .getSensorPairForInstance("virtual");
+
+ if (Utils.isVirtualEnabled(getContext())) {
+ if (virtualSensorProps != null) {
+ return virtualSensorProps;
+ } else {
+ Slog.e(TAG, "Could not find virtual interface while it is enabled");
+ return finalSensorPair;
+ }
+ } else {
+ if (virtualSensorProps != null) {
+ return faceSensorConfigurations.getSensorPairNotForInstance("virtual");
+ }
+ }
+ return finalSensorPair;
+ }
+
private Pair<List<FaceSensorPropertiesInternal>, List<String>>
filterAvailableHalInstances(
@NonNull List<FaceSensorPropertiesInternal> hidlInstances,
@@ -752,20 +812,36 @@
}
public FaceService(Context context) {
+ this(context, null /* faceProviderFunction */, () -> IBiometricService.Stub.asInterface(
+ ServiceManager.getService(Context.BIOMETRIC_SERVICE)));
+ }
+
+ @VisibleForTesting FaceService(Context context, FaceProviderFunction faceProviderFunction,
+ Supplier<IBiometricService> biometricServiceSupplier) {
super(context);
mServiceWrapper = new FaceServiceWrapper();
mLockoutResetDispatcher = new LockoutResetDispatcher(context);
mLockPatternUtils = new LockPatternUtils(context);
mBiometricStateCallback = new BiometricStateCallback<>(UserManager.get(context));
- mRegistry = new FaceServiceRegistry(mServiceWrapper,
- () -> IBiometricService.Stub.asInterface(
- ServiceManager.getService(Context.BIOMETRIC_SERVICE)));
+ mRegistry = new FaceServiceRegistry(mServiceWrapper, biometricServiceSupplier);
mRegistry.addAllRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
@Override
public void onAllAuthenticatorsRegistered(List<FaceSensorPropertiesInternal> sensors) {
mBiometricStateCallback.start(mRegistry.getProviders());
}
});
+
+ if (Flags.deHidl()) {
+ mFaceProviderFunction = faceProviderFunction != null ? faceProviderFunction :
+ ((filteredSensorProps, resetLockoutRequiresChallenge) -> new FaceProvider(
+ getContext(), mBiometricStateCallback,
+ filteredSensorProps.second,
+ filteredSensorProps.first, mLockoutResetDispatcher,
+ BiometricContext.getInstance(getContext()),
+ resetLockoutRequiresChallenge));
+ } else {
+ mFaceProviderFunction = ((filteredSensorProps, resetLockoutRequiresChallenge) -> null);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java
index e5d4a63..ef2be79 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java
@@ -24,7 +24,8 @@
* the user changes.
*/
public class LockoutHalImpl implements LockoutTracker {
- private @LockoutMode int mCurrentUserLockoutMode;
+ @LockoutMode
+ private int mCurrentUserLockoutMode;
@Override
public int getLockoutModeForUser(int userId) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
index 57f5b34..098be21 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
@@ -27,6 +27,7 @@
import android.hardware.keymaster.HardwareAuthToken;
import android.util.Slog;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.AcquisitionClient;
@@ -59,6 +60,20 @@
void onHardwareUnavailable();
}
+ /**
+ * Interface to send results to the AidlResponseHandler's owner.
+ */
+ public interface AidlResponseHandlerCallback {
+ /**
+ * Invoked when enrollment is successful.
+ */
+ void onEnrollSuccess();
+ /**
+ * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
+ */
+ void onHardwareUnavailable();
+ }
+
private static final String TAG = "AidlResponseHandler";
@NonNull
@@ -68,7 +83,7 @@
private final int mSensorId;
private final int mUserId;
@NonNull
- private final LockoutTracker mLockoutCache;
+ private final LockoutTracker mLockoutTracker;
@NonNull
private final LockoutResetDispatcher mLockoutResetDispatcher;
@@ -76,6 +91,8 @@
private final AuthSessionCoordinator mAuthSessionCoordinator;
@NonNull
private final HardwareUnavailableCallback mHardwareUnavailableCallback;
+ @NonNull
+ private final AidlResponseHandlerCallback mAidlResponseHandlerCallback;
public AidlResponseHandler(@NonNull Context context,
@NonNull BiometricScheduler scheduler, int sensorId, int userId,
@@ -83,14 +100,33 @@
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull AuthSessionCoordinator authSessionCoordinator,
@NonNull HardwareUnavailableCallback hardwareUnavailableCallback) {
+ this(context, scheduler, sensorId, userId, lockoutTracker, lockoutResetDispatcher,
+ authSessionCoordinator, hardwareUnavailableCallback,
+ new AidlResponseHandlerCallback() {
+ @Override
+ public void onEnrollSuccess() {}
+
+ @Override
+ public void onHardwareUnavailable() {}
+ });
+ }
+
+ public AidlResponseHandler(@NonNull Context context,
+ @NonNull BiometricScheduler scheduler, int sensorId, int userId,
+ @NonNull LockoutTracker lockoutTracker,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull AuthSessionCoordinator authSessionCoordinator,
+ @NonNull HardwareUnavailableCallback hardwareUnavailableCallback,
+ @NonNull AidlResponseHandlerCallback aidlResponseHandlerCallback) {
mContext = context;
mScheduler = scheduler;
mSensorId = sensorId;
mUserId = userId;
- mLockoutCache = lockoutTracker;
+ mLockoutTracker = lockoutTracker;
mLockoutResetDispatcher = lockoutResetDispatcher;
mAuthSessionCoordinator = authSessionCoordinator;
mHardwareUnavailableCallback = hardwareUnavailableCallback;
+ mAidlResponseHandlerCallback = aidlResponseHandlerCallback;
}
@Override
@@ -106,13 +142,13 @@
@Override
public void onChallengeGenerated(long challenge) {
handleResponse(FaceGenerateChallengeClient.class, (c) -> c.onChallengeGenerated(mSensorId,
- mUserId, challenge), null);
+ mUserId, challenge));
}
@Override
public void onChallengeRevoked(long challenge) {
handleResponse(FaceRevokeChallengeClient.class, (c) -> c.onChallengeRevoked(mSensorId,
- mUserId, challenge), null);
+ mUserId, challenge));
}
@Override
@@ -123,7 +159,7 @@
return;
}
c.onAuthenticationFrame(AidlConversionUtils.toFrameworkAuthenticationFrame(frame));
- }, null);
+ });
}
@Override
@@ -134,7 +170,7 @@
return;
}
c.onEnrollmentFrame(AidlConversionUtils.toFrameworkEnrollmentFrame(frame));
- }, null);
+ });
}
@Override
@@ -149,9 +185,13 @@
handleResponse(ErrorConsumer.class, (c) -> {
c.onError(error, vendorCode);
if (error == Error.HW_UNAVAILABLE) {
- mHardwareUnavailableCallback.onHardwareUnavailable();
+ if (Flags.deHidl()) {
+ mAidlResponseHandlerCallback.onHardwareUnavailable();
+ } else {
+ mHardwareUnavailableCallback.onHardwareUnavailable();
+ }
}
- }, null);
+ });
}
@Override
@@ -167,7 +207,12 @@
.getUniqueName(mContext, currentUserId);
final Face face = new Face(name, enrollmentId, mSensorId);
- handleResponse(FaceEnrollClient.class, (c) -> c.onEnrollResult(face, remaining), null);
+ handleResponse(FaceEnrollClient.class, (c) -> {
+ c.onEnrollResult(face, remaining);
+ if (remaining == 0) {
+ mAidlResponseHandlerCallback.onEnrollSuccess();
+ }
+ });
}
@Override
@@ -179,37 +224,37 @@
byteList.add(b);
}
handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(face,
- true /* authenticated */, byteList), null);
+ true /* authenticated */, byteList));
}
@Override
public void onAuthenticationFailed() {
final Face face = new Face("" /* name */, 0 /* faceId */, mSensorId);
handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(face,
- false /* authenticated */, null /* hat */), null);
+ false /* authenticated */, null /* hat */));
}
@Override
public void onLockoutTimed(long durationMillis) {
- handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis), null);
+ handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis));
}
@Override
public void onLockoutPermanent() {
- handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent, null);
+ handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent);
}
@Override
public void onLockoutCleared() {
handleResponse(FaceResetLockoutClient.class, FaceResetLockoutClient::onLockoutCleared,
(c) -> FaceResetLockoutClient.resetLocalLockoutStateToNone(mSensorId, mUserId,
- mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
- Utils.getCurrentStrength(mSensorId), -1 /* requestId */));
+ mLockoutTracker, mLockoutResetDispatcher, mAuthSessionCoordinator,
+ Utils.getCurrentStrength(mSensorId), -1 /* requestId */));
}
@Override
public void onInteractionDetected() {
- handleResponse(FaceDetectClient.class, FaceDetectClient::onInteractionDetected, null);
+ handleResponse(FaceDetectClient.class, FaceDetectClient::onInteractionDetected);
}
@Override
@@ -219,23 +264,23 @@
final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId);
final int finalI = i;
handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(face,
- enrollmentIds.length - finalI - 1), null);
+ enrollmentIds.length - finalI - 1 /* remaining */));
}
} else {
handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(
- null /* identifier */, 0 /* remaining */), null);
+ null /* identifier */, 0 /* remaining */));
}
}
@Override
public void onFeaturesRetrieved(byte[] features) {
handleResponse(FaceGetFeatureClient.class, (c) -> c.onFeatureGet(true /* success */,
- features), null);
+ features));
}
@Override
public void onFeatureSet(byte feature) {
- handleResponse(FaceSetFeatureClient.class, (c) -> c.onFeatureSet(true /* success */), null);
+ handleResponse(FaceSetFeatureClient.class, (c) -> c.onFeatureSet(true /* success */));
}
@Override
@@ -245,33 +290,32 @@
final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId);
final int finalI = i;
handleResponse(RemovalConsumer.class,
- (c) -> c.onRemoved(face, enrollmentIds.length - finalI - 1),
- null);
+ (c) -> c.onRemoved(face,
+ enrollmentIds.length - finalI - 1 /* remaining */));
}
} else {
handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(null /* identifier */,
- 0 /* remaining */), null);
+ 0 /* remaining */));
}
}
@Override
public void onAuthenticatorIdRetrieved(long authenticatorId) {
handleResponse(FaceGetAuthenticatorIdClient.class, (c) -> c.onAuthenticatorIdRetrieved(
- authenticatorId), null);
+ authenticatorId));
}
@Override
public void onAuthenticatorIdInvalidated(long newAuthenticatorId) {
handleResponse(FaceInvalidationClient.class, (c) -> c.onAuthenticatorIdInvalidated(
- newAuthenticatorId), null);
+ newAuthenticatorId));
}
/**
* Handles acquired messages sent by the HAL (specifically for HIDL HAL).
*/
public void onAcquired(int acquiredInfo, int vendorCode) {
- handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(acquiredInfo, vendorCode),
- null);
+ handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(acquiredInfo, vendorCode));
}
/**
@@ -288,7 +332,7 @@
lockoutMode = LockoutTracker.LOCKOUT_TIMED;
}
- mLockoutCache.setLockoutModeForUser(mUserId, lockoutMode);
+ mLockoutTracker.setLockoutModeForUser(mUserId, lockoutMode);
if (duration == 0) {
mLockoutResetDispatcher.notifyLockoutResetCallbacks(mSensorId);
@@ -296,6 +340,20 @@
});
}
+ /**
+ * Handle clients which are not supported in HIDL HAL. For face, FaceInvalidationClient
+ * is the only AIDL client which is not supported in HIDL.
+ */
+ public void onUnsupportedClientScheduled() {
+ Slog.e(TAG, "FaceInvalidationClient is not supported in the HAL.");
+ handleResponse(FaceInvalidationClient.class, BaseClientMonitor::cancel);
+ }
+
+ private <T> void handleResponse(@NonNull Class<T> className,
+ @NonNull Consumer<T> action) {
+ handleResponse(className, action, null /* alternateAction */);
+ }
+
private <T> void handleResponse(@NonNull Class<T> className,
@NonNull Consumer<T> actionIfClassMatchesClient,
@Nullable Consumer<BaseClientMonitor> alternateAction) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
index e70e25a..af46f44 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
@@ -21,7 +21,7 @@
import android.hardware.biometrics.face.ISession;
import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter;
+import com.android.server.biometrics.sensors.face.hidl.HidlToAidlSessionAdapter;
import java.util.function.Supplier;
@@ -47,7 +47,7 @@
public AidlSession(Context context, Supplier<IBiometricsFace> session, int userId,
AidlResponseHandler aidlResponseHandler) {
- mSession = new AidlToHidlAdapter(context, session, userId, aidlResponseHandler);
+ mSession = new HidlToAidlSessionAdapter(context, session, userId, aidlResponseHandler);
mHalInterfaceVersion = 0;
mUserId = userId;
mAidlResponseHandler = aidlResponseHandler;
@@ -64,7 +64,7 @@
}
/** The HAL callback, which should only be used in tests {@See BiometricTestSessionImpl}. */
- AidlResponseHandler getHalSessionCallback() {
+ public AidlResponseHandler getHalSessionCallback() {
return mAidlResponseHandler;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
index c15049b..c41b706 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
@@ -34,7 +34,7 @@
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.ErrorConsumer;
import com.android.server.biometrics.sensors.HalClientMonitor;
-import com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter;
+import com.android.server.biometrics.sensors.face.hidl.HidlToAidlSessionAdapter;
import java.util.HashMap;
import java.util.Map;
@@ -75,8 +75,8 @@
protected void startHalOperation() {
try {
ISession session = getFreshDaemon().getSession();
- if (session instanceof AidlToHidlAdapter) {
- ((AidlToHidlAdapter) session).setFeature(mFeature);
+ if (session instanceof HidlToAidlSessionAdapter) {
+ ((HidlToAidlSessionAdapter) session).setFeature(mFeature);
}
session.getFeatures();
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index dd9c6d5..9fa15b8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -24,6 +24,7 @@
import android.app.TaskStackListener;
import android.content.Context;
import android.content.pm.UserInfo;
+import android.hardware.biometrics.BiometricFaceConstants;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IInvalidationCallback;
@@ -51,6 +52,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
import com.android.server.biometrics.AuthenticationStatsCollector;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
@@ -64,11 +66,13 @@
import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
import com.android.server.biometrics.sensors.InvalidationRequesterClient;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.PerformanceTracker;
import com.android.server.biometrics.sensors.SensorList;
import com.android.server.biometrics.sensors.face.FaceUtils;
import com.android.server.biometrics.sensors.face.ServiceProvider;
import com.android.server.biometrics.sensors.face.UsageStats;
+import com.android.server.biometrics.sensors.face.hidl.HidlToAidlSensorAdapter;
import org.json.JSONArray;
import org.json.JSONException;
@@ -152,9 +156,11 @@
@NonNull SensorProps[] props,
@NonNull String halInstanceName,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull BiometricContext biometricContext) {
+ @NonNull BiometricContext biometricContext,
+ boolean resetLockoutRequiresChallenge) {
this(context, biometricStateCallback, props, halInstanceName, lockoutResetDispatcher,
- biometricContext, null /* daemon */);
+ biometricContext, null /* daemon */, resetLockoutRequiresChallenge,
+ false /* testHalEnabled */);
}
@VisibleForTesting FaceProvider(@NonNull Context context,
@@ -163,7 +169,8 @@
@NonNull String halInstanceName,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull BiometricContext biometricContext,
- IFace daemon) {
+ @Nullable IFace daemon, boolean resetLockoutRequiresChallenge,
+ boolean testHalEnabled) {
mContext = context;
mBiometricStateCallback = biometricStateCallback;
mHalInstanceName = halInstanceName;
@@ -176,48 +183,118 @@
mBiometricContext = biometricContext;
mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
mDaemon = daemon;
+ mTestHalEnabled = testHalEnabled;
- AuthenticationStatsBroadcastReceiver mBroadcastReceiver =
- new AuthenticationStatsBroadcastReceiver(
- mContext,
- BiometricsProtoEnums.MODALITY_FACE,
- (AuthenticationStatsCollector collector) -> {
- Slog.d(getTag(), "Initializing AuthenticationStatsCollector");
- mAuthenticationStatsCollector = collector;
- });
+ initAuthenticationBroadcastReceiver();
+ initSensors(resetLockoutRequiresChallenge, props);
+ }
- for (SensorProps prop : props) {
- final int sensorId = prop.commonProps.sensorId;
+ private void initAuthenticationBroadcastReceiver() {
+ new AuthenticationStatsBroadcastReceiver(
+ mContext,
+ BiometricsProtoEnums.MODALITY_FACE,
+ (AuthenticationStatsCollector collector) -> {
+ Slog.d(getTag(), "Initializing AuthenticationStatsCollector");
+ mAuthenticationStatsCollector = collector;
+ });
+ }
- final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
- if (prop.commonProps.componentInfo != null) {
- for (ComponentInfo info : prop.commonProps.componentInfo) {
- componentInfo.add(new ComponentInfoInternal(info.componentId,
- info.hardwareVersion, info.firmwareVersion, info.serialNumber,
- info.softwareVersion));
+ private void initSensors(boolean resetLockoutRequiresChallenge, SensorProps[] props) {
+ if (Flags.deHidl()) {
+ if (resetLockoutRequiresChallenge) {
+ Slog.d(getTag(), "Adding HIDL configs");
+ for (SensorProps prop : props) {
+ addHidlSensors(prop, resetLockoutRequiresChallenge);
+ }
+ } else {
+ Slog.d(getTag(), "Adding AIDL configs");
+ for (SensorProps prop : props) {
+ addAidlSensors(prop, resetLockoutRequiresChallenge);
}
}
+ } else {
+ for (SensorProps prop : props) {
+ final int sensorId = prop.commonProps.sensorId;
- final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
- prop.commonProps.sensorId, prop.commonProps.sensorStrength,
- prop.commonProps.maxEnrollmentsPerUser, componentInfo, prop.sensorType,
- prop.supportsDetectInteraction, prop.halControlsPreview,
- false /* resetLockoutRequiresChallenge */);
- final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
- internalProp, lockoutResetDispatcher, mBiometricContext);
- final int userId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
- sensor.getLazySession().get().getUserId();
- mFaceSensors.addSensor(sensorId, sensor, userId,
- new SynchronousUserSwitchObserver() {
- @Override
- public void onUserSwitching(int newUserId) {
- scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
- }
- });
- Slog.d(getTag(), "Added: " + internalProp);
+ final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
+ if (prop.commonProps.componentInfo != null) {
+ for (ComponentInfo info : prop.commonProps.componentInfo) {
+ componentInfo.add(new ComponentInfoInternal(info.componentId,
+ info.hardwareVersion, info.firmwareVersion, info.serialNumber,
+ info.softwareVersion));
+ }
+ }
+
+ final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
+ prop.commonProps.sensorId, prop.commonProps.sensorStrength,
+ prop.commonProps.maxEnrollmentsPerUser, componentInfo, prop.sensorType,
+ prop.supportsDetectInteraction, prop.halControlsPreview,
+ false /* resetLockoutRequiresChallenge */);
+ final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this,
+ mContext, mHandler, internalProp, mLockoutResetDispatcher,
+ mBiometricContext);
+ sensor.init(mLockoutResetDispatcher, this);
+ final int userId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
+ sensor.getLazySession().get().getUserId();
+ mFaceSensors.addSensor(sensorId, sensor, userId,
+ new SynchronousUserSwitchObserver() {
+ @Override
+ public void onUserSwitching(int newUserId) {
+ scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
+ }
+ });
+ Slog.d(getTag(), "Added: " + internalProp);
+ }
}
}
+ private void addHidlSensors(SensorProps prop, boolean resetLockoutRequiresChallenge) {
+ final int sensorId = prop.commonProps.sensorId;
+ final Sensor sensor = new HidlToAidlSensorAdapter(getTag() + "/" + sensorId, this,
+ mContext, mHandler, prop, mLockoutResetDispatcher,
+ mBiometricContext, resetLockoutRequiresChallenge,
+ () -> {
+ //TODO: update to make this testable
+ scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser(),
+ null /* callback */);
+ scheduleGetFeature(sensorId, new Binder(), ActivityManager.getCurrentUser(),
+ BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION, null,
+ mContext.getOpPackageName());
+ });
+ sensor.init(mLockoutResetDispatcher, this);
+ final int userId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
+ sensor.getLazySession().get().getUserId();
+ mFaceSensors.addSensor(sensorId, sensor, userId,
+ new SynchronousUserSwitchObserver() {
+ @Override
+ public void onUserSwitching(int newUserId) {
+ scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
+ scheduleGetFeature(sensorId, new Binder(), newUserId,
+ BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION,
+ null, mContext.getOpPackageName());
+ }
+ });
+ Slog.d(getTag(), "Added: " + mFaceSensors.get(sensorId));
+ }
+
+ private void addAidlSensors(SensorProps prop, boolean resetLockoutRequiresChallenge) {
+ final int sensorId = prop.commonProps.sensorId;
+ final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext,
+ mHandler, prop, mLockoutResetDispatcher, mBiometricContext,
+ resetLockoutRequiresChallenge);
+ sensor.init(mLockoutResetDispatcher, this);
+ final int userId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
+ sensor.getLazySession().get().getUserId();
+ mFaceSensors.addSensor(sensorId, sensor, userId,
+ new SynchronousUserSwitchObserver() {
+ @Override
+ public void onUserSwitching(int newUserId) {
+ scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
+ }
+ });
+ Slog.d(getTag(), "Added: " + mFaceSensors.get(sensorId));
+ }
+
private String getTag() {
return "FaceProvider/" + mHalInstanceName;
}
@@ -290,7 +367,10 @@
}
}
- private void scheduleLoadAuthenticatorIdsForUser(int sensorId, int userId) {
+ /**
+ * Schedules FaceGetAuthenticatorIdClient for specific sensor and user.
+ */
+ protected void scheduleLoadAuthenticatorIdsForUser(int sensorId, int userId) {
mHandler.post(() -> {
final FaceGetAuthenticatorIdClient client = new FaceGetAuthenticatorIdClient(
mContext, mFaceSensors.get(sensorId).getLazySession(), userId,
@@ -365,8 +445,12 @@
@Override
public int getLockoutModeForUser(int sensorId, int userId) {
- return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
- Utils.getCurrentStrength(sensorId));
+ if (Flags.deHidl()) {
+ return mFaceSensors.get(sensorId).getLockoutModeForUser(userId);
+ } else {
+ return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
+ Utils.getCurrentStrength(sensorId));
+ }
}
@Override
@@ -376,13 +460,18 @@
@Override
public boolean isHardwareDetected(int sensorId) {
- return hasHalInstance();
+ if (Flags.deHidl()) {
+ return mFaceSensors.get(sensorId).isHardwareDetected(mHalInstanceName);
+ } else {
+ return hasHalInstance();
+ }
}
@Override
public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token,
@NonNull IFaceServiceReceiver receiver, String opPackageName) {
mHandler.post(() -> {
+ mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
mFaceSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), userId, opPackageName, sensorId,
@@ -416,6 +505,7 @@
@Nullable Surface previewSurface, boolean debugConsent) {
final long id = mRequestCounter.incrementAndGet();
mHandler.post(() -> {
+ mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
final int maxTemplatesPerUser = mFaceSensors.get(
sensorId).getSensorProperties().maxEnrollmentsPerUser;
final FaceEnrollClient client = new FaceEnrollClient(mContext,
@@ -427,18 +517,23 @@
BiometricsProtoEnums.CLIENT_UNKNOWN,
mAuthenticationStatsCollector),
mBiometricContext, maxTemplatesPerUser, debugConsent);
- scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
- mBiometricStateCallback, new ClientMonitorCallback() {
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
- boolean success) {
- ClientMonitorCallback.super.onClientFinished(clientMonitor, success);
- if (success) {
- scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
- scheduleInvalidationRequest(sensorId, userId);
+ if (Flags.deHidl()) {
+ scheduleForSensor(sensorId, client, mBiometricStateCallback);
+ } else {
+ scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
+ mBiometricStateCallback, new ClientMonitorCallback() {
+ @Override
+ public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+ boolean success) {
+ ClientMonitorCallback.super.onClientFinished(clientMonitor,
+ success);
+ if (success) {
+ scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
+ scheduleInvalidationRequest(sensorId, userId);
+ }
}
- }
- }));
+ }));
+ }
});
return id;
}
@@ -486,6 +581,13 @@
final int userId = options.getUserId();
final int sensorId = options.getSensorId();
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
+ mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
+ final LockoutTracker lockoutTracker;
+ if (Flags.deHidl()) {
+ lockoutTracker = mFaceSensors.get(sensorId).getLockoutTracker(true /* forAuth */);
+ } else {
+ lockoutTracker = null;
+ }
final FaceAuthenticationClient client = new FaceAuthenticationClient(
mContext, mFaceSensors.get(sensorId).getLazySession(), token, requestId,
callback, operationId, restricted, options, cookie,
@@ -493,7 +595,7 @@
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
mAuthenticationStatsCollector),
mBiometricContext, isStrongBiometric,
- mUsageStats, null /* lockoutTracker */,
+ mUsageStats, lockoutTracker,
allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId));
scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
@Override
@@ -555,6 +657,7 @@
private void scheduleRemoveSpecifiedIds(int sensorId, @NonNull IBinder token, int[] faceIds,
int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
mHandler.post(() -> {
+ mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
final FaceRemovalClient client = new FaceRemovalClient(mContext,
mFaceSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), faceIds, userId,
@@ -571,6 +674,7 @@
@Override
public void scheduleResetLockout(int sensorId, int userId, @NonNull byte[] hardwareAuthToken) {
mHandler.post(() -> {
+ mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
final FaceResetLockoutClient client = new FaceResetLockoutClient(
mContext, mFaceSensors.get(sensorId).getLazySession(), userId,
mContext.getOpPackageName(), sensorId,
@@ -578,8 +682,8 @@
BiometricsProtoEnums.CLIENT_UNKNOWN,
mAuthenticationStatsCollector),
mBiometricContext, hardwareAuthToken,
- mFaceSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher,
- Utils.getCurrentStrength(sensorId));
+ mFaceSensors.get(sensorId).getLockoutTracker(false/* forAuth */),
+ mLockoutResetDispatcher, Utils.getCurrentStrength(sensorId));
scheduleForSensor(sensorId, client);
});
@@ -590,6 +694,7 @@
boolean enabled, @NonNull byte[] hardwareAuthToken,
@NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
mHandler.post(() -> {
+ mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
final List<Face> faces = FaceUtils.getInstance(sensorId)
.getBiometricsForUser(mContext, userId);
if (faces.isEmpty()) {
@@ -610,6 +715,7 @@
public void scheduleGetFeature(int sensorId, @NonNull IBinder token, int userId, int feature,
@NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName) {
mHandler.post(() -> {
+ mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
final List<Face> faces = FaceUtils.getInstance(sensorId)
.getBiometricsForUser(mContext, userId);
if (faces.isEmpty()) {
@@ -641,6 +747,7 @@
public void scheduleInternalCleanup(int sensorId, int userId,
@Nullable ClientMonitorCallback callback, boolean favorHalEnrollments) {
mHandler.post(() -> {
+ mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
final FaceInternalCleanupClient client =
new FaceInternalCleanupClient(mContext,
mFaceSensors.get(sensorId).getLazySession(), userId,
@@ -760,4 +867,8 @@
}
biometricScheduler.startWatchdog();
}
+
+ public boolean getTestHalEnabled() {
+ return mTestHalEnabled;
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
index 77b5592..d02eefa 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.hardware.biometrics.BiometricManager.Authenticators;
import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.face.ISession;
import android.hardware.keymaster.HardwareAuthToken;
import android.os.RemoteException;
import android.util.Slog;
@@ -34,6 +35,7 @@
import com.android.server.biometrics.sensors.HalClientMonitor;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.face.hidl.HidlToAidlSessionAdapter;
import java.util.function.Supplier;
@@ -47,7 +49,7 @@
private static final String TAG = "FaceResetLockoutClient";
private final HardwareAuthToken mHardwareAuthToken;
- private final LockoutTracker mLockoutCache;
+ private final LockoutTracker mLockoutTracker;
private final LockoutResetDispatcher mLockoutResetDispatcher;
private final int mBiometricStrength;
@@ -60,7 +62,7 @@
super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
0 /* cookie */, sensorId, logger, biometricContext);
mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
- mLockoutCache = lockoutTracker;
+ mLockoutTracker = lockoutTracker;
mLockoutResetDispatcher = lockoutResetDispatcher;
mBiometricStrength = biometricStrength;
}
@@ -79,7 +81,11 @@
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().getSession().resetLockout(mHardwareAuthToken);
+ final ISession session = getFreshDaemon().getSession();
+ session.resetLockout(mHardwareAuthToken);
+ if (session instanceof HidlToAidlSessionAdapter) {
+ mCallback.onClientFinished(this, true /* success */);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Unable to reset lockout", e);
mCallback.onClientFinished(this, false /* success */);
@@ -87,7 +93,7 @@
}
void onLockoutCleared() {
- resetLocalLockoutStateToNone(getSensorId(), getTargetUserId(), mLockoutCache,
+ resetLocalLockoutStateToNone(getSensorId(), getTargetUserId(), mLockoutTracker,
mLockoutResetDispatcher, getBiometricContext().getAuthSessionCoordinator(),
mBiometricStrength, getRequestId());
mCallback.onClientFinished(this, true /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index 54e66eb..3e5c599 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -21,16 +21,20 @@
import android.content.Context;
import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
+import android.hardware.biometrics.common.ComponentInfo;
import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.ISession;
+import android.hardware.biometrics.face.SensorProps;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
@@ -38,6 +42,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.SensorServiceStateProto;
import com.android.server.biometrics.SensorStateProto;
import com.android.server.biometrics.UserStateProto;
@@ -49,12 +54,15 @@
import com.android.server.biometrics.sensors.ErrorConsumer;
import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.StartUserClient;
import com.android.server.biometrics.sensors.StopUserClient;
import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
import com.android.server.biometrics.sensors.face.FaceUtils;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
@@ -71,25 +79,53 @@
@NonNull private final IBinder mToken;
@NonNull private final Handler mHandler;
@NonNull private final FaceSensorPropertiesInternal mSensorProperties;
- @NonNull private final UserAwareBiometricScheduler mScheduler;
- @NonNull private final LockoutCache mLockoutCache;
+ @NonNull private BiometricScheduler mScheduler;
+ @Nullable private LockoutTracker mLockoutTracker;
@NonNull private final Map<Integer, Long> mAuthenticatorIds;
- @NonNull private final Supplier<AidlSession> mLazySession;
+ @NonNull private Supplier<AidlSession> mLazySession;
@Nullable AidlSession mCurrentSession;
+ @NonNull BiometricContext mBiometricContext;
Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
@NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull BiometricContext biometricContext, AidlSession session) {
+ @NonNull BiometricContext biometricContext, @Nullable AidlSession session) {
mTag = tag;
mProvider = provider;
mContext = context;
mToken = new Binder();
mHandler = handler;
mSensorProperties = sensorProperties;
- mScheduler = new UserAwareBiometricScheduler(tag,
+ mBiometricContext = biometricContext;
+ mAuthenticatorIds = new HashMap<>();
+ }
+
+ Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
+ @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull BiometricContext biometricContext) {
+ this(tag, provider, context, handler, sensorProperties, lockoutResetDispatcher,
+ biometricContext, null);
+ }
+
+ public Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
+ @NonNull Handler handler, @NonNull SensorProps prop,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull BiometricContext biometricContext,
+ boolean resetLockoutRequiresChallenge) {
+ this(tag, provider, context, handler,
+ getFaceSensorPropertiesInternal(prop, resetLockoutRequiresChallenge),
+ lockoutResetDispatcher, biometricContext, null);
+ }
+
+ /**
+ * Initialize biometric scheduler, lockout tracker and session for the sensor.
+ */
+ public void init(LockoutResetDispatcher lockoutResetDispatcher,
+ FaceProvider provider) {
+ mScheduler = new UserAwareBiometricScheduler(mTag,
BiometricScheduler.SENSOR_TYPE_FACE, null /* gestureAvailabilityDispatcher */,
() -> mCurrentSession != null ? mCurrentSession.getUserId() : UserHandle.USER_NULL,
new UserAwareBiometricScheduler.UserSwitchCallback() {
@@ -98,7 +134,7 @@
public StopUserClient<?> getStopUserClient(int userId) {
return new FaceStopUserClient(mContext, mLazySession, mToken, userId,
mSensorProperties.sensorId,
- BiometricLogger.ofUnknown(mContext), biometricContext,
+ BiometricLogger.ofUnknown(mContext), mBiometricContext,
() -> mCurrentSession = null);
}
@@ -107,13 +143,36 @@
public StartUserClient<?, ?> getStartUserClient(int newUserId) {
final int sensorId = mSensorProperties.sensorId;
- final AidlResponseHandler resultController = new AidlResponseHandler(
- mContext, mScheduler, sensorId, newUserId,
- mLockoutCache, lockoutResetDispatcher,
- biometricContext.getAuthSessionCoordinator(), () -> {
- Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
- mCurrentSession = null;
- });
+ final AidlResponseHandler resultController;
+ if (Flags.deHidl()) {
+ resultController = new AidlResponseHandler(
+ mContext, mScheduler, sensorId, newUserId,
+ mLockoutTracker, lockoutResetDispatcher,
+ mBiometricContext.getAuthSessionCoordinator(), () -> {},
+ new AidlResponseHandler.AidlResponseHandlerCallback() {
+ @Override
+ public void onEnrollSuccess() {
+ mProvider.scheduleLoadAuthenticatorIdsForUser(sensorId,
+ newUserId);
+ mProvider.scheduleInvalidationRequest(sensorId,
+ newUserId);
+ }
+
+ @Override
+ public void onHardwareUnavailable() {
+ Slog.e(mTag, "Face sensor hardware unavailable.");
+ mCurrentSession = null;
+ }
+ });
+ } else {
+ resultController = new AidlResponseHandler(
+ mContext, mScheduler, sensorId, newUserId,
+ mLockoutTracker, lockoutResetDispatcher,
+ mBiometricContext.getAuthSessionCoordinator(), () -> {
+ Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
+ mCurrentSession = null;
+ });
+ }
final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
(userIdStarted, newSession, halInterfaceVersion) -> {
@@ -136,32 +195,42 @@
return new FaceStartUserClient(mContext, provider::getHalInstance,
mToken, newUserId, mSensorProperties.sensorId,
- BiometricLogger.ofUnknown(mContext), biometricContext,
+ BiometricLogger.ofUnknown(mContext), mBiometricContext,
resultController, userStartedCallback);
}
});
- mLockoutCache = new LockoutCache();
- mAuthenticatorIds = new HashMap<>();
mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
+ mLockoutTracker = new LockoutCache();
}
- Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
- @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull BiometricContext biometricContext) {
- this(tag, provider, context, handler, sensorProperties, lockoutResetDispatcher,
- biometricContext, null);
+ private static FaceSensorPropertiesInternal getFaceSensorPropertiesInternal(SensorProps prop,
+ boolean resetLockoutRequiresChallenge) {
+ final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
+ if (prop.commonProps.componentInfo != null) {
+ for (ComponentInfo info : prop.commonProps.componentInfo) {
+ componentInfo.add(new ComponentInfoInternal(info.componentId,
+ info.hardwareVersion, info.firmwareVersion, info.serialNumber,
+ info.softwareVersion));
+ }
+ }
+ final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
+ prop.commonProps.sensorId, prop.commonProps.sensorStrength,
+ prop.commonProps.maxEnrollmentsPerUser, componentInfo, prop.sensorType,
+ prop.supportsDetectInteraction, prop.halControlsPreview,
+ resetLockoutRequiresChallenge);
+
+ return internalProp;
}
- @NonNull Supplier<AidlSession> getLazySession() {
+ @NonNull public Supplier<AidlSession> getLazySession() {
return mLazySession;
}
- @NonNull FaceSensorPropertiesInternal getSensorProperties() {
+ @NonNull protected FaceSensorPropertiesInternal getSensorProperties() {
return mSensorProperties;
}
- @VisibleForTesting @Nullable AidlSession getSessionForUser(int userId) {
+ @VisibleForTesting @Nullable protected AidlSession getSessionForUser(int userId) {
if (mCurrentSession != null && mCurrentSession.getUserId() == userId) {
return mCurrentSession;
} else {
@@ -174,15 +243,18 @@
mProvider, this);
}
- @NonNull BiometricScheduler getScheduler() {
+ @NonNull public BiometricScheduler getScheduler() {
return mScheduler;
}
- @NonNull LockoutCache getLockoutCache() {
- return mLockoutCache;
+ @NonNull protected LockoutTracker getLockoutTracker(boolean forAuth) {
+ if (forAuth) {
+ return null;
+ }
+ return mLockoutTracker;
}
- @NonNull Map<Integer, Long> getAuthenticatorIds() {
+ @NonNull protected Map<Integer, Long> getAuthenticatorIds() {
return mAuthenticatorIds;
}
@@ -253,4 +325,49 @@
mScheduler.reset();
mCurrentSession = null;
}
+
+ protected BiometricContext getBiometricContext() {
+ return mBiometricContext;
+ }
+
+ protected Handler getHandler() {
+ return mHandler;
+ }
+
+ protected Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * Schedules FaceUpdateActiveUserClient for user id.
+ */
+ public void scheduleFaceUpdateActiveUserClient(int userId) {}
+
+ /**
+ * Returns true if the sensor hardware is detected.
+ */
+ public boolean isHardwareDetected(String halInstanceName) {
+ if (mTestHalEnabled) {
+ return true;
+ }
+ return ServiceManager.checkService(IFace.DESCRIPTOR + "/" + halInstanceName) != null;
+ }
+
+ /**
+ * Returns lockout mode of this sensor.
+ */
+ @LockoutTracker.LockoutMode
+ public int getLockoutModeForUser(int userId) {
+ return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
+ Utils.getCurrentStrength(mSensorProperties.sensorId));
+ }
+
+ public void setScheduler(BiometricScheduler scheduler) {
+ mScheduler = scheduler;
+ }
+
+ public void setLazySession(
+ Supplier<AidlSession> lazySession) {
+ mLazySession = lazySession;
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java
index 8385c3f..0c34d70 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java
@@ -27,13 +27,14 @@
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.HalClientMonitor;
+import com.android.server.biometrics.sensors.StartUserClient;
+import com.android.server.biometrics.sensors.face.aidl.AidlSession;
import java.io.File;
import java.util.Map;
import java.util.function.Supplier;
-public class FaceUpdateActiveUserClient extends HalClientMonitor<IBiometricsFace> {
+public class FaceUpdateActiveUserClient extends StartUserClient<IBiometricsFace, AidlSession> {
private static final String TAG = "FaceUpdateActiveUserClient";
private static final String FACE_DATA_DIR = "facedata";
@@ -45,8 +46,18 @@
int sensorId, @NonNull BiometricLogger logger,
@NonNull BiometricContext biometricContext, boolean hasEnrolledBiometrics,
@NonNull Map<Integer, Long> authenticatorIds) {
- super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
- 0 /* cookie */, sensorId, logger, biometricContext);
+ this(context, lazyDaemon, (newUserId, newUser, halInterfaceVersion) -> {},
+ userId, owner, sensorId, logger, biometricContext, hasEnrolledBiometrics,
+ authenticatorIds);
+ }
+
+ FaceUpdateActiveUserClient(@NonNull Context context,
+ @NonNull Supplier<IBiometricsFace> lazyDaemon, UserStartedCallback userStartedCallback,
+ int userId, @NonNull String owner, int sensorId, @NonNull BiometricLogger logger,
+ @NonNull BiometricContext biometricContext, boolean hasEnrolledBiometrics,
+ @NonNull Map<Integer, Long> authenticatorIds) {
+ super(context, lazyDaemon, null /* token */, userId, sensorId, logger, biometricContext,
+ userStartedCallback);
mHasEnrolledBiometrics = hasEnrolledBiometrics;
mAuthenticatorIds = authenticatorIds;
}
@@ -77,6 +88,7 @@
daemon.setActiveUser(getTargetUserId(), storePath.getAbsolutePath());
mAuthenticatorIds.put(getTargetUserId(),
mHasEnrolledBiometrics ? daemon.getAuthenticatorId().value : 0L);
+ mUserStartedCallback.onUserStarted(getTargetUserId(), null, 0);
mCallback.onClientFinished(this, true /* success */);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to setActiveUser: " + e);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java
index 36a9790..7a574ce 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java
@@ -112,4 +112,8 @@
void onAuthenticatorIdRetrieved(long authenticatorId) {
mAidlResponseHandler.onAuthenticatorIdRetrieved(authenticatorId);
}
+
+ void onUnsupportedClientScheduled() {
+ mAidlResponseHandler.onUnsupportedClientScheduled();
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java
new file mode 100644
index 0000000..6355cb5
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.biometrics.sensors.face.hidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.hardware.biometrics.face.SensorProps;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.os.Handler;
+import android.os.IHwBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.StartUserClient;
+import com.android.server.biometrics.sensors.face.FaceUtils;
+import com.android.server.biometrics.sensors.face.LockoutHalImpl;
+import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler;
+import com.android.server.biometrics.sensors.face.aidl.AidlSession;
+import com.android.server.biometrics.sensors.face.aidl.FaceProvider;
+import com.android.server.biometrics.sensors.face.aidl.Sensor;
+
+/**
+ * Convert HIDL sensor configurations to an AIDL Sensor.
+ */
+public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRecipient{
+
+ private static final String TAG = "HidlToAidlSensorAdapter";
+
+ private IBiometricsFace mDaemon;
+ private AidlSession mSession;
+ private int mCurrentUserId = UserHandle.USER_NULL;
+ private final Runnable mInternalCleanupAndGetFeatureRunnable;
+ private final FaceProvider mFaceProvider;
+ private final LockoutResetDispatcher mLockoutResetDispatcher;
+ private final AuthSessionCoordinator mAuthSessionCoordinator;
+ private final AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
+ private final StartUserClient.UserStartedCallback<AidlSession> mUserStartedCallback =
+ (newUserId, newUser, halInterfaceVersion) -> {
+ if (newUserId != mCurrentUserId) {
+ handleUserChanged(newUserId);
+ }
+ };
+ private LockoutHalImpl mLockoutTracker;
+
+ public HidlToAidlSensorAdapter(@NonNull String tag,
+ @NonNull FaceProvider provider,
+ @NonNull Context context,
+ @NonNull Handler handler,
+ @NonNull SensorProps prop,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull BiometricContext biometricContext,
+ boolean resetLockoutRequiresChallenge,
+ @NonNull Runnable internalCleanupAndGetFeatureRunnable) {
+ this(tag, provider, context, handler, prop, lockoutResetDispatcher, biometricContext,
+ resetLockoutRequiresChallenge, internalCleanupAndGetFeatureRunnable,
+ new AuthSessionCoordinator(), null /* daemon */,
+ null /* onEnrollSuccessCallback */);
+ }
+
+ @VisibleForTesting
+ HidlToAidlSensorAdapter(@NonNull String tag,
+ @NonNull FaceProvider provider,
+ @NonNull Context context,
+ @NonNull Handler handler,
+ @NonNull SensorProps prop,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull BiometricContext biometricContext,
+ boolean resetLockoutRequiresChallenge,
+ @NonNull Runnable internalCleanupAndGetFeatureRunnable,
+ @NonNull AuthSessionCoordinator authSessionCoordinator,
+ @Nullable IBiometricsFace daemon,
+ @Nullable AidlResponseHandler.AidlResponseHandlerCallback aidlResponseHandlerCallback) {
+ super(tag, provider, context, handler, prop, lockoutResetDispatcher, biometricContext,
+ resetLockoutRequiresChallenge);
+ mInternalCleanupAndGetFeatureRunnable = internalCleanupAndGetFeatureRunnable;
+ mFaceProvider = provider;
+ mLockoutResetDispatcher = lockoutResetDispatcher;
+ mAuthSessionCoordinator = authSessionCoordinator;
+ mDaemon = daemon;
+ mAidlResponseHandlerCallback = aidlResponseHandlerCallback == null
+ ? new AidlResponseHandler.AidlResponseHandlerCallback() {
+ @Override
+ public void onEnrollSuccess() {
+ scheduleFaceUpdateActiveUserClient(mCurrentUserId);
+ }
+
+ @Override
+ public void onHardwareUnavailable() {
+ mDaemon = null;
+ mCurrentUserId = UserHandle.USER_NULL;
+ }
+ } : aidlResponseHandlerCallback;
+ }
+
+ @Override
+ public void scheduleFaceUpdateActiveUserClient(int userId) {
+ getScheduler().scheduleClientMonitor(getFaceUpdateActiveUserClient(userId));
+ }
+
+ @Override
+ public void serviceDied(long cookie) {
+ Slog.d(TAG, "HAL died.");
+ mDaemon = null;
+ }
+
+ @Override
+ public boolean isHardwareDetected(String halInstanceName) {
+ return getIBiometricsFace() != null;
+ }
+
+ @Override
+ @LockoutTracker.LockoutMode
+ public int getLockoutModeForUser(int userId) {
+ return mLockoutTracker.getLockoutModeForUser(userId);
+ }
+
+ @Override
+ public void init(LockoutResetDispatcher lockoutResetDispatcher,
+ FaceProvider provider) {
+ setScheduler(new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_FACE,
+ null /* gestureAvailabilityTracker */));
+ setLazySession(this::getSession);
+ mLockoutTracker = new LockoutHalImpl();
+ }
+
+ @Override
+ @VisibleForTesting @Nullable protected AidlSession getSessionForUser(int userId) {
+ if (mSession != null && mSession.getUserId() == userId) {
+ return mSession;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ protected LockoutTracker getLockoutTracker(boolean forAuth) {
+ return mLockoutTracker;
+ }
+
+ @NonNull AidlSession getSession() {
+ if (mDaemon != null && mSession != null) {
+ return mSession;
+ } else {
+ return mSession = new AidlSession(getContext(), this::getIBiometricsFace,
+ mCurrentUserId, getAidlResponseHandler());
+ }
+ }
+
+ private AidlResponseHandler getAidlResponseHandler() {
+ return new AidlResponseHandler(getContext(), getScheduler(), getSensorProperties().sensorId,
+ mCurrentUserId, mLockoutTracker, mLockoutResetDispatcher,
+ mAuthSessionCoordinator, () -> {}, mAidlResponseHandlerCallback);
+ }
+
+ private IBiometricsFace getIBiometricsFace() {
+ if (mFaceProvider.getTestHalEnabled()) {
+ final TestHal testHal = new TestHal(getContext(), getSensorProperties().sensorId);
+ testHal.setCallback(new HidlToAidlCallbackConverter(getAidlResponseHandler()));
+ return testHal;
+ }
+
+ if (mDaemon != null) {
+ return mDaemon;
+ }
+
+ Slog.d(TAG, "Daemon was null, reconnecting, current operation: "
+ + getScheduler().getCurrentClient());
+
+ try {
+ mDaemon = IBiometricsFace.getService();
+ } catch (java.util.NoSuchElementException e) {
+ // Service doesn't exist or cannot be opened.
+ Slog.w(TAG, "NoSuchElementException", e);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get face HAL", e);
+ }
+
+ if (mDaemon == null) {
+ Slog.w(TAG, "Face HAL not available");
+ return null;
+ }
+
+ mDaemon.asBinder().linkToDeath(this, 0 /* flags */);
+
+ scheduleLoadAuthenticatorIds();
+ mInternalCleanupAndGetFeatureRunnable.run();
+ return mDaemon;
+ }
+
+ @VisibleForTesting void handleUserChanged(int newUserId) {
+ Slog.d(TAG, "User changed. Current user is " + newUserId);
+ mSession = null;
+ mCurrentUserId = newUserId;
+ }
+
+ private void scheduleLoadAuthenticatorIds() {
+ // Note that this can be performed on the scheduler (as opposed to being done immediately
+ // when the HAL is (re)loaded, since
+ // 1) If this is truly the first time it's being performed (e.g. system has just started),
+ // this will be run very early and way before any applications need to generate keys.
+ // 2) If this is being performed to refresh the authenticatorIds (e.g. HAL crashed and has
+ // just been reloaded), the framework already has a cache of the authenticatorIds. This
+ // is safe because authenticatorIds only change when A) new template has been enrolled,
+ // or B) all templates are removed.
+ getHandler().post(() -> {
+ for (UserInfo user : UserManager.get(getContext()).getAliveUsers()) {
+ final int targetUserId = user.id;
+ if (!getAuthenticatorIds().containsKey(targetUserId)) {
+ scheduleFaceUpdateActiveUserClient(targetUserId);
+ }
+ }
+ });
+ }
+
+ private FaceUpdateActiveUserClient getFaceUpdateActiveUserClient(int userId) {
+ return new FaceUpdateActiveUserClient(getContext(), this::getIBiometricsFace,
+ mUserStartedCallback, userId, TAG, getSensorProperties().sensorId,
+ BiometricLogger.ofUnknown(getContext()), getBiometricContext(),
+ !FaceUtils.getInstance(getSensorProperties().sensorId).getBiometricsForUser(
+ getContext(), userId).isEmpty(),
+ getAuthenticatorIds());
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java
similarity index 88%
rename from services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java
rename to services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java
index 489b213..5daf2d4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java
@@ -47,34 +47,37 @@
import java.util.function.Supplier;
/**
- * Adapter to convert AIDL-specific interface {@link ISession} methods to HIDL implementation.
+ * Adapter to convert HIDL methods into AIDL interface {@link ISession}.
*/
-public class AidlToHidlAdapter implements ISession {
+public class HidlToAidlSessionAdapter implements ISession {
- private final String TAG = "AidlToHidlAdapter";
+ private static final String TAG = "HidlToAidlSessionAdapter";
+
private static final int CHALLENGE_TIMEOUT_SEC = 600;
@DurationMillisLong
private static final int GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS = 60 * 1000;
@DurationMillisLong
private static final int GENERATE_CHALLENGE_COUNTER_TTL_MILLIS = CHALLENGE_TIMEOUT_SEC * 1000;
private static final int INVALID_VALUE = -1;
+ @VisibleForTesting static final int ENROLL_TIMEOUT_SEC = 75;
+
private final Clock mClock;
private final List<Long> mGeneratedChallengeCount = new ArrayList<>();
- @VisibleForTesting static final int ENROLL_TIMEOUT_SEC = 75;
+ private final int mUserId;
+ private final Context mContext;
+
private long mGenerateChallengeCreatedAt = INVALID_VALUE;
private long mGenerateChallengeResult = INVALID_VALUE;
@NonNull private Supplier<IBiometricsFace> mSession;
- private final int mUserId;
private HidlToAidlCallbackConverter mHidlToAidlCallbackConverter;
- private final Context mContext;
private int mFeature = INVALID_VALUE;
- public AidlToHidlAdapter(Context context, Supplier<IBiometricsFace> session,
+ public HidlToAidlSessionAdapter(Context context, Supplier<IBiometricsFace> session,
int userId, AidlResponseHandler aidlResponseHandler) {
this(context, session, userId, aidlResponseHandler, Clock.systemUTC());
}
- AidlToHidlAdapter(Context context, Supplier<IBiometricsFace> session, int userId,
+ HidlToAidlSessionAdapter(Context context, Supplier<IBiometricsFace> session, int userId,
AidlResponseHandler aidlResponseHandler, Clock clock) {
mSession = session;
mUserId = userId;
@@ -83,42 +86,11 @@
setCallback(aidlResponseHandler);
}
- private void setCallback(AidlResponseHandler aidlResponseHandler) {
- mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler);
- try {
- mSession.get().setCallback(mHidlToAidlCallbackConverter);
- } catch (RemoteException e) {
- Slog.d(TAG, "Failed to set callback");
- }
- }
-
@Override
public IBinder asBinder() {
return null;
}
- private boolean isGeneratedChallengeCacheValid() {
- return mGenerateChallengeCreatedAt != INVALID_VALUE
- && mGenerateChallengeResult != INVALID_VALUE
- && mClock.millis() - mGenerateChallengeCreatedAt
- < GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS;
- }
-
- private void incrementChallengeCount() {
- mGeneratedChallengeCount.add(0, mClock.millis());
- }
-
- private int decrementChallengeCount() {
- final long now = mClock.millis();
- // ignore values that are old in case generate/revoke calls are not matched
- // this doesn't ensure revoke if calls are mismatched but it keeps the list from growing
- mGeneratedChallengeCount.removeIf(x -> now - x > GENERATE_CHALLENGE_COUNTER_TTL_MILLIS);
- if (!mGeneratedChallengeCount.isEmpty()) {
- mGeneratedChallengeCount.remove(0);
- }
- return mGeneratedChallengeCount.size();
- }
-
@Override
public void generateChallenge() throws RemoteException {
incrementChallengeCount();
@@ -150,7 +122,7 @@
@Override
public EnrollmentStageConfig[] getEnrollmentConfig(byte enrollmentType) throws RemoteException {
- //unsupported in HIDL
+ Slog.e(TAG, "getEnrollmentConfig unsupported in HIDL");
return null;
}
@@ -244,19 +216,6 @@
}
}
- private int getFaceId() {
- FaceManager faceManager = mContext.getSystemService(FaceManager.class);
- List<Face> faces = faceManager.getEnrolledFaces(mUserId);
- if (faces.isEmpty()) {
- Slog.d(TAG, "No faces to get feature from.");
- mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId,
- BiometricFaceConstants.FACE_ERROR_NOT_ENROLLED, 0 /* vendorCode */);
- return INVALID_VALUE;
- }
-
- return faces.get(0).getBiometricId();
- }
-
@Override
public void getAuthenticatorId() throws RemoteException {
long authenticatorId = mSession.get().getAuthenticatorId().value;
@@ -265,7 +224,8 @@
@Override
public void invalidateAuthenticatorId() throws RemoteException {
- //unsupported in HIDL
+ Slog.e(TAG, "invalidateAuthenticatorId unsupported in HIDL");
+ mHidlToAidlCallbackConverter.onUnsupportedClientScheduled();
}
@Override
@@ -279,47 +239,105 @@
@Override
public void close() throws RemoteException {
- //Unsupported in HIDL
+ Slog.e(TAG, "close unsupported in HIDL");
}
@Override
public ICancellationSignal authenticateWithContext(long operationId, OperationContext context)
throws RemoteException {
- //Unsupported in HIDL
- return null;
+ Slog.e(TAG, "authenticateWithContext unsupported in HIDL");
+ return authenticate(operationId);
}
@Override
public ICancellationSignal enrollWithContext(HardwareAuthToken hat, byte type, byte[] features,
NativeHandle previewSurface, OperationContext context) throws RemoteException {
- //Unsupported in HIDL
- return null;
+ Slog.e(TAG, "enrollWithContext unsupported in HIDL");
+ return enroll(hat, type, features, previewSurface);
}
@Override
public ICancellationSignal detectInteractionWithContext(OperationContext context)
throws RemoteException {
- //Unsupported in HIDL
- return null;
+ Slog.e(TAG, "detectInteractionWithContext unsupported in HIDL");
+ return detectInteraction();
}
@Override
public void onContextChanged(OperationContext context) throws RemoteException {
- //Unsupported in HIDL
+ Slog.e(TAG, "onContextChanged unsupported in HIDL");
}
@Override
public int getInterfaceVersion() throws RemoteException {
- //Unsupported in HIDL
+ Slog.e(TAG, "getInterfaceVersion unsupported in HIDL");
return 0;
}
@Override
public String getInterfaceHash() throws RemoteException {
+ Slog.e(TAG, "getInterfaceHash unsupported in HIDL");
+ return null;
+ }
+
+ @Override
+ public ICancellationSignal enrollWithOptions(FaceEnrollOptions options) {
//Unsupported in HIDL
return null;
}
+ private boolean isGeneratedChallengeCacheValid() {
+ return mGenerateChallengeCreatedAt != INVALID_VALUE
+ && mGenerateChallengeResult != INVALID_VALUE
+ && mClock.millis() - mGenerateChallengeCreatedAt
+ < GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS;
+ }
+
+ private void incrementChallengeCount() {
+ mGeneratedChallengeCount.add(0, mClock.millis());
+ }
+
+ private int decrementChallengeCount() {
+ final long now = mClock.millis();
+ // ignore values that are old in case generate/revoke calls are not matched
+ // this doesn't ensure revoke if calls are mismatched but it keeps the list from growing
+ mGeneratedChallengeCount.removeIf(x -> now - x > GENERATE_CHALLENGE_COUNTER_TTL_MILLIS);
+ if (!mGeneratedChallengeCount.isEmpty()) {
+ mGeneratedChallengeCount.remove(0);
+ }
+ return mGeneratedChallengeCount.size();
+ }
+
+ private void setCallback(AidlResponseHandler aidlResponseHandler) {
+ mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler);
+ try {
+ if (mSession.get() != null) {
+ long halId = mSession.get().setCallback(mHidlToAidlCallbackConverter).value;
+ Slog.d(TAG, "Face HAL ready, HAL ID: " + halId);
+ if (halId == 0) {
+ Slog.d(TAG, "Unable to set HIDL callback.");
+ }
+ } else {
+ Slog.e(TAG, "Unable to set HIDL callback. HIDL daemon is null.");
+ }
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Failed to set callback");
+ }
+ }
+
+ private int getFaceId() {
+ FaceManager faceManager = mContext.getSystemService(FaceManager.class);
+ List<Face> faces = faceManager.getEnrolledFaces(mUserId);
+ if (faces.isEmpty()) {
+ Slog.d(TAG, "No faces to get feature from.");
+ mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId,
+ BiometricFaceConstants.FACE_ERROR_NOT_ENROLLED, 0 /* vendorCode */);
+ return INVALID_VALUE;
+ }
+
+ return faces.get(0).getBiometricId();
+ }
+
/**
* Cancellation in HIDL occurs for the entire session, instead of a specific client.
*/
@@ -345,10 +363,4 @@
return null;
}
}
-
- @Override
- public ICancellationSignal enrollWithOptions(FaceEnrollOptions options) {
- //Unsupported in HIDL
- return null;
- }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 83b306b..e01d672 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -45,9 +45,11 @@
import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.biometrics.fingerprint.IFingerprint;
import android.hardware.biometrics.fingerprint.PointerContext;
+import android.hardware.biometrics.fingerprint.SensorProps;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorConfigurations;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.FingerprintServiceReceiver;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
@@ -80,6 +82,7 @@
import com.android.internal.util.DumpUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.SystemService;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.AuthenticationStateListeners;
@@ -127,6 +130,8 @@
@NonNull
private final Function<String, FingerprintProvider> mFingerprintProvider;
@NonNull
+ private final FingerprintProviderFunction mFingerprintProviderFunction;
+ @NonNull
private final BiometricStateCallback<ServiceProvider, FingerprintSensorPropertiesInternal>
mBiometricStateCallback;
@NonNull
@@ -136,6 +141,11 @@
@NonNull
private final FingerprintServiceRegistry mRegistry;
+ interface FingerprintProviderFunction {
+ FingerprintProvider getFingerprintProvider(Pair<String, SensorProps[]> filteredSensorProp,
+ boolean resetLockoutRequiresHardwareAuthToken);
+ }
+
/** Receives the incoming binder calls from FingerprintManager. */
@VisibleForTesting
final IFingerprintService.Stub mServiceWrapper = new IFingerprintService.Stub() {
@@ -874,6 +884,18 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
+ public void registerAuthenticatorsLegacy(
+ @NonNull FingerprintSensorConfigurations fingerprintSensorConfigurations) {
+ super.registerAuthenticatorsLegacy_enforcePermission();
+ if (!fingerprintSensorConfigurations.hasSensorConfigurations()) {
+ Slog.d(TAG, "No fingerprint sensors available.");
+ return;
+ }
+ mRegistry.registerAll(() -> getProviders(fingerprintSensorConfigurations));
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+ @Override // Binder call
public void registerAuthenticators(
@NonNull List<FingerprintSensorPropertiesInternal> hidlSensors) {
super.registerAuthenticators_enforcePermission();
@@ -1021,7 +1043,8 @@
() -> IBiometricService.Stub.asInterface(
ServiceManager.getService(Context.BIOMETRIC_SERVICE)),
() -> ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR),
- null /* fingerprintProvider */);
+ null /* fingerprintProvider */,
+ null /* fingerprintProviderFunction */);
}
@VisibleForTesting
@@ -1029,7 +1052,8 @@
BiometricContext biometricContext,
Supplier<IBiometricService> biometricServiceSupplier,
Supplier<String[]> aidlInstanceNameSupplier,
- Function<String, FingerprintProvider> fingerprintProvider) {
+ Function<String, FingerprintProvider> fingerprintProvider,
+ FingerprintProviderFunction fingerprintProviderFunction) {
super(context);
mBiometricContext = biometricContext;
mAidlInstanceNameSupplier = aidlInstanceNameSupplier;
@@ -1049,7 +1073,8 @@
return new FingerprintProvider(getContext(),
mBiometricStateCallback, mAuthenticationStateListeners,
fp.getSensorProps(), name, mLockoutResetDispatcher,
- mGestureAvailabilityDispatcher, mBiometricContext);
+ mGestureAvailabilityDispatcher, mBiometricContext,
+ true /* resetLockoutRequiresHardwareAuthToken */);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
}
@@ -1059,6 +1084,22 @@
return null;
};
+ if (Flags.deHidl()) {
+ mFingerprintProviderFunction = fingerprintProviderFunction == null
+ ? (filteredSensorProps, resetLockoutRequiresHardwareAuthToken) ->
+ new FingerprintProvider(
+ getContext(), mBiometricStateCallback,
+ mAuthenticationStateListeners,
+ filteredSensorProps.second,
+ filteredSensorProps.first, mLockoutResetDispatcher,
+ mGestureAvailabilityDispatcher,
+ mBiometricContext,
+ resetLockoutRequiresHardwareAuthToken)
+ : fingerprintProviderFunction;
+ } else {
+ mFingerprintProviderFunction =
+ (filteredSensorProps, resetLockoutRequiresHardwareAuthToken) -> null;
+ }
mHandler = new Handler(Looper.getMainLooper());
mRegistry = new FingerprintServiceRegistry(mServiceWrapper, biometricServiceSupplier);
mRegistry.addAllRegisteredCallback(new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
@@ -1070,6 +1111,44 @@
});
}
+ @NonNull
+ private List<ServiceProvider> getProviders(@NonNull FingerprintSensorConfigurations
+ fingerprintSensorConfigurations) {
+ final List<ServiceProvider> providers = new ArrayList<>();
+ final Pair<String, SensorProps[]> filteredSensorProps = filterAvailableHalInstances(
+ fingerprintSensorConfigurations);
+ providers.add(mFingerprintProviderFunction.getFingerprintProvider(filteredSensorProps,
+ fingerprintSensorConfigurations.getResetLockoutRequiresHardwareAuthToken()));
+
+ return providers;
+ }
+
+ @NonNull
+ private Pair<String, SensorProps[]> filterAvailableHalInstances(
+ FingerprintSensorConfigurations fingerprintSensorConfigurations) {
+ Pair<String, SensorProps[]> finalSensorPair =
+ fingerprintSensorConfigurations.getSensorPair();
+ if (fingerprintSensorConfigurations.isSingleSensorConfigurationPresent()) {
+ return finalSensorPair;
+ }
+
+ final Pair<String, SensorProps[]> virtualSensorPropsPair = fingerprintSensorConfigurations
+ .getSensorPairForInstance("virtual");
+ if (Utils.isVirtualEnabled(getContext())) {
+ if (virtualSensorPropsPair != null) {
+ return virtualSensorPropsPair;
+ } else {
+ Slog.e(TAG, "Could not find virtual interface while it is enabled");
+ return finalSensorPair;
+ }
+ } else {
+ if (virtualSensorPropsPair != null) {
+ return fingerprintSensorConfigurations.getSensorPairNotForInstance("virtual");
+ }
+ }
+ return finalSensorPair;
+ }
+
private Pair<List<FingerprintSensorPropertiesInternal>, List<String>>
filterAvailableHalInstances(
@NonNull List<FingerprintSensorPropertiesInternal> hidlInstances,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
index 4a01943..bd21cf4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
@@ -25,6 +25,7 @@
import android.hardware.keymaster.HardwareAuthToken;
import android.util.Slog;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.AcquisitionClient;
@@ -34,9 +35,9 @@
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.EnumerateConsumer;
import com.android.server.biometrics.sensors.ErrorConsumer;
-import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutConsumer;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.RemovalConsumer;
import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
@@ -59,6 +60,21 @@
void onHardwareUnavailable();
}
+ /**
+ * Interface to send results to the AidlResponseHandler's owner.
+ */
+ public interface AidlResponseHandlerCallback {
+ /**
+ * Invoked when enrollment is successful.
+ */
+ void onEnrollSuccess();
+
+ /**
+ * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
+ */
+ void onHardwareUnavailable();
+ }
+
private static final String TAG = "AidlResponseHandler";
@NonNull
@@ -68,28 +84,49 @@
private final int mSensorId;
private final int mUserId;
@NonNull
- private final LockoutCache mLockoutCache;
+ private final LockoutTracker mLockoutTracker;
@NonNull
private final LockoutResetDispatcher mLockoutResetDispatcher;
@NonNull
private final AuthSessionCoordinator mAuthSessionCoordinator;
@NonNull
private final HardwareUnavailableCallback mHardwareUnavailableCallback;
+ @NonNull
+ private final AidlResponseHandlerCallback mAidlResponseHandlerCallback;
public AidlResponseHandler(@NonNull Context context,
@NonNull BiometricScheduler scheduler, int sensorId, int userId,
- @NonNull LockoutCache lockoutTracker,
+ @NonNull LockoutTracker lockoutTracker,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull AuthSessionCoordinator authSessionCoordinator,
@NonNull HardwareUnavailableCallback hardwareUnavailableCallback) {
+ this(context, scheduler, sensorId, userId, lockoutTracker, lockoutResetDispatcher,
+ authSessionCoordinator, hardwareUnavailableCallback,
+ new AidlResponseHandlerCallback() {
+ @Override
+ public void onEnrollSuccess() {}
+
+ @Override
+ public void onHardwareUnavailable() {}
+ });
+ }
+
+ public AidlResponseHandler(@NonNull Context context,
+ @NonNull BiometricScheduler scheduler, int sensorId, int userId,
+ @NonNull LockoutTracker lockoutTracker,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull AuthSessionCoordinator authSessionCoordinator,
+ @NonNull HardwareUnavailableCallback hardwareUnavailableCallback,
+ @NonNull AidlResponseHandlerCallback aidlResponseHandlerCallback) {
mContext = context;
mScheduler = scheduler;
mSensorId = sensorId;
mUserId = userId;
- mLockoutCache = lockoutTracker;
+ mLockoutTracker = lockoutTracker;
mLockoutResetDispatcher = lockoutResetDispatcher;
mAuthSessionCoordinator = authSessionCoordinator;
mHardwareUnavailableCallback = hardwareUnavailableCallback;
+ mAidlResponseHandlerCallback = aidlResponseHandlerCallback;
}
@Override
@@ -105,27 +142,26 @@
@Override
public void onChallengeGenerated(long challenge) {
handleResponse(FingerprintGenerateChallengeClient.class, (c) -> c.onChallengeGenerated(
- mSensorId, mUserId, challenge), null);
+ mSensorId, mUserId, challenge));
}
@Override
public void onChallengeRevoked(long challenge) {
handleResponse(FingerprintRevokeChallengeClient.class, (c) -> c.onChallengeRevoked(
- challenge), null);
+ challenge));
}
/**
* Handles acquired messages sent by the HAL (specifically for HIDL HAL).
*/
public void onAcquired(int acquiredInfo, int vendorCode) {
- handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(acquiredInfo, vendorCode),
- null);
+ handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(acquiredInfo, vendorCode));
}
@Override
public void onAcquired(byte info, int vendorCode) {
handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(
- AidlConversionUtils.toFrameworkAcquiredInfo(info), vendorCode), null);
+ AidlConversionUtils.toFrameworkAcquiredInfo(info), vendorCode));
}
/**
@@ -135,9 +171,13 @@
handleResponse(ErrorConsumer.class, (c) -> {
c.onError(error, vendorCode);
if (error == Error.HW_UNAVAILABLE) {
- mHardwareUnavailableCallback.onHardwareUnavailable();
+ if (Flags.deHidl()) {
+ mAidlResponseHandlerCallback.onHardwareUnavailable();
+ } else {
+ mHardwareUnavailableCallback.onHardwareUnavailable();
+ }
}
- }, null);
+ });
}
@Override
@@ -158,8 +198,12 @@
.getUniqueName(mContext, currentUserId);
final Fingerprint fingerprint = new Fingerprint(name, currentUserId,
enrollmentId, mSensorId);
- handleResponse(FingerprintEnrollClient.class, (c) -> c.onEnrollResult(fingerprint,
- remaining), null);
+ handleResponse(FingerprintEnrollClient.class, (c) -> {
+ c.onEnrollResult(fingerprint, remaining);
+ if (remaining == 0) {
+ mAidlResponseHandlerCallback.onEnrollSuccess();
+ }
+ });
}
@Override
@@ -184,13 +228,12 @@
@Override
public void onLockoutTimed(long durationMillis) {
- handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis),
- null);
+ handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis));
}
@Override
public void onLockoutPermanent() {
- handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent, null);
+ handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent);
}
@Override
@@ -198,7 +241,7 @@
handleResponse(FingerprintResetLockoutClient.class,
FingerprintResetLockoutClient::onLockoutCleared,
(c) -> FingerprintResetLockoutClient.resetLocalLockoutStateToNone(
- mSensorId, mUserId, mLockoutCache, mLockoutResetDispatcher,
+ mSensorId, mUserId, mLockoutTracker, mLockoutResetDispatcher,
mAuthSessionCoordinator, Utils.getCurrentStrength(mSensorId),
-1 /* requestId */));
}
@@ -206,49 +249,74 @@
@Override
public void onInteractionDetected() {
handleResponse(FingerprintDetectClient.class,
- FingerprintDetectClient::onInteractionDetected, null);
+ FingerprintDetectClient::onInteractionDetected);
}
@Override
public void onEnrollmentsEnumerated(int[] enrollmentIds) {
if (enrollmentIds.length > 0) {
for (int i = 0; i < enrollmentIds.length; i++) {
- final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId);
- int finalI = i;
- handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(fp,
- enrollmentIds.length - finalI - 1), null);
+ onEnrollmentEnumerated(enrollmentIds[i],
+ enrollmentIds.length - i - 1 /* remaining */);
}
} else {
- handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(null,
- 0), null);
+ handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(
+ null /* identifier */,
+ 0 /* remaining */));
}
}
+ /**
+ * Handle enumerated fingerprint.
+ */
+ public void onEnrollmentEnumerated(int enrollmentId, int remaining) {
+ final Fingerprint fp = new Fingerprint("", enrollmentId, mSensorId);
+ handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(fp, remaining));
+ }
+
+ /**
+ * Handle removal of fingerprint.
+ */
+ public void onEnrollmentRemoved(int enrollmentId, int remaining) {
+ final Fingerprint fp = new Fingerprint("", enrollmentId, mSensorId);
+ handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(fp, remaining));
+ }
+
@Override
public void onEnrollmentsRemoved(int[] enrollmentIds) {
if (enrollmentIds.length > 0) {
for (int i = 0; i < enrollmentIds.length; i++) {
- final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId);
- int finalI = i;
- handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(fp,
- enrollmentIds.length - finalI - 1), null);
+ onEnrollmentRemoved(enrollmentIds[i], enrollmentIds.length - i - 1 /* remaining */);
}
} else {
- handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(null, 0),
- null);
+ handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(null /* identifier */,
+ 0 /* remaining */));
}
}
@Override
public void onAuthenticatorIdRetrieved(long authenticatorId) {
handleResponse(FingerprintGetAuthenticatorIdClient.class,
- (c) -> c.onAuthenticatorIdRetrieved(authenticatorId), null);
+ (c) -> c.onAuthenticatorIdRetrieved(authenticatorId));
}
@Override
public void onAuthenticatorIdInvalidated(long newAuthenticatorId) {
handleResponse(FingerprintInvalidationClient.class, (c) -> c.onAuthenticatorIdInvalidated(
- newAuthenticatorId), null);
+ newAuthenticatorId));
+ }
+
+ /**
+ * Handle clients which are not supported in HIDL HAL.
+ */
+ public <T extends BaseClientMonitor> void onUnsupportedClientScheduled(Class<T> className) {
+ Slog.e(TAG, className + " is not supported in the HAL.");
+ handleResponse(className, (c) -> c.cancel());
+ }
+
+ private <T> void handleResponse(@NonNull Class<T> className,
+ @NonNull Consumer<T> action) {
+ handleResponse(className, action, null /* alternateAction */);
}
private <T> void handleResponse(@NonNull Class<T> className,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
index 299a310..8ff105b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
@@ -20,7 +20,7 @@
import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import com.android.server.biometrics.sensors.fingerprint.hidl.AidlToHidlAdapter;
+import com.android.server.biometrics.sensors.fingerprint.hidl.HidlToAidlSessionAdapter;
import java.util.function.Supplier;
@@ -45,7 +45,7 @@
public AidlSession(@NonNull Supplier<IBiometricsFingerprint> session,
int userId, AidlResponseHandler aidlResponseHandler) {
- mSession = new AidlToHidlAdapter(session, userId, aidlResponseHandler);
+ mSession = new HidlToAidlSessionAdapter(session, userId, aidlResponseHandler);
mHalInterfaceVersion = 0;
mUserId = userId;
mAidlResponseHandler = aidlResponseHandler;
@@ -62,7 +62,7 @@
}
/** The HAL callback, which should only be used in tests {@See BiometricTestSessionImpl}. */
- AidlResponseHandler getHalSessionCallback() {
+ public AidlResponseHandler getHalSessionCallback() {
return mAidlResponseHandler;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
index ea1a622..0353969 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
@@ -30,7 +30,7 @@
import java.util.Map;
import java.util.function.Supplier;
-class FingerprintGetAuthenticatorIdClient extends HalClientMonitor<AidlSession> {
+public class FingerprintGetAuthenticatorIdClient extends HalClientMonitor<AidlSession> {
private static final String TAG = "FingerprintGetAuthenticatorIdClient";
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 032ab87..88a11d9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -59,6 +59,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
import com.android.server.biometrics.AuthenticationStatsCollector;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
@@ -73,6 +74,7 @@
import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
import com.android.server.biometrics.sensors.InvalidationRequesterClient;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.PerformanceTracker;
import com.android.server.biometrics.sensors.SensorList;
import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
@@ -80,6 +82,7 @@
import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;
import com.android.server.biometrics.sensors.fingerprint.ServiceProvider;
import com.android.server.biometrics.sensors.fingerprint.Udfps;
+import com.android.server.biometrics.sensors.fingerprint.hidl.HidlToAidlSensorAdapter;
import org.json.JSONArray;
import org.json.JSONException;
@@ -165,10 +168,12 @@
@NonNull SensorProps[] props, @NonNull String halInstanceName,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
- @NonNull BiometricContext biometricContext) {
+ @NonNull BiometricContext biometricContext,
+ boolean resetLockoutRequiresHardwareAuthToken) {
this(context, biometricStateCallback, authenticationStateListeners, props, halInstanceName,
lockoutResetDispatcher, gestureAvailabilityDispatcher, biometricContext,
- null /* daemon */);
+ null /* daemon */, resetLockoutRequiresHardwareAuthToken,
+ false /* testHalEnabled */);
}
@VisibleForTesting FingerprintProvider(@NonNull Context context,
@@ -178,7 +183,9 @@
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
@NonNull BiometricContext biometricContext,
- IFingerprint daemon) {
+ @Nullable IFingerprint daemon,
+ boolean resetLockoutRequiresHardwareAuthToken,
+ boolean testHalEnabled) {
mContext = context;
mBiometricStateCallback = biometricStateCallback;
mAuthenticationStateListeners = authenticationStateListeners;
@@ -191,62 +198,136 @@
mBiometricContext = biometricContext;
mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
mDaemon = daemon;
+ mTestHalEnabled = testHalEnabled;
- AuthenticationStatsBroadcastReceiver mBroadcastReceiver =
- new AuthenticationStatsBroadcastReceiver(
- mContext,
- BiometricsProtoEnums.MODALITY_FINGERPRINT,
- (AuthenticationStatsCollector collector) -> {
- Slog.d(getTag(), "Initializing AuthenticationStatsCollector");
- mAuthenticationStatsCollector = collector;
- });
+ initAuthenticationBroadcastReceiver();
+ initSensors(resetLockoutRequiresHardwareAuthToken, props, gestureAvailabilityDispatcher);
+ }
- final List<SensorLocationInternal> workaroundLocations = getWorkaroundSensorProps(context);
+ private void initAuthenticationBroadcastReceiver() {
+ new AuthenticationStatsBroadcastReceiver(
+ mContext,
+ BiometricsProtoEnums.MODALITY_FINGERPRINT,
+ (AuthenticationStatsCollector collector) -> {
+ Slog.d(getTag(), "Initializing AuthenticationStatsCollector");
+ mAuthenticationStatsCollector = collector;
+ });
+ }
- for (SensorProps prop : props) {
- final int sensorId = prop.commonProps.sensorId;
-
- final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
- if (prop.commonProps.componentInfo != null) {
- for (ComponentInfo info : prop.commonProps.componentInfo) {
- componentInfo.add(new ComponentInfoInternal(info.componentId,
- info.hardwareVersion, info.firmwareVersion, info.serialNumber,
- info.softwareVersion));
+ private void initSensors(boolean resetLockoutRequiresHardwareAuthToken, SensorProps[] props,
+ GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+ if (Flags.deHidl()) {
+ if (!resetLockoutRequiresHardwareAuthToken) {
+ Slog.d(getTag(), "Adding HIDL configs");
+ for (SensorProps sensorConfig: props) {
+ addHidlSensors(sensorConfig, gestureAvailabilityDispatcher,
+ resetLockoutRequiresHardwareAuthToken);
+ }
+ } else {
+ Slog.d(getTag(), "Adding AIDL configs");
+ final List<SensorLocationInternal> workaroundLocations =
+ getWorkaroundSensorProps(mContext);
+ for (SensorProps prop : props) {
+ addAidlSensors(prop, gestureAvailabilityDispatcher, workaroundLocations,
+ resetLockoutRequiresHardwareAuthToken);
}
}
+ } else {
+ final List<SensorLocationInternal> workaroundLocations =
+ getWorkaroundSensorProps(mContext);
- final FingerprintSensorPropertiesInternal internalProp =
- new FingerprintSensorPropertiesInternal(prop.commonProps.sensorId,
- prop.commonProps.sensorStrength,
- prop.commonProps.maxEnrollmentsPerUser,
- componentInfo,
- prop.sensorType,
- prop.halControlsIllumination,
- true /* resetLockoutRequiresHardwareAuthToken */,
- !workaroundLocations.isEmpty() ? workaroundLocations :
- Arrays.stream(prop.sensorLocations).map(location ->
- new SensorLocationInternal(
- location.display,
- location.sensorLocationX,
- location.sensorLocationY,
- location.sensorRadius))
- .collect(Collectors.toList()));
- final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
- internalProp, lockoutResetDispatcher, gestureAvailabilityDispatcher,
- mBiometricContext);
- final int sessionUserId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
- sensor.getLazySession().get().getUserId();
- mFingerprintSensors.addSensor(sensorId, sensor, sessionUserId,
- new SynchronousUserSwitchObserver() {
- @Override
- public void onUserSwitching(int newUserId) {
- scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
- }
- });
- Slog.d(getTag(), "Added: " + internalProp);
+ for (SensorProps prop : props) {
+ final int sensorId = prop.commonProps.sensorId;
+ final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
+ if (prop.commonProps.componentInfo != null) {
+ for (ComponentInfo info : prop.commonProps.componentInfo) {
+ componentInfo.add(new ComponentInfoInternal(info.componentId,
+ info.hardwareVersion, info.firmwareVersion, info.serialNumber,
+ info.softwareVersion));
+ }
+ }
+ final FingerprintSensorPropertiesInternal internalProp =
+ new FingerprintSensorPropertiesInternal(prop.commonProps.sensorId,
+ prop.commonProps.sensorStrength,
+ prop.commonProps.maxEnrollmentsPerUser,
+ componentInfo,
+ prop.sensorType,
+ prop.halControlsIllumination,
+ true /* resetLockoutRequiresHardwareAuthToken */,
+ !workaroundLocations.isEmpty() ? workaroundLocations :
+ Arrays.stream(prop.sensorLocations).map(
+ location -> new SensorLocationInternal(
+ location.display,
+ location.sensorLocationX,
+ location.sensorLocationY,
+ location.sensorRadius))
+ .collect(Collectors.toList()));
+ final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext,
+ mHandler, internalProp, mLockoutResetDispatcher,
+ gestureAvailabilityDispatcher, mBiometricContext);
+ sensor.init(gestureAvailabilityDispatcher,
+ mLockoutResetDispatcher);
+ final int sessionUserId =
+ sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
+ sensor.getLazySession().get().getUserId();
+ mFingerprintSensors.addSensor(sensorId, sensor, sessionUserId,
+ new SynchronousUserSwitchObserver() {
+ @Override
+ public void onUserSwitching(int newUserId) {
+ scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
+ }
+ });
+ Slog.d(getTag(), "Added: " + mFingerprintSensors.get(sensorId).toString());
+ }
}
}
+ private void addHidlSensors(@NonNull SensorProps prop,
+ @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ boolean resetLockoutRequiresHardwareAuthToken) {
+ final int sensorId = prop.commonProps.sensorId;
+ final Sensor sensor = new HidlToAidlSensorAdapter(getTag() + "/"
+ + sensorId, this, mContext, mHandler,
+ prop, mLockoutResetDispatcher, gestureAvailabilityDispatcher,
+ mBiometricContext, resetLockoutRequiresHardwareAuthToken,
+ () -> scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser(),
+ null /* callback */));
+ sensor.init(gestureAvailabilityDispatcher, mLockoutResetDispatcher);
+ final int sessionUserId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
+ sensor.getLazySession().get().getUserId();
+ mFingerprintSensors.addSensor(sensorId, sensor, sessionUserId,
+ new SynchronousUserSwitchObserver() {
+ @Override
+ public void onUserSwitching(int newUserId) {
+ scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
+ }
+ });
+ Slog.d(getTag(), "Added: " + mFingerprintSensors.get(sensorId).toString());
+ }
+
+ private void addAidlSensors(@NonNull SensorProps prop,
+ @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ List<SensorLocationInternal> workaroundLocations,
+ boolean resetLockoutRequiresHardwareAuthToken) {
+ final int sensorId = prop.commonProps.sensorId;
+ final Sensor sensor = new Sensor(getTag() + "/" + sensorId,
+ this, mContext, mHandler,
+ prop, mLockoutResetDispatcher, gestureAvailabilityDispatcher,
+ mBiometricContext, workaroundLocations,
+ resetLockoutRequiresHardwareAuthToken);
+ sensor.init(gestureAvailabilityDispatcher, mLockoutResetDispatcher);
+ final int sessionUserId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
+ sensor.getLazySession().get().getUserId();
+ mFingerprintSensors.addSensor(sensorId, sensor, sessionUserId,
+ new SynchronousUserSwitchObserver() {
+ @Override
+ public void onUserSwitching(int newUserId) {
+ scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
+ }
+ });
+ Slog.d(getTag(), "Added: " + mFingerprintSensors.get(sensorId).toString());
+ }
+
private String getTag() {
return "FingerprintProvider/" + mHalInstanceName;
}
@@ -351,7 +432,10 @@
}
}
- private void scheduleLoadAuthenticatorIdsForUser(int sensorId, int userId) {
+ /**
+ * Schedules FingerprintGetAuthenticatorIdClient for specific sensor and user.
+ */
+ protected void scheduleLoadAuthenticatorIdsForUser(int sensorId, int userId) {
mHandler.post(() -> {
final FingerprintGetAuthenticatorIdClient client =
new FingerprintGetAuthenticatorIdClient(mContext,
@@ -387,8 +471,8 @@
BiometricsProtoEnums.CLIENT_UNKNOWN,
mAuthenticationStatsCollector),
mBiometricContext, hardwareAuthToken,
- mFingerprintSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher,
- Utils.getCurrentStrength(sensorId));
+ mFingerprintSensors.get(sensorId).getLockoutTracker(false /* forAuth */),
+ mLockoutResetDispatcher, Utils.getCurrentStrength(sensorId));
scheduleForSensor(sensorId, client);
});
}
@@ -443,18 +527,23 @@
mFingerprintSensors.get(sensorId).getSensorProperties(),
mUdfpsOverlayController, mSidefpsController,
mAuthenticationStateListeners, maxTemplatesPerUser, enrollReason);
- scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
- mBiometricStateCallback, new ClientMonitorCallback() {
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
- boolean success) {
- ClientMonitorCallback.super.onClientFinished(clientMonitor, success);
- if (success) {
- scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
- scheduleInvalidationRequest(sensorId, userId);
- }
- }
- }));
+ if (Flags.deHidl()) {
+ scheduleForSensor(sensorId, client, mBiometricStateCallback);
+ } else {
+ scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
+ mBiometricStateCallback, new ClientMonitorCallback() {
+ @Override
+ public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+ boolean success) {
+ ClientMonitorCallback.super.onClientFinished(
+ clientMonitor, success);
+ if (success) {
+ scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
+ scheduleInvalidationRequest(sensorId, userId);
+ }
+ }
+ }));
+ }
});
return id;
}
@@ -497,6 +586,13 @@
final int userId = options.getUserId();
final int sensorId = options.getSensorId();
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
+ final LockoutTracker lockoutTracker;
+ if (Flags.deHidl()) {
+ lockoutTracker = mFingerprintSensors.get(sensorId)
+ .getLockoutTracker(true /* forAuth */);
+ } else {
+ lockoutTracker = null;
+ }
final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
mContext, mFingerprintSensors.get(sensorId).getLazySession(), token, requestId,
callback, operationId, restricted, options, cookie,
@@ -510,7 +606,7 @@
mFingerprintSensors.get(sensorId).getSensorProperties(), mHandler,
Utils.getCurrentStrength(sensorId),
SystemClock.elapsedRealtimeClock(),
- null /* lockoutTracker */);
+ lockoutTracker);
scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
@Override
@@ -636,6 +732,9 @@
@Override
public boolean isHardwareDetected(int sensorId) {
+ if (Flags.deHidl()) {
+ return mFingerprintSensors.get(sensorId).isHardwareDetected(mHalInstanceName);
+ }
return hasHalInstance();
}
@@ -674,8 +773,12 @@
@Override
public int getLockoutModeForUser(int sensorId, int userId) {
- return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
- Utils.getCurrentStrength(sensorId));
+ if (Flags.deHidl()) {
+ return mFingerprintSensors.get(sensorId).getLockoutModeForUser(userId);
+ } else {
+ return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
+ Utils.getCurrentStrength(sensorId));
+ }
}
@Override
@@ -829,6 +932,10 @@
mTestHalEnabled = enabled;
}
+ public boolean getTestHalEnabled() {
+ return mTestHalEnabled;
+ }
+
// TODO(b/174868353): workaround for gaps in HAL interface (remove and get directly from HAL)
// reads values via an overlay instead of querying the HAL
@NonNull
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
index ec225a6..387ae12 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
@@ -60,7 +60,8 @@
@Authenticators.Types int biometricStrength) {
super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
0 /* cookie */, sensorId, biometricLogger, biometricContext);
- mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
+ mHardwareAuthToken = hardwareAuthToken == null ? null :
+ HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
mLockoutCache = lockoutTracker;
mLockoutResetDispatcher = lockoutResetDispatcher;
mBiometricStrength = biometricStrength;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index 893cb8f..dd887bb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -21,21 +21,28 @@
import android.content.Context;
import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
+import android.hardware.biometrics.SensorLocationInternal;
+import android.hardware.biometrics.common.ComponentInfo;
+import android.hardware.biometrics.fingerprint.IFingerprint;
import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.biometrics.fingerprint.SensorProps;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.SensorServiceStateProto;
import com.android.server.biometrics.SensorStateProto;
import com.android.server.biometrics.UserStateProto;
@@ -48,15 +55,20 @@
import com.android.server.biometrics.sensors.ErrorConsumer;
import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.StartUserClient;
import com.android.server.biometrics.sensors.StopUserClient;
import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
+import java.util.stream.Collectors;
/**
* Maintains the state of a single sensor within an instance of the
@@ -73,15 +85,17 @@
@NonNull private final IBinder mToken;
@NonNull private final Handler mHandler;
@NonNull private final FingerprintSensorPropertiesInternal mSensorProperties;
- @NonNull private final UserAwareBiometricScheduler mScheduler;
- @NonNull private final LockoutCache mLockoutCache;
+ @NonNull private BiometricScheduler mScheduler;
+ @NonNull private LockoutTracker mLockoutTracker;
@NonNull private final Map<Integer, Long> mAuthenticatorIds;
+ @NonNull private final BiometricContext mBiometricContext;
@Nullable AidlSession mCurrentSession;
- @NonNull private final Supplier<AidlSession> mLazySession;
+ @NonNull private Supplier<AidlSession> mLazySession;
- Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
- @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
+ public Sensor(@NonNull String tag, @NonNull FingerprintProvider provider,
+ @NonNull Context context, @NonNull Handler handler,
+ @NonNull FingerprintSensorPropertiesInternal sensorProperties,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
@NonNull BiometricContext biometricContext, AidlSession session) {
@@ -91,8 +105,38 @@
mToken = new Binder();
mHandler = handler;
mSensorProperties = sensorProperties;
- mLockoutCache = new LockoutCache();
- mScheduler = new UserAwareBiometricScheduler(tag,
+ mBiometricContext = biometricContext;
+ mAuthenticatorIds = new HashMap<>();
+ mCurrentSession = session;
+ }
+
+ Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
+ @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ @NonNull BiometricContext biometricContext) {
+ this(tag, provider, context, handler, sensorProperties, lockoutResetDispatcher,
+ gestureAvailabilityDispatcher, biometricContext, null);
+ }
+
+ Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
+ @NonNull Handler handler, @NonNull SensorProps sensorProp,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ @NonNull BiometricContext biometricContext,
+ @NonNull List<SensorLocationInternal> workaroundLocation,
+ boolean resetLockoutRequiresHardwareAuthToken) {
+ this(tag, provider, context, handler, getFingerprintSensorPropertiesInternal(sensorProp,
+ workaroundLocation, resetLockoutRequiresHardwareAuthToken),
+ lockoutResetDispatcher, gestureAvailabilityDispatcher, biometricContext, null);
+ }
+
+ /**
+ * Initialize biometric scheduler, lockout tracker and session for the sensor.
+ */
+ public void init(GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ LockoutResetDispatcher lockoutResetDispatcher) {
+ mScheduler = new UserAwareBiometricScheduler(mTag,
BiometricScheduler.sensorTypeFromFingerprintProperties(mSensorProperties),
gestureAvailabilityDispatcher,
() -> mCurrentSession != null ? mCurrentSession.getUserId() : UserHandle.USER_NULL,
@@ -102,7 +146,7 @@
public StopUserClient<?> getStopUserClient(int userId) {
return new FingerprintStopUserClient(mContext, mLazySession, mToken,
userId, mSensorProperties.sensorId,
- BiometricLogger.ofUnknown(mContext), biometricContext,
+ BiometricLogger.ofUnknown(mContext), mBiometricContext,
() -> mCurrentSession = null);
}
@@ -111,13 +155,38 @@
public StartUserClient<?, ?> getStartUserClient(int newUserId) {
final int sensorId = mSensorProperties.sensorId;
- final AidlResponseHandler resultController = new AidlResponseHandler(
- mContext, mScheduler, sensorId, newUserId,
- mLockoutCache, lockoutResetDispatcher,
- biometricContext.getAuthSessionCoordinator(), () -> {
- Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
- mCurrentSession = null;
- });
+ final AidlResponseHandler resultController;
+
+ if (Flags.deHidl()) {
+ resultController = new AidlResponseHandler(
+ mContext, mScheduler, sensorId, newUserId,
+ mLockoutTracker, lockoutResetDispatcher,
+ mBiometricContext.getAuthSessionCoordinator(), () -> {},
+ new AidlResponseHandler.AidlResponseHandlerCallback() {
+ @Override
+ public void onEnrollSuccess() {
+ mProvider.scheduleLoadAuthenticatorIdsForUser(sensorId,
+ newUserId);
+ mProvider.scheduleInvalidationRequest(sensorId,
+ newUserId);
+ }
+
+ @Override
+ public void onHardwareUnavailable() {
+ Slog.e(mTag,
+ "Fingerprint sensor hardware unavailable.");
+ mCurrentSession = null;
+ }
+ });
+ } else {
+ resultController = new AidlResponseHandler(
+ mContext, mScheduler, sensorId, newUserId,
+ mLockoutTracker, lockoutResetDispatcher,
+ mBiometricContext.getAuthSessionCoordinator(), () -> {
+ Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
+ mCurrentSession = null;
+ });
+ }
final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
(userIdStarted, newSession, halInterfaceVersion) -> {
@@ -133,40 +202,58 @@
+ "sensor: "
+ sensorId
+ ", user: " + userIdStarted);
- provider.scheduleInvalidationRequest(sensorId,
+ mProvider.scheduleInvalidationRequest(sensorId,
userIdStarted);
}
};
- return new FingerprintStartUserClient(mContext, provider::getHalInstance,
+ return new FingerprintStartUserClient(mContext, mProvider::getHalInstance,
mToken, newUserId, mSensorProperties.sensorId,
- BiometricLogger.ofUnknown(mContext), biometricContext,
+ BiometricLogger.ofUnknown(mContext), mBiometricContext,
resultController, userStartedCallback);
}
});
- mAuthenticatorIds = new HashMap<>();
+ mLockoutTracker = new LockoutCache();
mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
- mCurrentSession = session;
}
- Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
- @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
- @NonNull BiometricContext biometricContext) {
- this(tag, provider, context, handler, sensorProperties, lockoutResetDispatcher,
- gestureAvailabilityDispatcher, biometricContext, null);
+ protected static FingerprintSensorPropertiesInternal getFingerprintSensorPropertiesInternal(
+ SensorProps prop, List<SensorLocationInternal> workaroundLocations,
+ boolean resetLockoutRequiresHardwareAuthToken) {
+ final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
+ if (prop.commonProps.componentInfo != null) {
+ for (ComponentInfo info : prop.commonProps.componentInfo) {
+ componentInfo.add(new ComponentInfoInternal(info.componentId,
+ info.hardwareVersion, info.firmwareVersion, info.serialNumber,
+ info.softwareVersion));
+ }
+ }
+ return new FingerprintSensorPropertiesInternal(prop.commonProps.sensorId,
+ prop.commonProps.sensorStrength,
+ prop.commonProps.maxEnrollmentsPerUser,
+ componentInfo,
+ prop.sensorType,
+ prop.halControlsIllumination,
+ resetLockoutRequiresHardwareAuthToken,
+ !workaroundLocations.isEmpty() ? workaroundLocations :
+ Arrays.stream(prop.sensorLocations).map(location ->
+ new SensorLocationInternal(
+ location.display,
+ location.sensorLocationX,
+ location.sensorLocationY,
+ location.sensorRadius))
+ .collect(Collectors.toList()));
}
- @NonNull Supplier<AidlSession> getLazySession() {
+ @NonNull public Supplier<AidlSession> getLazySession() {
return mLazySession;
}
- @NonNull FingerprintSensorPropertiesInternal getSensorProperties() {
+ @NonNull public FingerprintSensorPropertiesInternal getSensorProperties() {
return mSensorProperties;
}
- @Nullable AidlSession getSessionForUser(int userId) {
+ @Nullable protected AidlSession getSessionForUser(int userId) {
if (mCurrentSession != null && mCurrentSession.getUserId() == userId) {
return mCurrentSession;
} else {
@@ -180,15 +267,18 @@
biometricStateCallback, mProvider, this);
}
- @NonNull BiometricScheduler getScheduler() {
+ @NonNull public BiometricScheduler getScheduler() {
return mScheduler;
}
- @NonNull LockoutCache getLockoutCache() {
- return mLockoutCache;
+ @NonNull protected LockoutTracker getLockoutTracker(boolean forAuth) {
+ if (forAuth) {
+ return null;
+ }
+ return mLockoutTracker;
}
- @NonNull Map<Integer, Long> getAuthenticatorIds() {
+ @NonNull public Map<Integer, Long> getAuthenticatorIds() {
return mAuthenticatorIds;
}
@@ -262,4 +352,49 @@
mScheduler.reset();
mCurrentSession = null;
}
+
+ @NonNull protected Handler getHandler() {
+ return mHandler;
+ }
+
+ @NonNull protected Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * Returns true if the sensor hardware is detected.
+ */
+ protected boolean isHardwareDetected(String halInstance) {
+ if (mTestHalEnabled) {
+ return true;
+ }
+ return (ServiceManager.checkService(IFingerprint.DESCRIPTOR + "/" + halInstance)
+ != null);
+ }
+
+ @NonNull protected BiometricContext getBiometricContext() {
+ return mBiometricContext;
+ }
+
+ /**
+ * Returns lockout mode of this sensor.
+ */
+ @LockoutTracker.LockoutMode
+ public int getLockoutModeForUser(int userId) {
+ return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
+ Utils.getCurrentStrength(mSensorProperties.sensorId));
+ }
+
+ public void setScheduler(BiometricScheduler scheduler) {
+ mScheduler = scheduler;
+ }
+
+ public void setLazySession(
+ Supplier<AidlSession> lazySession) {
+ mLazySession = lazySession;
+ }
+
+ public FingerprintProvider getProvider() {
+ return mProvider;
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
index a4e6025..5c5b992 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
@@ -29,7 +29,8 @@
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.HalClientMonitor;
+import com.android.server.biometrics.sensors.StartUserClient;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession;
import java.io.File;
import java.util.Map;
@@ -38,7 +39,8 @@
/**
* Sets the HAL's current active user, and updates the framework's authenticatorId cache.
*/
-public class FingerprintUpdateActiveUserClient extends HalClientMonitor<IBiometricsFingerprint> {
+public class FingerprintUpdateActiveUserClient extends
+ StartUserClient<IBiometricsFingerprint, AidlSession> {
private static final String TAG = "FingerprintUpdateActiveUserClient";
private static final String FP_DATA_DIR = "fpdata";
@@ -53,11 +55,24 @@
@NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId,
@NonNull String owner, int sensorId,
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
- Supplier<Integer> currentUserId,
+ @NonNull Supplier<Integer> currentUserId,
boolean hasEnrolledBiometrics, @NonNull Map<Integer, Long> authenticatorIds,
boolean forceUpdateAuthenticatorId) {
- super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
- 0 /* cookie */, sensorId, logger, biometricContext);
+ this(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext, currentUserId,
+ hasEnrolledBiometrics, authenticatorIds, forceUpdateAuthenticatorId,
+ (newUserId, newUser, halInterfaceVersion) -> {});
+ }
+
+ FingerprintUpdateActiveUserClient(@NonNull Context context,
+ @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId,
+ @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ @NonNull Supplier<Integer> currentUserId,
+ boolean hasEnrolledBiometrics, @NonNull Map<Integer, Long> authenticatorIds,
+ boolean forceUpdateAuthenticatorId,
+ @NonNull UserStartedCallback<AidlSession> userStartedCallback) {
+ super(context, lazyDaemon, null /* token */, userId, sensorId, logger, biometricContext,
+ userStartedCallback);
mCurrentUserId = currentUserId;
mForceUpdateAuthenticatorId = forceUpdateAuthenticatorId;
mHasEnrolledBiometrics = hasEnrolledBiometrics;
@@ -70,6 +85,7 @@
if (mCurrentUserId.get() == getTargetUserId() && !mForceUpdateAuthenticatorId) {
Slog.d(TAG, "Already user: " + mCurrentUserId + ", returning");
+ mUserStartedCallback.onUserStarted(getTargetUserId(), null, 0);
callback.onClientFinished(this, true /* success */);
return;
}
@@ -119,6 +135,7 @@
getFreshDaemon().setActiveGroup(targetId, mDirectory.getAbsolutePath());
mAuthenticatorIds.put(targetId, mHasEnrolledBiometrics
? getFreshDaemon().getAuthenticatorId() : 0L);
+ mUserStartedCallback.onUserStarted(targetId, null, 0);
mCallback.onClientFinished(this, true /* success */);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to setActiveGroup: " + e);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java
index c3e5cbe..e9a48e7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java
@@ -20,6 +20,7 @@
import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback;
import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
import java.util.ArrayList;
@@ -73,12 +74,12 @@
@Override
public void onRemoved(long deviceId, int fingerId, int groupId, int remaining) {
- mAidlResponseHandler.onEnrollmentsRemoved(new int[]{fingerId});
+ mAidlResponseHandler.onEnrollmentRemoved(fingerId, remaining);
}
@Override
public void onEnumerate(long deviceId, int fingerId, int groupId, int remaining) {
- mAidlResponseHandler.onEnrollmentsEnumerated(new int[]{fingerId});
+ mAidlResponseHandler.onEnrollmentEnumerated(fingerId, remaining);
}
void onChallengeGenerated(long challenge) {
@@ -92,4 +93,8 @@
void onResetLockout() {
mAidlResponseHandler.onLockoutCleared();
}
+
+ <T extends BaseClientMonitor> void unsupportedClientScheduled(Class<T> className) {
+ mAidlResponseHandler.onUnsupportedClientScheduled(className);
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
new file mode 100644
index 0000000..0bb61415
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.biometrics.sensors.fingerprint.hidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.hardware.biometrics.fingerprint.SensorProps;
+import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.os.Handler;
+import android.os.IHwBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.StartUserClient;
+import com.android.server.biometrics.sensors.StopUserClient;
+import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
+import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession;
+import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider;
+import com.android.server.biometrics.sensors.fingerprint.aidl.Sensor;
+
+import java.util.ArrayList;
+
+/**
+ * Convert HIDL sensor configurations to an AIDL Sensor.
+ */
+public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRecipient {
+ private static final String TAG = "HidlToAidlSensorAdapter";
+
+ private final Runnable mInternalCleanupRunnable;
+ private final LockoutResetDispatcher mLockoutResetDispatcher;
+ private LockoutFrameworkImpl mLockoutTracker;
+ private final AuthSessionCoordinator mAuthSessionCoordinator;
+ private final AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
+ private int mCurrentUserId = UserHandle.USER_NULL;
+ private IBiometricsFingerprint mDaemon;
+ private AidlSession mSession;
+
+ private final StartUserClient.UserStartedCallback<AidlSession> mUserStartedCallback =
+ (newUserId, newUser, halInterfaceVersion) -> {
+ if (mCurrentUserId != newUserId) {
+ handleUserChanged(newUserId);
+ }
+ };
+
+ public HidlToAidlSensorAdapter(@NonNull String tag, @NonNull FingerprintProvider provider,
+ @NonNull Context context, @NonNull Handler handler,
+ @NonNull SensorProps prop,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ @NonNull BiometricContext biometricContext,
+ boolean resetLockoutRequiresHardwareAuthToken,
+ @NonNull Runnable internalCleanupRunnable) {
+ this(tag, provider, context, handler, prop, lockoutResetDispatcher,
+ gestureAvailabilityDispatcher, biometricContext,
+ resetLockoutRequiresHardwareAuthToken, internalCleanupRunnable,
+ new AuthSessionCoordinator(), null /* daemon */,
+ null /* onEnrollSuccessCallback */);
+ }
+
+ @VisibleForTesting
+ HidlToAidlSensorAdapter(@NonNull String tag, @NonNull FingerprintProvider provider,
+ @NonNull Context context, @NonNull Handler handler,
+ @NonNull SensorProps prop,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ @NonNull BiometricContext biometricContext,
+ boolean resetLockoutRequiresHardwareAuthToken,
+ @NonNull Runnable internalCleanupRunnable,
+ @NonNull AuthSessionCoordinator authSessionCoordinator,
+ @Nullable IBiometricsFingerprint daemon,
+ @Nullable AidlResponseHandler.AidlResponseHandlerCallback aidlResponseHandlerCallback) {
+ super(tag, provider, context, handler, getFingerprintSensorPropertiesInternal(prop,
+ new ArrayList<>(), resetLockoutRequiresHardwareAuthToken),
+ lockoutResetDispatcher,
+ gestureAvailabilityDispatcher,
+ biometricContext, null /* session */);
+ mLockoutResetDispatcher = lockoutResetDispatcher;
+ mInternalCleanupRunnable = internalCleanupRunnable;
+ mAuthSessionCoordinator = authSessionCoordinator;
+ mDaemon = daemon;
+ mAidlResponseHandlerCallback = aidlResponseHandlerCallback == null
+ ? new AidlResponseHandler.AidlResponseHandlerCallback() {
+ @Override
+ public void onEnrollSuccess() {
+ getScheduler()
+ .scheduleClientMonitor(getFingerprintUpdateActiveUserClient(
+ mCurrentUserId, true /* forceUpdateAuthenticatorIds */));
+ }
+
+ @Override
+ public void onHardwareUnavailable() {
+ mDaemon = null;
+ mSession = null;
+ mCurrentUserId = UserHandle.USER_NULL;
+ }
+ } : aidlResponseHandlerCallback;
+ }
+
+ @Override
+ public void serviceDied(long cookie) {
+ Slog.d(TAG, "HAL died.");
+ mSession = null;
+ mDaemon = null;
+ }
+
+ @Override
+ @LockoutTracker.LockoutMode
+ public int getLockoutModeForUser(int userId) {
+ return mLockoutTracker.getLockoutModeForUser(userId);
+ }
+
+ @Override
+ public void init(GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ LockoutResetDispatcher lockoutResetDispatcher) {
+ setLazySession(this::getSession);
+ setScheduler(new UserAwareBiometricScheduler(TAG,
+ BiometricScheduler.sensorTypeFromFingerprintProperties(getSensorProperties()),
+ gestureAvailabilityDispatcher, () -> mCurrentUserId, getUserSwitchCallback()));
+ mLockoutTracker = new LockoutFrameworkImpl(getContext(),
+ userId -> mLockoutResetDispatcher.notifyLockoutResetCallbacks(
+ getSensorProperties().sensorId));
+ }
+
+ @Override
+ @Nullable
+ protected AidlSession getSessionForUser(int userId) {
+ if (mSession != null && mSession.getUserId() == userId) {
+ return mSession;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ protected boolean isHardwareDetected(String halInstance) {
+ return getIBiometricsFingerprint() != null;
+ }
+
+ @NonNull
+ @Override
+ protected LockoutTracker getLockoutTracker(boolean forAuth) {
+ return mLockoutTracker;
+ }
+
+ private synchronized AidlSession getSession() {
+ if (mSession != null && mDaemon != null) {
+ return mSession;
+ } else {
+ return mSession = new AidlSession(this::getIBiometricsFingerprint,
+ mCurrentUserId, getAidlResponseHandler());
+ }
+ }
+
+ private AidlResponseHandler getAidlResponseHandler() {
+ return new AidlResponseHandler(getContext(),
+ getScheduler(),
+ getSensorProperties().sensorId,
+ mCurrentUserId,
+ mLockoutTracker,
+ mLockoutResetDispatcher,
+ mAuthSessionCoordinator,
+ () -> {}, mAidlResponseHandlerCallback);
+ }
+
+ @VisibleForTesting IBiometricsFingerprint getIBiometricsFingerprint() {
+ if (getProvider().getTestHalEnabled()) {
+ final TestHal testHal = new TestHal(getContext(), getSensorProperties().sensorId);
+ testHal.setNotify(new HidlToAidlCallbackConverter(getAidlResponseHandler()));
+ return testHal;
+ }
+
+ if (mDaemon != null) {
+ return mDaemon;
+ }
+
+ try {
+ mDaemon = IBiometricsFingerprint.getService();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get fingerprint HAL", e);
+ } catch (java.util.NoSuchElementException e) {
+ // Service doesn't exist or cannot be opened.
+ Slog.w(TAG, "NoSuchElementException", e);
+ }
+
+ if (mDaemon == null) {
+ Slog.w(TAG, "Fingerprint HAL not available");
+ return null;
+ }
+
+ mDaemon.asBinder().linkToDeath(this, 0 /* flags */);
+
+ Slog.d(TAG, "Fingerprint HAL ready");
+
+ scheduleLoadAuthenticatorIds();
+ mInternalCleanupRunnable.run();
+ return mDaemon;
+ }
+
+ private UserAwareBiometricScheduler.UserSwitchCallback getUserSwitchCallback() {
+ return new UserAwareBiometricScheduler.UserSwitchCallback() {
+ @NonNull
+ @Override
+ public StopUserClient<?> getStopUserClient(int userId) {
+ return new StopUserClient<IBiometricsFingerprint>(getContext(),
+ HidlToAidlSensorAdapter.this::getIBiometricsFingerprint,
+ null /* token */, userId, getSensorProperties().sensorId,
+ BiometricLogger.ofUnknown(getContext()), getBiometricContext(),
+ () -> {
+ mCurrentUserId = UserHandle.USER_NULL;
+ mSession = null;
+ }) {
+ @Override
+ public void start(@NonNull ClientMonitorCallback callback) {
+ super.start(callback);
+ startHalOperation();
+ }
+
+ @Override
+ protected void startHalOperation() {
+ onUserStopped();
+ }
+
+ @Override
+ public void unableToStart() {
+ getCallback().onClientFinished(this, false /* success */);
+ }
+ };
+ }
+
+ @NonNull
+ @Override
+ public StartUserClient<?, ?> getStartUserClient(int newUserId) {
+ return getFingerprintUpdateActiveUserClient(newUserId,
+ false /* forceUpdateAuthenticatorId */);
+ }
+ };
+ }
+
+ private FingerprintUpdateActiveUserClient getFingerprintUpdateActiveUserClient(int newUserId,
+ boolean forceUpdateAuthenticatorIds) {
+ return new FingerprintUpdateActiveUserClient(getContext(),
+ this::getIBiometricsFingerprint, newUserId, TAG,
+ getSensorProperties().sensorId, BiometricLogger.ofUnknown(getContext()),
+ getBiometricContext(), () -> mCurrentUserId,
+ !FingerprintUtils.getInstance(getSensorProperties().sensorId)
+ .getBiometricsForUser(getContext(),
+ newUserId).isEmpty(), getAuthenticatorIds(), forceUpdateAuthenticatorIds,
+ mUserStartedCallback);
+ }
+
+ private void scheduleLoadAuthenticatorIds() {
+ getHandler().post(() -> {
+ for (UserInfo user : UserManager.get(getContext()).getAliveUsers()) {
+ final int targetUserId = user.id;
+ if (!getAuthenticatorIds().containsKey(targetUserId)) {
+ getScheduler().scheduleClientMonitor(getFingerprintUpdateActiveUserClient(
+ targetUserId, true /* forceUpdateAuthenticatorIds */));
+ }
+ }
+ });
+ }
+
+ @VisibleForTesting void handleUserChanged(int newUserId) {
+ Slog.d(TAG, "User changed. Current user is " + newUserId);
+ mSession = null;
+ mCurrentUserId = newUserId;
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapter.java
similarity index 73%
rename from services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java
rename to services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapter.java
index b48d232..2fc00e1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapter.java
@@ -25,20 +25,25 @@
import android.hardware.keymaster.HardwareAuthToken;
import android.os.IBinder;
import android.os.RemoteException;
+import android.util.Log;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
+import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintGetAuthenticatorIdClient;
+import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintInvalidationClient;
import java.util.function.Supplier;
/**
- * Adapter to convert AIDL-specific interface {@link ISession} methods to HIDL implementation.
+ * Adapter to convert HIDL methods into AIDL interface {@link ISession}.
*/
-public class AidlToHidlAdapter implements ISession {
- private final String TAG = "AidlToHidlAdapter";
+public class HidlToAidlSessionAdapter implements ISession {
+
+ private final String TAG = "HidlToAidlSessionAdapter";
+
@VisibleForTesting
static final int ENROLL_TIMEOUT_SEC = 60;
@NonNull
@@ -46,22 +51,13 @@
private final int mUserId;
private HidlToAidlCallbackConverter mHidlToAidlCallbackConverter;
- public AidlToHidlAdapter(Supplier<IBiometricsFingerprint> session, int userId,
+ public HidlToAidlSessionAdapter(Supplier<IBiometricsFingerprint> session, int userId,
AidlResponseHandler aidlResponseHandler) {
mSession = session;
mUserId = userId;
setCallback(aidlResponseHandler);
}
- private void setCallback(AidlResponseHandler aidlResponseHandler) {
- mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler);
- try {
- mSession.get().setNotify(mHidlToAidlCallbackConverter);
- } catch (RemoteException e) {
- Slog.d(TAG, "Failed to set callback");
- }
- }
-
@Override
public IBinder asBinder() {
return null;
@@ -125,12 +121,16 @@
@Override
public void getAuthenticatorId() throws RemoteException {
- //Unsupported in HIDL
+ Log.e(TAG, "getAuthenticatorId unsupported in HIDL");
+ mHidlToAidlCallbackConverter.unsupportedClientScheduled(
+ FingerprintGetAuthenticatorIdClient.class);
}
@Override
public void invalidateAuthenticatorId() throws RemoteException {
- //Unsupported in HIDL
+ Log.e(TAG, "invalidateAuthenticatorId unsupported in HIDL");
+ mHidlToAidlCallbackConverter.unsupportedClientScheduled(
+ FingerprintInvalidationClient.class);
}
@Override
@@ -140,72 +140,92 @@
@Override
public void close() throws RemoteException {
- //Unsupported in HIDL
+ Log.e(TAG, "close unsupported in HIDL");
}
@Override
public void onUiReady() throws RemoteException {
- //Unsupported in HIDL
+ Log.e(TAG, "onUiReady unsupported in HIDL");
}
@Override
public ICancellationSignal authenticateWithContext(long operationId, OperationContext context)
throws RemoteException {
- //Unsupported in HIDL
- return null;
+ Log.e(TAG, "authenticateWithContext unsupported in HIDL");
+ return authenticate(operationId);
}
@Override
public ICancellationSignal enrollWithContext(HardwareAuthToken hat, OperationContext context)
throws RemoteException {
- //Unsupported in HIDL
- return null;
+ Log.e(TAG, "enrollWithContext unsupported in HIDL");
+ return enroll(hat);
}
@Override
public ICancellationSignal detectInteractionWithContext(OperationContext context)
throws RemoteException {
- //Unsupported in HIDL
- return null;
+ Log.e(TAG, "enrollWithContext unsupported in HIDL");
+ return detectInteraction();
}
@Override
public void onPointerDownWithContext(PointerContext context) throws RemoteException {
- //Unsupported in HIDL
+ Log.e(TAG, "onPointerDownWithContext unsupported in HIDL");
+ onPointerDown(context.pointerId, (int) context.x, (int) context.y, context.minor,
+ context.major);
}
@Override
public void onPointerUpWithContext(PointerContext context) throws RemoteException {
- //Unsupported in HIDL
+ Log.e(TAG, "onPointerUpWithContext unsupported in HIDL");
+ onPointerUp(context.pointerId);
}
@Override
public void onContextChanged(OperationContext context) throws RemoteException {
- //Unsupported in HIDL
+ Log.e(TAG, "onContextChanged unsupported in HIDL");
}
@Override
public void onPointerCancelWithContext(PointerContext context) throws RemoteException {
- //Unsupported in HIDL
+ Log.e(TAG, "onPointerCancelWithContext unsupported in HIDL");
}
@Override
public void setIgnoreDisplayTouches(boolean shouldIgnore) throws RemoteException {
- //Unsupported in HIDL
+ Log.e(TAG, "setIgnoreDisplayTouches unsupported in HIDL");
}
@Override
public int getInterfaceVersion() throws RemoteException {
- //Unsupported in HIDL
+ Log.e(TAG, "getInterfaceVersion unsupported in HIDL");
return 0;
}
@Override
public String getInterfaceHash() throws RemoteException {
- //Unsupported in HIDL
+ Log.e(TAG, "getInterfaceHash unsupported in HIDL");
return null;
}
+ private void setCallback(AidlResponseHandler aidlResponseHandler) {
+ mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler);
+ try {
+ if (mSession.get() != null) {
+ long halId = mSession.get().setNotify(mHidlToAidlCallbackConverter);
+ Slog.d(TAG, "Fingerprint HAL ready, HAL ID: " + halId);
+ if (halId == 0) {
+ Slog.d(TAG, "Unable to set HIDL callback.");
+ }
+ } else {
+ Slog.e(TAG, "Unable to set HIDL callback. HIDL daemon is null.");
+ }
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Failed to set callback");
+ }
+ }
+
private class Cancellation extends ICancellationSignal.Stub {
Cancellation() {}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
index 0730c67..2f77275 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
@@ -18,6 +18,7 @@
import static android.Manifest.permission.RESET_FINGERPRINT_LOCKOUT;
+import android.annotation.NonNull;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
@@ -31,15 +32,18 @@
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.sensors.LockoutTracker;
+import java.util.function.Function;
+
/**
* Tracks and enforces biometric lockout for biometric sensors that do not support lockout in the
* HAL.
*/
public class LockoutFrameworkImpl implements LockoutTracker {
- private static final String TAG = "LockoutTracker";
+ private static final String TAG = "LockoutFrameworkImpl";
private static final String ACTION_LOCKOUT_RESET =
"com.android.server.biometrics.sensors.fingerprint.ACTION_LOCKOUT_RESET";
private static final int MAX_FAILED_ATTEMPTS_LOCKOUT_TIMED = 5;
@@ -65,22 +69,32 @@
void onLockoutReset(int userId);
}
- private final Context mContext;
private final LockoutResetCallback mLockoutResetCallback;
private final SparseBooleanArray mTimedLockoutCleared;
private final SparseIntArray mFailedAttempts;
private final AlarmManager mAlarmManager;
private final LockoutReceiver mLockoutReceiver;
private final Handler mHandler;
+ private final Function<Integer, PendingIntent> mLockoutResetIntent;
- public LockoutFrameworkImpl(Context context, LockoutResetCallback lockoutResetCallback) {
- mContext = context;
+ public LockoutFrameworkImpl(@NonNull Context context,
+ @NonNull LockoutResetCallback lockoutResetCallback) {
+ this(context, lockoutResetCallback, (userId) -> PendingIntent.getBroadcast(context, userId,
+ new Intent(ACTION_LOCKOUT_RESET).putExtra(KEY_LOCKOUT_RESET_USER, userId),
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE));
+ }
+
+ @VisibleForTesting
+ LockoutFrameworkImpl(@NonNull Context context,
+ @NonNull LockoutResetCallback lockoutResetCallback,
+ @NonNull Function<Integer, PendingIntent> lockoutResetIntent) {
mLockoutResetCallback = lockoutResetCallback;
mTimedLockoutCleared = new SparseBooleanArray();
mFailedAttempts = new SparseIntArray();
mAlarmManager = context.getSystemService(AlarmManager.class);
mLockoutReceiver = new LockoutReceiver();
mHandler = new Handler(Looper.getMainLooper());
+ mLockoutResetIntent = lockoutResetIntent;
context.registerReceiver(mLockoutReceiver, new IntentFilter(ACTION_LOCKOUT_RESET),
RESET_FINGERPRINT_LOCKOUT, null /* handler */, Context.RECEIVER_EXPORTED);
@@ -129,34 +143,18 @@
return LOCKOUT_NONE;
}
- /**
- * Clears lockout for Fingerprint HIDL HAL
- */
@Override
- public void setLockoutModeForUser(int userId, int mode) {
- mFailedAttempts.put(userId, 0);
- mTimedLockoutCleared.put(userId, true);
- // If we're asked to reset failed attempts externally (i.e. from Keyguard),
- // the alarm might still be pending; remove it.
- cancelLockoutResetForUser(userId);
- mLockoutResetCallback.onLockoutReset(userId);
- }
+ public void setLockoutModeForUser(int userId, int mode) {}
private void cancelLockoutResetForUser(int userId) {
- mAlarmManager.cancel(getLockoutResetIntentForUser(userId));
+ mAlarmManager.cancel(mLockoutResetIntent.apply(userId));
}
private void scheduleLockoutResetForUser(int userId) {
mHandler.post(() -> {
mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
- SystemClock.elapsedRealtime() + FAIL_LOCKOUT_TIMEOUT_MS,
- getLockoutResetIntentForUser(userId));
+ SystemClock.elapsedRealtime() + FAIL_LOCKOUT_TIMEOUT_MS,
+ mLockoutResetIntent.apply(userId));
});
}
-
- private PendingIntent getLockoutResetIntentForUser(int userId) {
- return PendingIntent.getBroadcast(mContext, userId,
- new Intent(ACTION_LOCKOUT_RESET).putExtra(KEY_LOCKOUT_RESET_USER, userId),
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
- }
}
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 2314bb7..3024dd2 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -16,6 +16,8 @@
package com.android.server.display;
+import static com.android.server.display.config.DisplayBrightnessMappingConfig.autoBrightnessModeToString;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -72,12 +74,14 @@
@IntDef(prefix = { "AUTO_BRIGHTNESS_MODE_" }, value = {
AUTO_BRIGHTNESS_MODE_DEFAULT,
AUTO_BRIGHTNESS_MODE_IDLE,
+ AUTO_BRIGHTNESS_MODE_DOZE
})
@Retention(RetentionPolicy.SOURCE)
public @interface AutomaticBrightnessMode{}
public static final int AUTO_BRIGHTNESS_MODE_DEFAULT = 0;
public static final int AUTO_BRIGHTNESS_MODE_IDLE = 1;
+ public static final int AUTO_BRIGHTNESS_MODE_DOZE = 2;
// How long the current sensor reading is assumed to be valid beyond the current time.
// This provides a bit of prediction, as well as ensures that the weight for the last sample is
@@ -616,12 +620,13 @@
pw.println(" mPendingForegroundAppPackageName=" + mPendingForegroundAppPackageName);
pw.println(" mForegroundAppCategory=" + mForegroundAppCategory);
pw.println(" mPendingForegroundAppCategory=" + mPendingForegroundAppCategory);
- pw.println(" Current mode=" + mCurrentBrightnessMapper.getMode());
+ pw.println(" Current mode="
+ + autoBrightnessModeToString(mCurrentBrightnessMapper.getMode()));
pw.println();
for (int i = 0; i < mBrightnessMappingStrategyMap.size(); i++) {
- pw.println(" Mapper for mode " + modeToString(mBrightnessMappingStrategyMap.keyAt(i))
- + "=");
+ pw.println(" Mapper for mode "
+ + autoBrightnessModeToString(mBrightnessMappingStrategyMap.keyAt(i)) + "=");
mBrightnessMappingStrategyMap.valueAt(i).dump(pw,
mBrightnessRangeController.getNormalBrightnessMax());
}
@@ -1224,14 +1229,6 @@
}
}
- private String modeToString(@AutomaticBrightnessMode int mode) {
- return switch (mode) {
- case AUTO_BRIGHTNESS_MODE_DEFAULT -> "default";
- case AUTO_BRIGHTNESS_MODE_IDLE -> "idle";
- default -> Integer.toString(mode);
- };
- }
-
private class ShortTermModel {
// When the short term model is invalidated, we don't necessarily reset it (i.e. clear the
// user's adjustment) immediately, but wait for a drastic enough change in the ambient
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index acd253b..8405e0a 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -19,15 +19,18 @@
import static android.text.TextUtils.formatSimple;
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 android.annotation.Nullable;
+import android.content.Context;
import android.content.pm.ApplicationInfo;
-import android.content.res.Resources;
import android.content.res.TypedArray;
import android.hardware.display.BrightnessConfiguration;
import android.hardware.display.BrightnessCorrection;
import android.os.PowerManager;
+import android.os.UserHandle;
+import android.provider.Settings;
import android.util.LongArray;
import android.util.MathUtils;
import android.util.Pair;
@@ -35,7 +38,6 @@
import android.util.Spline;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.display.BrightnessUtils;
import com.android.internal.util.Preconditions;
import com.android.server.display.utils.Plog;
@@ -80,52 +82,50 @@
* Creates a BrightnessMapping strategy. We do not create a simple mapping strategy for idle
* mode.
*
- * @param resources
+ * @param context
* @param displayDeviceConfig
* @param mode The auto-brightness mode. Different modes use different brightness curves
* @param displayWhiteBalanceController
* @return the BrightnessMappingStrategy
*/
@Nullable
- static BrightnessMappingStrategy create(Resources resources,
+ static BrightnessMappingStrategy create(Context context,
DisplayDeviceConfig displayDeviceConfig,
@AutomaticBrightnessController.AutomaticBrightnessMode int mode,
- DisplayWhiteBalanceController displayWhiteBalanceController) {
+ @Nullable DisplayWhiteBalanceController displayWhiteBalanceController) {
// Display independent, mode dependent values
float[] brightnessLevelsNits = null;
float[] brightnessLevels = null;
float[] luxLevels = null;
+ int preset = Settings.System.getIntForUser(context.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL, UserHandle.USER_CURRENT);
switch (mode) {
- case AUTO_BRIGHTNESS_MODE_DEFAULT:
+ case AUTO_BRIGHTNESS_MODE_DEFAULT -> {
brightnessLevelsNits = displayDeviceConfig.getAutoBrightnessBrighteningLevelsNits();
- luxLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevelsLux();
-
- brightnessLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevels();
- if (brightnessLevels == null || brightnessLevels.length == 0) {
- // Load the old configuration in the range [0, 255]. The values need to be
- // normalized to the range [0, 1].
- int[] brightnessLevelsInt = resources.getIntArray(
- com.android.internal.R.array.config_autoBrightnessLcdBacklightValues);
- brightnessLevels = new float[brightnessLevelsInt.length];
- for (int i = 0; i < brightnessLevels.length; i++) {
- brightnessLevels[i] = normalizeAbsoluteBrightness(brightnessLevelsInt[i]);
- }
- }
- break;
- case AUTO_BRIGHTNESS_MODE_IDLE:
- brightnessLevelsNits = getFloatArray(resources.obtainTypedArray(
+ luxLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(mode, preset);
+ brightnessLevels =
+ displayDeviceConfig.getAutoBrightnessBrighteningLevels(mode, preset);
+ }
+ case AUTO_BRIGHTNESS_MODE_IDLE -> {
+ brightnessLevelsNits = getFloatArray(context.getResources().obtainTypedArray(
com.android.internal.R.array.config_autoBrightnessDisplayValuesNitsIdle));
- luxLevels = getLuxLevels(resources.getIntArray(
+ luxLevels = getLuxLevels(context.getResources().getIntArray(
com.android.internal.R.array.config_autoBrightnessLevelsIdle));
- break;
+ }
+ case AUTO_BRIGHTNESS_MODE_DOZE -> {
+ luxLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(mode, preset);
+ brightnessLevels =
+ displayDeviceConfig.getAutoBrightnessBrighteningLevels(mode, preset);
+ }
}
// Display independent, mode independent values
- float autoBrightnessAdjustmentMaxGamma = resources.getFraction(
+ float autoBrightnessAdjustmentMaxGamma = context.getResources().getFraction(
com.android.internal.R.fraction.config_autoBrightnessAdjustmentMaxGamma,
1, 1);
- long shortTermModelTimeout = resources.getInteger(
+ long shortTermModelTimeout = context.getResources().getInteger(
com.android.internal.R.integer.config_autoBrightnessShortTermModelTimeout);
// Display dependent values - used for physical mapping strategy nits -> brightness
@@ -426,11 +426,6 @@
}
}
- // Normalize entire brightness range to 0 - 1.
- protected static float normalizeAbsoluteBrightness(int brightness) {
- return BrightnessSynchronizer.brightnessIntToFloat(brightness);
- }
-
private Pair<float[], float[]> insertControlPoint(
float[] luxLevels, float[] brightnessLevels, float lux, float brightness) {
final int idx = findInsertionPoint(luxLevels, lux);
@@ -835,6 +830,8 @@
private float mAutoBrightnessAdjustment;
private float mUserLux;
private float mUserBrightness;
+
+ @Nullable
private final DisplayWhiteBalanceController mDisplayWhiteBalanceController;
@AutomaticBrightnessController.AutomaticBrightnessMode
@@ -850,7 +847,7 @@
public PhysicalMappingStrategy(BrightnessConfiguration config, float[] nits,
float[] brightness, float maxGamma,
@AutomaticBrightnessController.AutomaticBrightnessMode int mode,
- DisplayWhiteBalanceController displayWhiteBalanceController) {
+ @Nullable DisplayWhiteBalanceController displayWhiteBalanceController) {
Preconditions.checkArgument(nits.length != 0 && brightness.length != 0,
"Nits and brightness arrays must not be empty!");
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index d97127c..bd22e1d 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -49,6 +49,7 @@
import com.android.server.display.config.BrightnessThrottlingMap;
import com.android.server.display.config.BrightnessThrottlingPoint;
import com.android.server.display.config.Density;
+import com.android.server.display.config.DisplayBrightnessMappingConfig;
import com.android.server.display.config.DisplayBrightnessPoint;
import com.android.server.display.config.DisplayConfiguration;
import com.android.server.display.config.DisplayQuirks;
@@ -57,7 +58,6 @@
import com.android.server.display.config.HighBrightnessMode;
import com.android.server.display.config.IntegerArray;
import com.android.server.display.config.LuxThrottling;
-import com.android.server.display.config.LuxToBrightnessMapping;
import com.android.server.display.config.NitsMap;
import com.android.server.display.config.NonNegativeFloatToFloatPoint;
import com.android.server.display.config.Point;
@@ -313,6 +313,21 @@
* 1000
* </darkeningLightDebounceIdleMillis>
* <luxToBrightnessMapping>
+ * <mode>default</mode>
+ * <map>
+ * <point>
+ * <first>0</first>
+ * <second>0.2</second>
+ * </point>
+ * <point>
+ * <first>80</first>
+ * <second>0.3</second>
+ * </point>
+ * </map>
+ * </luxToBrightnessMapping>
+ * <luxToBrightnessMapping>
+ * <mode>doze</mode>
+ * <setting>dim</setting>
* <map>
* <point>
* <first>0</first>
@@ -634,36 +649,8 @@
// for the corresponding values above
private float[] mBrightness;
- /**
- * Array of desired screen brightness in nits corresponding to the lux values
- * in the mBrightnessLevelsLux array. The display brightness is defined as the
- * measured brightness of an all-white image. The brightness values must be non-negative and
- * non-decreasing. This must be overridden in platform specific overlays
- */
- private float[] mBrightnessLevelsNits;
-
- /**
- * Array of desired screen brightness corresponding to the lux values
- * in the mBrightnessLevelsLux array. The brightness values must be non-negative and
- * non-decreasing. They must be between {@link PowerManager.BRIGHTNESS_MIN} and
- * {@link PowerManager.BRIGHTNESS_MAX}. This must be overridden in platform specific overlays
- */
- private float[] mBrightnessLevels;
-
- /**
- * Array of light sensor lux values to define our levels for auto-brightness support.
- *
- * The first lux value is always 0.
- *
- * The control points must be strictly increasing. Each control point corresponds to an entry
- * in the brightness values arrays. For example, if lux == luxLevels[1] (second element
- * of the levels array) then the brightness will be determined by brightnessLevels[1] (second
- * element of the brightness values array).
- *
- * Spline interpolation is used to determine the auto-brightness values for lux levels between
- * these control points.
- */
- private float[] mBrightnessLevelsLux;
+ @Nullable
+ private DisplayBrightnessMappingConfig mDisplayBrightnessMapping;
private float mBacklightMinimum = Float.NaN;
private float mBacklightMaximum = Float.NaN;
@@ -1604,24 +1591,42 @@
}
/**
- * @return Auto brightness brightening ambient lux levels
+ * @param mode The auto-brightness mode
+ * @param preset The brightness preset. Presets are used on devices that allow users to choose
+ * from a set of predefined options in display auto-brightness settings.
+ * @return The default auto-brightness brightening ambient lux levels for the specified mode
+ * and preset
*/
- public float[] getAutoBrightnessBrighteningLevelsLux() {
- return mBrightnessLevelsLux;
+ public float[] getAutoBrightnessBrighteningLevelsLux(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode, int preset) {
+ if (mDisplayBrightnessMapping == null) {
+ return null;
+ }
+ return mDisplayBrightnessMapping.getLuxArray(mode, preset);
}
/**
* @return Auto brightness brightening nits levels
*/
public float[] getAutoBrightnessBrighteningLevelsNits() {
- return mBrightnessLevelsNits;
+ if (mDisplayBrightnessMapping == null) {
+ return null;
+ }
+ return mDisplayBrightnessMapping.getNitsArray();
}
/**
- * @return Auto brightness brightening levels
+ * @param mode The auto-brightness mode
+ * @param preset The brightness preset. Presets are used on devices that allow users to choose
+ * from a set of predefined options in display auto-brightness settings.
+ * @return The default auto-brightness brightening levels for the specified mode and preset
*/
- public float[] getAutoBrightnessBrighteningLevels() {
- return mBrightnessLevels;
+ public float[] getAutoBrightnessBrighteningLevels(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode, int preset) {
+ if (mDisplayBrightnessMapping == null) {
+ return null;
+ }
+ return mDisplayBrightnessMapping.getBrightnessArray(mode, preset);
}
/**
@@ -1875,9 +1880,7 @@
+ mAutoBrightnessBrighteningLightDebounceIdle
+ ", mAutoBrightnessDarkeningLightDebounceIdle= "
+ mAutoBrightnessDarkeningLightDebounceIdle
- + ", mBrightnessLevelsLux= " + Arrays.toString(mBrightnessLevelsLux)
- + ", mBrightnessLevelsNits= " + Arrays.toString(mBrightnessLevelsNits)
- + ", mBrightnessLevels= " + Arrays.toString(mBrightnessLevels)
+ + ", mDisplayBrightnessMapping= " + mDisplayBrightnessMapping
+ ", mDdcAutoBrightnessAvailable= " + mDdcAutoBrightnessAvailable
+ ", mAutoBrightnessAvailable= " + mAutoBrightnessAvailable
+ "\n"
@@ -2568,7 +2571,8 @@
// Idle must be called after interactive, since we fall back to it if needed.
loadAutoBrightnessBrighteningLightDebounceIdle(autoBrightness);
loadAutoBrightnessDarkeningLightDebounceIdle(autoBrightness);
- loadAutoBrightnessDisplayBrightnessMapping(autoBrightness);
+ mDisplayBrightnessMapping = new DisplayBrightnessMappingConfig(mContext, mFlags,
+ autoBrightness, mBacklightToBrightnessSpline);
loadEnableAutoBrightness(autoBrightness);
}
@@ -2633,38 +2637,6 @@
}
}
- /**
- * Loads the auto-brightness display brightness mappings. Internally, this takes care of
- * loading the value from the display config, and if not present, falls back to config.xml.
- */
- private void loadAutoBrightnessDisplayBrightnessMapping(AutoBrightness autoBrightnessConfig) {
- if (mFlags.areAutoBrightnessModesEnabled() && autoBrightnessConfig != null
- && autoBrightnessConfig.getLuxToBrightnessMapping() != null) {
- LuxToBrightnessMapping mapping = autoBrightnessConfig.getLuxToBrightnessMapping();
- final int size = mapping.getMap().getPoint().size();
- mBrightnessLevels = new float[size];
- mBrightnessLevelsLux = new float[size];
- for (int i = 0; i < size; i++) {
- float backlight = mapping.getMap().getPoint().get(i).getSecond().floatValue();
- mBrightnessLevels[i] = mBacklightToBrightnessSpline.interpolate(backlight);
- mBrightnessLevelsLux[i] = mapping.getMap().getPoint().get(i).getFirst()
- .floatValue();
- }
- if (size > 0 && mBrightnessLevelsLux[0] != 0) {
- throw new IllegalArgumentException(
- "The first lux value in the display brightness mapping must be 0");
- }
- } else {
- mBrightnessLevelsNits = getFloatArray(mContext.getResources()
- .obtainTypedArray(com.android.internal.R.array
- .config_autoBrightnessDisplayValuesNits), PowerManager
- .BRIGHTNESS_OFF_FLOAT);
- mBrightnessLevelsLux = getLuxLevels(mContext.getResources()
- .getIntArray(com.android.internal.R.array
- .config_autoBrightnessLevels));
- }
- }
-
private void loadAutoBrightnessAvailableFromConfigXml() {
mAutoBrightnessAvailable = mContext.getResources().getBoolean(
R.bool.config_automatic_brightness_available);
@@ -2977,7 +2949,8 @@
}
private void loadAutoBrightnessConfigsFromConfigXml() {
- loadAutoBrightnessDisplayBrightnessMapping(null /*AutoBrightnessConfig*/);
+ mDisplayBrightnessMapping = new DisplayBrightnessMappingConfig(mContext, mFlags,
+ /* autoBrightnessConfig= */ null, mBacklightToBrightnessSpline);
}
private void loadBrightnessChangeThresholdsFromXml() {
@@ -3347,7 +3320,12 @@
return vals;
}
- private static float[] getLuxLevels(int[] lux) {
+ /**
+ * @param lux The lux array
+ * @return The lux array with 0 appended at the beginning - the first lux value should always
+ * be 0
+ */
+ public static float[] getLuxLevels(int[] lux) {
// The first control point is implicit and always at 0 lux.
float[] levels = new float[lux.length + 1];
for (int i = 0; i < lux.length; i++) {
diff --git a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
index 1bd556b..4e341a9 100644
--- a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
+++ b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
@@ -56,6 +56,15 @@
}
}
+ @Override
+ public boolean blockScreenOn(Runnable unblocker) {
+ if (mDisplayOffloader == null) {
+ return false;
+ }
+ mDisplayOffloader.onBlockingScreenOn(unblocker);
+ return true;
+ }
+
/**
* Start the offload session. The method returns if the session is already active.
* @return Whether the session was started successfully
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 2685efe..06e5f99 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -735,7 +735,7 @@
mCdsi = null;
}
- setUpAutoBrightness(resources, handler);
+ setUpAutoBrightness(context, handler);
mColorFadeEnabled = !ActivityManager.isLowRamDeviceStatic()
&& !resources.getBoolean(
@@ -1075,7 +1075,7 @@
loadBrightnessRampRates();
loadProximitySensor();
loadNitsRange(mContext.getResources());
- setUpAutoBrightness(mContext.getResources(), mHandler);
+ setUpAutoBrightness(mContext, mHandler);
reloadReduceBrightColours();
setAnimatorRampSpeeds(/* isIdleMode= */ false);
mBrightnessRangeController.loadFromConfig(hbmMetadata, token, info, mDisplayDeviceConfig);
@@ -1145,7 +1145,7 @@
handleBrightnessModeChange();
}
- private void setUpAutoBrightness(Resources resources, Handler handler) {
+ private void setUpAutoBrightness(Context context, Handler handler) {
mUseSoftwareAutoBrightnessConfig = mDisplayDeviceConfig.isAutoBrightnessAvailable();
if (!mUseSoftwareAutoBrightnessConfig) {
@@ -1155,21 +1155,19 @@
SparseArray<BrightnessMappingStrategy> brightnessMappers = new SparseArray<>();
BrightnessMappingStrategy defaultModeBrightnessMapper =
- mInjector.getDefaultModeBrightnessMapper(resources, mDisplayDeviceConfig,
+ mInjector.getDefaultModeBrightnessMapper(context, mDisplayDeviceConfig,
mDisplayWhiteBalanceController);
brightnessMappers.append(AUTO_BRIGHTNESS_MODE_DEFAULT,
defaultModeBrightnessMapper);
- final boolean isIdleScreenBrightnessEnabled = resources.getBoolean(
+ final boolean isIdleScreenBrightnessEnabled = context.getResources().getBoolean(
R.bool.config_enableIdleScreenBrightnessMode);
if (isIdleScreenBrightnessEnabled) {
BrightnessMappingStrategy idleModeBrightnessMapper =
- BrightnessMappingStrategy.create(resources, mDisplayDeviceConfig,
- AUTO_BRIGHTNESS_MODE_IDLE,
- mDisplayWhiteBalanceController);
+ BrightnessMappingStrategy.create(context, mDisplayDeviceConfig,
+ AUTO_BRIGHTNESS_MODE_IDLE, mDisplayWhiteBalanceController);
if (idleModeBrightnessMapper != null) {
- brightnessMappers.append(AUTO_BRIGHTNESS_MODE_IDLE,
- idleModeBrightnessMapper);
+ brightnessMappers.append(AUTO_BRIGHTNESS_MODE_IDLE, idleModeBrightnessMapper);
}
}
@@ -1181,7 +1179,7 @@
}
if (defaultModeBrightnessMapper != null) {
- final float dozeScaleFactor = resources.getFraction(
+ final float dozeScaleFactor = context.getResources().getFraction(
com.android.internal.R.fraction.config_screenAutoBrightnessDozeScaleFactor,
1, 1);
@@ -1265,14 +1263,14 @@
.getAutoBrightnessBrighteningLightDebounceIdle();
long darkeningLightDebounceIdle = mDisplayDeviceConfig
.getAutoBrightnessDarkeningLightDebounceIdle();
- boolean autoBrightnessResetAmbientLuxAfterWarmUp = resources.getBoolean(
+ boolean autoBrightnessResetAmbientLuxAfterWarmUp = context.getResources().getBoolean(
com.android.internal.R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp);
- int lightSensorWarmUpTimeConfig = resources.getInteger(
+ int lightSensorWarmUpTimeConfig = context.getResources().getInteger(
com.android.internal.R.integer.config_lightSensorWarmupTime);
- int lightSensorRate = resources.getInteger(
+ int lightSensorRate = context.getResources().getInteger(
com.android.internal.R.integer.config_autoBrightnessLightSensorRate);
- int initialLightSensorRate = resources.getInteger(
+ int initialLightSensorRate = context.getResources().getInteger(
com.android.internal.R.integer.config_autoBrightnessInitialLightSensorRate);
if (initialLightSensorRate == -1) {
initialLightSensorRate = lightSensorRate;
@@ -3645,12 +3643,11 @@
userNits);
}
- BrightnessMappingStrategy getDefaultModeBrightnessMapper(Resources resources,
+ BrightnessMappingStrategy getDefaultModeBrightnessMapper(Context context,
DisplayDeviceConfig displayDeviceConfig,
DisplayWhiteBalanceController displayWhiteBalanceController) {
- return BrightnessMappingStrategy.create(resources,
- displayDeviceConfig, AUTO_BRIGHTNESS_MODE_DEFAULT,
- displayWhiteBalanceController);
+ return BrightnessMappingStrategy.create(context, displayDeviceConfig,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, displayWhiteBalanceController);
}
HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 52c53f3..519224a 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -17,7 +17,9 @@
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;
@@ -126,6 +128,8 @@
// 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
@@ -155,6 +159,7 @@
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;
@@ -339,6 +344,7 @@
// 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
@@ -348,10 +354,15 @@
// 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;
@@ -472,7 +483,7 @@
private boolean mBootCompleted;
private final DisplayManagerFlags mFlags;
- private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession;
+ private DisplayOffloadSession mDisplayOffloadSession;
/**
* Creates the display power controller.
@@ -614,7 +625,7 @@
mCdsi = null;
}
- setUpAutoBrightness(resources, handler);
+ setUpAutoBrightness(context, handler);
mColorFadeEnabled = mInjector.isColorFadeEnabled()
&& !resources.getBoolean(
@@ -772,6 +783,10 @@
@Override
public void setDisplayOffloadSession(DisplayOffloadSession session) {
+ if (session == mDisplayOffloadSession) {
+ return;
+ }
+ unblockScreenOnByDisplayOffload();
mDisplayOffloadSession = session;
}
@@ -891,7 +906,7 @@
// updated here.
loadBrightnessRampRates();
loadNitsRange(mContext.getResources());
- setUpAutoBrightness(mContext.getResources(), mHandler);
+ setUpAutoBrightness(mContext, mHandler);
reloadReduceBrightColours();
setAnimatorRampSpeeds(/* isIdleMode= */ false);
@@ -962,10 +977,15 @@
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(Resources resources, Handler handler) {
+ private void setUpAutoBrightness(Context context, Handler handler) {
mUseSoftwareAutoBrightnessConfig = mDisplayDeviceConfig.isAutoBrightnessAvailable();
if (!mUseSoftwareAutoBrightnessConfig) {
@@ -975,16 +995,16 @@
SparseArray<BrightnessMappingStrategy> brightnessMappers = new SparseArray<>();
BrightnessMappingStrategy defaultModeBrightnessMapper =
- mInjector.getDefaultModeBrightnessMapper(resources, mDisplayDeviceConfig,
+ mInjector.getDefaultModeBrightnessMapper(context, mDisplayDeviceConfig,
mDisplayWhiteBalanceController);
brightnessMappers.append(AUTO_BRIGHTNESS_MODE_DEFAULT,
defaultModeBrightnessMapper);
- final boolean isIdleScreenBrightnessEnabled = resources.getBoolean(
+ final boolean isIdleScreenBrightnessEnabled = context.getResources().getBoolean(
R.bool.config_enableIdleScreenBrightnessMode);
if (isIdleScreenBrightnessEnabled) {
BrightnessMappingStrategy idleModeBrightnessMapper =
- BrightnessMappingStrategy.create(resources, mDisplayDeviceConfig,
+ BrightnessMappingStrategy.create(context, mDisplayDeviceConfig,
AUTO_BRIGHTNESS_MODE_IDLE,
mDisplayWhiteBalanceController);
if (idleModeBrightnessMapper != null) {
@@ -993,6 +1013,13 @@
}
}
+ 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) {
@@ -1001,7 +1028,7 @@
}
if (defaultModeBrightnessMapper != null) {
- final float dozeScaleFactor = resources.getFraction(
+ final float dozeScaleFactor = context.getResources().getFraction(
R.fraction.config_screenAutoBrightnessDozeScaleFactor,
1, 1);
@@ -1085,14 +1112,14 @@
.getAutoBrightnessBrighteningLightDebounceIdle();
long darkeningLightDebounceIdle = mDisplayDeviceConfig
.getAutoBrightnessDarkeningLightDebounceIdle();
- boolean autoBrightnessResetAmbientLuxAfterWarmUp = resources.getBoolean(
+ boolean autoBrightnessResetAmbientLuxAfterWarmUp = context.getResources().getBoolean(
R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp);
- int lightSensorWarmUpTimeConfig = resources.getInteger(
+ int lightSensorWarmUpTimeConfig = context.getResources().getInteger(
R.integer.config_lightSensorWarmupTime);
- int lightSensorRate = resources.getInteger(
+ int lightSensorRate = context.getResources().getInteger(
R.integer.config_autoBrightnessLightSensorRate);
- int initialLightSensorRate = resources.getInteger(
+ int initialLightSensorRate = context.getResources().getInteger(
R.integer.config_autoBrightnessInitialLightSensorRate);
if (initialLightSensorRate == -1) {
initialLightSensorRate = lightSensorRate;
@@ -1336,6 +1363,13 @@
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();
@@ -1735,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);
@@ -1983,15 +2018,69 @@
}
}
+ 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 && !mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()) {
@@ -2007,8 +2096,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";
@@ -2060,12 +2150,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() {
@@ -2813,6 +2907,12 @@
updatePowerState();
}
break;
+ case MSG_OFFLOADING_SCREEN_ON_UNBLOCKED:
+ if (mDisplayOffloadSession == msg.obj) {
+ unblockScreenOnByDisplayOffload();
+ updatePowerState();
+ }
+ break;
case MSG_CONFIGURE_BRIGHTNESS:
BrightnessConfiguration brightnessConfiguration =
(BrightnessConfiguration) msg.obj;
@@ -2905,6 +3005,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 */);
}
@@ -3023,12 +3133,11 @@
userNits);
}
- BrightnessMappingStrategy getDefaultModeBrightnessMapper(Resources resources,
+ BrightnessMappingStrategy getDefaultModeBrightnessMapper(Context context,
DisplayDeviceConfig displayDeviceConfig,
DisplayWhiteBalanceController displayWhiteBalanceController) {
- return BrightnessMappingStrategy.create(resources,
- displayDeviceConfig, AUTO_BRIGHTNESS_MODE_DEFAULT,
- displayWhiteBalanceController);
+ return BrightnessMappingStrategy.create(context, displayDeviceConfig,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, displayWhiteBalanceController);
}
HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 22898a6..25576ce 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -25,6 +25,7 @@
import android.content.res.Resources;
import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession;
import android.hardware.sidekick.SidekickInternal;
+import android.media.MediaDrm;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
@@ -242,6 +243,10 @@
private SurfaceControl.DisplayMode mActiveSfDisplayMode;
// The active display vsync period in SurfaceFlinger
private float mActiveRenderFrameRate;
+ // The current HDCP level supported by the display, 0 indicates unset
+ // values are defined in hardware/interfaces/drm/aidl/android/hardware/drm/HdcpLevel.aidl
+ private int mConnectedHdcpLevel;
+
private DisplayEventReceiver.FrameRateOverride[] mFrameRateOverrides =
new DisplayEventReceiver.FrameRateOverride[0];
@@ -675,8 +680,9 @@
mInfo.yDpi = mActiveSfDisplayMode.yDpi;
mInfo.deviceProductInfo = mStaticDisplayInfo.deviceProductInfo;
- // Assume that all built-in displays that have secure output (eg. HDCP) also
- // support compositing from gralloc protected buffers.
+ if (mConnectedHdcpLevel != 0) {
+ mStaticDisplayInfo.secure = mConnectedHdcpLevel >= MediaDrm.HDCP_V1;
+ }
if (mStaticDisplayInfo.secure) {
mInfo.flags = DisplayDeviceInfo.FLAG_SECURE
| DisplayDeviceInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS;
@@ -1093,6 +1099,12 @@
}
}
+ public void onHdcpLevelsChangedLocked(int connectedLevel, int maxLevel) {
+ if (updateHdcpLevelsLocked(connectedLevel, maxLevel)) {
+ updateDeviceInfoLocked();
+ }
+ }
+
public boolean updateActiveModeLocked(int activeSfModeId, float renderFrameRate) {
if (mActiveSfDisplayMode.id == activeSfModeId
&& mActiveRenderFrameRate == renderFrameRate) {
@@ -1118,6 +1130,22 @@
return true;
}
+ public boolean updateHdcpLevelsLocked(int connectedLevel, int maxLevel) {
+ if (connectedLevel > maxLevel) {
+ Slog.w(TAG, "HDCP connected level: " + connectedLevel
+ + " is larger than max level: " + maxLevel
+ + ", ignoring request.");
+ return false;
+ }
+
+ if (mConnectedHdcpLevel == connectedLevel) {
+ return false;
+ }
+
+ mConnectedHdcpLevel = connectedLevel;
+ return true;
+ }
+
public void requestColorModeLocked(int colorMode) {
if (mActiveColorMode == colorMode) {
return;
@@ -1387,6 +1415,7 @@
long renderPeriod);
void onFrameRateOverridesChanged(long timestampNanos, long physicalDisplayId,
DisplayEventReceiver.FrameRateOverride[] overrides);
+ void onHdcpLevelsChanged(long physicalDisplayId, int connectedLevel, int maxLevel);
}
@@ -1420,6 +1449,11 @@
DisplayEventReceiver.FrameRateOverride[] overrides) {
mListener.onFrameRateOverridesChanged(timestampNanos, physicalDisplayId, overrides);
}
+
+ @Override
+ public void onHdcpLevelsChanged(long physicalDisplayId, int connectedLevel, int maxLevel) {
+ mListener.onHdcpLevelsChanged(physicalDisplayId, connectedLevel, maxLevel);
+ }
}
private final class LocalDisplayEventListener implements DisplayEventListener {
@@ -1489,6 +1523,26 @@
device.onFrameRateOverridesChanged(overrides);
}
}
+
+ @Override
+ public void onHdcpLevelsChanged(long physicalDisplayId, int connectedLevel, int maxLevel) {
+ if (DEBUG) {
+ Slog.d(TAG, "onHdcpLevelsChanged(physicalDisplayId=" + physicalDisplayId
+ + ", connectedLevel=" + connectedLevel + ", maxLevel=" + maxLevel + ")");
+ }
+ synchronized (getSyncRoot()) {
+ LocalDisplayDevice device = mDevices.get(physicalDisplayId);
+ if (device == null) {
+ if (DEBUG) {
+ Slog.d(TAG, "Received hdcp levels change for unhandled physical display: "
+ + "physicalDisplayId=" + physicalDisplayId);
+ }
+ return;
+ }
+
+ device.onHdcpLevelsChangedLocked(connectedLevel, maxLevel);
+ }
+ }
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java b/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java
new file mode 100644
index 0000000..6978686
--- /dev/null
+++ b/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.config;
+
+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 android.content.Context;
+import android.os.PowerManager;
+import android.provider.Settings;
+import android.util.Spline;
+
+import com.android.internal.display.BrightnessSynchronizer;
+import com.android.server.display.AutomaticBrightnessController;
+import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.feature.DisplayManagerFlags;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Provides a mapping between lux and brightness values in order to support auto-brightness.
+ */
+public class DisplayBrightnessMappingConfig {
+
+ private static final String DEFAULT_BRIGHTNESS_MAPPING_KEY =
+ AutoBrightnessModeName._default.getRawName() + "_"
+ + AutoBrightnessSettingName.normal.getRawName();
+
+ /**
+ * Array of desired screen brightness in nits corresponding to the lux values
+ * in the mBrightnessLevelsLuxMap.get(DEFAULT_ID) array. The display brightness is defined as
+ * the measured brightness of an all-white image. The brightness values must be non-negative and
+ * non-decreasing. This must be overridden in platform specific overlays
+ */
+ private float[] mBrightnessLevelsNits;
+
+ /**
+ * Map of arrays of desired screen brightness corresponding to the lux values
+ * in mBrightnessLevelsLuxMap, indexed by the auto-brightness mode and the brightness preset.
+ * The brightness values must be non-negative and non-decreasing. They must be between
+ * {@link PowerManager.BRIGHTNESS_MIN} and {@link PowerManager.BRIGHTNESS_MAX}.
+ *
+ * The keys are a concatenation of the auto-brightness mode and the brightness preset separated
+ * by an underscore, e.g. default_normal, default_dim, default_bright, doze_normal, doze_dim,
+ * doze_bright.
+ *
+ * The presets are used on devices that allow users to choose from a set of predefined options
+ * in display auto-brightness settings.
+ */
+ private final Map<String, float[]> mBrightnessLevelsMap = new HashMap<>();
+
+ /**
+ * Map of arrays of light sensor lux values to define our levels for auto-brightness support,
+ * indexed by the auto-brightness mode and the brightness preset.
+ *
+ * The first lux value in every array is always 0.
+ *
+ * The control points must be strictly increasing. Each control point corresponds to an entry
+ * in the brightness values arrays. For example, if lux == luxLevels[1] (second element
+ * of the levels array) then the brightness will be determined by brightnessLevels[1] (second
+ * element of the brightness values array).
+ *
+ * Spline interpolation is used to determine the auto-brightness values for lux levels between
+ * these control points.
+ *
+ * The keys are a concatenation of the auto-brightness mode and the brightness preset separated
+ * by an underscore, e.g. default_normal, default_dim, default_bright, doze_normal, doze_dim,
+ * doze_bright.
+ *
+ * The presets are used on devices that allow users to choose from a set of predefined options
+ * in display auto-brightness settings.
+ */
+ private final Map<String, float[]> mBrightnessLevelsLuxMap = new HashMap<>();
+
+ /**
+ * Loads the auto-brightness display brightness mappings. Internally, this takes care of
+ * loading the value from the display config, and if not present, falls back to config.xml.
+ */
+ public DisplayBrightnessMappingConfig(Context context, DisplayManagerFlags flags,
+ AutoBrightness autoBrightnessConfig, Spline backlightToBrightnessSpline) {
+ if (flags.areAutoBrightnessModesEnabled() && autoBrightnessConfig != null
+ && autoBrightnessConfig.getLuxToBrightnessMapping() != null
+ && autoBrightnessConfig.getLuxToBrightnessMapping().size() > 0) {
+ for (LuxToBrightnessMapping mapping
+ : autoBrightnessConfig.getLuxToBrightnessMapping()) {
+ final int size = mapping.getMap().getPoint().size();
+ float[] brightnessLevels = new float[size];
+ float[] brightnessLevelsLux = new float[size];
+ for (int i = 0; i < size; i++) {
+ float backlight = mapping.getMap().getPoint().get(i).getSecond().floatValue();
+ brightnessLevels[i] = backlightToBrightnessSpline.interpolate(backlight);
+ brightnessLevelsLux[i] = mapping.getMap().getPoint().get(i).getFirst()
+ .floatValue();
+ }
+ if (size == 0) {
+ throw new IllegalArgumentException(
+ "A display brightness mapping should not be empty");
+ }
+ if (brightnessLevelsLux[0] != 0) {
+ throw new IllegalArgumentException(
+ "The first lux value in the display brightness mapping must be 0");
+ }
+
+ String key = (mapping.getMode() == null ? "default" : mapping.getMode()) + "_"
+ + (mapping.getSetting() == null ? "normal" : mapping.getSetting());
+ if (mBrightnessLevelsMap.containsKey(key)
+ || mBrightnessLevelsLuxMap.containsKey(key)) {
+ throw new IllegalArgumentException(
+ "A display brightness mapping with key " + key + " already exists");
+ }
+ mBrightnessLevelsMap.put(key, brightnessLevels);
+ mBrightnessLevelsLuxMap.put(key, brightnessLevelsLux);
+ }
+ }
+
+ if (!mBrightnessLevelsMap.containsKey(DEFAULT_BRIGHTNESS_MAPPING_KEY)
+ || !mBrightnessLevelsLuxMap.containsKey(DEFAULT_BRIGHTNESS_MAPPING_KEY)) {
+ mBrightnessLevelsNits = DisplayDeviceConfig.getFloatArray(context.getResources()
+ .obtainTypedArray(com.android.internal.R.array
+ .config_autoBrightnessDisplayValuesNits), PowerManager
+ .BRIGHTNESS_OFF_FLOAT);
+
+ float[] brightnessLevelsLux = DisplayDeviceConfig.getLuxLevels(context.getResources()
+ .getIntArray(com.android.internal.R.array
+ .config_autoBrightnessLevels));
+ mBrightnessLevelsLuxMap.put(DEFAULT_BRIGHTNESS_MAPPING_KEY, brightnessLevelsLux);
+
+ // Load the old configuration in the range [0, 255]. The values need to be normalized
+ // to the range [0, 1].
+ int[] brightnessLevels = context.getResources().getIntArray(
+ com.android.internal.R.array.config_autoBrightnessLcdBacklightValues);
+ mBrightnessLevelsMap.put(DEFAULT_BRIGHTNESS_MAPPING_KEY,
+ brightnessArrayIntToFloat(brightnessLevels, backlightToBrightnessSpline));
+ }
+ }
+
+ /**
+ * @param mode The auto-brightness mode
+ * @param preset The brightness preset. Presets are used on devices that allow users to choose
+ * from a set of predefined options in display auto-brightness settings.
+ * @return The default auto-brightness brightening ambient lux levels for the specified mode
+ * and preset
+ */
+ public float[] getLuxArray(@AutomaticBrightnessController.AutomaticBrightnessMode int mode,
+ int preset) {
+ return mBrightnessLevelsLuxMap.get(
+ autoBrightnessModeToString(mode) + "_" + autoBrightnessPresetToString(preset));
+ }
+
+ /**
+ * @return Auto brightness brightening nits levels
+ */
+ public float[] getNitsArray() {
+ return mBrightnessLevelsNits;
+ }
+
+ /**
+ * @param mode The auto-brightness mode
+ * @param preset The brightness preset. Presets are used on devices that allow users to choose
+ * from a set of predefined options in display auto-brightness settings.
+ * @return The default auto-brightness brightening levels for the specified mode and preset
+ */
+ public float[] getBrightnessArray(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode, int preset) {
+ return mBrightnessLevelsMap.get(
+ autoBrightnessModeToString(mode) + "_" + autoBrightnessPresetToString(preset));
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder brightnessLevelsLuxMapString = new StringBuilder("{");
+ for (Map.Entry<String, float[]> entry : mBrightnessLevelsLuxMap.entrySet()) {
+ brightnessLevelsLuxMapString.append(entry.getKey()).append("=").append(
+ Arrays.toString(entry.getValue())).append(", ");
+ }
+ if (brightnessLevelsLuxMapString.length() > 2) {
+ brightnessLevelsLuxMapString.delete(brightnessLevelsLuxMapString.length() - 2,
+ brightnessLevelsLuxMapString.length());
+ }
+ brightnessLevelsLuxMapString.append("}");
+
+ StringBuilder brightnessLevelsMapString = new StringBuilder("{");
+ for (Map.Entry<String, float[]> entry : mBrightnessLevelsMap.entrySet()) {
+ brightnessLevelsMapString.append(entry.getKey()).append("=").append(
+ Arrays.toString(entry.getValue())).append(", ");
+ }
+ if (brightnessLevelsMapString.length() > 2) {
+ brightnessLevelsMapString.delete(brightnessLevelsMapString.length() - 2,
+ brightnessLevelsMapString.length());
+ }
+ brightnessLevelsMapString.append("}");
+
+ return "mBrightnessLevelsNits= " + Arrays.toString(mBrightnessLevelsNits)
+ + ", mBrightnessLevelsLuxMap= " + brightnessLevelsLuxMapString
+ + ", mBrightnessLevelsMap= " + brightnessLevelsMapString;
+ }
+
+ /**
+ * @param mode The auto-brightness mode
+ * @return The string representing the mode
+ */
+ public static String autoBrightnessModeToString(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+ switch (mode) {
+ case AUTO_BRIGHTNESS_MODE_DEFAULT -> {
+ return AutoBrightnessModeName._default.getRawName();
+ }
+ case AUTO_BRIGHTNESS_MODE_IDLE -> {
+ return AutoBrightnessModeName.idle.getRawName();
+ }
+ case AUTO_BRIGHTNESS_MODE_DOZE -> {
+ return AutoBrightnessModeName.doze.getRawName();
+ }
+ default -> throw new IllegalArgumentException("Unknown auto-brightness mode: " + mode);
+ }
+ }
+
+ /**
+ * @param preset The brightness preset. Presets are used on devices that allow users to choose
+ * from a set of predefined options in display auto-brightness settings.
+ * @return The string representing the preset
+ */
+ public static String autoBrightnessPresetToString(int preset) {
+ return switch (preset) {
+ case Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_DIM ->
+ AutoBrightnessSettingName.dim.getRawName();
+ case Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL ->
+ AutoBrightnessSettingName.normal.getRawName();
+ case Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_BRIGHT ->
+ AutoBrightnessSettingName.bright.getRawName();
+ default -> throw new IllegalArgumentException(
+ "Unknown auto-brightness preset value: " + preset);
+ };
+ }
+
+ private float[] brightnessArrayIntToFloat(int[] brightnessInt,
+ Spline backlightToBrightnessSpline) {
+ float[] brightnessFloat = new float[brightnessInt.length];
+ for (int i = 0; i < brightnessInt.length; i++) {
+ brightnessFloat[i] = backlightToBrightnessSpline.interpolate(
+ BrightnessSynchronizer.brightnessIntToFloat(brightnessInt[i]));
+ }
+ return brightnessFloat;
+ }
+}
diff --git a/services/core/java/com/android/server/flags/OWNERS b/services/core/java/com/android/server/flags/OWNERS
index 535a750..60ceb12 100644
--- a/services/core/java/com/android/server/flags/OWNERS
+++ b/services/core/java/com/android/server/flags/OWNERS
@@ -1 +1,2 @@
-per-file pinner.aconfig = edgararriaga@google.com
\ No newline at end of file
+per-file pinner.aconfig = edgararriaga@google.com
+per-file compaction.aconfig = edgararriaga@google.com
\ No newline at end of file
diff --git a/services/core/java/com/android/server/flags/compaction.aconfig b/services/core/java/com/android/server/flags/compaction.aconfig
new file mode 100644
index 0000000..58cc560
--- /dev/null
+++ b/services/core/java/com/android/server/flags/compaction.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.flags"
+
+flag {
+ name: "disable_system_compaction"
+ namespace: "system_performance"
+ description: "This flag controls if all processes compaction should happen during idle maintenance."
+ bug: "314328789"
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 64abb81..81204ef 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -1314,7 +1314,6 @@
*/
protected void disableDevice(
boolean initiatedByCec, final PendingActionClearedCallback originalCallback) {
- removeAction(AbsoluteVolumeAudioStatusAction.class);
removeAction(SetAudioVolumeLevelDiscoveryAction.class);
removeAction(ActiveSourceAction.class);
removeAction(ResendCecCommandAction.class);
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
index 29303aa..6157402 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
@@ -308,7 +308,6 @@
protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
removeAction(OneTouchPlayAction.class);
removeAction(DevicePowerStatusAction.class);
- removeAction(AbsoluteVolumeAudioStatusAction.class);
super.disableDevice(initiatedByCec, callback);
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 5831b29..1cd267d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -1336,7 +1336,6 @@
removeAction(OneTouchRecordAction.class);
removeAction(TimerRecordingAction.class);
removeAction(NewDeviceAction.class);
- removeAction(AbsoluteVolumeAudioStatusAction.class);
// Remove pending actions.
removeAction(RequestActiveSourceAction.class);
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 9253706..eaf754d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -3793,6 +3793,11 @@
}
}
});
+
+ // Make sure we switch away from absolute volume behavior (AVB) when entering standby.
+ // We do this because AVB should not be used unless AbsoluteVolumeAudioStatusAction exists,
+ // and the action cannot exist in standby because there are no local devices.
+ checkAndUpdateAbsoluteVolumeBehavior();
}
boolean canGoToStandby() {
@@ -4446,10 +4451,11 @@
* This allows the volume level of the System Audio device to be tracked and set by Android.
*
* Absolute volume behavior requires the following conditions:
- * 1. If the System Audio Device is an Audio System: System Audio Mode is active
- * 2. All AVB-capable audio output devices are already using full/absolute volume behavior
- * 3. CEC volume is enabled
- * 4. The System Audio device supports the <Set Audio Volume Level> message
+ * 1. The device is not in standby or transient to standby
+ * 2. If the System Audio Device is an Audio System: System Audio Mode is active
+ * 3. All AVB-capable audio output devices are already using full/absolute volume behavior
+ * 4. CEC volume is enabled
+ * 5. The System Audio device supports the <Set Audio Volume Level> message
*
* This method enables adjust-only absolute volume behavior on TV panels when conditions
* 1, 2, and 3 are met, but condition 4 is not. This allows TVs to track the volume level of
@@ -4465,10 +4471,16 @@
return;
}
+ // Condition 1: The device is not in standby or transient to standby
+ if (mPowerStatusController != null && isPowerStandbyOrTransient()) {
+ switchToFullVolumeBehavior();
+ return;
+ }
+
HdmiCecLocalDevice localCecDevice;
if (isTvDevice() && tv() != null) {
localCecDevice = tv();
- // Condition 1: TVs need System Audio Mode to be active
+ // Condition 2: TVs need System Audio Mode to be active
// (Doesn't apply to Playback Devices, where if SAM isn't active, we assume the
// TV is the System Audio Device instead.)
if (!isSystemAudioActivated()) {
@@ -4485,7 +4497,7 @@
HdmiDeviceInfo systemAudioDeviceInfo = getDeviceInfo(
localCecDevice.findAudioReceiverAddress());
- // Condition 2: All AVB-capable audio outputs already use full/absolute volume behavior
+ // Condition 3: All AVB-capable audio outputs already use full/absolute volume behavior
// We only need to check the first AVB-capable audio output because only TV panels
// have more than one of them, and they always have the same volume behavior.
@AudioManager.DeviceVolumeBehavior int currentVolumeBehavior =
@@ -4493,7 +4505,7 @@
boolean alreadyUsingFullOrAbsoluteVolume =
FULL_AND_ABSOLUTE_VOLUME_BEHAVIORS.contains(currentVolumeBehavior);
- // Condition 3: CEC volume is enabled
+ // Condition 4: CEC volume is enabled
boolean cecVolumeEnabled =
getHdmiCecVolumeControl() == HdmiControlManager.VOLUME_CONTROL_ENABLED;
@@ -4509,7 +4521,7 @@
return;
}
- // Condition 4: The System Audio device supports <Set Audio Volume Level>
+ // Condition 5: The System Audio device supports <Set Audio Volume Level>
switch (systemAudioDeviceInfo.getDeviceFeatures().getSetAudioVolumeLevelSupport()) {
case DeviceFeatures.FEATURE_SUPPORTED:
if (currentVolumeBehavior != AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) {
@@ -4556,6 +4568,8 @@
* are currently used. Removes the action for handling volume updates for these behaviors.
*/
private void switchToFullVolumeBehavior() {
+ Slog.d(TAG, "Switching to full volume behavior");
+
if (playback() != null) {
playback().removeAvbAudioStatusAction();
} else if (tv() != null) {
@@ -4597,12 +4611,14 @@
// Otherwise, enable adjust-only AVB on TVs only.
if (systemAudioDevice.getDeviceFeatures().getSetAudioVolumeLevelSupport()
== DeviceFeatures.FEATURE_SUPPORTED) {
+ Slog.d(TAG, "Enabling absolute volume behavior");
for (AudioDeviceAttributes device : getAvbCapableAudioOutputDevices()) {
getAudioDeviceVolumeManager().setDeviceAbsoluteVolumeBehavior(
device, volumeInfo, mServiceThreadExecutor,
mAbsoluteVolumeChangedListener, true);
}
} else if (tv() != null) {
+ Slog.d(TAG, "Enabling adjust-only absolute volume behavior");
for (AudioDeviceAttributes device : getAvbCapableAudioOutputDevices()) {
getAudioDeviceVolumeManager().setDeviceAbsoluteVolumeAdjustOnlyBehavior(
device, volumeInfo, mServiceThreadExecutor,
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index 4089a81..dda50ca 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -167,13 +167,17 @@
/**
* Indicates that the IME window has re-parented to the new target when the IME control changed.
+ *
+ * @param displayId the display hosting the IME window
*/
- public abstract void onImeParentChanged();
+ public abstract void onImeParentChanged(int displayId);
/**
- * Destroys the IME surface.
+ * Destroys the IME surface for the given display.
+ *
+ * @param displayId the display hosting the IME window
*/
- public abstract void removeImeSurface();
+ public abstract void removeImeSurface(int displayId);
/**
* Updates the IME visibility, back disposition and show IME picker status for SystemUI.
@@ -298,11 +302,11 @@
}
@Override
- public void onImeParentChanged() {
+ public void onImeParentChanged(int displayId) {
}
@Override
- public void removeImeSurface() {
+ public void removeImeSurface(int displayId) {
}
@Override
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 16e043c..0d29b7d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -5671,7 +5671,7 @@
}
@Override
- public void onImeParentChanged() {
+ public void onImeParentChanged(int displayId) {
synchronized (ImfLock.class) {
// Hide the IME method menu only when the IME surface parent is changed by the
// input target changed, in case seeing the dialog dismiss flickering during
@@ -5683,7 +5683,7 @@
}
@Override
- public void removeImeSurface() {
+ public void removeImeSurface(int displayId) {
mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE).sendToTarget();
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index 773293f..a6c5ad5 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -356,15 +356,6 @@
new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR));
}
- List<String> getEnabledInputMethodNames() {
- List<String> result = new ArrayList<>();
- for (Pair<String, ArrayList<String>> pair :
- getEnabledInputMethodsAndSubtypeListLocked()) {
- result.add(pair.first);
- }
- return result;
- }
-
void appendAndPutEnabledInputMethodLocked(String id) {
if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) {
// Add in the newly enabled input method.
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
index f2dcba4..5514ec7 100644
--- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
@@ -59,6 +59,7 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.parsing.PackageParser2;
import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
@@ -67,7 +68,7 @@
import com.android.server.integrity.model.IntegrityCheckResult;
import com.android.server.integrity.model.RuleMetadata;
import com.android.server.pm.PackageManagerServiceUtils;
-import com.android.server.pm.parsing.PackageParser2;
+import com.android.server.pm.parsing.PackageParserUtils;
import java.io.ByteArrayInputStream;
import java.io.File;
@@ -141,7 +142,7 @@
return new AppIntegrityManagerServiceImpl(
context,
LocalServices.getService(PackageManagerInternal.class),
- PackageParser2::forParsingFileWithDefaults,
+ PackageParserUtils::forParsingFileWithDefaults,
RuleEvaluationEngine.getRuleEvaluationEngine(),
IntegrityFileManager.getInstance(),
handlerThread.getThreadHandler());
diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
index 78c8cde..403b421 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
@@ -19,6 +19,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
import android.content.Context;
+import android.location.flags.Flags;
import android.net.ConnectivityManager;
import android.net.LinkAddress;
import android.net.LinkProperties;
@@ -48,6 +49,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.TimeUnit;
/**
* Handles network connection requests and network state change updates for AGPS data download.
@@ -91,6 +93,10 @@
// network with SUPL connectivity or report an error.
private static final int SUPL_NETWORK_REQUEST_TIMEOUT_MILLIS = 20 * 1000;
+ // If the chipset does not request to release a SUPL connection before the specified timeout in
+ // milliseconds, the connection will be automatically released.
+ private static final long SUPL_CONNECTION_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(1);
+
private static final int HASH_MAP_INITIAL_CAPACITY_TO_TRACK_CONNECTED_NETWORKS = 5;
// Keeps track of networks and their state as notified by the network request callbacks.
@@ -121,6 +127,8 @@
private static final long WAKELOCK_TIMEOUT_MILLIS = 60 * 1000;
private final PowerManager.WakeLock mWakeLock;
+ private final Object mSuplConnectionReleaseOnTimeoutToken = new Object();
+
/**
* Network attributes needed when updating HAL about network connectivity status changes.
*/
@@ -609,6 +617,13 @@
mSuplConnectivityCallback,
mHandler,
SUPL_NETWORK_REQUEST_TIMEOUT_MILLIS);
+ if (Flags.releaseSuplConnectionOnTimeout()) {
+ // Schedule to release the SUPL connection after timeout
+ mHandler.removeCallbacksAndMessages(mSuplConnectionReleaseOnTimeoutToken);
+ mHandler.postDelayed(() -> handleReleaseSuplConnection(GPS_RELEASE_AGPS_DATA_CONN),
+ mSuplConnectionReleaseOnTimeoutToken,
+ SUPL_CONNECTION_TIMEOUT_MILLIS);
+ }
} catch (RuntimeException e) {
Log.e(TAG, "Failed to request network.", e);
mSuplConnectivityCallback = null;
@@ -639,6 +654,10 @@
Log.d(TAG, message);
}
+ if (Flags.releaseSuplConnectionOnTimeout()) {
+ // Remove pending task to avoid releasing an incorrect connection
+ mHandler.removeCallbacksAndMessages(mSuplConnectionReleaseOnTimeoutToken);
+ }
if (mAGpsDataConnectionState == AGPS_DATA_CONNECTION_CLOSED) {
return;
}
diff --git a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
index 8504495..0eb9166 100644
--- a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
@@ -87,6 +87,8 @@
@NonNull private final AudioDeviceCallback mAudioDeviceCallback = new AudioDeviceCallbackImpl();
+ @MediaRoute2Info.SuitabilityStatus private final int mBuiltInSpeakerSuitabilityStatus;
+
@NonNull
private final AudioManager.OnDevicesForAttributesChangedListener
mOnDevicesForAttributesChangedListener = this::onDevicesForAttributesChangedListener;
@@ -113,6 +115,10 @@
mHandler = new Handler(Objects.requireNonNull(looper));
mStrategyForMedia = Objects.requireNonNull(strategyForMedia);
mOnDeviceRouteChangedListener = Objects.requireNonNull(onDeviceRouteChangedListener);
+
+ mBuiltInSpeakerSuitabilityStatus =
+ DeviceRouteController.getBuiltInSpeakerSuitabilityStatus(mContext);
+
mBluetoothRouteController =
new AudioPoliciesBluetoothRouteController(
mContext, btAdapter, this::rebuildAvailableRoutesAndNotify);
@@ -373,14 +379,19 @@
// from getting an id using BluetoothRouteController#getRouteIdForBluetoothAddress.
routeId = systemRouteInfo.mDefaultRouteId;
}
- return new MediaRoute2Info.Builder(routeId, humanReadableName)
+ MediaRoute2Info.Builder builder = new MediaRoute2Info.Builder(routeId, humanReadableName)
.setType(systemRouteInfo.mMediaRoute2InfoType)
.setAddress(address)
.setSystemRoute(true)
.addFeature(FEATURE_LIVE_AUDIO)
.addFeature(FEATURE_LOCAL_PLAYBACK)
- .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED)
- .build();
+ .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED);
+
+ if (systemRouteInfo.mMediaRoute2InfoType == MediaRoute2Info.TYPE_BUILTIN_SPEAKER) {
+ builder.setSuitabilityStatus(mBuiltInSpeakerSuitabilityStatus);
+ }
+
+ return builder.build();
}
/**
diff --git a/services/core/java/com/android/server/media/DeviceRouteController.java b/services/core/java/com/android/server/media/DeviceRouteController.java
index 9f175a9..8b62cc9 100644
--- a/services/core/java/com/android/server/media/DeviceRouteController.java
+++ b/services/core/java/com/android/server/media/DeviceRouteController.java
@@ -81,6 +81,30 @@
}
}
+ /** Returns device route availability status. */
+ @MediaRoute2Info.SuitabilityStatus
+ static int getBuiltInSpeakerSuitabilityStatus(@NonNull Context context) {
+ if (!Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
+ // Route is always suitable if the flag is disabled.
+ return MediaRoute2Info.SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER;
+ }
+
+ int availabilityStatus =
+ context.getResources()
+ .getInteger(
+ com.android.internal.R.integer
+ .config_mediaRouter_builtInSpeakerSuitability);
+
+ switch (availabilityStatus) {
+ case MediaRoute2Info.SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER:
+ case MediaRoute2Info.SUITABILITY_STATUS_SUITABLE_FOR_MANUAL_TRANSFER:
+ case MediaRoute2Info.SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER:
+ return availabilityStatus;
+ default:
+ return MediaRoute2Info.SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER;
+ }
+ }
+
/** Returns the currently selected device (built-in or wired) route. */
@NonNull
MediaRoute2Info getSelectedRoute();
diff --git a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
index c0f2834..65b0ad0 100644
--- a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
@@ -72,6 +72,8 @@
@NonNull
private final AudioRoutesObserver mAudioRoutesObserver = new AudioRoutesObserver();
+ @MediaRoute2Info.SuitabilityStatus private final int mBuiltInSpeakerSuitabilityStatus;
+
private int mDeviceVolume;
private MediaRoute2Info mDeviceRoute;
@@ -90,6 +92,9 @@
mAudioManager = audioManager;
mAudioService = audioService;
+ mBuiltInSpeakerSuitabilityStatus =
+ DeviceRouteController.getBuiltInSpeakerSuitabilityStatus(mContext);
+
AudioRoutesInfo newAudioRoutes = null;
try {
newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver);
@@ -165,19 +170,28 @@
}
synchronized (this) {
- return new MediaRoute2Info.Builder(
- DEVICE_ROUTE_ID, mContext.getResources().getText(name).toString())
- .setVolumeHandling(mAudioManager.isVolumeFixed()
- ? MediaRoute2Info.PLAYBACK_VOLUME_FIXED
- : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
- .setVolume(mDeviceVolume)
- .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
- .setType(type)
- .addFeature(FEATURE_LIVE_AUDIO)
- .addFeature(FEATURE_LIVE_VIDEO)
- .addFeature(FEATURE_LOCAL_PLAYBACK)
- .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED)
- .build();
+ MediaRoute2Info.Builder builder =
+ new MediaRoute2Info.Builder(
+ DEVICE_ROUTE_ID,
+ mContext.getResources().getText(name).toString())
+ .setVolumeHandling(
+ mAudioManager.isVolumeFixed()
+ ? MediaRoute2Info.PLAYBACK_VOLUME_FIXED
+ : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
+ .setVolume(mDeviceVolume)
+ .setVolumeMax(
+ mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
+ .setType(type)
+ .addFeature(FEATURE_LIVE_AUDIO)
+ .addFeature(FEATURE_LIVE_VIDEO)
+ .addFeature(FEATURE_LOCAL_PLAYBACK)
+ .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED);
+
+ if (type == TYPE_BUILTIN_SPEAKER) {
+ builder.setSuitabilityStatus(mBuiltInSpeakerSuitabilityStatus);
+ }
+
+ return builder.build();
}
}
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index 8149847..1bc2a5e 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -24,6 +24,7 @@
import android.media.RouteDiscoveryPreference;
import android.media.RoutingSessionInfo;
import android.os.Bundle;
+import android.os.UserHandle;
import com.android.internal.annotations.GuardedBy;
@@ -54,8 +55,15 @@
mCallback = callback;
}
- public abstract void requestCreateSession(long requestId, String packageName, String routeId,
- @Nullable Bundle sessionHints);
+ public abstract void requestCreateSession(
+ long requestId,
+ String packageName,
+ String routeId,
+ @Nullable Bundle sessionHints,
+ @RoutingSessionInfo.TransferReason int transferReason,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName);
+
public abstract void releaseSession(long requestId, String sessionId);
public abstract void updateDiscoveryPreference(
@@ -63,7 +71,14 @@
public abstract void selectRoute(long requestId, String sessionId, String routeId);
public abstract void deselectRoute(long requestId, String sessionId, String routeId);
- public abstract void transferToRoute(long requestId, String sessionId, String routeId);
+
+ public abstract void transferToRoute(
+ long requestId,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName,
+ String sessionId,
+ String routeId,
+ @RoutingSessionInfo.TransferReason int transferReason);
public abstract void setRouteVolume(long requestId, String routeId, int volume);
public abstract void setSessionVolume(long requestId, String sessionId, int volume);
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index 330818e..ae889d8 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -98,8 +98,14 @@
}
@Override
- public void requestCreateSession(long requestId, String packageName, String routeId,
- Bundle sessionHints) {
+ public void requestCreateSession(
+ long requestId,
+ String packageName,
+ String routeId,
+ Bundle sessionHints,
+ @RoutingSessionInfo.TransferReason int transferReason,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName) {
if (mConnectionReady) {
mActiveConnection.requestCreateSession(requestId, packageName, routeId, sessionHints);
updateBinding();
@@ -141,7 +147,13 @@
}
@Override
- public void transferToRoute(long requestId, String sessionId, String routeId) {
+ public void transferToRoute(
+ long requestId,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName,
+ String sessionId,
+ String routeId,
+ @RoutingSessionInfo.TransferReason int transferReason) {
if (mConnectionReady) {
mActiveConnection.transferToRoute(requestId, sessionId, routeId);
}
@@ -649,6 +661,14 @@
+ "Disallowed route: "
+ route);
}
+
+ if (route.getSuitabilityStatus()
+ == MediaRoute2Info.SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER) {
+ throw new SecurityException(
+ "Only the system is allowed to set not suitable for transfer status. "
+ + "Disallowed route: "
+ + route);
+ }
}
Connection connection = mConnectionRef.get();
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 5e18727..38f0df4 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -337,18 +337,47 @@
}
}
- public void requestCreateSessionWithRouter2(@NonNull IMediaRouter2 router, int requestId,
- long managerRequestId, @NonNull RoutingSessionInfo oldSession,
- @NonNull MediaRoute2Info route, Bundle sessionHints) {
+ public void requestCreateSessionWithRouter2(
+ @NonNull IMediaRouter2 router,
+ int requestId,
+ long managerRequestId,
+ @NonNull RoutingSessionInfo oldSession,
+ @NonNull MediaRoute2Info route,
+ Bundle sessionHints,
+ @Nullable UserHandle transferInitiatorUserHandle,
+ @Nullable String transferInitiatorPackageName) {
Objects.requireNonNull(router, "router must not be null");
Objects.requireNonNull(oldSession, "oldSession must not be null");
Objects.requireNonNull(route, "route must not be null");
+ synchronized (mLock) {
+ if (managerRequestId == MediaRoute2ProviderService.REQUEST_ID_NONE
+ || transferInitiatorUserHandle == null
+ || transferInitiatorPackageName == null) {
+ final IBinder binder = router.asBinder();
+ final RouterRecord routerRecord = mAllRouterRecords.get(binder);
+
+ transferInitiatorUserHandle = Binder.getCallingUserHandle();
+ if (routerRecord != null) {
+ transferInitiatorPackageName = routerRecord.mPackageName;
+ } else {
+ transferInitiatorPackageName = mContext.getPackageName();
+ }
+ }
+ }
+
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- requestCreateSessionWithRouter2Locked(requestId, managerRequestId,
- router, oldSession, route, sessionHints);
+ requestCreateSessionWithRouter2Locked(
+ requestId,
+ managerRequestId,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName,
+ router,
+ oldSession,
+ route,
+ sessionHints);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -399,10 +428,11 @@
throw new IllegalArgumentException("uniqueSessionId must not be empty");
}
+ UserHandle userHandle = Binder.getCallingUserHandle();
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- transferToRouteWithRouter2Locked(router, uniqueSessionId, route);
+ transferToRouteWithRouter2Locked(router, userHandle, uniqueSessionId, route);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -588,16 +618,28 @@
}
}
- public void requestCreateSessionWithManager(@NonNull IMediaRouter2Manager manager,
- int requestId, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route) {
+ public void requestCreateSessionWithManager(
+ @NonNull IMediaRouter2Manager manager,
+ int requestId,
+ @NonNull RoutingSessionInfo oldSession,
+ @NonNull MediaRoute2Info route,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName) {
Objects.requireNonNull(manager, "manager must not be null");
Objects.requireNonNull(oldSession, "oldSession must not be null");
Objects.requireNonNull(route, "route must not be null");
+ Objects.requireNonNull(transferInitiatorUserHandle);
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- requestCreateSessionWithManagerLocked(requestId, manager, oldSession, route);
+ requestCreateSessionWithManagerLocked(
+ requestId,
+ manager,
+ oldSession,
+ route,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -640,18 +682,32 @@
}
}
- public void transferToRouteWithManager(@NonNull IMediaRouter2Manager manager, int requestId,
- @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
+ public void transferToRouteWithManager(
+ @NonNull IMediaRouter2Manager manager,
+ int requestId,
+ @NonNull String uniqueSessionId,
+ @NonNull MediaRoute2Info route,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName) {
Objects.requireNonNull(manager, "manager must not be null");
if (TextUtils.isEmpty(uniqueSessionId)) {
throw new IllegalArgumentException("uniqueSessionId must not be empty");
}
Objects.requireNonNull(route, "route must not be null");
+ Objects.requireNonNull(transferInitiatorUserHandle);
+ Objects.requireNonNull(transferInitiatorPackageName);
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- transferToRouteWithManagerLocked(requestId, manager, uniqueSessionId, route);
+ transferToRouteWithManagerLocked(
+ requestId,
+ manager,
+ uniqueSessionId,
+ route,
+ RoutingSessionInfo.TRANSFER_REASON_SYSTEM_REQUEST,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -1038,9 +1094,15 @@
}
@GuardedBy("mLock")
- private void requestCreateSessionWithRouter2Locked(int requestId, long managerRequestId,
- @NonNull IMediaRouter2 router, @NonNull RoutingSessionInfo oldSession,
- @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints) {
+ private void requestCreateSessionWithRouter2Locked(
+ int requestId,
+ long managerRequestId,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName,
+ @NonNull IMediaRouter2 router,
+ @NonNull RoutingSessionInfo oldSession,
+ @NonNull MediaRoute2Info route,
+ @Nullable Bundle sessionHints) {
final IBinder binder = router.asBinder();
final RouterRecord routerRecord = mAllRouterRecords.get(binder);
@@ -1114,9 +1176,16 @@
long uniqueRequestId = toUniqueRequestId(routerRecord.mRouterId, requestId);
routerRecord.mUserRecord.mHandler.sendMessage(
- obtainMessage(UserHandler::requestCreateSessionWithRouter2OnHandler,
+ obtainMessage(
+ UserHandler::requestCreateSessionWithRouter2OnHandler,
routerRecord.mUserRecord.mHandler,
- uniqueRequestId, managerRequestId, routerRecord, oldSession, route,
+ uniqueRequestId,
+ managerRequestId,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName,
+ routerRecord,
+ oldSession,
+ route,
sessionHints));
}
@@ -1165,8 +1234,11 @@
}
@GuardedBy("mLock")
- private void transferToRouteWithRouter2Locked(@NonNull IMediaRouter2 router,
- @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
+ private void transferToRouteWithRouter2Locked(
+ @NonNull IMediaRouter2 router,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String uniqueSessionId,
+ @NonNull MediaRoute2Info route) {
final IBinder binder = router.asBinder();
final RouterRecord routerRecord = mAllRouterRecords.get(binder);
@@ -1191,9 +1263,16 @@
routerRecord, toOriginalRequestId(DUMMY_REQUEST_ID)));
} else {
routerRecord.mUserRecord.mHandler.sendMessage(
- obtainMessage(UserHandler::transferToRouteOnHandler,
+ obtainMessage(
+ UserHandler::transferToRouteOnHandler,
routerRecord.mUserRecord.mHandler,
- DUMMY_REQUEST_ID, routerRecord, uniqueSessionId, route));
+ DUMMY_REQUEST_ID,
+ transferInitiatorUserHandle,
+ routerRecord.mPackageName,
+ routerRecord,
+ uniqueSessionId,
+ route,
+ RoutingSessionInfo.TRANSFER_REASON_APP));
}
}
@@ -1416,9 +1495,13 @@
}
@GuardedBy("mLock")
- private void requestCreateSessionWithManagerLocked(int requestId,
- @NonNull IMediaRouter2Manager manager, @NonNull RoutingSessionInfo oldSession,
- @NonNull MediaRoute2Info route) {
+ private void requestCreateSessionWithManagerLocked(
+ int requestId,
+ @NonNull IMediaRouter2Manager manager,
+ @NonNull RoutingSessionInfo oldSession,
+ @NonNull MediaRoute2Info route,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName) {
ManagerRecord managerRecord = mAllManagerRecords.get(manager.asBinder());
if (managerRecord == null) {
return;
@@ -1464,9 +1547,16 @@
// Before requesting to the provider, get session hints from the media router.
// As a return, media router will request to create a session.
routerRecord.mUserRecord.mHandler.sendMessage(
- obtainMessage(UserHandler::requestRouterCreateSessionOnHandler,
+ obtainMessage(
+ UserHandler::requestRouterCreateSessionOnHandler,
routerRecord.mUserRecord.mHandler,
- uniqueRequestId, routerRecord, managerRecord, oldSession, route));
+ uniqueRequestId,
+ routerRecord,
+ managerRecord,
+ oldSession,
+ route,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName));
}
@GuardedBy("mLock")
@@ -1521,9 +1611,14 @@
}
@GuardedBy("mLock")
- private void transferToRouteWithManagerLocked(int requestId,
+ private void transferToRouteWithManagerLocked(
+ int requestId,
@NonNull IMediaRouter2Manager manager,
- @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
+ @NonNull String uniqueSessionId,
+ @NonNull MediaRoute2Info route,
+ @RoutingSessionInfo.TransferReason int transferReason,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName) {
final IBinder binder = manager.asBinder();
ManagerRecord managerRecord = mAllManagerRecords.get(binder);
@@ -1541,9 +1636,16 @@
long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
managerRecord.mUserRecord.mHandler.sendMessage(
- obtainMessage(UserHandler::transferToRouteOnHandler,
+ obtainMessage(
+ UserHandler::transferToRouteOnHandler,
managerRecord.mUserRecord.mHandler,
- uniqueRequestId, routerRecord, uniqueSessionId, route));
+ uniqueRequestId,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName,
+ routerRecord,
+ uniqueSessionId,
+ route,
+ transferReason));
}
@GuardedBy("mLock")
@@ -1850,6 +1952,19 @@
}
}
+ public void notifySessionCreated(int requestId, @NonNull RoutingSessionInfo sessionInfo) {
+ try {
+ mRouter.notifySessionCreated(
+ requestId, maybeClearTransferInitiatorIdentity(sessionInfo));
+ } catch (RemoteException ex) {
+ Slog.w(
+ TAG,
+ "Failed to notify router of the session creation."
+ + " Router probably died.",
+ ex);
+ }
+ }
+
/**
* Sends the corresponding router an update for the given session.
*
@@ -1857,12 +1972,27 @@
*/
public void notifySessionInfoChanged(RoutingSessionInfo sessionInfo) {
try {
- mRouter.notifySessionInfoChanged(sessionInfo);
+ mRouter.notifySessionInfoChanged(maybeClearTransferInitiatorIdentity(sessionInfo));
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify session info changed. Router probably died.", ex);
}
}
+ private RoutingSessionInfo maybeClearTransferInitiatorIdentity(
+ @NonNull RoutingSessionInfo sessionInfo) {
+ UserHandle transferInitiatorUserHandle = sessionInfo.getTransferInitiatorUserHandle();
+ String transferInitiatorPackageName = sessionInfo.getTransferInitiatorPackageName();
+
+ if (!Objects.equals(UserHandle.of(mUserRecord.mUserId), transferInitiatorUserHandle)
+ || !Objects.equals(mPackageName, transferInitiatorPackageName)) {
+ return new RoutingSessionInfo.Builder(sessionInfo)
+ .setTransferInitiator(null, null)
+ .build();
+ }
+
+ return sessionInfo;
+ }
+
/**
* Returns a filtered copy of {@code routes} that contains only the routes that are {@link
* MediaRoute2Info#isVisibleTo visible} to the router corresponding to this record.
@@ -2307,9 +2437,14 @@
return -1;
}
- private void requestRouterCreateSessionOnHandler(long uniqueRequestId,
- @NonNull RouterRecord routerRecord, @NonNull ManagerRecord managerRecord,
- @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route) {
+ private void requestRouterCreateSessionOnHandler(
+ long uniqueRequestId,
+ @NonNull RouterRecord routerRecord,
+ @NonNull ManagerRecord managerRecord,
+ @NonNull RoutingSessionInfo oldSession,
+ @NonNull MediaRoute2Info route,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName) {
try {
if (route.isSystemRoute() && !routerRecord.hasSystemRoutingPermission()) {
// The router lacks permission to modify system routing, so we hide system
@@ -2317,7 +2452,11 @@
route = mSystemProvider.getDefaultRoute();
}
routerRecord.mRouter.requestCreateSessionByManager(
- uniqueRequestId, oldSession, route);
+ uniqueRequestId,
+ oldSession,
+ route,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName);
} catch (RemoteException ex) {
Slog.w(TAG, "getSessionHintsForCreatingSessionOnHandler: "
+ "Failed to request. Router probably died.", ex);
@@ -2326,10 +2465,15 @@
}
}
- private void requestCreateSessionWithRouter2OnHandler(long uniqueRequestId,
- long managerRequestId, @NonNull RouterRecord routerRecord,
+ private void requestCreateSessionWithRouter2OnHandler(
+ long uniqueRequestId,
+ long managerRequestId,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName,
+ @NonNull RouterRecord routerRecord,
@NonNull RoutingSessionInfo oldSession,
- @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints) {
+ @NonNull MediaRoute2Info route,
+ @Nullable Bundle sessionHints) {
final MediaRoute2Provider provider = findProvider(route.getProviderId());
if (provider == null) {
@@ -2345,8 +2489,19 @@
managerRequestId, oldSession, route);
mSessionCreationRequests.add(request);
- provider.requestCreateSession(uniqueRequestId, routerRecord.mPackageName,
- route.getOriginalId(), sessionHints);
+ int transferReason = RoutingSessionInfo.TRANSFER_REASON_APP;
+ if (managerRequestId != MediaRoute2ProviderService.REQUEST_ID_NONE) {
+ transferReason = RoutingSessionInfo.TRANSFER_REASON_SYSTEM_REQUEST;
+ }
+
+ provider.requestCreateSession(
+ uniqueRequestId,
+ routerRecord.mPackageName,
+ route.getOriginalId(),
+ sessionHints,
+ transferReason,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName);
}
// routerRecord can be null if the session is system's or RCN.
@@ -2386,9 +2541,14 @@
}
// routerRecord can be null if the session is system's or RCN.
- private void transferToRouteOnHandler(long uniqueRequestId,
+ private void transferToRouteOnHandler(
+ long uniqueRequestId,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName,
@Nullable RouterRecord routerRecord,
- @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
+ @NonNull String uniqueSessionId,
+ @NonNull MediaRoute2Info route,
+ @RoutingSessionInfo.TransferReason int transferReason) {
if (!checkArgumentsForSessionControl(routerRecord, uniqueSessionId, route,
"transferring to")) {
return;
@@ -2399,8 +2559,13 @@
if (provider == null) {
return;
}
- provider.transferToRoute(uniqueRequestId, getOriginalId(uniqueSessionId),
- route.getOriginalId());
+ provider.transferToRoute(
+ uniqueRequestId,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName,
+ getOriginalId(uniqueSessionId),
+ route.getOriginalId(),
+ transferReason);
}
// routerRecord is null if and only if the session is created without the request, which
@@ -2535,6 +2700,7 @@
// session info from them.
sessionInfo = mSystemProvider.getDefaultSessionInfo();
}
+ // TODO: b/279555229 - replace with matchingRequest.mRouterRecord.notifySessionCreated.
notifySessionCreatedToRouter(
matchingRequest.mRouterRecord,
toOriginalRequestId(uniqueRequestId),
@@ -2648,12 +2814,7 @@
private void notifySessionCreatedToRouter(@NonNull RouterRecord routerRecord,
int requestId, @NonNull RoutingSessionInfo sessionInfo) {
- try {
- routerRecord.mRouter.notifySessionCreated(requestId, sessionInfo);
- } catch (RemoteException ex) {
- Slog.w(TAG, "Failed to notify router of the session creation."
- + " Router probably died.", ex);
- }
+ routerRecord.notifySessionCreated(requestId, sessionInfo);
}
private void notifySessionCreationFailedToRouter(@NonNull RouterRecord routerRecord,
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index e562b3f..7dd1314 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -461,11 +461,24 @@
// Binder call
@Override
- public void requestCreateSessionWithRouter2(IMediaRouter2 router, int requestId,
- long managerRequestId, RoutingSessionInfo oldSession,
- MediaRoute2Info route, Bundle sessionHints) {
- mService2.requestCreateSessionWithRouter2(router, requestId, managerRequestId,
- oldSession, route, sessionHints);
+ public void requestCreateSessionWithRouter2(
+ IMediaRouter2 router,
+ int requestId,
+ long managerRequestId,
+ RoutingSessionInfo oldSession,
+ MediaRoute2Info route,
+ Bundle sessionHints,
+ @Nullable UserHandle transferInitiatorUserHandle,
+ @Nullable String transferInitiatorPackageName) {
+ mService2.requestCreateSessionWithRouter2(
+ router,
+ requestId,
+ managerRequestId,
+ oldSession,
+ route,
+ sessionHints,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName);
}
// Binder call
@@ -580,9 +593,20 @@
// Binder call
@Override
- public void requestCreateSessionWithManager(IMediaRouter2Manager manager,
- int requestId, RoutingSessionInfo oldSession, MediaRoute2Info route) {
- mService2.requestCreateSessionWithManager(manager, requestId, oldSession, route);
+ public void requestCreateSessionWithManager(
+ IMediaRouter2Manager manager,
+ int requestId,
+ RoutingSessionInfo oldSession,
+ MediaRoute2Info route,
+ UserHandle transferInitiatorUserHandle,
+ String transferInitiatorPackageName) {
+ mService2.requestCreateSessionWithManager(
+ manager,
+ requestId,
+ oldSession,
+ route,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName);
}
// Binder call
@@ -601,9 +625,20 @@
// Binder call
@Override
- public void transferToRouteWithManager(IMediaRouter2Manager manager, int requestId,
- String sessionId, MediaRoute2Info route) {
- mService2.transferToRouteWithManager(manager, requestId, sessionId, route);
+ public void transferToRouteWithManager(
+ IMediaRouter2Manager manager,
+ int requestId,
+ String sessionId,
+ MediaRoute2Info route,
+ UserHandle transferInitiatorUserHandle,
+ String transferInitiatorPackageName) {
+ mService2.transferToRouteWithManager(
+ manager,
+ requestId,
+ sessionId,
+ route,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName);
}
// Binder call
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 9d151c2..f7210dd 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -16,6 +16,7 @@
package com.android.server.media;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -26,6 +27,7 @@
import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
import android.media.MediaRoute2ProviderService;
+import android.media.MediaRouter2Utils;
import android.media.RouteDiscoveryPreference;
import android.media.RoutingSessionInfo;
import android.os.Bundle;
@@ -39,6 +41,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.media.flags.Flags;
+import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -79,6 +82,10 @@
@GuardedBy("mRequestLock")
private volatile SessionCreationRequest mPendingSessionCreationRequest;
+ private final Object mTransferLock = new Object();
+ @GuardedBy("mTransferLock")
+ @Nullable private volatile SessionCreationRequest mPendingTransferRequest;
+
SystemMediaRoute2Provider(Context context, UserHandle user) {
super(COMPONENT_NAME);
mIsSystemRouteProvider = true;
@@ -146,17 +153,30 @@
}
@Override
- public void requestCreateSession(long requestId, String packageName, String routeId,
- Bundle sessionHints) {
+ public void requestCreateSession(
+ long requestId,
+ String packageName,
+ String routeId,
+ Bundle sessionHints,
+ @RoutingSessionInfo.TransferReason int transferReason,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName) {
// Assume a router without MODIFY_AUDIO_ROUTING permission can't request with
// a route ID different from the default route ID. The service should've filtered.
if (TextUtils.equals(routeId, MediaRoute2Info.ROUTE_ID_DEFAULT)) {
mCallback.onSessionCreated(this, requestId, mDefaultSessionInfo);
return;
}
- if (TextUtils.equals(routeId, mSelectedRouteId)) {
- mCallback.onSessionCreated(this, requestId, mSessionInfos.get(0));
- return;
+
+ if (!Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
+ if (TextUtils.equals(routeId, mSelectedRouteId)) {
+ RoutingSessionInfo currentSessionInfo;
+ synchronized (mLock) {
+ currentSessionInfo = mSessionInfos.get(0);
+ }
+ mCallback.onSessionCreated(this, requestId, currentSessionInfo);
+ return;
+ }
}
synchronized (mRequestLock) {
@@ -165,10 +185,23 @@
mCallback.onRequestFailed(this, mPendingSessionCreationRequest.mRequestId,
MediaRoute2ProviderService.REASON_UNKNOWN_ERROR);
}
- mPendingSessionCreationRequest = new SessionCreationRequest(requestId, routeId);
+ mPendingSessionCreationRequest =
+ new SessionCreationRequest(
+ requestId,
+ routeId,
+ RoutingSessionInfo.TRANSFER_REASON_FALLBACK,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName);
}
- transferToRoute(requestId, SYSTEM_SESSION_ID, routeId);
+ // Only unprivileged routers call this method, therefore we use TRANSFER_REASON_APP.
+ transferToRoute(
+ requestId,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName,
+ SYSTEM_SESSION_ID,
+ routeId,
+ transferReason);
}
@Override
@@ -193,12 +226,31 @@
}
@Override
- public void transferToRoute(long requestId, String sessionId, String routeId) {
+ public void transferToRoute(
+ long requestId,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName,
+ String sessionId,
+ String routeId,
+ @RoutingSessionInfo.TransferReason int transferReason) {
if (TextUtils.equals(routeId, MediaRoute2Info.ROUTE_ID_DEFAULT)) {
// The currently selected route is the default route.
Log.w(TAG, "Ignoring transfer to " + MediaRoute2Info.ROUTE_ID_DEFAULT);
return;
}
+
+ if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
+ synchronized (mTransferLock) {
+ mPendingTransferRequest =
+ new SessionCreationRequest(
+ requestId,
+ routeId,
+ transferReason,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName);
+ }
+ }
+
MediaRoute2Info selectedDeviceRoute = mDeviceRouteController.getSelectedRoute();
boolean isAvailableDeviceRoute =
mDeviceRouteController.getAvailableRoutes().stream()
@@ -218,6 +270,11 @@
mDeviceRouteController.transferTo(null);
mBluetoothRouteController.transferTo(routeId);
}
+
+ if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()
+ && updateSessionInfosIfNeeded()) {
+ notifySessionInfoUpdated();
+ }
}
@Override
@@ -322,9 +379,11 @@
MediaRoute2Info selectedDeviceRoute = mDeviceRouteController.getSelectedRoute();
MediaRoute2Info selectedRoute = selectedDeviceRoute;
MediaRoute2Info selectedBtRoute = mBluetoothRouteController.getSelectedRoute();
+ List<String> transferableRoutes = new ArrayList<>();
+
if (selectedBtRoute != null) {
selectedRoute = selectedBtRoute;
- builder.addTransferableRoute(selectedDeviceRoute.getId());
+ transferableRoutes.add(selectedDeviceRoute.getId());
}
mSelectedRouteId = selectedRoute.getId();
mDefaultRoute =
@@ -337,12 +396,54 @@
for (MediaRoute2Info route : mDeviceRouteController.getAvailableRoutes()) {
String routeId = route.getId();
if (!mSelectedRouteId.equals(routeId)) {
- builder.addTransferableRoute(routeId);
+ transferableRoutes.add(routeId);
}
}
}
for (MediaRoute2Info route : mBluetoothRouteController.getTransferableRoutes()) {
- builder.addTransferableRoute(route.getId());
+ transferableRoutes.add(route.getId());
+ }
+
+ for (String route : transferableRoutes) {
+ builder.addTransferableRoute(route);
+ }
+
+ if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
+ int transferReason = RoutingSessionInfo.TRANSFER_REASON_FALLBACK;
+ UserHandle transferInitiatorUserHandle = null;
+ String transferInitiatorPackageName = null;
+
+ if (oldSessionInfo != null
+ && containsSelectedRouteWithId(oldSessionInfo, selectedRoute.getId())) {
+ transferReason = oldSessionInfo.getTransferReason();
+ transferInitiatorUserHandle = oldSessionInfo.getTransferInitiatorUserHandle();
+ transferInitiatorPackageName = oldSessionInfo.getTransferInitiatorPackageName();
+ }
+
+ synchronized (mTransferLock) {
+ if (mPendingTransferRequest != null) {
+ boolean isTransferringToTheSelectedRoute =
+ mPendingTransferRequest.isTargetRoute(selectedRoute);
+ boolean canBePotentiallyTransferred =
+ mPendingTransferRequest.isInsideOfRoutesList(transferableRoutes);
+
+ if (isTransferringToTheSelectedRoute) {
+ transferReason = mPendingTransferRequest.mTransferReason;
+ transferInitiatorUserHandle =
+ mPendingTransferRequest.mTransferInitiatorUserHandle;
+ transferInitiatorPackageName =
+ mPendingTransferRequest.mTransferInitiatorPackageName;
+
+ mPendingTransferRequest = null;
+ } else if (!canBePotentiallyTransferred) {
+ mPendingTransferRequest = null;
+ }
+ }
+ }
+
+ builder.setTransferReason(transferReason)
+ .setTransferInitiator(
+ transferInitiatorUserHandle, transferInitiatorPackageName);
}
RoutingSessionInfo newSessionInfo = builder.setProviderId(mUniqueId).build();
@@ -424,6 +525,22 @@
return false;
}
+ private boolean containsSelectedRouteWithId(
+ @Nullable RoutingSessionInfo sessionInfo, @NonNull String selectedRouteId) {
+ if (sessionInfo == null) {
+ return false;
+ }
+
+ List<String> selectedRoutes = sessionInfo.getSelectedRoutes();
+
+ if (selectedRoutes.size() != 1) {
+ throw new IllegalStateException("Selected routes list should contain only 1 route id.");
+ }
+
+ String oldSelectedRouteId = MediaRouter2Utils.getOriginalId(selectedRoutes.get(0));
+ return oldSelectedRouteId != null && oldSelectedRouteId.equals(selectedRouteId);
+ }
+
void publishProviderState() {
updateProviderState();
notifyProviderState();
@@ -452,12 +569,47 @@
}
private static class SessionCreationRequest {
- final long mRequestId;
- final String mRouteId;
+ private final long mRequestId;
+ @NonNull private final String mRouteId;
- SessionCreationRequest(long requestId, String routeId) {
- this.mRequestId = requestId;
- this.mRouteId = routeId;
+ @RoutingSessionInfo.TransferReason private final int mTransferReason;
+
+ @NonNull private final UserHandle mTransferInitiatorUserHandle;
+ @NonNull private final String mTransferInitiatorPackageName;
+
+ SessionCreationRequest(
+ long requestId,
+ @NonNull String routeId,
+ @RoutingSessionInfo.TransferReason int transferReason,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName) {
+ mRequestId = requestId;
+ mRouteId = routeId;
+ mTransferReason = transferReason;
+ mTransferInitiatorUserHandle = transferInitiatorUserHandle;
+ mTransferInitiatorPackageName = transferInitiatorPackageName;
+ }
+
+ private boolean isTargetRoute(@Nullable MediaRoute2Info route2Info) {
+ if (route2Info == null) {
+ return false;
+ }
+
+ return isTargetRoute(route2Info.getId());
+ }
+
+ private boolean isTargetRoute(@Nullable String routeId) {
+ return mRouteId.equals(routeId);
+ }
+
+ private boolean isInsideOfRoutesList(@NonNull List<String> routesList) {
+ for (String routeId : routesList) {
+ if (isTargetRoute(routeId)) {
+ return true;
+ }
+ }
+
+ return false;
}
}
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 2da1a68..66e61c0 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -234,7 +234,7 @@
if (pkgList != null && (pkgList.length > 0)) {
for (String pkgName : pkgList) {
try {
- inm.removeAutomaticZenRules(pkgName);
+ inm.removeAutomaticZenRules(pkgName, /* fromUser= */ false);
inm.setNotificationPolicyAccessGranted(pkgName, false);
} catch (Exception e) {
Slog.e(TAG, "Failed to clean up rules for " + pkgName, e);
diff --git a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
index 8855666..71a6b5e 100644
--- a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
+++ b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
@@ -109,7 +109,6 @@
if (origin == ZenModeConfig.UPDATE_ORIGIN_INIT
|| origin == ZenModeConfig.UPDATE_ORIGIN_INIT_USER
|| origin == ZenModeConfig.UPDATE_ORIGIN_USER
- || origin == ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
|| !mPowerManager.isInteractive()) {
unregisterScreenOffReceiver();
updateNightModeImmediately(useNightMode);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 75d3dce..13bf336c 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5343,13 +5343,14 @@
}
@Override
- public void setZenMode(int mode, Uri conditionId, String reason) throws RemoteException {
+ public void setZenMode(int mode, Uri conditionId, String reason, boolean fromUser) {
enforceSystemOrSystemUI("INotificationManager.setZenMode");
final int callingUid = Binder.getCallingUid();
final long identity = Binder.clearCallingIdentity();
+ enforceUserOriginOnlyFromSystem(fromUser, "setZenMode");
+
try {
- mZenModeHelper.setManualZenMode(mode, conditionId,
- ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, // Checked by enforce()
+ mZenModeHelper.setManualZenMode(mode, conditionId, computeZenOrigin(fromUser),
reason, /* caller= */ null, callingUid);
} finally {
Binder.restoreCallingIdentity(identity);
@@ -5380,7 +5381,8 @@
}
@Override
- public String addAutomaticZenRule(AutomaticZenRule automaticZenRule, String pkg) {
+ public String addAutomaticZenRule(AutomaticZenRule automaticZenRule, String pkg,
+ boolean fromUser) {
validateAutomaticZenRule(automaticZenRule);
checkCallerIsSameApp(pkg);
if (automaticZenRule.getZenPolicy() != null
@@ -5389,6 +5391,7 @@
+ "INTERRUPTION_FILTER_PRIORITY filters");
}
enforcePolicyAccess(Binder.getCallingUid(), "addAutomaticZenRule");
+ enforceUserOriginOnlyFromSystem(fromUser, "addAutomaticZenRule");
// If the calling app is the system (from any user), take the package name from the
// rule's owner rather than from the caller's package.
@@ -5400,24 +5403,18 @@
}
return mZenModeHelper.addAutomaticZenRule(rulePkg, automaticZenRule,
- // TODO: b/308670715: Distinguish origin properly (e.g. USER if creating a rule
- // manually in Settings).
- isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
- : ZenModeConfig.UPDATE_ORIGIN_APP,
- "addAutomaticZenRule", Binder.getCallingUid());
+ computeZenOrigin(fromUser), "addAutomaticZenRule", Binder.getCallingUid());
}
@Override
- public boolean updateAutomaticZenRule(String id, AutomaticZenRule automaticZenRule) {
+ public boolean updateAutomaticZenRule(String id, AutomaticZenRule automaticZenRule,
+ boolean fromUser) throws RemoteException {
validateAutomaticZenRule(automaticZenRule);
enforcePolicyAccess(Binder.getCallingUid(), "updateAutomaticZenRule");
+ enforceUserOriginOnlyFromSystem(fromUser, "updateAutomaticZenRule");
- // TODO: b/308670715: Distinguish origin properly (e.g. USER if updating a rule
- // manually in Settings).
return mZenModeHelper.updateAutomaticZenRule(id, automaticZenRule,
- isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
- : ZenModeConfig.UPDATE_ORIGIN_APP,
- "updateAutomaticZenRule", Binder.getCallingUid());
+ computeZenOrigin(fromUser), "updateAutomaticZenRule", Binder.getCallingUid());
}
private void validateAutomaticZenRule(AutomaticZenRule rule) {
@@ -5445,27 +5442,24 @@
}
@Override
- public boolean removeAutomaticZenRule(String id) throws RemoteException {
+ public boolean removeAutomaticZenRule(String id, boolean fromUser) throws RemoteException {
Objects.requireNonNull(id, "Id is null");
// Verify that they can modify zen rules.
enforcePolicyAccess(Binder.getCallingUid(), "removeAutomaticZenRule");
+ enforceUserOriginOnlyFromSystem(fromUser, "removeAutomaticZenRule");
- // TODO: b/308670715: Distinguish origin properly (e.g. USER if removing a rule
- // manually in Settings).
- return mZenModeHelper.removeAutomaticZenRule(id,
- isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
- : ZenModeConfig.UPDATE_ORIGIN_APP,
+ return mZenModeHelper.removeAutomaticZenRule(id, computeZenOrigin(fromUser),
"removeAutomaticZenRule", Binder.getCallingUid());
}
@Override
- public boolean removeAutomaticZenRules(String packageName) throws RemoteException {
+ public boolean removeAutomaticZenRules(String packageName, boolean fromUser)
+ throws RemoteException {
Objects.requireNonNull(packageName, "Package name is null");
enforceSystemOrSystemUI("removeAutomaticZenRules");
+ enforceUserOriginOnlyFromSystem(fromUser, "removeAutomaticZenRules");
- return mZenModeHelper.removeAutomaticZenRules(packageName,
- isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
- : ZenModeConfig.UPDATE_ORIGIN_APP,
+ return mZenModeHelper.removeAutomaticZenRules(packageName, computeZenOrigin(fromUser),
packageName + "|removeAutomaticZenRules", Binder.getCallingUid());
}
@@ -5478,28 +5472,54 @@
}
@Override
- public void setAutomaticZenRuleState(String id, Condition condition) {
+ public void setAutomaticZenRuleState(String id, Condition condition, boolean fromUser) {
Objects.requireNonNull(id, "id is null");
Objects.requireNonNull(condition, "Condition is null");
condition.validate();
enforcePolicyAccess(Binder.getCallingUid(), "setAutomaticZenRuleState");
- // TODO: b/308670715: Distinguish origin properly (e.g. USER if toggling a rule
- // manually in Settings).
- mZenModeHelper.setAutomaticZenRuleState(id, condition,
- isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
- : ZenModeConfig.UPDATE_ORIGIN_APP,
+ if (android.app.Flags.modesApi()) {
+ if (fromUser != (condition.source == Condition.SOURCE_USER_ACTION)) {
+ throw new IllegalArgumentException(String.format(
+ "Mismatch between fromUser (%s) and condition.source (%s)",
+ fromUser, Condition.sourceToString(condition.source)));
+ }
+ }
+
+ mZenModeHelper.setAutomaticZenRuleState(id, condition, computeZenOrigin(fromUser),
Binder.getCallingUid());
}
+ @ZenModeConfig.ConfigChangeOrigin
+ private int computeZenOrigin(boolean fromUser) {
+ // "fromUser" is introduced with MODES_API, so only consider it in that case.
+ // (Non-MODES_API behavior should also not depend at all on UPDATE_ORIGIN_USER).
+ if (android.app.Flags.modesApi() && fromUser) {
+ return ZenModeConfig.UPDATE_ORIGIN_USER;
+ } else if (isCallerSystemOrSystemUi()) {
+ return ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI;
+ } else {
+ return ZenModeConfig.UPDATE_ORIGIN_APP;
+ }
+ }
+
+ private void enforceUserOriginOnlyFromSystem(boolean fromUser, String method) {
+ if (android.app.Flags.modesApi()
+ && fromUser
+ && !isCallerSystemOrSystemUiOrShell()) {
+ throw new SecurityException(String.format(
+ "Calling %s with fromUser == true is only allowed for system", method));
+ }
+ }
+
@Override
- public void setInterruptionFilter(String pkg, int filter) throws RemoteException {
+ public void setInterruptionFilter(String pkg, int filter, boolean fromUser) {
enforcePolicyAccess(pkg, "setInterruptionFilter");
final int zen = NotificationManager.zenModeFromInterruptionFilter(filter, -1);
if (zen == -1) throw new IllegalArgumentException("Invalid filter: " + filter);
final int callingUid = Binder.getCallingUid();
- final boolean isSystemOrSystemUi = isCallerSystemOrSystemUi();
+ enforceUserOriginOnlyFromSystem(fromUser, "setInterruptionFilter");
if (android.app.Flags.modesApi() && !canManageGlobalZenPolicy(pkg, callingUid)) {
mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, callingUid, zen);
@@ -5508,9 +5528,7 @@
final long identity = Binder.clearCallingIdentity();
try {
- mZenModeHelper.setManualZenMode(zen, null,
- isSystemOrSystemUi ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
- : ZenModeConfig.UPDATE_ORIGIN_APP,
+ mZenModeHelper.setManualZenMode(zen, null, computeZenOrigin(fromUser),
/* reason= */ "setInterruptionFilter", /* caller= */ pkg,
callingUid);
} finally {
@@ -5599,7 +5617,8 @@
return !isCompatChangeEnabled
|| isCallerSystemOrSystemUi()
|| hasCompanionDevice(callingPkg, UserHandle.getUserId(callingUid),
- AssociationRequest.DEVICE_PROFILE_WATCH);
+ Set.of(AssociationRequest.DEVICE_PROFILE_WATCH,
+ AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION));
}
private void enforcePolicyAccess(String pkg, String method) {
@@ -5825,10 +5844,11 @@
* {@link Policy#PRIORITY_CATEGORY_MEDIA} from bypassing dnd
*/
@Override
- public void setNotificationPolicy(String pkg, Policy policy) {
+ public void setNotificationPolicy(String pkg, Policy policy, boolean fromUser) {
enforcePolicyAccess(pkg, "setNotificationPolicy");
+ enforceUserOriginOnlyFromSystem(fromUser, "setNotificationPolicy");
int callingUid = Binder.getCallingUid();
- boolean isSystemOrSystemUi = isCallerSystemOrSystemUi();
+ @ZenModeConfig.ConfigChangeOrigin int origin = computeZenOrigin(fromUser);
boolean shouldApplyAsImplicitRule = android.app.Flags.modesApi()
&& !canManageGlobalZenPolicy(pkg, callingUid);
@@ -5873,14 +5893,12 @@
newVisualEffects, policy.priorityConversationSenders);
if (shouldApplyAsImplicitRule) {
- mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, callingUid, policy);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, callingUid, policy,
+ origin);
} else {
ZenLog.traceSetNotificationPolicy(pkg, applicationInfo.targetSdkVersion,
policy);
- mZenModeHelper.setNotificationPolicy(policy,
- isSystemOrSystemUi ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
- : ZenModeConfig.UPDATE_ORIGIN_APP,
- callingUid);
+ mZenModeHelper.setNotificationPolicy(policy, origin, callingUid);
}
} catch (RemoteException e) {
Slog.e(TAG, "Failed to set notification policy", e);
@@ -7001,12 +7019,14 @@
return false;
}
- final boolean hasBitmap = n.extras.containsKey(Notification.EXTRA_PICTURE);
+ final boolean hasBitmap = n.extras.containsKey(Notification.EXTRA_PICTURE)
+ && n.extras.getParcelable(Notification.EXTRA_PICTURE) != null;
if (hasBitmap) {
return true;
}
- final boolean hasIcon = n.extras.containsKey(Notification.EXTRA_PICTURE_ICON);
+ final boolean hasIcon = n.extras.containsKey(Notification.EXTRA_PICTURE_ICON)
+ && n.extras.getParcelable(Notification.EXTRA_PICTURE_ICON) != null;
if (hasIcon) {
return true;
}
@@ -7022,9 +7042,10 @@
if (!isBigPictureWithBitmapOrIcon(r.getNotification())) {
return;
}
- // Remove Notification object's reference to picture bitmap or URI
- r.getNotification().extras.remove(Notification.EXTRA_PICTURE);
- r.getNotification().extras.remove(Notification.EXTRA_PICTURE_ICON);
+ // Remove Notification object's reference to picture bitmap or URI. Leave the extras set to
+ // null to avoid crashing apps that came to expect them to be present but null.
+ r.getNotification().extras.putParcelable(Notification.EXTRA_PICTURE, null);
+ r.getNotification().extras.putParcelable(Notification.EXTRA_PICTURE_ICON, null);
// Make Notification silent
r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
@@ -10733,6 +10754,14 @@
final String key = record.getSbn().getKey();
final NotificationListenerService.Ranking ranking =
new NotificationListenerService.Ranking();
+ ArrayList<Notification.Action> smartActions = record.getSystemGeneratedSmartActions();
+ ArrayList<CharSequence> smartReplies = record.getSmartReplies();
+ if (redactSensitiveNotificationsFromUntrustedListeners()
+ && !mListeners.isUidTrusted(info.uid)
+ && mListeners.hasSensitiveContent(record)) {
+ smartActions = null;
+ smartReplies = null;
+ }
ranking.populate(
key,
rankings.size(),
@@ -10750,8 +10779,8 @@
record.isHidden(),
record.getLastAudiblyAlertedMs(),
record.getSound() != null || record.getVibration() != null,
- record.getSystemGeneratedSmartActions(),
- record.getSmartReplies(),
+ smartActions,
+ smartReplies,
record.canBubble(),
record.isTextChanged(),
record.isConversation(),
@@ -10780,7 +10809,7 @@
}
private boolean hasCompanionDevice(String pkg, @UserIdInt int userId,
- @Nullable @AssociationRequest.DeviceProfile String withDeviceProfile) {
+ @Nullable Set</* @AssociationRequest.DeviceProfile */ String> withDeviceProfiles) {
if (mCompanionManager == null) {
mCompanionManager = getCompanionManager();
}
@@ -10792,7 +10821,7 @@
try {
List<AssociationInfo> associations = mCompanionManager.getAssociations(pkg, userId);
for (AssociationInfo association : associations) {
- if (withDeviceProfile == null || withDeviceProfile.equals(
+ if (withDeviceProfiles == null || withDeviceProfiles.contains(
association.getDeviceProfile())) {
return true;
}
@@ -11501,20 +11530,16 @@
super.setPackageOrComponentEnabled(pkgOrComponent, userId, isPrimary, enabled, userSet);
String pkgName = getPackageName(pkgOrComponent);
if (redactSensitiveNotificationsFromUntrustedListeners()) {
- try {
- int uid = mPackageManagerClient.getPackageUidAsUser(pkgName, userId);
- if (!enabled) {
- synchronized (mTrustedListenerUids) {
- mTrustedListenerUids.remove(uid);
- }
+ int uid = mPackageManagerInternal.getPackageUid(pkgName, 0, userId);
+ if (!enabled && uid >= 0) {
+ synchronized (mTrustedListenerUids) {
+ mTrustedListenerUids.remove(uid);
}
- if (enabled && isAppTrustedNotificationListenerService(uid, pkgName)) {
- synchronized (mTrustedListenerUids) {
- mTrustedListenerUids.add(uid);
- }
+ }
+ if (enabled && uid >= 0 && isAppTrustedNotificationListenerService(uid, pkgName)) {
+ synchronized (mTrustedListenerUids) {
+ mTrustedListenerUids.add(uid);
}
- } catch (NameNotFoundException e) {
- Slog.e(TAG, "PackageManager could not find package " + pkgName, e);
}
}
@@ -11934,8 +11959,10 @@
for (final ManagedServiceInfo info : getServices()) {
boolean isTrusted = isUidTrusted(info.uid);
- boolean sendRedacted = isNewSensitive && !isTrusted;
- boolean sendOldRedacted = isOldSensitive && !isTrusted;
+ boolean sendRedacted = redactSensitiveNotificationsFromUntrustedListeners()
+ && isNewSensitive && !isTrusted;
+ boolean sendOldRedacted = redactSensitiveNotificationsFromUntrustedListeners()
+ && isOldSensitive && !isTrusted;
boolean sbnVisible = isVisibleToListener(sbn, r.getNotificationType(), info);
boolean oldSbnVisible = (oldSbn != null)
&& isVisibleToListener(oldSbn, old.getNotificationType(), info);
@@ -12034,7 +12061,7 @@
StatusBarNotification redactStatusBarNotification(StatusBarNotification sbn) {
if (!redactSensitiveNotificationsFromUntrustedListeners()) {
- return sbn;
+ throw new RuntimeException("redactStatusBarNotification called while flag is off");
}
ApplicationInfo appInfo = sbn.getNotification().extras.getParcelable(
@@ -12206,6 +12233,7 @@
public void notifyRankingUpdateLocked(List<NotificationRecord> changedHiddenNotifications) {
boolean isHiddenRankingUpdate = changedHiddenNotifications != null
&& changedHiddenNotifications.size() > 0;
+
// TODO (b/73052211): if the ranking update changed the notification type,
// cancel notifications for NLSes that can't see them anymore
for (final ManagedServiceInfo serviceInfo : getServices()) {
@@ -12229,7 +12257,6 @@
if (notifyThisListener || !isHiddenRankingUpdate) {
final NotificationRankingUpdate update = makeRankingUpdateLocked(
serviceInfo);
-
mHandler.post(() -> notifyRankingUpdate(serviceInfo, update));
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationShellCmd.java b/services/core/java/com/android/server/notification/NotificationShellCmd.java
index dc0cf4e..9f3104c 100644
--- a/services/core/java/com/android/server/notification/NotificationShellCmd.java
+++ b/services/core/java/com/android/server/notification/NotificationShellCmd.java
@@ -117,7 +117,6 @@
private final NotificationManagerService mDirectService;
private final INotificationManager mBinderService;
private final PackageManager mPm;
- private NotificationChannel mChannel;
public NotificationShellCmd(NotificationManagerService service) {
mDirectService = service;
@@ -183,7 +182,13 @@
interruptionFilter = INTERRUPTION_FILTER_ALL;
}
final int filter = interruptionFilter;
- mBinderService.setInterruptionFilter(callingPackage, filter);
+ if (android.app.Flags.modesApi()) {
+ mBinderService.setInterruptionFilter(callingPackage, filter,
+ /* fromUser= */ true);
+ } else {
+ mBinderService.setInterruptionFilter(callingPackage, filter,
+ /* fromUser= */ false);
+ }
}
break;
case "allow_dnd": {
diff --git a/services/core/java/com/android/server/notification/ZenModeEventLogger.java b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
index 87158cd..df570a0 100644
--- a/services/core/java/com/android/server/notification/ZenModeEventLogger.java
+++ b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
@@ -29,6 +29,7 @@
import android.os.Process;
import android.service.notification.DNDPolicyProto;
import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
import android.service.notification.ZenModeDiff;
import android.service.notification.ZenPolicy;
import android.util.ArrayMap;
@@ -58,7 +59,7 @@
// mode change.
ZenModeEventLogger.ZenStateChanges mChangeState = new ZenModeEventLogger.ZenStateChanges();
- private PackageManager mPm;
+ private final PackageManager mPm;
ZenModeEventLogger(PackageManager pm) {
mPm = pm;
@@ -97,11 +98,11 @@
* @param newInfo ZenModeInfo after this change takes effect
* @param callingUid the calling UID associated with the change; may be used to attribute the
* change to a particular package or determine if this is a user action
- * @param fromSystemOrSystemUi whether the calling UID is either system UID or system UI
+ * @param origin The origin of the Zen change.
*/
public final void maybeLogZenChange(ZenModeInfo prevInfo, ZenModeInfo newInfo, int callingUid,
- boolean fromSystemOrSystemUi) {
- mChangeState.init(prevInfo, newInfo, callingUid, fromSystemOrSystemUi);
+ @ConfigChangeOrigin int origin) {
+ mChangeState.init(prevInfo, newInfo, callingUid, origin);
if (mChangeState.shouldLogChanges()) {
maybeReassignCallingUid();
logChanges();
@@ -124,7 +125,7 @@
// We don't consider the manual rule in the old config because if a manual rule is turning
// off with a call from system, that could easily be a user action to explicitly turn it off
if (mChangeState.getChangedRuleType() == RULE_TYPE_MANUAL) {
- if (!mChangeState.mFromSystemOrSystemUi
+ if (!mChangeState.isFromSystemOrSystemUi()
|| mChangeState.getNewManualRuleEnabler() == null) {
return;
}
@@ -136,7 +137,7 @@
// - we've determined it's not a user action
// - our current best guess is that the calling uid is system/sysui
if (mChangeState.getChangedRuleType() == RULE_TYPE_AUTOMATIC) {
- if (mChangeState.getIsUserAction() || !mChangeState.mFromSystemOrSystemUi) {
+ if (mChangeState.getIsUserAction() || !mChangeState.isFromSystemOrSystemUi()) {
return;
}
@@ -221,10 +222,10 @@
ZenModeConfig mPrevConfig, mNewConfig;
NotificationManager.Policy mPrevPolicy, mNewPolicy;
int mCallingUid = Process.INVALID_UID;
- boolean mFromSystemOrSystemUi = false;
+ @ConfigChangeOrigin int mOrigin = ZenModeConfig.UPDATE_ORIGIN_UNKNOWN;
private void init(ZenModeInfo prevInfo, ZenModeInfo newInfo, int callingUid,
- boolean fromSystemOrSystemUi) {
+ @ConfigChangeOrigin int origin) {
// previous & new may be the same -- that would indicate that zen mode hasn't changed.
mPrevZenMode = prevInfo.mZenMode;
mNewZenMode = newInfo.mZenMode;
@@ -233,7 +234,7 @@
mPrevPolicy = prevInfo.mPolicy;
mNewPolicy = newInfo.mPolicy;
mCallingUid = callingUid;
- mFromSystemOrSystemUi = fromSystemOrSystemUi;
+ mOrigin = origin;
}
/**
@@ -389,12 +390,16 @@
/**
* Return our best guess as to whether the changes observed are due to a user action.
- * Note that this won't be 100% accurate as we can't necessarily distinguish between a
- * system uid call indicating "user interacted with Settings" vs "a system app changed
- * something automatically".
+ * Note that this (before {@code MODES_API}) won't be 100% accurate as we can't necessarily
+ * distinguish between a system uid call indicating "user interacted with Settings" vs "a
+ * system app changed something automatically".
*/
boolean getIsUserAction() {
- // Approach:
+ if (Flags.modesApi()) {
+ return mOrigin == ZenModeConfig.UPDATE_ORIGIN_USER;
+ }
+
+ // Approach for pre-MODES_API:
// - if manual rule turned on or off, the calling UID is system, and the new manual
// rule does not have an enabler set, guess that this is likely to be a user action.
// This may represent a system app turning on DND automatically, but we guess "user"
@@ -419,13 +424,13 @@
switch (getChangedRuleType()) {
case RULE_TYPE_MANUAL:
// TODO(b/278888961): Distinguish the automatically-turned-off state
- return mFromSystemOrSystemUi && (getNewManualRuleEnabler() == null);
+ return isFromSystemOrSystemUi() && (getNewManualRuleEnabler() == null);
case RULE_TYPE_AUTOMATIC:
for (ZenModeDiff.RuleDiff d : getChangedAutomaticRules().values()) {
if (d.wasAdded() || d.wasRemoved()) {
// If the change comes from system, a rule being added/removed indicates
// a likely user action. From an app, it's harder to know for sure.
- return mFromSystemOrSystemUi;
+ return isFromSystemOrSystemUi();
}
ZenModeDiff.FieldDiff enabled = d.getDiffForField(
ZenModeDiff.RuleDiff.FIELD_ENABLED);
@@ -455,6 +460,13 @@
return false;
}
+ boolean isFromSystemOrSystemUi() {
+ return mOrigin == ZenModeConfig.UPDATE_ORIGIN_INIT
+ || mOrigin == ZenModeConfig.UPDATE_ORIGIN_INIT_USER
+ || mOrigin == ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+ || mOrigin == ZenModeConfig.UPDATE_ORIGIN_RESTORE_BACKUP;
+ }
+
/**
* Get the package UID associated with this change, which is just the calling UID for the
* relevant method changes. This may get reset by ZenModeEventLogger, which has access to
@@ -612,7 +624,7 @@
copy.mPrevPolicy = mPrevPolicy.copy();
copy.mNewPolicy = mNewPolicy.copy();
copy.mCallingUid = mCallingUid;
- copy.mFromSystemOrSystemUi = mFromSystemOrSystemUi;
+ copy.mOrigin = mOrigin;
return copy;
}
}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 3f8b595..0a46901 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -561,7 +561,7 @@
* {@link Global#ZEN_MODE_IMPORTANT_INTERRUPTIONS}.
*/
void applyGlobalPolicyAsImplicitZenRule(String callingPkg, int callingUid,
- NotificationManager.Policy policy) {
+ NotificationManager.Policy policy, @ConfigChangeOrigin int origin) {
if (!android.app.Flags.modesApi()) {
Log.wtf(TAG, "applyGlobalPolicyAsImplicitZenRule called with flag off!");
return;
@@ -579,7 +579,7 @@
}
// TODO: b/308673679 - Keep user customization of this rule!
rule.zenPolicy = ZenAdapters.notificationPolicyToZenPolicy(policy);
- setConfigLocked(newConfig, /* triggeringComponent= */ null, UPDATE_ORIGIN_APP,
+ setConfigLocked(newConfig, /* triggeringComponent= */ null, origin,
"applyGlobalPolicyAsImplicitZenRule", callingUid);
}
}
@@ -1371,12 +1371,8 @@
if (logZenModeEvents) {
ZenModeEventLogger.ZenModeInfo newInfo = new ZenModeEventLogger.ZenModeInfo(
mZenMode, mConfig, mConsolidatedPolicy);
- boolean fromSystemOrSystemUi = origin == UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
- || origin == UPDATE_ORIGIN_INIT
- || origin == UPDATE_ORIGIN_INIT_USER
- || origin == UPDATE_ORIGIN_RESTORE_BACKUP;
mZenModeEventLogger.maybeLogZenChange(prevInfo, newInfo, callingUid,
- fromSystemOrSystemUi);
+ origin);
}
}
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index 55d8a0f..8e79922a 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -41,3 +41,12 @@
description: "This flag controls the fix for notifications autogroup summary icon updates"
bug: "227693160"
}
+
+flag {
+ name: "sensitive_notification_app_protection"
+ namespace: "systemui"
+ description: "This flag controls the sensitive notification app protections while screen sharing"
+ bug: "312784351"
+ # Referenced in WM where WM starts before DeviceConfig
+ is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/pm/InitAppsHelper.java b/services/core/java/com/android/server/pm/InitAppsHelper.java
index 3b9f9c8..41d0176 100644
--- a/services/core/java/com/android/server/pm/InitAppsHelper.java
+++ b/services/core/java/com/android/server/pm/InitAppsHelper.java
@@ -46,11 +46,11 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.om.OverlayConfig;
+import com.android.internal.pm.parsing.PackageParser2;
import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.EventLogTags;
import com.android.server.pm.parsing.PackageCacher;
-import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.utils.WatchedArrayMap;
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 65bfb2f..b638d30 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -146,6 +146,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.EventLog;
+import android.util.ExceptionUtils;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
@@ -154,6 +155,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.F2fsUtils;
+import com.android.internal.pm.parsing.PackageParser2;
import com.android.internal.pm.parsing.PackageParserException;
import com.android.internal.pm.parsing.pkg.AndroidPackageLegacyUtils;
import com.android.internal.pm.parsing.pkg.ParsedPackage;
@@ -178,7 +180,6 @@
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.DexoptOptions;
import com.android.server.pm.parsing.PackageCacher;
-import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
import com.android.server.pm.permission.Permission;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
@@ -672,6 +673,9 @@
if (pkgSetting == null || pkgSetting.getPkg() == null) {
return Pair.create(PackageManager.INSTALL_FAILED_INVALID_URI, intentSender);
}
+ if (instantApp && (pkgSetting.isSystem() || pkgSetting.isUpdatedSystemApp())) {
+ return Pair.create(PackageManager.INSTALL_FAILED_INVALID_URI, intentSender);
+ }
if (!snapshot.canViewInstantApps(callingUid, UserHandle.getUserId(callingUid))) {
// only allow the existing package to be used if it's installed as a full
// application for at least one user
@@ -1167,8 +1171,9 @@
parseFlags);
archivedPackage = request.getPackageLite().getArchivedPackage();
}
- } catch (PackageManagerException | PackageParserException e) {
- throw new PrepareFailure("Failed parse during installPackageLI", e);
+ } catch (PackageParserException e) {
+ throw new PrepareFailure(e.error,
+ ExceptionUtils.getCompleteMessage("Failed parse during installPackageLI", e));
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
@@ -2858,14 +2863,17 @@
mPm.notifyPackageChanged(packageName, request.getAppId());
}
- for (int userId : firstUserIds) {
- // Apply restricted settings on potentially dangerous packages. Needs to happen
- // after appOpsManager is notified of the new package
- if (request.getPackageSource() == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE
- || request.getPackageSource()
- == PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE) {
- enableRestrictedSettings(packageName, request.getAppId(), userId);
- }
+ // Apply restricted settings on potentially dangerous packages. Needs to happen
+ // after appOpsManager is notified of the new package
+ if (request.getPackageSource() == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE
+ || request.getPackageSource()
+ == PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE) {
+ final int appId = request.getAppId();
+ mPm.mHandler.post(() -> {
+ for (int userId : firstUserIds) {
+ enableRestrictedSettings(packageName, appId, userId);
+ }
+ });
}
// Log current value of "unknown sources" setting
@@ -3680,6 +3688,8 @@
final ParsedPackage parsedPackage;
try (PackageParser2 pp = mPm.mInjector.getScanningPackageParser()) {
parsedPackage = pp.parsePackage(scanFile, parseFlags, false);
+ } catch (PackageParserException e) {
+ throw new PackageManagerException(e.error, e.getMessage(), e);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index 187cada..e970d2c 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -51,8 +51,8 @@
import com.android.internal.content.F2fsUtils;
import com.android.internal.content.InstallLocationUtils;
import com.android.internal.content.NativeLibraryHelper;
+import com.android.internal.pm.parsing.PackageParser2;
import com.android.internal.util.Preconditions;
-import com.android.server.pm.parsing.PackageParser2;
import libcore.io.IoUtils;
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 2864a8b..dcfc855d 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -132,6 +132,11 @@
private static final String EXTRA_INSTALLER_TITLE =
"com.android.content.pm.extra.UNARCHIVE_INSTALLER_TITLE";
+ private static final PorterDuffColorFilter OPACITY_LAYER_FILTER =
+ new PorterDuffColorFilter(
+ Color.argb(0.5f /* alpha */, 0f /* red */, 0f /* green */, 0f /* blue */),
+ PorterDuff.Mode.SRC_ATOP);
+
private final Context mContext;
private final PackageManagerService mPm;
@@ -746,11 +751,7 @@
return bitmap;
}
BitmapDrawable appIconDrawable = new BitmapDrawable(mContext.getResources(), bitmap);
- PorterDuffColorFilter colorFilter =
- new PorterDuffColorFilter(
- Color.argb(0.32f /* alpha */, 0f /* red */, 0f /* green */, 0f /* blue */),
- PorterDuff.Mode.SRC_ATOP);
- appIconDrawable.setColorFilter(colorFilter);
+ appIconDrawable.setColorFilter(OPACITY_LAYER_FILTER);
appIconDrawable.setBounds(
0 /* left */,
0 /* top */,
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 2942bbb..cbd65a4 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -111,6 +111,7 @@
import com.android.internal.content.InstallLocationUtils;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.pm.parsing.PackageParser2;
import com.android.internal.util.ImageUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.modules.utils.TypedXmlPullParser;
@@ -120,7 +121,6 @@
import com.android.server.SystemConfig;
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
-import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.utils.RequestThrottle;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 1d5b8c3..81d5d81 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -181,6 +181,7 @@
import com.android.internal.content.F2fsUtils;
import com.android.internal.content.InstallLocationUtils;
import com.android.internal.content.om.OverlayConfig;
+import com.android.internal.pm.parsing.PackageParser2;
import com.android.internal.pm.parsing.pkg.AndroidPackageInternal;
import com.android.internal.pm.parsing.pkg.ParsedPackage;
import com.android.internal.pm.pkg.component.ParsedInstrumentation;
@@ -221,8 +222,8 @@
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.DynamicCodeLogger;
import com.android.server.pm.local.PackageManagerLocalImpl;
+import com.android.server.pm.parsing.PackageCacher;
import com.android.server.pm.parsing.PackageInfoUtils;
-import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
import com.android.server.pm.permission.LegacyPermissionManagerService;
@@ -1698,7 +1699,7 @@
() -> LocalServices.getService(UserManagerInternal.class)),
(i, pm) -> new DisplayMetrics(),
(i, pm) -> new PackageParser2(pm.mSeparateProcesses, i.getDisplayMetrics(),
- pm.mCacheDir,
+ new PackageCacher(pm.mCacheDir),
pm.mPackageParserCallback) /* scanningCachingPackageParserProducer */,
(i, pm) -> new PackageParser2(pm.mSeparateProcesses, i.getDisplayMetrics(), null,
pm.mPackageParserCallback) /* scanningPackageParserProducer */,
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
index ebf1c04..049737d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
@@ -27,12 +27,12 @@
import android.util.DisplayMetrics;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.parsing.PackageParser2;
import com.android.server.SystemConfig;
import com.android.server.compat.PlatformCompat;
import com.android.server.pm.dex.ArtManagerService;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.DynamicCodeLogger;
-import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.pm.resolution.ComponentResolver;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index 655b9c9..86d78dc 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -31,10 +31,10 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.om.OverlayConfig;
+import com.android.internal.pm.parsing.PackageParser2;
import com.android.server.pm.dex.ArtManagerService;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.DynamicCodeLogger;
-import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
import com.android.server.pm.pkg.AndroidPackage;
diff --git a/services/core/java/com/android/server/pm/PackageSessionVerifier.java b/services/core/java/com/android/server/pm/PackageSessionVerifier.java
index 15d2fdc..1fe49c7 100644
--- a/services/core/java/com/android/server/pm/PackageSessionVerifier.java
+++ b/services/core/java/com/android/server/pm/PackageSessionVerifier.java
@@ -41,10 +41,11 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.InstallLocationUtils;
+import com.android.internal.pm.parsing.PackageParser2;
+import com.android.internal.pm.parsing.PackageParserException;
import com.android.internal.pm.parsing.pkg.ParsedPackage;
import com.android.server.LocalServices;
import com.android.server.SystemConfig;
-import com.android.server.pm.parsing.PackageParser2;
import com.android.server.rollback.RollbackManagerInternal;
import java.io.File;
@@ -399,7 +400,7 @@
try (PackageParser2 packageParser = mPackageParserSupplier.get()) {
File apexFile = new File(apexInfo.modulePath);
parsedPackage = packageParser.parsePackage(apexFile, 0, false);
- } catch (PackageManagerException e) {
+ } catch (PackageParserException e) {
throw new PackageManagerException(
PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
"Failed to parse APEX package " + apexInfo.modulePath + " : " + e, e);
diff --git a/services/core/java/com/android/server/pm/ParallelPackageParser.java b/services/core/java/com/android/server/pm/ParallelPackageParser.java
index 1089ac9..0511639 100644
--- a/services/core/java/com/android/server/pm/ParallelPackageParser.java
+++ b/services/core/java/com/android/server/pm/ParallelPackageParser.java
@@ -22,9 +22,10 @@
import android.os.Trace;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.parsing.PackageParser2;
+import com.android.internal.pm.parsing.PackageParserException;
import com.android.internal.pm.parsing.pkg.ParsedPackage;
import com.android.internal.util.ConcurrentUtils;
-import com.android.server.pm.parsing.PackageParser2;
import java.io.File;
import java.util.concurrent.ArrayBlockingQueue;
@@ -125,6 +126,10 @@
@VisibleForTesting
protected ParsedPackage parsePackage(File scanFile, int parseFlags)
throws PackageManagerException {
- return mPackageParser.parsePackage(scanFile, parseFlags, true);
+ try {
+ return mPackageParser.parsePackage(scanFile, parseFlags, true);
+ } catch (PackageParserException e) {
+ throw new PackageManagerException(e.error, e.getMessage(), e);
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index c48eccf..2305d6c 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -7237,7 +7237,8 @@
@Override
public boolean isUserInitialized(@UserIdInt int userId) {
- return (getUserInfo(userId).flags & UserInfo.FLAG_INITIALIZED) != 0;
+ final UserInfo userInfo = getUserInfo(userId);
+ return userInfo != null && (userInfo.flags & UserInfo.FLAG_INITIALIZED) != 0;
}
@Override
diff --git a/services/core/java/com/android/server/pm/parsing/PackageCacher.java b/services/core/java/com/android/server/pm/parsing/PackageCacher.java
index 79c9c8e..b6267c4 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageCacher.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageCacher.java
@@ -28,6 +28,7 @@
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.parsing.IPackageCacher;
import com.android.internal.pm.parsing.pkg.PackageImpl;
import com.android.internal.pm.parsing.pkg.ParsedPackage;
import com.android.server.pm.ApexManager;
@@ -39,7 +40,7 @@
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
-public class PackageCacher {
+public class PackageCacher implements IPackageCacher {
private static final String TAG = "PackageCacher";
@@ -162,6 +163,7 @@
* Returns the cached parse result for {@code packageFile} for parse flags {@code flags},
* or {@code null} if no cached result exists.
*/
+ @Override
public ParsedPackage getCachedResult(File packageFile, int flags) {
final String cacheKey = getCacheKey(packageFile, flags);
final File cacheFile = new File(mCacheDir, cacheKey);
@@ -192,6 +194,7 @@
/**
* Caches the parse result for {@code packageFile} with flags {@code flags}.
*/
+ @Override
public void cacheResult(File packageFile, int flags, ParsedPackage parsed) {
try {
final String cacheKey = getCacheKey(packageFile, flags);
diff --git a/services/core/java/com/android/server/pm/parsing/PackageParserUtils.java b/services/core/java/com/android/server/pm/parsing/PackageParserUtils.java
new file mode 100644
index 0000000..03a7a37
--- /dev/null
+++ b/services/core/java/com/android/server/pm/parsing/PackageParserUtils.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.pm.parsing;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.ServiceManager;
+import android.util.Slog;
+
+import com.android.internal.compat.IPlatformCompat;
+import com.android.internal.pm.parsing.PackageParser2;
+import com.android.internal.pm.pkg.parsing.ParsingUtils;
+import com.android.server.SystemConfig;
+import com.android.server.pm.PackageManagerService;
+
+import java.util.Set;
+
+public class PackageParserUtils {
+ /**
+ * For parsing inside the system server but outside of {@link PackageManagerService}.
+ * Generally used for parsing information in an APK that hasn't been installed yet.
+ *
+ * This must be called inside the system process as it relies on {@link ServiceManager}.
+ */
+ @NonNull
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ public static PackageParser2 forParsingFileWithDefaults() {
+ IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
+ ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+ return new PackageParser2(null /* separateProcesses */, null /* displayMetrics */,
+ null /* cacheDir */, new PackageParser2.Callback() {
+ @Override
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ public boolean isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo) {
+ try {
+ return platformCompat.isChangeEnabled(changeId, appInfo);
+ } catch (Exception e) {
+ // This shouldn't happen, but assume enforcement if it does
+ Slog.wtf(ParsingUtils.TAG, "IPlatformCompat query failed", e);
+ return true;
+ }
+ }
+
+ @Override
+ public boolean hasFeature(String feature) {
+ // Assume the device doesn't support anything. This will affect permission parsing
+ // and will force <uses-permission/> declarations to include all requiredNotFeature
+ // permissions and exclude all requiredFeature permissions. This mirrors the old
+ // behavior.
+ return false;
+ }
+
+ @Override
+ public Set<String> getHiddenApiWhitelistedApps() {
+ return SystemConfig.getInstance().getHiddenApiWhitelistedApps();
+ }
+
+ @Override
+ public Set<String> getInstallConstraintsAllowlist() {
+ return SystemConfig.getInstance().getInstallConstraintsAllowlist();
+ }
+ });
+ }
+}
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 671e031..3afba39 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -3291,7 +3291,7 @@
final PermissionAllowlist permissionAllowlist =
SystemConfig.getInstance().getPermissionAllowlist();
final String packageName = packageState.getPackageName();
- if (packageState.isVendor()) {
+ if (packageState.isVendor() || packageState.isOdm()) {
return permissionAllowlist.getVendorPrivilegedAppAllowlistState(packageName,
permissionName);
} else if (packageState.isProduct()) {
@@ -3386,7 +3386,7 @@
// the permission's protectionLevel does not have the extra 'vendorPrivileged'
// flag.
if (allowed && isPrivilegedPermission && !bp.isVendorPrivileged()
- && pkgSetting.isVendor()) {
+ && (pkgSetting.isVendor() || pkgSetting.isOdm())) {
Slog.w(TAG, "Permission " + permissionName
+ " cannot be granted to privileged vendor apk " + pkg.getPackageName()
+ " because it isn't a 'vendorPrivileged' permission.");
diff --git a/services/core/java/com/android/server/policy/DeferredKeyActionExecutor.java b/services/core/java/com/android/server/policy/DeferredKeyActionExecutor.java
index b531b0e..611e4ed 100644
--- a/services/core/java/com/android/server/policy/DeferredKeyActionExecutor.java
+++ b/services/core/java/com/android/server/policy/DeferredKeyActionExecutor.java
@@ -76,6 +76,18 @@
getActionsBufferWithLazyCleanUp(keyCode, downTime).setExecutable();
}
+ /**
+ * Clears all the queued action for given key code.
+ *
+ * @param keyCode the key code whose queued actions will be cleared.
+ */
+ public void cancelQueuedAction(int keyCode) {
+ TimedActionsBuffer actionsBuffer = mBuffers.get(keyCode);
+ if (actionsBuffer != null) {
+ actionsBuffer.clear();
+ }
+ }
+
private TimedActionsBuffer getActionsBufferWithLazyCleanUp(int keyCode, long downTime) {
TimedActionsBuffer buffer = mBuffers.get(keyCode);
if (buffer == null || buffer.getDownTime() != downTime) {
@@ -146,6 +158,10 @@
mActions.clear();
}
+ void clear() {
+ mActions.clear();
+ }
+
void dump(String prefix, PrintWriter pw) {
if (mExecutable) {
pw.println(prefix + " " + KeyEvent.keyCodeToString(mKeyCode) + ": executable");
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 938ed23..3000a1c 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -103,6 +103,7 @@
import android.app.ActivityManager.RecentTaskInfo;
import android.app.ActivityManagerInternal;
import android.app.ActivityTaskManager;
+import android.app.ActivityTaskManager.RootTaskInfo;
import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.app.IUiModeManager;
@@ -584,6 +585,10 @@
private int mLongPressOnStemPrimaryBehavior;
private RecentTaskInfo mBackgroundRecentTaskInfoOnStemPrimarySingleKeyUp;
+ // The focused task at the time when the first STEM_PRIMARY key was released. This can only
+ // be accessed from the looper thread.
+ private RootTaskInfo mFocusedTaskInfoOnStemPrimarySingleKeyUp;
+
private boolean mHandleVolumeKeysInWM;
private boolean mPendingKeyguardOccluded;
@@ -2135,12 +2140,10 @@
static class Injector {
private final Context mContext;
private final WindowManagerFuncs mWindowManagerFuncs;
- private final Looper mLooper;
- Injector(Context context, WindowManagerFuncs funcs, Looper looper) {
+ Injector(Context context, WindowManagerFuncs funcs) {
mContext = context;
mWindowManagerFuncs = funcs;
- mLooper = looper;
}
Context getContext() {
@@ -2152,7 +2155,7 @@
}
Looper getLooper() {
- return mLooper;
+ return Looper.myLooper();
}
AccessibilityShortcutController getAccessibilityShortcutController(
@@ -2195,7 +2198,7 @@
/** {@inheritDoc} */
@Override
public void init(Context context, WindowManagerFuncs funcs) {
- init(new Injector(context, funcs, Looper.myLooper()));
+ init(new Injector(context, funcs));
}
@VisibleForTesting
@@ -2723,7 +2726,7 @@
@Override
void onPress(long downTime, int unusedDisplayId) {
- if (mShouldEarlyShortPressOnStemPrimary) {
+ if (shouldHandleStemPrimaryEarlyShortPress()) {
return;
}
// Short-press should be triggered only if app doesn't handle it.
@@ -2747,6 +2750,11 @@
if (count == 3
&& mTriplePressOnStemPrimaryBehavior
== TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY) {
+ // Cancel any queued actions for current key code to prevent them from being
+ // launched after a11y layer enabled. If the action happens early,
+ // undoEarlySinglePress will make sure the correct task is on top.
+ mDeferredKeyActionExecutor.cancelQueuedAction(KeyEvent.KEYCODE_STEM_PRIMARY);
+ undoEarlySinglePress();
stemPrimaryPress(count);
} else {
// Other multi-press gestures should be triggered only if app doesn't handle it.
@@ -2755,6 +2763,27 @@
}
}
+ /**
+ * This method undo the previously launched early-single-press action by bringing the
+ * focused task before launching early-single-press back to top.
+ */
+ private void undoEarlySinglePress() {
+ if (shouldHandleStemPrimaryEarlyShortPress()
+ && mFocusedTaskInfoOnStemPrimarySingleKeyUp != null) {
+ try {
+ mActivityManagerService.startActivityFromRecents(
+ mFocusedTaskInfoOnStemPrimarySingleKeyUp.taskId, null);
+ } catch (RemoteException | IllegalArgumentException e) {
+ Slog.e(
+ TAG,
+ "Failed to start task "
+ + mFocusedTaskInfoOnStemPrimarySingleKeyUp.taskId
+ + " from recents",
+ e);
+ }
+ }
+ }
+
@Override
void onKeyUp(long eventTime, int count, int unusedDisplayId) {
if (count == 1) {
@@ -2763,15 +2792,49 @@
// It is possible that we may navigate away from this task before the double
// press is detected, as a result of the first press, so we save the current
// most recent task before that happens.
+ // TODO(b/311497918): guard this with DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP
mBackgroundRecentTaskInfoOnStemPrimarySingleKeyUp =
mActivityTaskManagerInternal.getMostRecentTaskFromBackground();
- if (mShouldEarlyShortPressOnStemPrimary) {
+
+ mFocusedTaskInfoOnStemPrimarySingleKeyUp = null;
+
+ if (shouldHandleStemPrimaryEarlyShortPress()) {
// Key-up gesture should be triggered only if app doesn't handle it.
mDeferredKeyActionExecutor.queueKeyAction(
- KeyEvent.KEYCODE_STEM_PRIMARY, eventTime, () -> stemPrimaryPress(1));
+ KeyEvent.KEYCODE_STEM_PRIMARY,
+ eventTime,
+ () -> {
+ // Save the info of the focused task on screen. This may be used
+ // later to bring the current focused task back to top. For
+ // example, stem primary triple press enables the A11y interface
+ // on top of the current focused task. When early single press is
+ // enabled for stem primary, the focused task could change to
+ // something else upon first key up event. In that case, we will
+ // bring the task recorded by this variable back to top. Then, start
+ // A11y interface.
+ try {
+ mFocusedTaskInfoOnStemPrimarySingleKeyUp =
+ mActivityManagerService.getFocusedRootTaskInfo();
+ } catch (RemoteException e) {
+ Slog.e(
+ TAG,
+ "StemPrimaryKeyRule: onKeyUp: error while getting "
+ + "focused task "
+ + "info.",
+ e);
+ }
+
+ stemPrimaryPress(1);
+ });
}
}
}
+
+ // TODO(b/311497918): make a shouldHandlePowerEarlyShortPress for power button.
+ private boolean shouldHandleStemPrimaryEarlyShortPress() {
+ return mShouldEarlyShortPressOnStemPrimary
+ && mShortPressOnStemPrimaryBehavior == SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS;
+ }
}
private void initSingleKeyGestureRules(Looper looper) {
@@ -6865,13 +6928,13 @@
static class ButtonOverridePermissionChecker {
boolean canAppOverrideSystemKey(Context context, int uid) {
return PermissionChecker.checkPermissionForDataDelivery(
- context,
- OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW,
- PID_UNKNOWN,
- uid,
- null,
- null,
- null)
+ context,
+ OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW,
+ PID_UNKNOWN,
+ uid,
+ null,
+ null,
+ null)
== PERMISSION_GRANTED;
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index f3922f9..145eb3b 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -125,6 +125,7 @@
import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
import static android.view.WindowManager.TRANSIT_OLD_UNSET;
import static android.view.WindowManager.TRANSIT_RELAUNCH;
+import static android.window.TransitionInfo.FLAGS_IS_OCCLUDED_NO_ANIMATION;
import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
@@ -565,7 +566,6 @@
boolean idle; // has the activity gone idle?
boolean hasBeenLaunched;// has this activity ever been launched?
boolean immersive; // immersive mode (don't interrupt if possible)
- boolean forceNewConfig; // force re-create with new config next time
boolean supportsEnterPipOnTaskSwitch; // This flag is set by the system to indicate that the
// activity can enter picture in picture while pausing (only when switching to another task)
// The PiP params used when deferring the entering of picture-in-picture.
@@ -684,6 +684,7 @@
private final WindowState.UpdateReportedVisibilityResults mReportedVisibilityResults =
new WindowState.UpdateReportedVisibilityResults();
+ // TODO(b/317000737): Replace it with visibility states lookup.
int mTransitionChangeFlags;
/** Whether we need to setup the animation to animate only within the letterbox. */
@@ -5469,8 +5470,13 @@
// Defer committing visibility until transition starts.
if (isCollecting) {
// It may be occluded by the activity above that calls convertFromTranslucent().
- if (!visible && mTransitionController.inPlayingTransition(this)) {
- mTransitionChangeFlags |= FLAG_IS_OCCLUDED;
+ // Or it may be restoring transient launch to invisible when finishing transition.
+ if (!visible) {
+ if (mTransitionController.inPlayingTransition(this)) {
+ mTransitionChangeFlags |= FLAG_IS_OCCLUDED;
+ } else if (mTransitionController.inFinishingTransition(this)) {
+ mTransitionChangeFlags |= FLAGS_IS_OCCLUDED_NO_ANIMATION;
+ }
}
return;
}
@@ -9600,7 +9606,7 @@
// configurations because there are cases (like moving a task to the root pinned task) where
// the combine configurations are equal, but would otherwise differ in the override config
mTmpConfig.setTo(mLastReportedConfiguration.getMergedConfiguration());
- if (getConfiguration().equals(mTmpConfig) && !forceNewConfig && !displayChanged) {
+ if (getConfiguration().equals(mTmpConfig) && !displayChanged) {
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration & display "
+ "unchanged in %s", this);
return true;
@@ -9627,7 +9633,7 @@
return true;
}
- if (changes == 0 && !forceNewConfig) {
+ if (changes == 0) {
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration no differences in %s",
this);
// There are no significant differences, so we won't relaunch but should still deliver
@@ -9649,7 +9655,6 @@
// pick that up next time it starts.
if (!attachedToProcess()) {
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration doesn't matter not running %s", this);
- forceNewConfig = false;
return true;
}
@@ -9659,11 +9664,10 @@
Integer.toHexString(changes), Integer.toHexString(info.getRealConfigChanged()),
mLastReportedConfiguration);
- if (shouldRelaunchLocked(changes, mTmpConfig) || forceNewConfig) {
+ if (shouldRelaunchLocked(changes, mTmpConfig)) {
// Aha, the activity isn't handling the change, so DIE DIE DIE.
configChangeFlags |= changes;
startFreezingScreenLocked(globalChanges);
- forceNewConfig = false;
// Do not preserve window if it is freezing screen because the original window won't be
// able to update drawn state that causes freeze timeout.
preserveWindow &= isResizeOnlyChange(changes) && !mFreezingScreen;
@@ -9883,7 +9887,6 @@
try {
ProtoLog.i(WM_DEBUG_STATES, "Moving to %s Relaunching %s callers=%s" ,
(andResume ? "RESUMED" : "PAUSED"), this, Debug.getCallers(6));
- forceNewConfig = false;
final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(token,
pendingResults, pendingNewIntents, configChangeFlags,
new MergedConfiguration(getProcessGlobalConfiguration(),
diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
index 86be6ba..7af494c 100644
--- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java
+++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
@@ -33,6 +33,7 @@
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider;
+import com.android.window.flags.Flags;
import java.io.File;
import java.util.ArrayList;
@@ -121,7 +122,8 @@
// TODO remove when enabled
static boolean isSnapshotEnabled() {
- return SystemProperties.getInt("persist.wm.debug.activity_screenshot", 0) != 0;
+ return SystemProperties.getInt("persist.wm.debug.activity_screenshot", 0) != 0
+ || Flags.activitySnapshotByDefault();
}
static PersistInfoProvider createPersistInfoProvider(
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index 1b45c1b..e7621ff 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -224,7 +224,7 @@
// before issuing the work challenge.
return true;
}
- if (interceptLockedManagedProfileIfNeeded()) {
+ if (interceptLockedProfileIfNeeded()) {
return true;
}
if (interceptHomeIfNeeded()) {
@@ -378,7 +378,7 @@
return true;
}
- private boolean interceptLockedManagedProfileIfNeeded() {
+ private boolean interceptLockedProfileIfNeeded() {
final Intent interceptingIntent = interceptWithConfirmCredentialsIfNeeded(mAInfo, mUserId);
if (interceptingIntent == null) {
return false;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 630b9e1..cb2adbc 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -579,30 +579,11 @@
computeResolveFilterUid(callingUid, realCallingUid, filterCallingUid),
realCallingPid);
if (resolveInfo == null) {
- final UserInfo userInfo = supervisor.getUserInfo(userId);
- if (userInfo != null && userInfo.isManagedProfile()) {
- // Special case for managed profiles, if attempting to launch non-cryto aware
- // app in a locked managed profile from an unlocked parent allow it to resolve
- // as user will be sent via confirm credentials to unlock the profile.
- final UserManager userManager = UserManager.get(supervisor.mService.mContext);
- boolean profileLockedAndParentUnlockingOrUnlocked = false;
- final long token = Binder.clearCallingIdentity();
- try {
- final UserInfo parent = userManager.getProfileParent(userId);
- profileLockedAndParentUnlockingOrUnlocked = (parent != null)
- && userManager.isUserUnlockingOrUnlocked(parent.id)
- && !userManager.isUserUnlockingOrUnlocked(userId);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- if (profileLockedAndParentUnlockingOrUnlocked) {
- resolveInfo = supervisor.resolveIntent(intent, resolvedType, userId,
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
- computeResolveFilterUid(callingUid, realCallingUid,
- filterCallingUid), realCallingPid);
- }
- }
+ // Special case for profiles: If attempting to launch non-crypto aware app in a
+ // locked profile or launch an app in a profile that is stopped by quiet mode from
+ // an unlocked parent, allow it to resolve as user will be sent via confirm
+ // credentials to unlock the profile.
+ resolveInfo = resolveIntentForLockedOrStoppedProfiles(supervisor);
}
// Collect information about the target of the Intent.
@@ -616,6 +597,36 @@
UserHandle.getUserId(activityInfo.applicationInfo.uid));
}
}
+
+ /**
+ * Resolve intent for locked or stopped profiles if the parent profile is unlocking or
+ * unlocked.
+ */
+ ResolveInfo resolveIntentForLockedOrStoppedProfiles(
+ ActivityTaskSupervisor supervisor) {
+ final UserInfo userInfo = supervisor.getUserInfo(userId);
+ if (userInfo != null && userInfo.isProfile()) {
+ final UserManager userManager = UserManager.get(supervisor.mService.mContext);
+ boolean profileLockedAndParentUnlockingOrUnlocked = false;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ final UserInfo parent = userManager.getProfileParent(userId);
+ profileLockedAndParentUnlockingOrUnlocked = (parent != null)
+ && userManager.isUserUnlockingOrUnlocked(parent.id)
+ && !userManager.isUserUnlockingOrUnlocked(userId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ if (profileLockedAndParentUnlockingOrUnlocked) {
+ return supervisor.resolveIntent(intent, resolvedType, userId,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+ computeResolveFilterUid(callingUid, realCallingUid,
+ filterCallingUid), realCallingPid);
+ }
+ }
+ return null;
+ }
}
ActivityStarter(ActivityStartController controller, ActivityTaskManagerService service,
@@ -2767,10 +2778,7 @@
}
}
- // If the target task is not in the front, then we need to bring it to the front...
- // except... well, with SINGLE_TASK_LAUNCH it's not entirely clear. We'd like to have
- // the same behavior as if a new instance was being started, which means not bringing it
- // to the front if the caller is not itself in the front.
+ // If the target task is not in the front, then we need to bring it to the front.
final boolean differentTopTask;
if (mTargetRootTask.getDisplayArea() == mPreferredTaskDisplayArea) {
final Task focusRootTask = mTargetRootTask.mDisplayContent.getFocusedRootTask();
@@ -2787,49 +2795,47 @@
if (differentTopTask && !avoidMoveToFront()) {
mStartActivity.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
- if (mSourceRecord == null || inTopNonFinishingTask(mSourceRecord)) {
- // We really do want to push this one into the user's face, right now.
- if (mLaunchTaskBehind && mSourceRecord != null) {
- intentActivity.setTaskToAffiliateWith(mSourceRecord.getTask());
- }
-
- if (intentActivity.isDescendantOf(mTargetRootTask)) {
- // TODO(b/151572268): Figure out a better way to move tasks in above 2-levels
- // tasks hierarchies.
- if (mTargetRootTask != intentTask
- && mTargetRootTask != intentTask.getParent().asTask()) {
- intentTask.getParent().positionChildAt(POSITION_TOP, intentTask,
- false /* includingParents */);
- intentTask = intentTask.getParent().asTaskFragment().getTask();
- }
- // If the activity is visible in multi-windowing mode, it may already be on
- // the top (visible to user but not the global top), then the result code
- // should be START_DELIVERED_TO_TOP instead of START_TASK_TO_FRONT.
- final boolean wasTopOfVisibleRootTask = intentActivity.isVisibleRequested()
- && intentActivity.inMultiWindowMode()
- && intentActivity == mTargetRootTask.topRunningActivity()
- && !intentActivity.mTransitionController.isTransientHide(
- mTargetRootTask);
- // We only want to move to the front, if we aren't going to launch on a
- // different root task. If we launch on a different root task, we will put the
- // task on top there.
- // Defer resuming the top activity while moving task to top, since the
- // current task-top activity may not be the activity that should be resumed.
- mTargetRootTask.moveTaskToFront(intentTask, mNoAnimation, mOptions,
- mStartActivity.appTimeTracker, DEFER_RESUME,
- "bringingFoundTaskToFront");
- mMovedToFront = !wasTopOfVisibleRootTask;
- } else if (intentActivity.getWindowingMode() != WINDOWING_MODE_PINNED) {
- // Leaves reparenting pinned task operations to task organizer to make sure it
- // dismisses pinned task properly.
- // TODO(b/199997762): Consider leaving all reparent operation of organized tasks
- // to task organizer.
- intentTask.reparent(mTargetRootTask, ON_TOP, REPARENT_MOVE_ROOT_TASK_TO_FRONT,
- ANIMATE, DEFER_RESUME, "reparentToTargetRootTask");
- mMovedToFront = true;
- }
- mOptions = null;
+ // We really do want to push this one into the user's face, right now.
+ if (mLaunchTaskBehind && mSourceRecord != null) {
+ intentActivity.setTaskToAffiliateWith(mSourceRecord.getTask());
}
+
+ if (intentActivity.isDescendantOf(mTargetRootTask)) {
+ // TODO(b/151572268): Figure out a better way to move tasks in above 2-levels
+ // tasks hierarchies.
+ if (mTargetRootTask != intentTask
+ && mTargetRootTask != intentTask.getParent().asTask()) {
+ intentTask.getParent().positionChildAt(POSITION_TOP, intentTask,
+ false /* includingParents */);
+ intentTask = intentTask.getParent().asTaskFragment().getTask();
+ }
+ // If the activity is visible in multi-windowing mode, it may already be on
+ // the top (visible to user but not the global top), then the result code
+ // should be START_DELIVERED_TO_TOP instead of START_TASK_TO_FRONT.
+ final boolean wasTopOfVisibleRootTask = intentActivity.isVisibleRequested()
+ && intentActivity.inMultiWindowMode()
+ && intentActivity == mTargetRootTask.topRunningActivity()
+ && !intentActivity.mTransitionController.isTransientHide(
+ mTargetRootTask);
+ // We only want to move to the front, if we aren't going to launch on a
+ // different root task. If we launch on a different root task, we will put the
+ // task on top there.
+ // Defer resuming the top activity while moving task to top, since the
+ // current task-top activity may not be the activity that should be resumed.
+ mTargetRootTask.moveTaskToFront(intentTask, mNoAnimation, mOptions,
+ mStartActivity.appTimeTracker, DEFER_RESUME,
+ "bringingFoundTaskToFront");
+ mMovedToFront = !wasTopOfVisibleRootTask;
+ } else if (intentActivity.getWindowingMode() != WINDOWING_MODE_PINNED) {
+ // Leaves reparenting pinned task operations to task organizer to make sure it
+ // dismisses pinned task properly.
+ // TODO(b/199997762): Consider leaving all reparent operation of organized tasks
+ // to task organizer.
+ intentTask.reparent(mTargetRootTask, ON_TOP, REPARENT_MOVE_ROOT_TASK_TO_FRONT,
+ ANIMATE, DEFER_RESUME, "reparentToTargetRootTask");
+ mMovedToFront = true;
+ }
+ mOptions = null;
}
if (differentTopTask) {
logPIOnlyCreatorAllowsBAL();
@@ -2850,20 +2856,6 @@
mRootWindowContainer.getDefaultTaskDisplayArea(), mTargetRootTask);
}
- private boolean inTopNonFinishingTask(ActivityRecord r) {
- if (r == null || r.getTask() == null) {
- return false;
- }
-
- final Task rTask = r.getTask();
- final Task parent = rTask.getCreatedByOrganizerTask() != null
- ? rTask.getCreatedByOrganizerTask() : r.getRootTask();
- final ActivityRecord topNonFinishingActivity = parent != null
- ? parent.getTopNonFinishingActivity() : null;
-
- return topNonFinishingActivity != null && topNonFinishingActivity.getTask() == rTask;
- }
-
private void resumeTargetRootTaskIfNeeded() {
if (mDoResume) {
final ActivityRecord next = mTargetRootTask.topRunningActivity(
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 908c49e..dbae29b 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -62,6 +62,7 @@
import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RTL;
import static android.provider.Settings.Global.HIDE_ERROR_DIALOGS;
import static android.provider.Settings.System.FONT_SCALE;
+import static android.service.controls.flags.Flags.homePanelDream;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.TRANSIT_CHANGE;
@@ -1501,14 +1502,19 @@
a.exported = true;
a.name = DreamActivity.class.getName();
a.enabled = true;
- a.launchMode = ActivityInfo.LAUNCH_SINGLE_INSTANCE;
a.persistableMode = ActivityInfo.PERSIST_NEVER;
a.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
a.colorMode = ActivityInfo.COLOR_MODE_DEFAULT;
a.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
- a.resizeMode = RESIZE_MODE_UNRESIZEABLE;
a.configChanges = 0xffffffff;
+ if (homePanelDream()) {
+ a.launchMode = ActivityInfo.LAUNCH_SINGLE_TASK;
+ } else {
+ a.resizeMode = RESIZE_MODE_UNRESIZEABLE;
+ a.launchMode = ActivityInfo.LAUNCH_SINGLE_INSTANCE;
+ }
+
final ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchActivityType(ACTIVITY_TYPE_DREAM);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index e59601c..10efb94 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -906,7 +906,6 @@
}
mService.getPackageManagerInternalLocked().notifyPackageUse(
r.intent.getComponent().getPackageName(), NOTIFY_PACKAGE_USE_ACTIVITY);
- r.forceNewConfig = false;
mService.getAppWarningsLocked().onStartActivity(r);
// Because we could be starting an Activity in the system process this may not go
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 4929df80..eed46fe 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -36,6 +36,7 @@
import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS;
import static java.lang.annotation.RetentionPolicy.SOURCE;
+import static java.util.Objects.requireNonNull;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -152,36 +153,25 @@
static final int BAL_ALLOW_SDK_SANDBOX = 10;
static String balCodeToString(@BalCode int balCode) {
- switch (balCode) {
- case BAL_ALLOW_ALLOWLISTED_COMPONENT:
- return "BAL_ALLOW_ALLOWLISTED_COMPONENT";
- case BAL_ALLOW_ALLOWLISTED_UID:
- return "BAL_ALLOW_ALLOWLISTED_UID";
- case BAL_ALLOW_DEFAULT:
- return "BAL_ALLOW_DEFAULT";
- case BAL_ALLOW_FOREGROUND:
- return "BAL_ALLOW_FOREGROUND";
- case BAL_ALLOW_GRACE_PERIOD:
- return "BAL_ALLOW_GRACE_PERIOD";
- case BAL_ALLOW_PENDING_INTENT:
- return "BAL_ALLOW_PENDING_INTENT";
- case BAL_ALLOW_PERMISSION:
- return "BAL_ALLOW_PERMISSION";
- case BAL_ALLOW_SAW_PERMISSION:
- return "BAL_ALLOW_SAW_PERMISSION";
- case BAL_ALLOW_SDK_SANDBOX:
- return "BAL_ALLOW_SDK_SANDBOX";
- case BAL_ALLOW_VISIBLE_WINDOW:
- return "BAL_ALLOW_VISIBLE_WINDOW";
- case BAL_BLOCK:
- return "BAL_BLOCK";
- default:
- throw new IllegalArgumentException("Unexpected value: " + balCode);
- }
+ return switch (balCode) {
+ case BAL_ALLOW_ALLOWLISTED_COMPONENT -> "BAL_ALLOW_ALLOWLISTED_COMPONENT";
+ case BAL_ALLOW_ALLOWLISTED_UID -> "BAL_ALLOW_ALLOWLISTED_UID";
+ 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_PENDING_INTENT -> "BAL_ALLOW_PENDING_INTENT";
+ case BAL_ALLOW_PERMISSION -> "BAL_ALLOW_PERMISSION";
+ case BAL_ALLOW_SAW_PERMISSION -> "BAL_ALLOW_SAW_PERMISSION";
+ case BAL_ALLOW_SDK_SANDBOX -> "BAL_ALLOW_SDK_SANDBOX";
+ case BAL_ALLOW_VISIBLE_WINDOW -> "BAL_ALLOW_VISIBLE_WINDOW";
+ case BAL_BLOCK -> "BAL_BLOCK";
+ default -> throw new IllegalArgumentException("Unexpected value: " + balCode);
+ };
}
@GuardedBy("mService.mGlobalLock")
- private HashMap<Integer, FinishedActivityEntry> mTaskIdToFinishedActivity = new HashMap<>();
+ private final HashMap<Integer, FinishedActivityEntry> mTaskIdToFinishedActivity =
+ new HashMap<>();
@GuardedBy("mService.mGlobalLock")
private FinishedActivityEntry mTopFinishedActivity = null;
@@ -467,9 +457,8 @@
return !blocks();
}
- BalVerdict setOnlyCreatorAllows(boolean onlyCreatorAllows) {
+ void setOnlyCreatorAllows(boolean onlyCreatorAllows) {
mOnlyCreatorAllows = onlyCreatorAllows;
- return this;
}
boolean onlyCreatorAllows() {
@@ -481,10 +470,6 @@
return this;
}
- private boolean isBasedOnRealCaller() {
- return mBasedOnRealCaller;
- }
-
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(balCodeToString(mCode));
@@ -583,15 +568,14 @@
BalVerdict resultForCaller = checkBackgroundActivityStartAllowedByCaller(state);
if (!state.hasRealCaller()) {
- BalVerdict resultForRealCaller = null; // nothing to compute
if (resultForCaller.allows()) {
if (DEBUG_ACTIVITY_STARTS) {
Slog.d(TAG, "Background activity start allowed. "
- + state.dump(resultForCaller, resultForRealCaller));
+ + state.dump(resultForCaller, resultForCaller));
}
return statsLog(resultForCaller, state);
}
- return abortLaunch(state, resultForCaller, resultForRealCaller);
+ return abortLaunch(state, resultForCaller, resultForCaller);
}
// The realCaller result is only calculated for PendingIntents (indicated by a valid
@@ -653,7 +637,7 @@
+ " if the PI creator upgrades target_sdk to 35+"
+ " AND the PI sender upgrades target_sdk to 34+! "
+ state.dump(resultForCaller, resultForRealCaller));
- showBalRiskToast("BAL would be blocked", state);
+ showBalRiskToast();
// return the realCaller result for backwards compatibility
return statsLog(resultForRealCaller, state);
}
@@ -679,7 +663,7 @@
+ " if the PI creator upgrades target_sdk to 35+! "
+ " (missing opt in by PI creator)! "
+ state.dump(resultForCaller, resultForRealCaller));
- showBalRiskToast("BAL would be blocked", state);
+ showBalRiskToast();
return statsLog(resultForCaller, state);
}
Slog.wtf(TAG,
@@ -696,7 +680,7 @@
+ " if the PI sender upgrades target_sdk to 34+! "
+ " (missing opt in by PI sender)! "
+ state.dump(resultForCaller, resultForRealCaller));
- showBalRiskToast("BAL would be blocked", state);
+ showBalRiskToast();
return statsLog(resultForRealCaller, state);
}
Slog.wtf(TAG, "Without Android 14 BAL hardening this activity start would be allowed"
@@ -712,7 +696,7 @@
BalVerdict resultForRealCaller) {
Slog.w(TAG, "Background activity launch blocked! "
+ state.dump(resultForCaller, resultForRealCaller));
- showBalBlockedToast("BAL blocked", state);
+ showBalBlockedToast();
return statsLog(BalVerdict.BLOCK, state);
}
@@ -910,7 +894,7 @@
/**
* Check if the app allows BAL.
- *
+ * <p>
* See {@link BackgroundLaunchProcessController#areBackgroundActivityStartsAllowed(int, int,
* String, int, boolean, boolean, boolean, long, long, long)} for details on the
* exceptions.
@@ -1104,19 +1088,15 @@
return true;
}
- private void showBalBlockedToast(String toastText, BalState state) {
+ private void showBalBlockedToast() {
if (balShowToastsBlocked()) {
- showToast(toastText
- + " caller:" + state.mCallingPackage
- + " realCaller:" + state.mRealCallingPackage);
+ showToast("BAL blocked. go/debug-bal");
}
}
- private void showBalRiskToast(String toastText, BalState state) {
+ private void showBalRiskToast() {
if (balShowToasts()) {
- showToast(toastText
- + " caller:" + state.mCallingPackage
- + " realCaller:" + state.mRealCallingPackage);
+ showToast("BAL allowed in compat mode. go/debug-bal");
}
}
@@ -1281,7 +1261,7 @@
* 2. Or top of an adjacent task fragment to (1)
* <p>
* The 'sourceRecord' can be considered top even if it is 'finishing'
- *
+ * <p>
* Returns a class where the elements are:
* <pre>
* shouldBlockActivityStart: {@code true} if we should actually block the transition (takes into
@@ -1344,7 +1324,7 @@
/**
* Determines if a source is allowed to add or remove activities from the task,
* if the current ActivityRecord is above it in the stack
- *
+ * <p>
* A transition is blocked ({@code false} returned) if all of the following are met:
* <pre>
* 1. The source activity and the current activity record belong to different apps
@@ -1489,8 +1469,8 @@
if (code == BAL_ALLOW_PENDING_INTENT
&& (callingUid == Process.SYSTEM_UID || realCallingUid == Process.SYSTEM_UID)) {
- String activityName =
- intent != null ? intent.getComponent().flattenToShortString() : "";
+ String activityName = intent != null
+ ? requireNonNull(intent.getComponent()).flattenToShortString() : "";
FrameworkStatsLog.write(FrameworkStatsLog.BAL_ALLOWED,
activityName,
BAL_ALLOW_PENDING_INTENT,
diff --git a/services/core/java/com/android/server/wm/CompatModePackages.java b/services/core/java/com/android/server/wm/CompatModePackages.java
index 73bcc8d..1a8927e 100644
--- a/services/core/java/com/android/server/wm/CompatModePackages.java
+++ b/services/core/java/com/android/server/wm/CompatModePackages.java
@@ -19,7 +19,6 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.CompatScaleProvider.COMPAT_SCALE_MODE_SYSTEM_FIRST;
import static com.android.server.wm.CompatScaleProvider.COMPAT_SCALE_MODE_SYSTEM_LAST;
@@ -47,6 +46,7 @@
import android.util.DisplayMetrics;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.util.Xml;
import com.android.internal.protolog.common.ProtoLog;
@@ -60,6 +60,7 @@
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
@@ -347,6 +348,7 @@
private GameManagerInternal mGameManager;
private final AtomicFile mFile;
private final HashMap<String, Integer> mPackages = new HashMap<>();
+ private final SparseBooleanArray mLegacyScreenCompatPackages = new SparseBooleanArray();
private final CompatHandler mHandler;
private final SparseArray<CompatScaleProvider> mProviders = new SparseArray<>();
@@ -427,6 +429,7 @@
mPackages.remove(packageName);
scheduleWrite();
}
+ mLegacyScreenCompatPackages.delete(packageName.hashCode());
}
public void handlePackageAddedLocked(String packageName, boolean updated) {
@@ -458,6 +461,17 @@
mHandler.sendMessageDelayed(msg, 10000);
}
+ /**
+ * Returns {@code true} if the windows belonging to the package should be scaled with
+ * {@link DisplayContent#mCompatibleScreenScale}.
+ */
+ boolean useLegacyScreenCompatMode(String packageName) {
+ if (mLegacyScreenCompatPackages.size() == 0) {
+ return false;
+ }
+ return mLegacyScreenCompatPackages.get(packageName.hashCode());
+ }
+
public CompatibilityInfo compatibilityInfoForPackageLocked(ApplicationInfo ai) {
final boolean forceCompat = getPackageCompatModeEnabledLocked(ai);
final CompatScale compatScale = getCompatScaleFromProvider(ai.packageName, ai.uid);
@@ -466,8 +480,18 @@
: getCompatScale(ai.packageName, ai.uid, /* checkProvider= */ false);
final float densityScale = compatScale != null ? compatScale.mDensityScaleFactor : appScale;
final Configuration config = mService.getGlobalConfiguration();
- return new CompatibilityInfo(ai, config.screenLayout, config.smallestScreenWidthDp,
- forceCompat, appScale, densityScale);
+ final CompatibilityInfo info = new CompatibilityInfo(ai, config.screenLayout,
+ config.smallestScreenWidthDp, forceCompat, appScale, densityScale);
+ // Ignore invalid info which may be a placeholder of isolated process.
+ if (ai.flags != 0 && ai.sourceDir != null) {
+ if (!info.supportsScreen() && !"android".equals(ai.packageName)) {
+ Slog.i(TAG, "Use legacy screen compat mode: " + ai.packageName);
+ mLegacyScreenCompatPackages.put(ai.packageName.hashCode(), true);
+ } else if (mLegacyScreenCompatPackages.size() > 0) {
+ mLegacyScreenCompatPackages.delete(ai.packageName.hashCode());
+ }
+ }
+ return info;
}
float getCompatScale(String packageName, int uid) {
@@ -718,14 +742,23 @@
scheduleWrite();
- final Task rootTask = mService.getTopDisplayFocusedRootTask();
- ActivityRecord starting = rootTask.restartPackage(packageName);
-
+ final ArrayList<WindowProcessController> restartedApps = new ArrayList<>();
+ mService.mRootWindowContainer.forAllWindows(w -> {
+ final ActivityRecord ar = w.mActivityRecord;
+ if (ar != null) {
+ if (ar.packageName.equals(packageName) && !restartedApps.contains(ar.app)) {
+ ar.restartProcessIfVisible();
+ restartedApps.add(ar.app);
+ }
+ } else if (w.getProcess().mInfo.packageName.equals(packageName)) {
+ w.updateGlobalScale();
+ }
+ }, true /* traverseTopToBottom */);
// Tell all processes that loaded this package about the change.
SparseArray<WindowProcessController> pidMap = mService.mProcessMap.getPidMap();
for (int i = pidMap.size() - 1; i >= 0; i--) {
final WindowProcessController app = pidMap.valueAt(i);
- if (!app.containsPackage(packageName)) {
+ if (!app.containsPackage(packageName) || restartedApps.contains(app)) {
continue;
}
try {
@@ -737,14 +770,6 @@
} catch (Exception e) {
}
}
-
- if (starting != null) {
- starting.ensureActivityConfiguration(0 /* globalChanges */,
- false /* preserveWindow */);
- // And we need to make sure at this point that all other activities
- // are made visible with the correct configuration.
- rootTask.ensureActivitiesVisible(starting, 0, !PRESERVE_WINDOWS);
- }
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ae10ce3..f8dc9c7 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -512,7 +512,11 @@
*/
private final DisplayMetrics mCompatDisplayMetrics = new DisplayMetrics();
- /** The desired scaling factor for compatible apps. */
+ /**
+ * The desired scaling factor for compatible apps. It limits the size of the window to be
+ * original size ([320x480] x density). Used to scale window for applications running under
+ * legacy compatibility mode.
+ */
float mCompatibleScreenScale;
/** @see #getCurrentOverrideConfigurationChanges */
@@ -4794,7 +4798,8 @@
assignRelativeLayerForIme(getSyncTransaction(), true /* forceUpdate */);
scheduleAnimation();
- mWmService.mH.post(() -> InputMethodManagerInternal.get().onImeParentChanged());
+ mWmService.mH.post(
+ () -> InputMethodManagerInternal.get().onImeParentChanged(getDisplayId()));
} else if (mImeControlTarget != null && mImeControlTarget == mImeLayeringTarget) {
// Even if the IME surface parent is not changed, the layer target belonging to the
// parent may have changes. Then attempt to reassign if the IME control target is
@@ -7090,7 +7095,7 @@
}
@Override
- public void notifyInsetsControlChanged() {
+ public void notifyInsetsControlChanged(int displayId) {
final InsetsStateController stateController = getInsetsStateController();
try {
mRemoteInsetsController.insetsControlChanged(stateController.getRawInsetsState(),
diff --git a/services/core/java/com/android/server/wm/InsetsControlTarget.java b/services/core/java/com/android/server/wm/InsetsControlTarget.java
index 8ecbc17..b74eb56 100644
--- a/services/core/java/com/android/server/wm/InsetsControlTarget.java
+++ b/services/core/java/com/android/server/wm/InsetsControlTarget.java
@@ -29,8 +29,10 @@
/**
* Notifies the control target that the insets control has changed.
+ *
+ * @param displayId the display hosting the window of this target
*/
- default void notifyInsetsControlChanged() {
+ default void notifyInsetsControlChanged(int displayId) {
};
/**
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 7815679..3c556bf 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -728,7 +728,7 @@
}
@Override
- public void notifyInsetsControlChanged() {
+ public void notifyInsetsControlChanged(int displayId) {
mHandler.post(this);
}
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index c4d0129..6b9fcf4 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -72,7 +72,7 @@
};
private final InsetsControlTarget mEmptyImeControlTarget = new InsetsControlTarget() {
@Override
- public void notifyInsetsControlChanged() {
+ public void notifyInsetsControlChanged(int displayId) {
InsetsSourceControl[] controls = getControlsForDispatch(this);
if (controls == null) {
return;
@@ -80,7 +80,7 @@
for (InsetsSourceControl control : controls) {
if (control.getType() == WindowInsets.Type.ime()) {
mDisplayContent.mWmService.mH.post(() ->
- InputMethodManagerInternal.get().removeImeSurface());
+ InputMethodManagerInternal.get().removeImeSurface(displayId));
}
}
}
@@ -370,9 +370,10 @@
provider.onSurfaceTransactionApplied();
}
final ArraySet<InsetsControlTarget> newControlTargets = new ArraySet<>();
+ int displayId = mDisplayContent.getDisplayId();
for (int i = mPendingControlChanged.size() - 1; i >= 0; i--) {
final InsetsControlTarget controlTarget = mPendingControlChanged.valueAt(i);
- controlTarget.notifyInsetsControlChanged();
+ controlTarget.notifyInsetsControlChanged(displayId);
if (mControlTargetProvidersMap.containsKey(controlTarget)) {
// We only collect targets who get controls, not lose controls.
newControlTargets.add(controlTarget);
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 5518de7..9305396 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -20,6 +20,7 @@
import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP;
import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP;
import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION;
+import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION_TO_USER;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
@@ -170,6 +171,8 @@
// Corresponds to OVERRIDE_ANY_ORIENTATION
private final boolean mIsOverrideAnyOrientationEnabled;
+ // Corresponds to OVERRIDE_ANY_ORIENTATION_TO_USER
+ private final boolean mIsOverrideToUserOrientationEnabled;
// Corresponds to OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT
private final boolean mIsOverrideToPortraitOrientationEnabled;
// Corresponds to OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR
@@ -254,9 +257,8 @@
// Counter for ActivityRecord#setRequestedOrientation
private int mSetOrientationRequestCounter = 0;
- // The min aspect ratio override set by user. Stores the last selected aspect ratio after
- // {@link #shouldApplyUserFullscreenOverride} or {@link #shouldApplyUserMinAspectRatioOverride}
- // have been invoked.
+ // TODO(b/315140179): Make mUserAspectRatio final
+ // The min aspect ratio override set by user
@PackageManager.UserMinAspectRatio
private int mUserAspectRatio = USER_MIN_ASPECT_RATIO_UNSET;
@@ -355,6 +357,8 @@
PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE);
mIsOverrideAnyOrientationEnabled = isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION);
+ mIsOverrideToUserOrientationEnabled =
+ isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION_TO_USER);
mIsOverrideToPortraitOrientationEnabled =
isCompatChangeEnabled(OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT);
mIsOverrideToReverseLandscapeOrientationEnabled =
@@ -663,9 +667,11 @@
@ScreenOrientation
int overrideOrientationIfNeeded(@ScreenOrientation int candidate) {
+ final DisplayContent displayContent = mActivityRecord.mDisplayContent;
+ final boolean isIgnoreOrientationRequestEnabled = displayContent != null
+ && displayContent.getIgnoreOrientationRequest();
if (shouldApplyUserFullscreenOverride()
- && mActivityRecord.mDisplayContent != null
- && mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()) {
+ && isIgnoreOrientationRequestEnabled) {
Slog.v(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for "
+ mActivityRecord + " is overridden to "
+ screenOrientationToString(SCREEN_ORIENTATION_USER)
@@ -690,7 +696,6 @@
return candidate;
}
- DisplayContent displayContent = mActivityRecord.mDisplayContent;
if (mIsOverrideOrientationOnlyForCameraEnabled && displayContent != null
&& (displayContent.mDisplayRotationCompatPolicy == null
|| !displayContent.mDisplayRotationCompatPolicy
@@ -698,6 +703,17 @@
return candidate;
}
+ // mUserAspectRatio is always initialized first in shouldApplyUserFullscreenOverride(),
+ // which will always come first before this check as user override > device
+ // manufacturer override.
+ if (mUserAspectRatio == PackageManager.USER_MIN_ASPECT_RATIO_UNSET
+ && mIsOverrideToUserOrientationEnabled && isIgnoreOrientationRequestEnabled) {
+ Slog.v(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for "
+ + mActivityRecord + " is overridden to "
+ + screenOrientationToString(SCREEN_ORIENTATION_USER));
+ return SCREEN_ORIENTATION_USER;
+ }
+
if (mIsOverrideToReverseLandscapeOrientationEnabled
&& (isFixedOrientationLandscape(candidate) || mIsOverrideAnyOrientationEnabled)) {
Slog.w(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for "
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 671acfc..dbfcc22 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -34,7 +34,6 @@
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
-import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
@@ -5917,22 +5916,6 @@
return activities;
}
- ActivityRecord restartPackage(String packageName) {
- ActivityRecord starting = topRunningActivity();
-
- // All activities that came from the package must be
- // restarted as if there was a config change.
- forAllActivities(r -> {
- if (!r.info.packageName.equals(packageName)) return;
- r.forceNewConfig = true;
- if (starting != null && r == starting && r.isVisibleRequested()) {
- r.startFreezingScreenLocked(CONFIG_SCREEN_LAYOUT);
- }
- });
-
- return starting;
- }
-
Task reuseOrCreateTask(ActivityInfo info, Intent intent, boolean toTop) {
return reuseOrCreateTask(info, intent, null /*voiceSession*/, null /*voiceInteractor*/,
toTop, null /*activity*/, null /*source*/, null /*options*/);
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index f020bfa..3117db5 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -43,6 +43,7 @@
import static android.view.WindowManager.TransitionType;
import static android.view.WindowManager.transitTypeToString;
import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR;
+import static android.window.TransitionInfo.FLAGS_IS_OCCLUDED_NO_ANIMATION;
import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
import static android.window.TransitionInfo.FLAG_FILLS_TASK;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
@@ -3067,6 +3068,10 @@
Slog.e(TAG, "Unexpected launch-task-behind operation in shell transition");
flags |= FLAG_TASK_LAUNCHING_BEHIND;
}
+ if ((topActivity.mTransitionChangeFlags & FLAGS_IS_OCCLUDED_NO_ANIMATION)
+ == FLAGS_IS_OCCLUDED_NO_ANIMATION) {
+ flags |= FLAGS_IS_OCCLUDED_NO_ANIMATION;
+ }
}
if (task.voiceSession != null) {
flags |= FLAG_IS_VOICE_INTERACTION;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f5f0dc6..ec4bdf9 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -52,7 +52,6 @@
import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NOT_MAGNIFIABLE;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY;
@@ -203,7 +202,6 @@
import android.os.Debug;
import android.os.IBinder;
import android.os.PowerManager;
-import android.os.PowerManager.WakeReason;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -703,11 +701,6 @@
*/
private final Region mTapExcludeRegion = new Region();
- /**
- * Used for testing because the real PowerManager is final.
- */
- private PowerManagerWrapper mPowerManagerWrapper;
-
private static final StringBuilder sTmpSB = new StringBuilder();
/**
@@ -1062,34 +1055,9 @@
return mOnBackInvokedCallbackInfo;
}
- interface PowerManagerWrapper {
- void wakeUp(long time, @WakeReason int reason, String details);
-
- boolean isInteractive();
-
- }
-
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
WindowState parentWindow, int appOp, WindowManager.LayoutParams a, int viewVisibility,
int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow) {
- this(service, s, c, token, parentWindow, appOp, a, viewVisibility, ownerId, showUserId,
- ownerCanAddInternalSystemWindow, new PowerManagerWrapper() {
- @Override
- public void wakeUp(long time, @WakeReason int reason, String details) {
- service.mPowerManager.wakeUp(time, reason, details);
- }
-
- @Override
- public boolean isInteractive() {
- return service.mPowerManager.isInteractive();
- }
- });
- }
-
- WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
- WindowState parentWindow, int appOp, WindowManager.LayoutParams a, int viewVisibility,
- int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow,
- PowerManagerWrapper powerManagerWrapper) {
super(service);
mTmpTransaction = service.mTransactionFactory.get();
mSession = s;
@@ -1107,7 +1075,6 @@
mViewVisibility = viewVisibility;
mPolicy = mWmService.mPolicy;
mContext = mWmService.mContext;
- mPowerManagerWrapper = powerManagerWrapper;
mForceSeamlesslyRotate = token.mRoundedCornerOverlay;
mInputWindowHandle = new InputWindowHandleWrapper(new InputWindowHandle(
mActivityRecord != null
@@ -1247,13 +1214,14 @@
* @see ActivityRecord#hasSizeCompatBounds()
*/
boolean hasCompatScale() {
- if ((mAttrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0) {
- return true;
- }
if (mAttrs.type == TYPE_APPLICATION_STARTING) {
// Exclude starting window because it is not displayed by the application.
return false;
}
+ if (mWmService.mAtmService.mCompatModePackages.useLegacyScreenCompatMode(
+ mSession.mProcess.mInfo.packageName)) {
+ return true;
+ }
return mActivityRecord != null && mActivityRecord.hasSizeCompatBounds()
|| mOverrideScale != 1f;
}
@@ -2831,12 +2799,12 @@
boolean canTurnScreenOn = mActivityRecord == null || mActivityRecord.currentLaunchCanTurnScreenOn();
if (allowTheaterMode && canTurnScreenOn
- && (mWmService.mAtmService.isDreaming()
- || !mPowerManagerWrapper.isInteractive())) {
+ && (mWmService.mAtmService.isDreaming()
+ || !mWmService.mPowerManager.isInteractive())) {
if (DEBUG_VISIBILITY || DEBUG_POWER) {
Slog.v(TAG, "Relayout window turning screen on: " + this);
}
- mPowerManagerWrapper.wakeUp(SystemClock.uptimeMillis(),
+ mWmService.mPowerManager.wakeUp(SystemClock.uptimeMillis(),
PowerManager.WAKE_REASON_APPLICATION, "android.server.wm:SCREEN_ON_FLAG");
}
@@ -3775,7 +3743,7 @@
}
@Override
- public void notifyInsetsControlChanged() {
+ public void notifyInsetsControlChanged(int displayId) {
ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "notifyInsetsControlChanged for %s ", this);
if (mRemoved) {
return;
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index 061fe0f..cc08488 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -31,5 +31,5 @@
per-file com_android_server_am_CachedAppOptimizer.cpp = timmurray@google.com, edgararriaga@google.com, dualli@google.com, carmenjackson@google.com, philipcuadra@google.com
per-file com_android_server_companion_virtual_InputController.cpp = file:/services/companion/java/com/android/server/companion/virtual/OWNERS
-# Bug component : 158088 = per-file com_android_server_utils_AnrTimer*.java
-per-file com_android_server_utils_AnrTimer*.java = file:/PERFORMANCE_OWNERS
+# Bug component : 158088 = per-file *AnrTimer*
+per-file *AnrTimer* = file:/PERFORMANCE_OWNERS
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 c625b1e..3cbceec 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -595,7 +595,7 @@
<!-- Sets the brightness mapping of the desired screen brightness to the corresponding
lux for the current display -->
<xs:element name="luxToBrightnessMapping" type="luxToBrightnessMapping"
- minOccurs="0" maxOccurs="1">
+ minOccurs="0" maxOccurs="unbounded">
<xs:annotation name="final"/>
</xs:element>
</xs:sequence>
@@ -619,12 +619,20 @@
This is used in place of config_autoBrightnessLevels and config_autoBrightnessLcdBacklightValues
defined in the config XML resource.
+
+ On devices that allow users to choose from a set of predefined options in display
+ auto-brightness settings, multiple mappings for different modes and settings can be defined.
+
+ 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.
-->
<xs:complexType name="luxToBrightnessMapping">
<xs:element name="map" type="nonNegativeFloatToFloatMap">
<xs:annotation name="nonnull"/>
<xs:annotation name="final"/>
</xs:element>
+ <xs:element name="mode" type="AutoBrightnessModeName" minOccurs="0"/>
+ <xs:element name="setting" type="AutoBrightnessSettingName" minOccurs="0"/>
</xs:complexType>
<!-- Represents a point in the display brightness mapping, representing the lux level from the
@@ -757,4 +765,23 @@
</xs:element>
</xs:sequence>
</xs:complexType>
+
+ <!-- Predefined type names as defined by
+ AutomaticBrightnessController.AutomaticBrightnessMode -->
+ <xs:simpleType name="AutoBrightnessModeName">
+ <xs:restriction base="xs:string">
+ <xs:enumeration value="default"/>
+ <xs:enumeration value="idle"/>
+ <xs:enumeration value="doze"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- Predefined auto-brighntess settings -->
+ <xs:simpleType name="AutoBrightnessSettingName">
+ <xs:restriction base="xs:string">
+ <xs:enumeration value="dim"/>
+ <xs:enumeration value="normal"/>
+ <xs:enumeration value="bright"/>
+ </xs:restriction>
+ </xs:simpleType>
</xs:schema>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 8c8c123..79ea274 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -8,13 +8,26 @@
method public final java.math.BigInteger getDarkeningLightDebounceIdleMillis();
method public final java.math.BigInteger getDarkeningLightDebounceMillis();
method public boolean getEnabled();
- method public final com.android.server.display.config.LuxToBrightnessMapping getLuxToBrightnessMapping();
+ method public final java.util.List<com.android.server.display.config.LuxToBrightnessMapping> getLuxToBrightnessMapping();
method public final void setBrighteningLightDebounceIdleMillis(java.math.BigInteger);
method public final void setBrighteningLightDebounceMillis(java.math.BigInteger);
method public final void setDarkeningLightDebounceIdleMillis(java.math.BigInteger);
method public final void setDarkeningLightDebounceMillis(java.math.BigInteger);
method public void setEnabled(boolean);
- method public final void setLuxToBrightnessMapping(com.android.server.display.config.LuxToBrightnessMapping);
+ }
+
+ public enum AutoBrightnessModeName {
+ method public String getRawName();
+ enum_constant public static final com.android.server.display.config.AutoBrightnessModeName _default;
+ enum_constant public static final com.android.server.display.config.AutoBrightnessModeName doze;
+ enum_constant public static final com.android.server.display.config.AutoBrightnessModeName idle;
+ }
+
+ public enum AutoBrightnessSettingName {
+ method public String getRawName();
+ enum_constant public static final com.android.server.display.config.AutoBrightnessSettingName bright;
+ enum_constant public static final com.android.server.display.config.AutoBrightnessSettingName dim;
+ enum_constant public static final com.android.server.display.config.AutoBrightnessSettingName normal;
}
public class BlockingZoneConfig {
@@ -220,7 +233,11 @@
public class LuxToBrightnessMapping {
ctor public LuxToBrightnessMapping();
method @NonNull public final com.android.server.display.config.NonNegativeFloatToFloatMap getMap();
+ method public com.android.server.display.config.AutoBrightnessModeName getMode();
+ method public com.android.server.display.config.AutoBrightnessSettingName getSetting();
method public final void setMap(@NonNull com.android.server.display.config.NonNegativeFloatToFloatMap);
+ method public void setMode(com.android.server.display.config.AutoBrightnessModeName);
+ method public void setSetting(com.android.server.display.config.AutoBrightnessSettingName);
}
public class NitsMap {
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 f69f628..022268d 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
@@ -1262,7 +1262,7 @@
val apexModuleName = packageState.apexModuleName
val packageName = packageState.packageName
return when {
- packageState.isVendor ->
+ packageState.isVendor || packageState.isOdm ->
permissionAllowlist.getVendorPrivilegedAppAllowlistState(
packageName,
permissionName
@@ -1471,12 +1471,15 @@
// In any case, don't grant a privileged permission to privileged vendor apps,
// if the permission's protectionLevel does not have the extra vendorPrivileged
// flag.
- if (packageState.isVendor && !permission.isVendorPrivileged) {
+ if (
+ (packageState.isVendor || packageState.isOdm) &&
+ !permission.isVendorPrivileged
+ ) {
Slog.w(
LOG_TAG,
"Permission $permissionName cannot be granted to privileged" +
- " vendor app $packageName because it isn't a vendorPrivileged" +
- " permission"
+ " vendor (or odm) app $packageName because it isn't a" +
+ " vendorPrivileged permission"
)
return false
}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
index 03e45a2..71f5c75 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
@@ -58,6 +58,8 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.pm.parsing.PackageParser2;
+import com.android.internal.pm.parsing.PackageParserException;
import com.android.internal.pm.parsing.pkg.PackageImpl;
import com.android.internal.pm.parsing.pkg.ParsedPackage;
import com.android.internal.pm.permission.CompatibilityPermissionInfo;
@@ -84,7 +86,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.server.pm.parsing.PackageCacher;
import com.android.server.pm.parsing.PackageInfoUtils;
-import com.android.server.pm.parsing.PackageParser2;
+import com.android.server.pm.parsing.PackageParserUtils;
import com.android.server.pm.parsing.TestPackageParser2;
import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
import com.android.server.pm.pkg.AndroidPackage;
@@ -185,7 +187,7 @@
@Test
public void test_serializePackage() throws Exception {
- try (PackageParser2 pp = PackageParser2.forParsingFileWithDefaults()) {
+ try (PackageParser2 pp = PackageParserUtils.forParsingFileWithDefaults()) {
AndroidPackage pkg = pp.parsePackage(FRAMEWORK, 0 /* parseFlags */,
true /* useCaches */).hideAsFinal();
@@ -363,7 +365,7 @@
actualDisplayCategory = activity.getRequiredDisplayCategory();
}
}
- } catch (PackageManagerException e) {
+ } catch (PackageParserException e) {
assertThat(e.getMessage()).contains(
"requiredDisplayCategory attribute can only consist"
+ " of alphanumeric characters, '_', and '.'");
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ParallelPackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ParallelPackageParserTest.java
index 8a74e24..3761240 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ParallelPackageParserTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ParallelPackageParserTest.java
@@ -21,8 +21,8 @@
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.pm.parsing.PackageParser2;
import com.android.internal.pm.parsing.pkg.ParsedPackage;
-import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.parsing.TestPackageParser2;
import junit.framework.Assert;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
index b63950c..a28b28a 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
@@ -38,6 +38,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.pm.parsing.PackageParserException;
import com.android.internal.pm.parsing.pkg.ParsedPackage;
import com.android.internal.pm.pkg.component.ParsedActivityUtils;
import com.android.internal.pm.pkg.component.ParsedComponent;
@@ -45,7 +46,6 @@
import com.android.internal.pm.pkg.component.ParsedPermission;
import com.android.internal.pm.pkg.component.ParsedPermissionUtils;
import com.android.internal.util.ArrayUtils;
-import com.android.server.pm.PackageManagerException;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.test.service.server.R;
@@ -608,7 +608,7 @@
try {
parsePackage(filename, resId, x -> x);
expect.withMessage("Expected parsing error %s from %s", result, filename).fail();
- } catch (PackageManagerException expected) {
+ } catch (PackageParserException expected) {
expect.that(expected.error).isEqualTo(result);
}
}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt
index 98af63c..1d668cd 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt
@@ -18,8 +18,8 @@
import android.content.pm.PackageManager
import android.platform.test.annotations.Postsubmit
+import com.android.internal.pm.parsing.PackageParserException
import com.android.internal.pm.pkg.parsing.ParsingPackageUtils
-import com.android.server.pm.PackageManagerException
import com.android.server.pm.PackageManagerService
import com.android.server.pm.PackageManagerServiceUtils
import java.io.File
@@ -39,7 +39,7 @@
@Postsubmit
class SystemPartitionParseTest {
- private val parser = PackageParser2.forParsingFileWithDefaults()
+ private val parser = PackageParserUtils.forParsingFileWithDefaults()
@get:Rule
val tempFolder = TemporaryFolder()
@@ -86,7 +86,7 @@
}
}
.mapNotNull { it.exceptionOrNull() }
- .filterNot { (it as? PackageManagerException)?.error ==
+ .filterNot { (it as? PackageParserException)?.error ==
PackageManager.INSTALL_PARSE_FAILED_SKIPPED }
if (exceptions.isEmpty()) return
diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java
index 189d9bb..fb73aff 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java
@@ -17,6 +17,7 @@
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 org.junit.Assert.assertEquals;
@@ -29,21 +30,22 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-import android.content.res.Resources;
import android.content.res.TypedArray;
import android.hardware.display.BrightnessConfiguration;
import android.os.PowerManager;
+import android.provider.Settings;
+import android.testing.TestableContext;
import android.util.MathUtils;
import android.util.Spline;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
-import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
-
+import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mock;
import java.util.Arrays;
@@ -107,20 +109,6 @@
468.5f,
};
- private static final int[] DISPLAY_LEVELS_INT = {
- 9,
- 30,
- 45,
- 62,
- 78,
- 96,
- 119,
- 146,
- 178,
- 221,
- 255
- };
-
private static final float[] DISPLAY_LEVELS = {
0.03f,
0.11f,
@@ -168,66 +156,35 @@
private static final float TOLERANCE = 0.0001f;
- @Mock
- DisplayWhiteBalanceController mMockDwbc;
+ @Rule
+ public final TestableContext mContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getContext());
+
+ @Before
+ public void setUp() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL);
+ }
@Test
- public void testSimpleStrategyMappingAtControlPoints_IntConfig() {
- Resources res = createResources(DISPLAY_LEVELS_INT);
- DisplayDeviceConfig ddc = createDdc();
- BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT,
- mMockDwbc);
+ public void testSimpleStrategyMappingAtControlPoints() {
+ setUpResources();
+ DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS).build();
+ BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
assertNotNull("BrightnessMappingStrategy should not be null", simple);
for (int i = 0; i < LUX_LEVELS.length; i++) {
- final float expectedLevel = MathUtils.map(PowerManager.BRIGHTNESS_OFF + 1,
- PowerManager.BRIGHTNESS_ON, PowerManager.BRIGHTNESS_MIN,
- PowerManager.BRIGHTNESS_MAX, DISPLAY_LEVELS_INT[i]);
- assertEquals(expectedLevel,
- simple.getBrightness(LUX_LEVELS[i]), 0.0001f /*tolerance*/);
+ assertEquals(DISPLAY_LEVELS[i], simple.getBrightness(LUX_LEVELS[i]), TOLERANCE);
}
}
@Test
- public void testSimpleStrategyMappingBetweenControlPoints_IntConfig() {
- Resources res = createResources(DISPLAY_LEVELS_INT);
- DisplayDeviceConfig ddc = createDdc();
- BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT,
- mMockDwbc);
- assertNotNull("BrightnessMappingStrategy should not be null", simple);
- for (int i = 1; i < LUX_LEVELS.length; i++) {
- final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2;
- final float backlight = simple.getBrightness(lux) * PowerManager.BRIGHTNESS_ON;
- assertTrue("Desired brightness should be between adjacent control points.",
- backlight > DISPLAY_LEVELS_INT[i - 1]
- && backlight < DISPLAY_LEVELS_INT[i]);
- }
- }
-
- @Test
- public void testSimpleStrategyMappingAtControlPoints_FloatConfig() {
- Resources res = createResources(EMPTY_INT_ARRAY);
- DisplayDeviceConfig ddc = createDdc(EMPTY_FLOAT_ARRAY, EMPTY_FLOAT_ARRAY, LUX_LEVELS,
- EMPTY_FLOAT_ARRAY, DISPLAY_LEVELS);
- BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT,
- mMockDwbc);
- assertNotNull("BrightnessMappingStrategy should not be null", simple);
- for (int i = 0; i < LUX_LEVELS.length; i++) {
- assertEquals(DISPLAY_LEVELS[i], simple.getBrightness(LUX_LEVELS[i]),
- /* tolerance= */ 0.0001f);
- }
- }
-
- @Test
- public void testSimpleStrategyMappingBetweenControlPoints_FloatConfig() {
- Resources res = createResources(EMPTY_INT_ARRAY);
- DisplayDeviceConfig ddc = createDdc(EMPTY_FLOAT_ARRAY, EMPTY_FLOAT_ARRAY, LUX_LEVELS,
- EMPTY_FLOAT_ARRAY, DISPLAY_LEVELS);
- BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT,
- mMockDwbc);
+ public void testSimpleStrategyMappingBetweenControlPoints() {
+ setUpResources();
+ DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS).build();
+ BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
assertNotNull("BrightnessMappingStrategy should not be null", simple);
for (int i = 1; i < LUX_LEVELS.length; i++) {
final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2;
@@ -239,10 +196,10 @@
@Test
public void testSimpleStrategyIgnoresNewConfiguration() {
- Resources res = createResources(DISPLAY_LEVELS_INT);
- DisplayDeviceConfig ddc = createDdc();
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+ setUpResources();
+ DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS).build();
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
final float[] lux = { 0f, 1f };
final float[] nits = { 0, PowerManager.BRIGHTNESS_ON };
@@ -255,27 +212,25 @@
@Test
public void testSimpleStrategyIgnoresNullConfiguration() {
- Resources res = createResources(DISPLAY_LEVELS_INT);
- DisplayDeviceConfig ddc = createDdc();
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+ setUpResources();
+ DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS).build();
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
strategy.setBrightnessConfiguration(null);
- final int n = DISPLAY_LEVELS_INT.length;
- final float expectedBrightness =
- (float) DISPLAY_LEVELS_INT[n - 1] / PowerManager.BRIGHTNESS_ON;
+ final int n = DISPLAY_LEVELS.length;
+ final float expectedBrightness = DISPLAY_LEVELS[n - 1];
assertEquals(expectedBrightness,
strategy.getBrightness(LUX_LEVELS[n - 1]), 0.0001f /*tolerance*/);
}
@Test
public void testPhysicalStrategyMappingAtControlPoints() {
- Resources res = createResources(EMPTY_INT_ARRAY);
- DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
- DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT,
- LUX_LEVELS, DISPLAY_LEVELS_NITS);
- BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+ setUpResources();
+ DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS)
+ .build();
+ BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
assertNotNull("BrightnessMappingStrategy should not be null", physical);
for (int i = 0; i < LUX_LEVELS.length; i++) {
final float expectedLevel = MathUtils.map(DISPLAY_RANGE_NITS[0], DISPLAY_RANGE_NITS[1],
@@ -290,11 +245,11 @@
@Test
public void testPhysicalStrategyMappingBetweenControlPoints() {
- Resources res = createResources(EMPTY_INT_ARRAY);
- DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE,
- LUX_LEVELS, DISPLAY_LEVELS_NITS);
- BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+ setUpResources();
+ DisplayDeviceConfig ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE)
+ .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build();
+ BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
assertNotNull("BrightnessMappingStrategy should not be null", physical);
Spline brightnessToNits =
Spline.createSpline(BACKLIGHT_RANGE_ZERO_TO_ONE, DISPLAY_RANGE_NITS);
@@ -309,11 +264,11 @@
@Test
public void testPhysicalStrategyUsesNewConfigurations() {
- Resources res = createResources(EMPTY_INT_ARRAY);
- DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
- DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, DISPLAY_LEVELS_NITS);
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+ setUpResources();
+ DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS)
+ .build();
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
final float[] lux = {0f, 1f};
final float[] nits = {
@@ -336,11 +291,11 @@
@Test
public void testPhysicalStrategyRecalculateSplines() {
- Resources res = createResources(EMPTY_INT_ARRAY);
- DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
- DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, DISPLAY_LEVELS_NITS);
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+ setUpResources();
+ DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS)
+ .build();
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
float[] adjustedNits50p = new float[DISPLAY_RANGE_NITS.length];
for (int i = 0; i < DISPLAY_RANGE_NITS.length; i++) {
adjustedNits50p[i] = DISPLAY_RANGE_NITS[i] * 0.5f;
@@ -381,11 +336,12 @@
@Test
public void testDefaultStrategyIsPhysical() {
- Resources res = createResources(DISPLAY_LEVELS_INT);
- DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
- DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, DISPLAY_LEVELS_NITS);
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+ setUpResources();
+ DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS)
+ .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS)
+ .build();
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
assertTrue(strategy instanceof BrightnessMappingStrategy.PhysicalMappingStrategy);
}
@@ -396,19 +352,19 @@
float tmp = lux[idx];
lux[idx] = lux[idx + 1];
lux[idx + 1] = tmp;
- Resources res = createResources(EMPTY_INT_ARRAY);
- DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
- DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, lux, DISPLAY_LEVELS_NITS);
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+ setUpResources();
+ DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(lux)
+ .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build();
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
assertNull(strategy);
// And make sure we get the same result even if it's monotone but not increasing.
lux[idx] = lux[idx + 1];
- ddc = createDdc(DISPLAY_RANGE_NITS, DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, lux,
- DISPLAY_LEVELS_NITS);
- strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
- mMockDwbc);
+ ddc = new DdcBuilder().setAutoBrightnessLevelsLux(lux)
+ .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build();
+ strategy = BrightnessMappingStrategy.create(mContext, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
+ /* displayWhiteBalanceController= */ null);
assertNull(strategy);
}
@@ -419,82 +375,74 @@
// Make sure it's strictly increasing so that the only failure is the differing array
// lengths
lux[lux.length - 1] = lux[lux.length - 2] + 1;
- Resources res = createResources(EMPTY_INT_ARRAY);
- DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
- DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, lux, DISPLAY_LEVELS_NITS);
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+ setUpResources();
+ DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(lux)
+ .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build();
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
assertNull(strategy);
- res = createResources(DISPLAY_LEVELS_INT);
- strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
- mMockDwbc);
+ ddc = new DdcBuilder().setAutoBrightnessLevelsLux(lux)
+ .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS)
+ .setAutoBrightnessLevels(DISPLAY_LEVELS).build();
+ strategy = BrightnessMappingStrategy.create(mContext, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
+ /* displayWhiteBalanceController= */ null);
assertNull(strategy);
// Extra backlight level
- final int[] backlight = Arrays.copyOf(
- DISPLAY_LEVELS_INT, DISPLAY_LEVELS_INT.length + 1);
+ final float[] backlight = Arrays.copyOf(DISPLAY_LEVELS, DISPLAY_LEVELS.length + 1);
backlight[backlight.length - 1] = backlight[backlight.length - 2] + 1;
- res = createResources(backlight);
- ddc = createDdc(DISPLAY_RANGE_NITS,
- DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, EMPTY_FLOAT_ARRAY);
- strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
- mMockDwbc);
+ setUpResources();
+ ddc = new DdcBuilder().setAutoBrightnessLevels(backlight).build();
+ strategy = BrightnessMappingStrategy.create(mContext, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
+ /* displayWhiteBalanceController= */ null);
assertNull(strategy);
// Extra nits level
final float[] nits = Arrays.copyOf(DISPLAY_RANGE_NITS, DISPLAY_LEVELS_NITS.length + 1);
nits[nits.length - 1] = nits[nits.length - 2] + 1;
- res = createResources(EMPTY_INT_ARRAY);
- ddc = createDdc(DISPLAY_RANGE_NITS,
- DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, nits);
- strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
- mMockDwbc);
+ setUpResources();
+ ddc = new DdcBuilder().setAutoBrightnessLevelsNits(nits)
+ .setAutoBrightnessLevels(EMPTY_FLOAT_ARRAY).build();
+ strategy = BrightnessMappingStrategy.create(mContext, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
+ /* displayWhiteBalanceController= */ null);
assertNull(strategy);
}
@Test
public void testPhysicalStrategyRequiresNitsMapping() {
- Resources res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/);
- DisplayDeviceConfig ddc = createDdc(EMPTY_FLOAT_ARRAY /*nitsRange*/);
- BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
- assertNull(physical);
-
- res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/);
- physical = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
- mMockDwbc);
- assertNull(physical);
-
- res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/);
- physical = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
- mMockDwbc);
+ setUpResources();
+ DisplayDeviceConfig ddc = new DdcBuilder().setNitsRange(EMPTY_FLOAT_ARRAY).build();
+ BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
assertNull(physical);
}
@Test
public void testStrategiesAdaptToUserDataPoint() {
- Resources res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/);
- DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE,
- LUX_LEVELS, DISPLAY_LEVELS_NITS);
- assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc));
- ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE);
- res = createResources(DISPLAY_LEVELS_INT);
- assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc));
+ setUpResources();
+ DisplayDeviceConfig ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE)
+ .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build();
+ assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null));
+ ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE)
+ .setAutoBrightnessLevels(DISPLAY_LEVELS).build();
+ setUpResources();
+ assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null));
}
@Test
public void testIdleModeConfigLoadsCorrectly() {
- Resources res = createResourcesIdle(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE);
- DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE);
+ setUpResources(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE);
+ DisplayDeviceConfig ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE)
+ .build();
// Create an idle mode bms
// This will fail if it tries to fetch the wrong configuration.
- BrightnessMappingStrategy bms = BrightnessMappingStrategy.create(res, ddc,
+ BrightnessMappingStrategy bms = BrightnessMappingStrategy.create(mContext, ddc,
AUTO_BRIGHTNESS_MODE_IDLE,
- mMockDwbc);
+ /* displayWhiteBalanceController= */ null);
assertNotNull("BrightnessMappingStrategy should not be null", bms);
// Ensure that the config is the one we set
@@ -562,81 +510,31 @@
assertEquals(minBrightness, strategy.getBrightness(LUX_LEVELS[0]), 0.0001f /*tolerance*/);
}
- private Resources createResources(int[] brightnessLevelsBacklight) {
- return createResources(brightnessLevelsBacklight, EMPTY_INT_ARRAY, EMPTY_FLOAT_ARRAY);
+ private void setUpResources() {
+ setUpResources(EMPTY_INT_ARRAY, EMPTY_FLOAT_ARRAY);
}
- private Resources createResourcesIdle(int[] luxLevelsIdle, float[] brightnessLevelsNitsIdle) {
- return createResources(EMPTY_INT_ARRAY,
- luxLevelsIdle, brightnessLevelsNitsIdle);
- }
-
- private Resources createResources(int[] brightnessLevelsBacklight, int[] luxLevelsIdle,
- float[] brightnessLevelsNitsIdle) {
-
- Resources mockResources = mock(Resources.class);
+ private void setUpResources(int[] luxLevelsIdle, float[] brightnessLevelsNitsIdle) {
if (luxLevelsIdle.length > 0) {
int[] luxLevelsIdleResource = Arrays.copyOfRange(luxLevelsIdle, 1,
luxLevelsIdle.length);
- when(mockResources.getIntArray(
- com.android.internal.R.array.config_autoBrightnessLevelsIdle))
- .thenReturn(luxLevelsIdleResource);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.array.config_autoBrightnessLevelsIdle,
+ luxLevelsIdleResource);
}
- when(mockResources.getIntArray(
- com.android.internal.R.array.config_autoBrightnessLcdBacklightValues))
- .thenReturn(brightnessLevelsBacklight);
-
TypedArray mockBrightnessLevelNitsIdle = createFloatTypedArray(brightnessLevelsNitsIdle);
- when(mockResources.obtainTypedArray(
- com.android.internal.R.array.config_autoBrightnessDisplayValuesNitsIdle))
- .thenReturn(mockBrightnessLevelNitsIdle);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.array.config_autoBrightnessDisplayValuesNitsIdle,
+ mockBrightnessLevelNitsIdle);
- when(mockResources.getInteger(
- com.android.internal.R.integer.config_screenBrightnessSettingMinimum))
- .thenReturn(1);
- when(mockResources.getInteger(
- com.android.internal.R.integer.config_screenBrightnessSettingMaximum))
- .thenReturn(255);
- when(mockResources.getFraction(
- com.android.internal.R.fraction.config_autoBrightnessAdjustmentMaxGamma, 1, 1))
- .thenReturn(MAXIMUM_GAMMA);
- return mockResources;
- }
-
- private DisplayDeviceConfig createDdc() {
- return createDdc(DISPLAY_RANGE_NITS);
- }
-
- private DisplayDeviceConfig createDdc(float[] nitsArray) {
- return createDdc(nitsArray, DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT);
- }
-
- private DisplayDeviceConfig createDdc(float[] nitsArray, float[] backlightArray) {
- DisplayDeviceConfig mockDdc = mock(DisplayDeviceConfig.class);
- when(mockDdc.getNits()).thenReturn(nitsArray);
- when(mockDdc.getBrightness()).thenReturn(backlightArray);
- when(mockDdc.getAutoBrightnessBrighteningLevelsLux()).thenReturn(LUX_LEVELS);
- when(mockDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(EMPTY_FLOAT_ARRAY);
- when(mockDdc.getAutoBrightnessBrighteningLevels()).thenReturn(EMPTY_FLOAT_ARRAY);
- return mockDdc;
- }
-
- private DisplayDeviceConfig createDdc(float[] nitsArray, float[] backlightArray,
- float[] luxLevelsFloat, float[] brightnessLevelsNits) {
- return createDdc(nitsArray, backlightArray, luxLevelsFloat, brightnessLevelsNits,
- EMPTY_FLOAT_ARRAY);
- }
-
- private DisplayDeviceConfig createDdc(float[] nitsArray, float[] backlightArray,
- float[] luxLevelsFloat, float[] brightnessLevelsNits, float[] brightnessLevels) {
- DisplayDeviceConfig mockDdc = mock(DisplayDeviceConfig.class);
- when(mockDdc.getNits()).thenReturn(nitsArray);
- when(mockDdc.getBrightness()).thenReturn(backlightArray);
- when(mockDdc.getAutoBrightnessBrighteningLevelsLux()).thenReturn(luxLevelsFloat);
- when(mockDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(brightnessLevelsNits);
- when(mockDdc.getAutoBrightnessBrighteningLevels()).thenReturn(brightnessLevels);
- return mockDdc;
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.integer.config_screenBrightnessSettingMinimum, 1);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.integer.config_screenBrightnessSettingMaximum, 255);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.fraction.config_autoBrightnessAdjustmentMaxGamma,
+ MAXIMUM_GAMMA);
}
private TypedArray createFloatTypedArray(float[] vals) {
@@ -677,12 +575,11 @@
final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3);
- Resources resources = createResources(EMPTY_INT_ARRAY);
- DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
- DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX,
- GAMMA_CORRECTION_NITS);
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+ setUpResources();
+ DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(GAMMA_CORRECTION_LUX)
+ .setAutoBrightnessLevelsNits(GAMMA_CORRECTION_NITS).build();
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
// Let's start with a validity check:
assertEquals(y1, strategy.getBrightness(x1), 0.0001f /* tolerance */);
assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */);
@@ -708,12 +605,11 @@
final float y1 = GAMMA_CORRECTION_SPLINE.interpolate(x1);
final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3);
- Resources resources = createResources(EMPTY_INT_ARRAY);
- DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
- DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX,
- GAMMA_CORRECTION_NITS);
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+ setUpResources();
+ DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(GAMMA_CORRECTION_LUX)
+ .setAutoBrightnessLevelsNits(GAMMA_CORRECTION_NITS).build();
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
// Validity check:
assertEquals(y1, strategy.getBrightness(x1), 0.0001f /* tolerance */);
assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */);
@@ -736,12 +632,11 @@
public void testGammaCorrectionExtremeChangeAtCenter() {
// Extreme changes (e.g. setting brightness to 0.0 or 1.0) can't be gamma corrected, so we
// just make sure the adjustment reflects the change.
- Resources resources = createResources(EMPTY_INT_ARRAY);
- DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
- DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX,
- GAMMA_CORRECTION_NITS);
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+ setUpResources();
+ DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(GAMMA_CORRECTION_LUX)
+ .setAutoBrightnessLevelsNits(GAMMA_CORRECTION_NITS).build();
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
assertEquals(0.0f, strategy.getAutoBrightnessAdjustment(), /* delta= */ 0.0001f);
strategy.addUserDataPoint(/* lux= */ 2500, /* brightness= */ 1.0f);
assertEquals(+1.0f, strategy.getAutoBrightnessAdjustment(), /* delta= */ 0.0001f);
@@ -760,12 +655,11 @@
final float y0 = GAMMA_CORRECTION_SPLINE.interpolate(x0);
final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
final float y4 = GAMMA_CORRECTION_SPLINE.interpolate(x4);
- Resources resources = createResources(EMPTY_INT_ARRAY);
- DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
- DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX,
- GAMMA_CORRECTION_NITS);
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+ setUpResources();
+ DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(GAMMA_CORRECTION_LUX)
+ .setAutoBrightnessLevelsNits(GAMMA_CORRECTION_NITS).build();
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
// Validity, as per tradition:
assertEquals(y0, strategy.getBrightness(x0), 0.0001f /* tolerance */);
assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */);
@@ -790,11 +684,93 @@
@Test
public void testGetMode() {
- Resources res = createResourcesIdle(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE);
- DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE);
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_IDLE,
- mMockDwbc);
+ setUpResources(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE);
+ DisplayDeviceConfig ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE)
+ .build();
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_IDLE, /* displayWhiteBalanceController= */ null);
assertEquals(AUTO_BRIGHTNESS_MODE_IDLE, strategy.getMode());
}
+
+ @Test
+ public void testAutoBrightnessModeAndPreset() {
+ int mode = AUTO_BRIGHTNESS_MODE_DOZE;
+ int preset = Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_DIM;
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_FOR_ALS, preset);
+
+ setUpResources();
+ DisplayDeviceConfig ddc = new DdcBuilder()
+ .setAutoBrightnessLevels(mode, preset, DISPLAY_LEVELS)
+ .setAutoBrightnessLevelsLux(mode, preset, LUX_LEVELS).build();
+ BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(mContext, ddc, mode,
+ /* displayWhiteBalanceController= */ null);
+ assertNotNull("BrightnessMappingStrategy should not be null", simple);
+ for (int i = 0; i < LUX_LEVELS.length; i++) {
+ assertEquals(DISPLAY_LEVELS[i], simple.getBrightness(LUX_LEVELS[i]), TOLERANCE);
+ }
+ }
+
+ private static class DdcBuilder {
+ private DisplayDeviceConfig mDdc;
+
+ DdcBuilder() {
+ mDdc = mock(DisplayDeviceConfig.class);
+ when(mDdc.getNits()).thenReturn(DISPLAY_RANGE_NITS);
+ when(mDdc.getBrightness()).thenReturn(DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT);
+ when(mDdc.getAutoBrightnessBrighteningLevelsLux(AUTO_BRIGHTNESS_MODE_DEFAULT,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL)).thenReturn(LUX_LEVELS);
+ when(mDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(EMPTY_FLOAT_ARRAY);
+ when(mDdc.getAutoBrightnessBrighteningLevels(AUTO_BRIGHTNESS_MODE_DEFAULT,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL))
+ .thenReturn(EMPTY_FLOAT_ARRAY);
+ }
+
+ DdcBuilder setNitsRange(float[] nitsArray) {
+ when(mDdc.getNits()).thenReturn(nitsArray);
+ return this;
+ }
+
+ DdcBuilder setBrightnessRange(float[] brightnessArray) {
+ when(mDdc.getBrightness()).thenReturn(brightnessArray);
+ return this;
+ }
+
+ DdcBuilder setAutoBrightnessLevelsLux(float[] luxLevels) {
+ when(mDdc.getAutoBrightnessBrighteningLevelsLux(AUTO_BRIGHTNESS_MODE_DEFAULT,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL)).thenReturn(luxLevels);
+ return this;
+ }
+
+ DdcBuilder setAutoBrightnessLevelsLux(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode, int preset,
+ float[] luxLevels) {
+ when(mDdc.getAutoBrightnessBrighteningLevelsLux(mode, preset)).thenReturn(luxLevels);
+ return this;
+ }
+
+ DdcBuilder setAutoBrightnessLevelsNits(float[] brightnessLevelsNits) {
+ when(mDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(brightnessLevelsNits);
+ return this;
+ }
+
+ DdcBuilder setAutoBrightnessLevels(float[] brightnessLevels) {
+ when(mDdc.getAutoBrightnessBrighteningLevels(AUTO_BRIGHTNESS_MODE_DEFAULT,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL))
+ .thenReturn(brightnessLevels);
+ return this;
+ }
+
+ DdcBuilder setAutoBrightnessLevels(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode, int preset,
+ float[] brightnessLevels) {
+ when(mDdc.getAutoBrightnessBrighteningLevels(mode, preset))
+ .thenReturn(brightnessLevels);
+ return this;
+ }
+
+ DisplayDeviceConfig build() {
+ return mDdc;
+ }
+ }
}
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 31d7e88..7a84406 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -17,6 +17,9 @@
package com.android.server.display;
+import static com.android.internal.display.BrightnessSynchronizer.brightnessIntToFloat;
+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.config.SensorData.SupportedMode;
import static com.android.server.display.utils.DeviceConfigParsingUtils.ambientBrightnessThresholdsIntToFloat;
import static com.android.server.display.utils.DeviceConfigParsingUtils.displayBrightnessThresholdsIntToFloat;
@@ -39,6 +42,7 @@
import android.content.res.TypedArray;
import android.hardware.display.DisplayManagerInternal;
import android.os.Temperature;
+import android.provider.Settings;
import android.util.SparseArray;
import android.util.Spline;
import android.view.SurfaceControl;
@@ -47,7 +51,6 @@
import androidx.test.filters.SmallTest;
import com.android.internal.R;
-import com.android.internal.display.BrightnessSynchronizer;
import com.android.server.display.config.HdrBrightnessData;
import com.android.server.display.config.ThermalStatus;
import com.android.server.display.feature.DisplayManagerFlags;
@@ -605,10 +608,15 @@
private void verifyConfigValuesFromConfigResource() {
assertNull(mDisplayDeviceConfig.getName());
- assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new
- float[]{2.0f, 200.0f, 600.0f}, ZERO_DELTA);
- assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new
- float[]{0.0f, 110.0f, 500.0f}, ZERO_DELTA);
+ assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(),
+ new float[]{2.0f, 200.0f, 600.0f}, ZERO_DELTA);
+ assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
+ AUTO_BRIGHTNESS_MODE_DEFAULT, Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL),
+ new float[]{0.0f, 110.0f, 500.0f}, ZERO_DELTA);
+ assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
+ AUTO_BRIGHTNESS_MODE_DEFAULT, Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL),
+ new float[]{brightnessIntToFloat(50), brightnessIntToFloat(100),
+ brightnessIntToFloat(150)}, SMALL_DELTA);
// Test thresholds
assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(), ZERO_DELTA);
@@ -674,7 +682,7 @@
assertEquals("test_light_sensor", mDisplayDeviceConfig.getAmbientLightSensor().type);
assertEquals("", mDisplayDeviceConfig.getAmbientLightSensor().name);
- assertEquals(BrightnessSynchronizer.brightnessIntToFloat(35),
+ assertEquals(brightnessIntToFloat(35),
mDisplayDeviceConfig.getBrightnessCapForWearBedtimeMode(), ZERO_DELTA);
}
@@ -734,9 +742,40 @@
getValidProxSensor(), /* includeIdleMode= */ false));
assertArrayEquals(new float[]{0.0f, 80},
- mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), ZERO_DELTA);
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
+ AUTO_BRIGHTNESS_MODE_DEFAULT,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL), ZERO_DELTA);
assertArrayEquals(new float[]{0.2f, 0.3f},
- mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(), SMALL_DELTA);
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
+ AUTO_BRIGHTNESS_MODE_DEFAULT,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL), SMALL_DELTA);
+
+ assertArrayEquals(new float[]{0.0f, 90},
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
+ AUTO_BRIGHTNESS_MODE_DEFAULT,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_DIM), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.3f, 0.4f},
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
+ AUTO_BRIGHTNESS_MODE_DEFAULT,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_DIM), SMALL_DELTA);
+
+ assertArrayEquals(new float[]{0.0f, 95},
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
+ AUTO_BRIGHTNESS_MODE_DOZE,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.35f, 0.45f},
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
+ AUTO_BRIGHTNESS_MODE_DOZE,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL), SMALL_DELTA);
+
+ assertArrayEquals(new float[]{0.0f, 100},
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
+ AUTO_BRIGHTNESS_MODE_DOZE,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_BRIGHT), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.4f, 0.5f},
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
+ AUTO_BRIGHTNESS_MODE_DOZE,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_BRIGHT), SMALL_DELTA);
}
@Test
@@ -746,9 +785,15 @@
setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(),
getValidProxSensor(), /* includeIdleMode= */ false));
- assertNull(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels());
+ assertArrayEquals(new float[]{brightnessIntToFloat(50), brightnessIntToFloat(100),
+ brightnessIntToFloat(150)},
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
+ AUTO_BRIGHTNESS_MODE_DEFAULT,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL), SMALL_DELTA);
assertArrayEquals(new float[]{0, 110, 500},
- mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), ZERO_DELTA);
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
+ AUTO_BRIGHTNESS_MODE_DEFAULT,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL), ZERO_DELTA);
assertArrayEquals(new float[]{2, 200, 600},
mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), SMALL_DELTA);
}
@@ -1138,6 +1183,46 @@
+ "</point>\n"
+ "</map>\n"
+ "</luxToBrightnessMapping>\n"
+ + "<luxToBrightnessMapping>\n"
+ + "<setting>dim</setting>\n"
+ + "<map>\n"
+ + "<point>\n"
+ + "<first>0</first>\n"
+ + "<second>0.3</second>\n"
+ + "</point>\n"
+ + "<point>\n"
+ + "<first>90</first>\n"
+ + "<second>0.4</second>\n"
+ + "</point>\n"
+ + "</map>\n"
+ + "</luxToBrightnessMapping>\n"
+ + "<luxToBrightnessMapping>\n"
+ + "<mode>doze</mode>\n"
+ + "<map>\n"
+ + "<point>\n"
+ + "<first>0</first>\n"
+ + "<second>0.35</second>\n"
+ + "</point>\n"
+ + "<point>\n"
+ + "<first>95</first>\n"
+ + "<second>0.45</second>\n"
+ + "</point>\n"
+ + "</map>\n"
+ + "</luxToBrightnessMapping>\n"
+ + "<luxToBrightnessMapping>\n"
+ + "<mode>doze</mode>\n"
+ + "<setting>bright</setting>\n"
+ + "<map>\n"
+ + "<point>\n"
+ + "<first>0</first>\n"
+ + "<second>0.4</second>\n"
+ + "</point>\n"
+ + "<point>\n"
+ + "<first>100</first>\n"
+ + "<second>0.5</second>\n"
+ + "</point>\n"
+ + "</map>\n"
+ + "</luxToBrightnessMapping>\n"
+ "</autoBrightness>\n"
+ getPowerThrottlingConfig()
+ "<highBrightnessMode enabled=\"true\">\n"
@@ -1435,6 +1520,10 @@
when(mResources.getIntArray(
com.android.internal.R.array.config_autoBrightnessLevels))
.thenReturn(screenBrightnessLevelLux);
+ int[] screenBrightnessLevels = new int[]{50, 100, 150};
+ when(mResources.getIntArray(
+ com.android.internal.R.array.config_autoBrightnessLcdBacklightValues))
+ .thenReturn(screenBrightnessLevels);
// Thresholds
// Config.xml requires the levels arrays to be of length N and the thresholds arrays to be
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
index dea838d..fbb14c3 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -88,4 +89,12 @@
verify(mDisplayPowerController).setBrightnessFromOffload(brightness);
}
+
+ @Test
+ public void testBlockScreenOn() {
+ Runnable unblocker = () -> {};
+ mSession.blockScreenOn(unblocker);
+
+ verify(mDisplayOffloader).onBlockingScreenOn(eq(unblocker));
+ }
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
index 02bd35a..4cc68cf 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.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;
@@ -1568,6 +1570,56 @@
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}.
*/
@@ -1853,7 +1905,7 @@
}
@Override
- BrightnessMappingStrategy getDefaultModeBrightnessMapper(Resources resources,
+ BrightnessMappingStrategy getDefaultModeBrightnessMapper(Context context,
DisplayDeviceConfig displayDeviceConfig,
DisplayWhiteBalanceController displayWhiteBalanceController) {
return mBrightnessMappingStrategy;
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 64cdac4..943862f 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -1669,7 +1669,7 @@
}
@Override
- BrightnessMappingStrategy getDefaultModeBrightnessMapper(Resources resources,
+ BrightnessMappingStrategy getDefaultModeBrightnessMapper(Context context,
DisplayDeviceConfig displayDeviceConfig,
DisplayWhiteBalanceController displayWhiteBalanceController) {
return mBrightnessMappingStrategy;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index f36854b..00f9892 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -206,6 +206,9 @@
when(mMockedResources.getIntArray(
com.android.internal.R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
.thenReturn(new int[]{});
+ when(mMockedResources.getIntArray(
+ com.android.internal.R.array.config_autoBrightnessLcdBacklightValues))
+ .thenReturn(new int[]{});
doReturn(true).when(mFlags).isDisplayOffloadEnabled();
initDisplayOffloadSession();
}
@@ -1235,6 +1238,9 @@
@Override
public void stopOffload() {}
+
+ @Override
+ public void onBlockingScreenOn(Runnable unblocker) {}
});
mDisplayOffloadSession = new DisplayOffloadSessionImpl(mDisplayOffloader,
diff --git a/services/tests/media/OWNERS b/services/tests/media/OWNERS
new file mode 100644
index 0000000..160767a6
--- /dev/null
+++ b/services/tests/media/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 137631
+include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
diff --git a/services/tests/media/mediarouterservicetest/Android.bp b/services/tests/media/mediarouterservicetest/Android.bp
new file mode 100644
index 0000000..aed3af6
--- /dev/null
+++ b/services/tests/media/mediarouterservicetest/Android.bp
@@ -0,0 +1,39 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "MediaRouterServiceTests",
+ srcs: [
+ "src/**/*.java",
+ ],
+
+ static_libs: [
+ "androidx.test.core",
+ "androidx.test.rules",
+ "androidx.test.runner",
+ "compatibility-device-util-axt",
+ "junit",
+ "platform-test-annotations",
+ "services.core",
+ "truth",
+ ],
+
+ platform_apis: true,
+
+ test_suites: [
+ // "device-tests",
+ "general-tests",
+ ],
+
+ certificate: "platform",
+ dxflags: ["--multi-dex"],
+ optimize: {
+ enabled: false,
+ },
+}
diff --git a/services/tests/media/mediarouterservicetest/AndroidManifest.xml b/services/tests/media/mediarouterservicetest/AndroidManifest.xml
new file mode 100644
index 0000000..fe65f86
--- /dev/null
+++ b/services/tests/media/mediarouterservicetest/AndroidManifest.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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.media.tests">
+
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
+ <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
+
+ <application android:testOnly="true" android:debuggable="true">
+ <uses-library android:name="android.test.runner"/>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.media.tests"
+ android:label="Frameworks Services Tests"/>
+</manifest>
diff --git a/services/tests/media/mediarouterservicetest/AndroidTest.xml b/services/tests/media/mediarouterservicetest/AndroidTest.xml
new file mode 100644
index 0000000..b065681
--- /dev/null
+++ b/services/tests/media/mediarouterservicetest/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?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 MediaRouter Service tests.">
+ <option name="test-tag" value="MediaRouterServiceTests" />
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="MediaRouterServiceTests.apk"/>
+ <option name="install-arg" value="-t" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="com.android.server.media.tests" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java b/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java
new file mode 100644
index 0000000..6f9b6fa
--- /dev/null
+++ b/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static com.android.server.media.AudioRoutingUtils.ATTRIBUTES_MEDIA;
+import static com.android.server.media.AudioRoutingUtils.getMediaAudioProductStrategy;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceCallback;
+import android.media.AudioDeviceInfo;
+import android.media.AudioDevicePort;
+import android.media.AudioManager;
+import android.media.AudioSystem;
+import android.media.MediaRoute2Info;
+import android.media.audiopolicy.AudioProductStrategy;
+import android.os.Looper;
+import android.os.UserHandle;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+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.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+@RunWith(JUnit4.class)
+public class AudioPoliciesDeviceRouteControllerTest {
+
+ private static final String FAKE_ROUTE_NAME = "fake name";
+ private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_INFO_BUILTIN_SPEAKER =
+ createAudioDeviceInfo(
+ AudioSystem.DEVICE_OUT_SPEAKER, "name_builtin", /* address= */ null);
+ private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET =
+ createAudioDeviceInfo(
+ AudioSystem.DEVICE_OUT_WIRED_HEADSET, "name_wired_hs", /* address= */ null);
+ private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP =
+ createAudioDeviceInfo(
+ AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, "name_a2dp", /* address= */ "12:34:45");
+
+ private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_BUILTIN_EARPIECE =
+ createAudioDeviceInfo(
+ AudioSystem.DEVICE_OUT_EARPIECE, /* name= */ null, /* address= */ null);
+
+ private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_NO_NAME =
+ createAudioDeviceInfo(
+ AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET,
+ /* name= */ null,
+ /* address= */ null);
+
+ private AudioDeviceInfo mSelectedAudioDeviceInfo;
+ private Set<AudioDeviceInfo> mAvailableAudioDeviceInfos;
+ @Mock private AudioManager mMockAudioManager;
+ @Mock private DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener;
+ private AudioPoliciesDeviceRouteController mControllerUnderTest;
+ private AudioDeviceCallback mAudioDeviceCallback;
+ private AudioProductStrategy mMediaAudioProductStrategy;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ Resources mockResources = Mockito.mock(Resources.class);
+ when(mockResources.getText(anyInt())).thenReturn(FAKE_ROUTE_NAME);
+ Context realContext = InstrumentationRegistry.getInstrumentation().getContext();
+ Context mockContext = Mockito.mock(Context.class);
+ when(mockContext.getResources()).thenReturn(mockResources);
+ // The bluetooth stack needs the application info, but we cannot use a spy because the
+ // concrete class is package private, so we just return the application info through the
+ // mock.
+ when(mockContext.getApplicationInfo()).thenReturn(realContext.getApplicationInfo());
+
+ // Setup the initial state so that the route controller is created in a sensible state.
+ mSelectedAudioDeviceInfo = FAKE_AUDIO_DEVICE_INFO_BUILTIN_SPEAKER;
+ mAvailableAudioDeviceInfos = Set.of(FAKE_AUDIO_DEVICE_INFO_BUILTIN_SPEAKER);
+ updateMockAudioManagerState();
+ mMediaAudioProductStrategy = getMediaAudioProductStrategy();
+
+ BluetoothAdapter btAdapter =
+ realContext.getSystemService(BluetoothManager.class).getAdapter();
+ mControllerUnderTest =
+ new AudioPoliciesDeviceRouteController(
+ mockContext,
+ mMockAudioManager,
+ Looper.getMainLooper(),
+ mMediaAudioProductStrategy,
+ btAdapter,
+ mOnDeviceRouteChangedListener);
+ mControllerUnderTest.start(UserHandle.CURRENT_OR_SELF);
+
+ ArgumentCaptor<AudioDeviceCallback> deviceCallbackCaptor =
+ ArgumentCaptor.forClass(AudioDeviceCallback.class);
+ verify(mMockAudioManager)
+ .registerAudioDeviceCallback(deviceCallbackCaptor.capture(), any());
+ mAudioDeviceCallback = deviceCallbackCaptor.getValue();
+
+ // We clear any invocations during setup.
+ clearInvocations(mOnDeviceRouteChangedListener);
+ }
+
+ @Test
+ public void getSelectedRoute_afterDevicesConnect_returnsRightSelectedRoute() {
+ assertThat(mControllerUnderTest.getSelectedRoute().getType())
+ .isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
+
+ addAvailableAudioDeviceInfo(
+ /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP,
+ /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP);
+ verify(mOnDeviceRouteChangedListener).onDeviceRouteChanged();
+ assertThat(mControllerUnderTest.getSelectedRoute().getType())
+ .isEqualTo(MediaRoute2Info.TYPE_BLUETOOTH_A2DP);
+
+ addAvailableAudioDeviceInfo(
+ /* newSelectedDevice= */ null, // Selected device doesn't change.
+ /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET);
+ assertThat(mControllerUnderTest.getSelectedRoute().getType())
+ .isEqualTo(MediaRoute2Info.TYPE_BLUETOOTH_A2DP);
+ }
+
+ @Test
+ public void getSelectedRoute_afterDeviceRemovals_returnsExpectedRoutes() {
+ addAvailableAudioDeviceInfo(
+ /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET,
+ /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP,
+ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET);
+ verify(mOnDeviceRouteChangedListener).onDeviceRouteChanged();
+
+ addAvailableAudioDeviceInfo(
+ /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP,
+ /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP);
+ verify(mOnDeviceRouteChangedListener, times(2)).onDeviceRouteChanged();
+ assertThat(mControllerUnderTest.getSelectedRoute().getType())
+ .isEqualTo(MediaRoute2Info.TYPE_BLUETOOTH_A2DP);
+
+ removeAvailableAudioDeviceInfos(
+ /* newSelectedDevice= */ null,
+ /* devicesToRemove...= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET);
+ assertThat(mControllerUnderTest.getSelectedRoute().getType())
+ .isEqualTo(MediaRoute2Info.TYPE_BLUETOOTH_A2DP);
+
+ removeAvailableAudioDeviceInfos(
+ /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_BUILTIN_SPEAKER,
+ /* devicesToRemove...= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET);
+ assertThat(mControllerUnderTest.getSelectedRoute().getType())
+ .isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
+ }
+
+ @Test
+ public void onAudioDevicesAdded_clearsAudioRoutingPoliciesCorrectly() {
+ clearInvocations(mMockAudioManager);
+ addAvailableAudioDeviceInfo(
+ /* newSelectedDevice= */ null, // Selected device doesn't change.
+ /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_BUILTIN_EARPIECE);
+ verifyNoMoreInteractions(mMockAudioManager);
+
+ addAvailableAudioDeviceInfo(
+ /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET,
+ /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP);
+ verify(mMockAudioManager).removePreferredDeviceForStrategy(mMediaAudioProductStrategy);
+ }
+
+ @Test
+ public void getAvailableDevices_ignoresInvalidMediaOutputs() {
+ addAvailableAudioDeviceInfo(
+ /* newSelectedDevice= */ null, // Selected device doesn't change.
+ /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_BUILTIN_EARPIECE);
+ verifyNoMoreInteractions(mOnDeviceRouteChangedListener);
+ assertThat(
+ mControllerUnderTest.getAvailableRoutes().stream()
+ .map(MediaRoute2Info::getType)
+ .toList())
+ .containsExactly(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
+ assertThat(mControllerUnderTest.getSelectedRoute().getType())
+ .isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
+ }
+
+ @Test
+ public void transferTo_setsTheExpectedRoutingPolicy() {
+ addAvailableAudioDeviceInfo(
+ /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET,
+ /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP,
+ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET);
+ MediaRoute2Info builtInSpeakerRoute =
+ getAvailableRouteWithType(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
+ mControllerUnderTest.transferTo(builtInSpeakerRoute.getId());
+ verify(mMockAudioManager)
+ .setPreferredDeviceForStrategy(
+ mMediaAudioProductStrategy,
+ createAudioDeviceAttribute(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER));
+
+ MediaRoute2Info wiredHeadsetRoute =
+ getAvailableRouteWithType(MediaRoute2Info.TYPE_WIRED_HEADSET);
+ mControllerUnderTest.transferTo(wiredHeadsetRoute.getId());
+ verify(mMockAudioManager)
+ .setPreferredDeviceForStrategy(
+ mMediaAudioProductStrategy,
+ createAudioDeviceAttribute(AudioDeviceInfo.TYPE_WIRED_HEADSET));
+ }
+
+ @Test
+ public void updateVolume_propagatesCorrectlyToRouteInfo() {
+ when(mMockAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)).thenReturn(2);
+ when(mMockAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)).thenReturn(3);
+ when(mMockAudioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC)).thenReturn(1);
+ when(mMockAudioManager.isVolumeFixed()).thenReturn(false);
+ addAvailableAudioDeviceInfo(
+ /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET,
+ /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET);
+
+ MediaRoute2Info selectedRoute = mControllerUnderTest.getSelectedRoute();
+ assertThat(selectedRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADSET);
+ assertThat(selectedRoute.getVolume()).isEqualTo(2);
+ assertThat(selectedRoute.getVolumeMax()).isEqualTo(3);
+ assertThat(selectedRoute.getVolumeHandling())
+ .isEqualTo(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE);
+
+ MediaRoute2Info onlyTransferrableRoute =
+ mControllerUnderTest.getAvailableRoutes().stream()
+ .filter(it -> !it.equals(selectedRoute))
+ .findAny()
+ .orElseThrow();
+ assertThat(onlyTransferrableRoute.getType())
+ .isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
+ assertThat(onlyTransferrableRoute.getVolume()).isEqualTo(0);
+ assertThat(onlyTransferrableRoute.getVolumeMax()).isEqualTo(0);
+ assertThat(onlyTransferrableRoute.getVolume()).isEqualTo(0);
+ assertThat(onlyTransferrableRoute.getVolumeHandling())
+ .isEqualTo(MediaRoute2Info.PLAYBACK_VOLUME_FIXED);
+
+ when(mMockAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)).thenReturn(0);
+ when(mMockAudioManager.isVolumeFixed()).thenReturn(true);
+ mControllerUnderTest.updateVolume(0);
+ MediaRoute2Info newSelectedRoute = mControllerUnderTest.getSelectedRoute();
+ assertThat(newSelectedRoute.getVolume()).isEqualTo(0);
+ assertThat(newSelectedRoute.getVolumeHandling())
+ .isEqualTo(MediaRoute2Info.PLAYBACK_VOLUME_FIXED);
+ }
+
+ @Test
+ public void getAvailableRoutes_whenNoProductNameIsProvided_usesTypeToPopulateName() {
+ assertThat(mControllerUnderTest.getSelectedRoute().getName().toString())
+ .isEqualTo(FAKE_AUDIO_DEVICE_INFO_BUILTIN_SPEAKER.getProductName().toString());
+
+ addAvailableAudioDeviceInfo(
+ /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_NO_NAME,
+ /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_NO_NAME);
+
+ MediaRoute2Info selectedRoute = mControllerUnderTest.getSelectedRoute();
+ assertThat(selectedRoute.getName().toString()).isEqualTo(FAKE_ROUTE_NAME);
+ }
+
+ // Internal methods.
+
+ @NonNull
+ private MediaRoute2Info getAvailableRouteWithType(int type) {
+ return mControllerUnderTest.getAvailableRoutes().stream()
+ .filter(it -> it.getType() == type)
+ .findFirst()
+ .orElseThrow();
+ }
+
+ private void addAvailableAudioDeviceInfo(
+ @Nullable AudioDeviceInfo newSelectedDevice, AudioDeviceInfo... newAvailableDevices) {
+ Set<AudioDeviceInfo> newAvailableDeviceInfos = new HashSet<>(mAvailableAudioDeviceInfos);
+ newAvailableDeviceInfos.addAll(List.of(newAvailableDevices));
+ mAvailableAudioDeviceInfos = newAvailableDeviceInfos;
+ if (newSelectedDevice != null) {
+ mSelectedAudioDeviceInfo = newSelectedDevice;
+ }
+ updateMockAudioManagerState();
+ mAudioDeviceCallback.onAudioDevicesAdded(newAvailableDevices);
+ }
+
+ private void removeAvailableAudioDeviceInfos(
+ @Nullable AudioDeviceInfo newSelectedDevice, AudioDeviceInfo... devicesToRemove) {
+ Set<AudioDeviceInfo> newAvailableDeviceInfos = new HashSet<>(mAvailableAudioDeviceInfos);
+ List.of(devicesToRemove).forEach(newAvailableDeviceInfos::remove);
+ mAvailableAudioDeviceInfos = newAvailableDeviceInfos;
+ if (newSelectedDevice != null) {
+ mSelectedAudioDeviceInfo = newSelectedDevice;
+ }
+ updateMockAudioManagerState();
+ mAudioDeviceCallback.onAudioDevicesRemoved(devicesToRemove);
+ }
+
+ private void updateMockAudioManagerState() {
+ when(mMockAudioManager.getDevicesForAttributes(ATTRIBUTES_MEDIA))
+ .thenReturn(
+ List.of(createAudioDeviceAttribute(mSelectedAudioDeviceInfo.getType())));
+ when(mMockAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS))
+ .thenReturn(mAvailableAudioDeviceInfos.toArray(new AudioDeviceInfo[0]));
+ }
+
+ private static AudioDeviceAttributes createAudioDeviceAttribute(int type) {
+ // Address is unused.
+ return new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT, type, /* address= */ "");
+ }
+
+ private static AudioDeviceInfo createAudioDeviceInfo(
+ int type, @NonNull String name, @NonNull String address) {
+ return new AudioDeviceInfo(AudioDevicePort.createForTesting(type, name, address));
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index 3b83e3c..fc2e5b0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -40,6 +40,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
@@ -109,6 +110,7 @@
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.function.Supplier;
@RunWith(AndroidJUnit4.class)
@@ -126,6 +128,8 @@
private MockitoSession mMockingSession;
private String mPackageName;
+ private Map<String, Integer> mPackageCategories;
+ private Map<String, Integer> mPackageUids;
private TestLooper mTestLooper;
@Mock
private PackageManager mMockPackageManager;
@@ -229,34 +233,50 @@
.strictness(Strictness.WARN)
.startMocking();
mMockContext = new MockContext(InstrumentationRegistry.getContext());
+ mPackageCategories = new HashMap<>();
+ mPackageUids = new HashMap<>();
mPackageName = mMockContext.getPackageName();
- mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_GAME);
+ mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_GAME);
+ LocalServices.addService(PowerManagerInternal.class, mMockPowerManager);
+
+ mSetFlagsRule.enableFlags(Flags.FLAG_GAME_DEFAULT_FRAME_RATE);
+ mSetFlagsRule.disableFlags(Flags.FLAG_DISABLE_GAME_MODE_WHEN_APP_TOP);
+ }
+
+ private void mockAppCategory(String packageName, int packageUid,
+ @ApplicationInfo.Category int category)
+ throws Exception {
+ reset(mMockPackageManager);
+ mPackageCategories.put(packageName, category);
+ mPackageUids.put(packageName, packageUid);
+ final List<PackageInfo> packageInfos = new ArrayList<>();
+ for (Map.Entry<String, Integer> entry : mPackageCategories.entrySet()) {
+
+ packageName = entry.getKey();
+ packageUid = mPackageUids.get(packageName);
+ category = entry.getValue();
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.packageName = packageName;
+ applicationInfo.category = category;
+ when(mMockPackageManager.getApplicationInfoAsUser(eq(packageName), anyInt(), anyInt()))
+ .thenReturn(applicationInfo);
+
+ final PackageInfo pi = new PackageInfo();
+ pi.packageName = packageName;
+ pi.applicationInfo = applicationInfo;
+ packageInfos.add(pi);
+
+ when(mMockPackageManager.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(
+ packageUid);
+ when(mMockPackageManager.getPackagesForUid(packageUid)).thenReturn(
+ new String[]{packageName});
+ }
+ when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), anyInt()))
+ .thenReturn(packageInfos);
final Resources resources =
InstrumentationRegistry.getInstrumentation().getContext().getResources();
when(mMockPackageManager.getResourcesForApplication(anyString()))
.thenReturn(resources);
- when(mMockPackageManager.getPackageUidAsUser(mPackageName, USER_ID_1)).thenReturn(
- DEFAULT_PACKAGE_UID);
- LocalServices.addService(PowerManagerInternal.class, mMockPowerManager);
-
- mSetFlagsRule.enableFlags(Flags.FLAG_GAME_DEFAULT_FRAME_RATE);
- }
-
- private void mockAppCategory(String packageName, @ApplicationInfo.Category int category)
- throws Exception {
- reset(mMockPackageManager);
- final ApplicationInfo gameApplicationInfo = new ApplicationInfo();
- gameApplicationInfo.category = category;
- gameApplicationInfo.packageName = packageName;
- final PackageInfo pi = new PackageInfo();
- pi.packageName = packageName;
- pi.applicationInfo = gameApplicationInfo;
- final List<PackageInfo> packages = new ArrayList<>();
- packages.add(pi);
- when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), anyInt()))
- .thenReturn(packages);
- when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
- .thenReturn(gameApplicationInfo);
}
@After
@@ -508,7 +528,7 @@
@Test
public void testGetGameMode_nonGame() throws Exception {
- mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_AUDIO);
+ mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_AUDIO);
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
mockModifyGameModeGranted();
assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
@@ -780,7 +800,7 @@
@Test
public void testDeviceConfig_nonGame() throws Exception {
- mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_AUDIO);
+ mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_AUDIO);
mockDeviceConfigAll();
mockModifyGameModeGranted();
checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1));
@@ -1588,7 +1608,7 @@
@Test
public void testSetGameState_nonGame() throws Exception {
- mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_AUDIO);
+ mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_AUDIO);
mockDeviceConfigNone();
mockModifyGameModeGranted();
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
@@ -1611,14 +1631,14 @@
gameManagerService.addGameStateListener(mockListener);
verify(binder).linkToDeath(mDeathRecipientCaptor.capture(), anyInt());
- mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_AUDIO);
+ mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_AUDIO);
GameState gameState = new GameState(true, GameState.MODE_NONE);
gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
assertFalse(gameManagerService.mHandler.hasMessages(SET_GAME_STATE));
mTestLooper.dispatchAll();
verify(mockListener, never()).onGameStateChanged(anyString(), any(), anyInt());
- mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_GAME);
+ mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_GAME);
gameState = new GameState(true, GameState.MODE_NONE);
gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
assertTrue(gameManagerService.mHandler.hasMessages(SET_GAME_STATE));
@@ -1654,7 +1674,7 @@
gameManagerService.addGameStateListener(mockListener);
gameManagerService.removeGameStateListener(mockListener);
- mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_GAME);
+ mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_GAME);
GameState gameState = new GameState(false, GameState.MODE_CONTENT);
gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
assertTrue(gameManagerService.mHandler.hasMessages(SET_GAME_STATE));
@@ -1670,13 +1690,17 @@
return output;
}
- private void mockInterventionListForMultipleUsers() {
+ private void mockInterventionListForMultipleUsers() throws Exception {
final String[] packageNames = new String[]{"com.android.app0",
"com.android.app1", "com.android.app2"};
+ int i = 1;
+ for (String p : packageNames) {
+ mockAppCategory(p, DEFAULT_PACKAGE_UID + i++, ApplicationInfo.CATEGORY_GAME);
+ }
final ApplicationInfo[] applicationInfos = new ApplicationInfo[3];
final PackageInfo[] pis = new PackageInfo[3];
- for (int i = 0; i < 3; ++i) {
+ for (i = 0; i < 3; ++i) {
applicationInfos[i] = new ApplicationInfo();
applicationInfos[i].category = ApplicationInfo.CATEGORY_GAME;
applicationInfos[i].packageName = packageNames[i];
@@ -1717,7 +1741,6 @@
new Injector());
startUser(gameManagerService, USER_ID_1);
startUser(gameManagerService, USER_ID_2);
-
gameManagerService.setGameModeConfigOverride("com.android.app0", USER_ID_2,
GameManager.GAME_MODE_PERFORMANCE, "120", "0.6");
gameManagerService.setGameModeConfigOverride("com.android.app2", USER_ID_2,
@@ -1953,7 +1976,7 @@
@Test
public void testUpdateCustomGameModeConfiguration_nonGame() throws Exception {
- mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_IMAGE);
+ mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_IMAGE);
mockModifyGameModeGranted();
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
gameManagerService.updateCustomGameModeConfiguration(mPackageName,
@@ -2013,13 +2036,14 @@
}
@Test
- public void testWritingSettingFile_onShutdown() throws InterruptedException {
+ public void testWritingSettingFile_onShutdown() throws Exception {
mockModifyGameModeGranted();
mockDeviceConfigAll();
GameManagerService gameManagerService = new GameManagerService(mMockContext);
gameManagerService.onBootCompleted();
startUser(gameManagerService, USER_ID_1);
Thread.sleep(500);
+ mockAppCategory("com.android.app1", DEFAULT_PACKAGE_UID + 1, ApplicationInfo.CATEGORY_GAME);
gameManagerService.setGameModeConfigOverride("com.android.app1", USER_ID_1,
GameManager.GAME_MODE_BATTERY, "60", "0.5");
gameManagerService.setGameMode("com.android.app1", USER_ID_1,
@@ -2259,7 +2283,7 @@
when(DeviceConfig.getProperty(anyString(), anyString()))
.thenReturn(configString);
mockModifyGameModeGranted();
- mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_IMAGE);
+ mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_IMAGE);
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
@@ -2277,9 +2301,7 @@
mockModifyGameModeGranted();
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
String someGamePkg = "some.game";
- mockAppCategory(someGamePkg, ApplicationInfo.CATEGORY_GAME);
- when(mMockPackageManager.getPackageUidAsUser(someGamePkg, USER_ID_1)).thenReturn(
- DEFAULT_PACKAGE_UID + 1);
+ mockAppCategory(someGamePkg, DEFAULT_PACKAGE_UID + 1, ApplicationInfo.CATEGORY_GAME);
gameManagerService.setGameMode(someGamePkg, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
assertEquals(GameManager.GAME_MODE_PERFORMANCE,
gameManagerService.getGameMode(someGamePkg, USER_ID_1));
@@ -2307,24 +2329,11 @@
}
@Test
- public void testGamePowerMode_gamePackage() throws Exception {
- GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
- String[] packages = {mPackageName};
- when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
- gameManagerService.mUidObserver.onUidStateChanged(
- DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
- verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
- }
-
- @Test
public void testGamePowerMode_twoGames() throws Exception {
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
- String[] packages1 = {mPackageName};
- when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages1);
String someGamePkg = "some.game";
- String[] packages2 = {someGamePkg};
int somePackageId = DEFAULT_PACKAGE_UID + 1;
- when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2);
+ mockAppCategory(someGamePkg, somePackageId, ApplicationInfo.CATEGORY_GAME);
HashMap<Integer, Boolean> powerState = new HashMap<>();
doAnswer(inv -> powerState.put(inv.getArgument(0), inv.getArgument(1)))
.when(mMockPowerManager).setPowerMode(anyInt(), anyBoolean());
@@ -2333,6 +2342,7 @@
assertTrue(powerState.get(Mode.GAME));
gameManagerService.mUidObserver.onUidStateChanged(
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ assertFalse(powerState.get(Mode.GAME));
gameManagerService.mUidObserver.onUidStateChanged(
somePackageId, ActivityManager.PROCESS_STATE_TOP, 0, 0);
assertTrue(powerState.get(Mode.GAME));
@@ -2344,12 +2354,9 @@
@Test
public void testGamePowerMode_twoGamesOverlap() throws Exception {
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
- String[] packages1 = {mPackageName};
- when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages1);
String someGamePkg = "some.game";
- String[] packages2 = {someGamePkg};
int somePackageId = DEFAULT_PACKAGE_UID + 1;
- when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2);
+ mockAppCategory(someGamePkg, somePackageId, ApplicationInfo.CATEGORY_GAME);
gameManagerService.mUidObserver.onUidStateChanged(
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
gameManagerService.mUidObserver.onUidStateChanged(
@@ -2363,49 +2370,162 @@
}
@Test
- public void testGamePowerMode_released() throws Exception {
- GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
- String[] packages = {mPackageName};
- when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
- gameManagerService.mUidObserver.onUidStateChanged(
- DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
- gameManagerService.mUidObserver.onUidStateChanged(
- DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
- verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
- }
-
- @Test
public void testGamePowerMode_noPackage() throws Exception {
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
String[] packages = {};
when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
gameManagerService.mUidObserver.onUidStateChanged(
- DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
- verify(mMockPowerManager, times(0)).setPowerMode(Mode.GAME, true);
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
}
@Test
- public void testGamePowerMode_notAGamePackage() throws Exception {
- mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_IMAGE);
+ public void testGamePowerMode_gameAndNotGameApps_flagOn() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_DISABLE_GAME_MODE_WHEN_APP_TOP);
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
- String[] packages = {"someapp"};
- when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
+
+ String nonGamePkg1 = "not.game1";
+ int nonGameUid1 = DEFAULT_PACKAGE_UID + 1;
+ mockAppCategory(nonGamePkg1, nonGameUid1, ApplicationInfo.CATEGORY_IMAGE);
+
+ String nonGamePkg2 = "not.game2";
+ int nonGameUid2 = DEFAULT_PACKAGE_UID + 2;
+ mockAppCategory(nonGamePkg2, nonGameUid2, ApplicationInfo.CATEGORY_IMAGE);
+
+ String gamePkg1 = "game1";
+ int gameUid1 = DEFAULT_PACKAGE_UID + 3;
+ mockAppCategory(gamePkg1, gameUid1, ApplicationInfo.CATEGORY_GAME);
+
+ String gamePkg2 = "game2";
+ int gameUid2 = DEFAULT_PACKAGE_UID + 4;
+ mockAppCategory(gamePkg2, gameUid2, ApplicationInfo.CATEGORY_GAME);
+
+ // non-game1 top and background with no-op
gameManagerService.mUidObserver.onUidStateChanged(
- DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
- verify(mMockPowerManager, times(0)).setPowerMode(Mode.GAME, true);
+ nonGameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+ gameManagerService.mUidObserver.onUidStateChanged(
+ nonGameUid1, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // game1 top to enable game mode
+ gameManagerService.mUidObserver.onUidStateChanged(
+ gameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // non-game1 in foreground to disable game mode
+ gameManagerService.mUidObserver.onUidStateChanged(
+ nonGameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // non-game2 in foreground with no-op
+ gameManagerService.mUidObserver.onUidStateChanged(
+ nonGameUid2, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // game 2 in foreground with no-op
+ gameManagerService.mUidObserver.onUidStateChanged(
+ gameUid2, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // non-game 1 in background with no-op
+ gameManagerService.mUidObserver.onUidStateChanged(
+ nonGameUid1, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // non-game2 in background to resume game mode
+ gameManagerService.mUidObserver.onUidStateChanged(
+ nonGameUid2, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // game 1 in background with no-op
+ gameManagerService.mUidObserver.onUidStateChanged(
+ gameUid1, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // game 2 in background to stop game mode
+ gameManagerService.mUidObserver.onUidStateChanged(
+ gameUid2, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
}
@Test
- public void testGamePowerMode_notAGamePackageNotReleased() throws Exception {
- mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_IMAGE);
+ public void testGamePowerMode_gameAndNotGameApps_flagOff() throws Exception {
+ mSetFlagsRule.disableFlags(Flags.FLAG_DISABLE_GAME_MODE_WHEN_APP_TOP);
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
- String[] packages = {"someapp"};
- when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
+
+ String nonGamePkg1 = "not.game1";
+ int nonGameUid1 = DEFAULT_PACKAGE_UID + 1;
+ mockAppCategory(nonGamePkg1, nonGameUid1, ApplicationInfo.CATEGORY_IMAGE);
+
+ String gamePkg1 = "game1";
+ int gameUid1 = DEFAULT_PACKAGE_UID + 3;
+ mockAppCategory(gamePkg1, gameUid1, ApplicationInfo.CATEGORY_GAME);
+
+ // non-game1 top and background with no-op
gameManagerService.mUidObserver.onUidStateChanged(
- DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+ nonGameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0);
gameManagerService.mUidObserver.onUidStateChanged(
- DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
- verify(mMockPowerManager, times(0)).setPowerMode(Mode.GAME, false);
+ nonGameUid1, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // game1 top to enable game mode
+ gameManagerService.mUidObserver.onUidStateChanged(
+ gameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // non-game1 in foreground to not interfere
+ gameManagerService.mUidObserver.onUidStateChanged(
+ nonGameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // non-game 1 in background to not interfere
+ gameManagerService.mUidObserver.onUidStateChanged(
+ nonGameUid1, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // move non-game1 to foreground again
+ gameManagerService.mUidObserver.onUidStateChanged(
+ nonGameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+
+ // with non-game1 on top, game 1 in background to still disable game mode
+ gameManagerService.mUidObserver.onUidStateChanged(
+ gameUid1, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // with non-game1 on top, game 1 in foreground to still enable game mode
+ gameManagerService.mUidObserver.onUidStateChanged(
+ gameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
}
@Test
@@ -2432,8 +2552,6 @@
gameManagerService.onBootCompleted();
// Set up a game in the foreground.
- String[] packages = {mPackageName};
- when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
gameManagerService.mUidObserver.onUidStateChanged(
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
@@ -2448,10 +2566,8 @@
// Adding another game to the foreground.
String anotherGamePkg = "another.game";
- String[] packages2 = {anotherGamePkg};
- mockAppCategory(anotherGamePkg, ApplicationInfo.CATEGORY_GAME);
int somePackageId = DEFAULT_PACKAGE_UID + 1;
- when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2);
+ mockAppCategory(anotherGamePkg, somePackageId, ApplicationInfo.CATEGORY_GAME);
gameManagerService.mUidObserver.onUidStateChanged(
somePackageId, ActivityManager.PROCESS_STATE_TOP, 0, 0);
@@ -2484,8 +2600,6 @@
}));
// Set up a game in the foreground.
- String[] packages = {mPackageName};
- when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
gameManagerService.mUidObserver.onUidStateChanged(
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
@@ -2503,10 +2617,8 @@
// Toggle game default frame rate off.
String anotherGamePkg = "another.game";
- String[] packages2 = {anotherGamePkg};
- mockAppCategory(anotherGamePkg, ApplicationInfo.CATEGORY_GAME);
int somePackageId = DEFAULT_PACKAGE_UID + 1;
- when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2);
+ mockAppCategory(anotherGamePkg, somePackageId, ApplicationInfo.CATEGORY_GAME);
gameManagerService.mUidObserver.onUidStateChanged(
somePackageId, ActivityManager.PROCESS_STATE_TOP, 0, 0);
gameManagerService.mUidObserver.onUidStateChanged(
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
index c2b52b4..57326b2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
@@ -50,9 +50,10 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.pm.parsing.PackageParser2;
+import com.android.internal.pm.parsing.PackageParserException;
import com.android.internal.pm.parsing.pkg.AndroidPackageLegacyUtils;
import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.pkg.AndroidPackage;
import org.junit.Before;
@@ -175,7 +176,7 @@
mPmService.getPlatformPackage(), /* isUpdatedSystemApp */ false);
// isUpdatedSystemApp is ignoreable above, only used for shared library adjustment
return parsedPackage.hideAsFinal();
- } catch (PackageManagerException e) {
+ } catch (PackageParserException e) {
throw new RuntimeException(e);
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 7b29e2a..538c0ee 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -56,6 +56,7 @@
import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder
import com.android.internal.R
+import com.android.internal.pm.parsing.PackageParser2
import com.android.internal.pm.parsing.pkg.PackageImpl
import com.android.internal.pm.parsing.pkg.ParsedPackage
import com.android.internal.pm.pkg.parsing.ParsingPackage
@@ -69,7 +70,6 @@
import com.android.server.extendedtestutils.wheneverStatic
import com.android.server.pm.dex.DexManager
import com.android.server.pm.dex.DynamicCodeLogger
-import com.android.server.pm.parsing.PackageParser2
import com.android.server.pm.permission.PermissionManagerServiceInternal
import com.android.server.pm.pkg.AndroidPackage
import com.android.server.pm.resolution.ComponentResolver
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
index da929af..7feafef 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
@@ -21,6 +21,7 @@
import android.os.Build
import android.os.Process
import android.util.Log
+import com.android.internal.pm.parsing.PackageParserException
import com.android.server.pm.pkg.AndroidPackage
import com.android.server.testutils.whenever
import java.io.File
@@ -120,7 +121,7 @@
argThat { path: File -> path.path.contains("a.data.package") },
anyInt(),
anyBoolean()))
- .thenThrow(PackageManagerException(
+ .thenThrow(PackageParserException(
PackageManager.INSTALL_FAILED_INVALID_APK, "Oh no!"))
val pm = createPackageManagerService()
verify(rule.mocks().settings, Mockito.never()).insertPackageSettingLPw(
diff --git a/services/tests/servicestests/src/com/android/server/OWNERS b/services/tests/servicestests/src/com/android/server/OWNERS
index 2d36ff3..d49bc43 100644
--- a/services/tests/servicestests/src/com/android/server/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/OWNERS
@@ -7,3 +7,4 @@
per-file BatteryServiceTest.java = file:platform/hardware/interfaces:/health/OWNERS
per-file GestureLauncherServiceTest.java = file:platform/packages/apps/EmergencyInfo:/OWNERS
per-file PinnerServiceTest.java = file:/apct-tests/perftests/OWNERS
+per-file SecurityStateTest.java = file:/SECURITY_STATE_OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/audio/FadeConfigurationsTest.java b/services/tests/servicestests/src/com/android/server/audio/FadeConfigurationsTest.java
index 6fca561..69817ad 100644
--- a/services/tests/servicestests/src/com/android/server/audio/FadeConfigurationsTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/FadeConfigurationsTest.java
@@ -16,17 +16,21 @@
package com.android.server.audio;
-import static android.media.AudioAttributes.USAGE_MEDIA;
-import static android.media.AudioAttributes.USAGE_GAME;
-import static android.media.AudioAttributes.USAGE_ASSISTANT;
import static android.media.AudioAttributes.CONTENT_TYPE_SPEECH;
+import static android.media.AudioAttributes.USAGE_ASSISTANT;
+import static android.media.AudioAttributes.USAGE_EMERGENCY;
+import static android.media.AudioAttributes.USAGE_GAME;
+import static android.media.AudioAttributes.USAGE_MEDIA;
import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_AAUDIO;
import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL;
import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_JAM_AUDIOTRACK;
import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_UNKNOWN;
+import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
import android.media.AudioAttributes;
+import android.media.FadeManagerConfiguration;
import android.media.VolumeShaper;
+import android.platform.test.flag.junit.SetFlagsRule;
import com.google.common.truth.Expect;
@@ -42,6 +46,7 @@
public final class FadeConfigurationsTest {
private FadeConfigurations mFadeConfigurations;
private static final long DEFAULT_FADE_OUT_DURATION_MS = 2_000;
+ private static final long DEFAULT_FADE_IN_DURATION_MS = 1_000;
private static final long DEFAULT_DELAY_FADE_IN_OFFENDERS_MS = 2000;
private static final long DURATION_FOR_UNFADEABLE_MS = 0;
private static final int TEST_UID_SYSTEM = 1000;
@@ -60,11 +65,19 @@
private static final VolumeShaper.Configuration DEFAULT_FADEOUT_VSHAPE =
new VolumeShaper.Configuration.Builder()
.setId(PlaybackActivityMonitor.VOLUME_SHAPER_SYSTEM_FADEOUT_ID)
- .setCurve(/* times= */new float[]{0.f, 0.25f, 1.0f} ,
- /* volumes= */new float[]{1.f, 0.65f, 0.0f})
+ .setCurve(/* times= */ new float[]{0.f, 0.25f, 1.0f},
+ /* volumes= */ new float[]{1.f, 0.65f, 0.0f})
.setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
.setDuration(DEFAULT_FADE_OUT_DURATION_MS)
.build();
+ private static final VolumeShaper.Configuration DEFAULT_FADEIN_VSHAPE =
+ new VolumeShaper.Configuration.Builder()
+ .setId(PlaybackActivityMonitor.VOLUME_SHAPER_SYSTEM_FADEOUT_ID)
+ .setCurve(/* times= */ new float[]{0.f, 0.50f, 1.0f},
+ /* volumes= */ new float[]{0.f, 0.30f, 1.0f})
+ .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
+ .setDuration(DEFAULT_FADE_IN_DURATION_MS)
+ .build();
private static final AudioAttributes TEST_MEDIA_AUDIO_ATTRIBUTE =
new AudioAttributes.Builder().setUsage(USAGE_MEDIA).build();
@@ -72,12 +85,18 @@
new AudioAttributes.Builder().setUsage(USAGE_GAME).build();
private static final AudioAttributes TEST_ASSISTANT_AUDIO_ATTRIBUTE =
new AudioAttributes.Builder().setUsage(USAGE_ASSISTANT).build();
+ private static final AudioAttributes TEST_EMERGENCY_AUDIO_ATTRIBUTE =
+ new AudioAttributes.Builder().setSystemUsage(USAGE_EMERGENCY).build();
+
private static final AudioAttributes TEST_SPEECH_AUDIO_ATTRIBUTE =
new AudioAttributes.Builder().setContentType(CONTENT_TYPE_SPEECH).build();
@Rule
public final Expect expect = Expect.create();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Before
public void setUp() {
mFadeConfigurations = new FadeConfigurations();
@@ -156,4 +175,110 @@
.that(mFadeConfigurations.isFadeable(TEST_GAME_AUDIO_ATTRIBUTE, TEST_UID_USER,
PLAYER_TYPE_AAUDIO)).isFalse();
}
+
+ @Test
+ public void testGetFadeableUsages_withFadeManagerConfigurations_equals() {
+ mSetFlagsRule.enableFlags(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION);
+ List<Integer> usageList = List.of(AudioAttributes.USAGE_ALARM,
+ AudioAttributes.USAGE_EMERGENCY);
+ FadeManagerConfiguration fmc = createFadeMgrConfig(/* fadeableUsages= */ usageList,
+ /* unfadeableContentTypes= */ null, /* unfadeableUids= */ null,
+ /* unfadeableAudioAttrs= */ null);
+ FadeConfigurations fadeConfigs = new FadeConfigurations();
+
+ fadeConfigs.setFadeManagerConfiguration(fmc);
+
+ expect.withMessage("Fadeable usages with fade manager configuration")
+ .that(fadeConfigs.getFadeableUsages()).isEqualTo(fmc.getFadeableUsages());
+ }
+
+ @Test
+ public void testGetUnfadeableContentTypes_withFadeManagerConfigurations_equals() {
+ mSetFlagsRule.enableFlags(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION);
+ List<Integer> contentTypesList = List.of(AudioAttributes.CONTENT_TYPE_MUSIC,
+ AudioAttributes.CONTENT_TYPE_MOVIE);
+ FadeManagerConfiguration fmc = createFadeMgrConfig(/* fadeableUsages= */ null,
+ /* unfadeableContentTypes= */ contentTypesList, /* unfadeableUids= */ null,
+ /* unfadeableAudioAttrs= */ null);
+ FadeConfigurations fadeConfigs = new FadeConfigurations();
+
+ fadeConfigs.setFadeManagerConfiguration(fmc);
+
+ expect.withMessage("Unfadeable content types with fade manager configuration")
+ .that(fadeConfigs.getUnfadeableContentTypes())
+ .isEqualTo(fmc.getUnfadeableContentTypes());
+ }
+
+ @Test
+ public void testGetUnfadeableAudioAttributes_withFadeManagerConfigurations_equals() {
+ mSetFlagsRule.enableFlags(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION);
+ List<AudioAttributes> attrsList = List.of(TEST_ASSISTANT_AUDIO_ATTRIBUTE,
+ TEST_EMERGENCY_AUDIO_ATTRIBUTE);
+ FadeManagerConfiguration fmc = createFadeMgrConfig(/* fadeableUsages= */ null,
+ /* unfadeableContentTypes= */ null, /* unfadeableUids= */ null,
+ /* unfadeableAudioAttrs= */ attrsList);
+ FadeConfigurations fadeConfigs = new FadeConfigurations();
+
+ fadeConfigs.setFadeManagerConfiguration(fmc);
+
+ expect.withMessage("Unfadeable audio attributes with fade manager configuration")
+ .that(fadeConfigs.getUnfadeableAudioAttributes())
+ .isEqualTo(fmc.getUnfadeableAudioAttributes());
+ }
+
+ @Test
+ public void testGetUnfadeableUids_withFadeManagerConfigurations_equals() {
+ mSetFlagsRule.enableFlags(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION);
+ List<Integer> uidsList = List.of(TEST_UID_SYSTEM, TEST_UID_USER);
+ FadeManagerConfiguration fmc = createFadeMgrConfig(/* fadeableUsages= */ null,
+ /* unfadeableContentTypes= */ null, /* unfadeableUids= */ uidsList,
+ /* unfadeableAudioAttrs= */ null);
+ FadeConfigurations fadeConfigs = new FadeConfigurations();
+
+ fadeConfigs.setFadeManagerConfiguration(fmc);
+
+ expect.withMessage("Unfadeable uids with fade manager configuration")
+ .that(fadeConfigs.getUnfadeableUids()).isEqualTo(fmc.getUnfadeableUids());
+ }
+
+ private static FadeManagerConfiguration createFadeMgrConfig(List<Integer> fadeableUsages,
+ List<Integer> unfadeableContentTypes, List<Integer> unfadeableUids,
+ List<AudioAttributes> unfadeableAudioAttrs) {
+ FadeManagerConfiguration.Builder builder = new FadeManagerConfiguration.Builder();
+ if (fadeableUsages != null) {
+ builder.setFadeableUsages(fadeableUsages);
+ }
+ if (unfadeableContentTypes != null) {
+ builder.setUnfadeableContentTypes(unfadeableContentTypes);
+ }
+ if (unfadeableUids != null) {
+ builder.setUnfadeableUids(unfadeableUids);
+ }
+ if (unfadeableAudioAttrs != null) {
+ builder.setUnfadeableAudioAttributes(unfadeableAudioAttrs);
+ }
+ if (fadeableUsages != null) {
+ for (int index = 0; index < fadeableUsages.size(); index++) {
+ builder.setFadeOutVolumeShaperConfigForAudioAttributes(
+ createGenericAudioAttributesForUsage(fadeableUsages.get(index)),
+ DEFAULT_FADEOUT_VSHAPE);
+ }
+ }
+ if (fadeableUsages != null) {
+ for (int index = 0; index < fadeableUsages.size(); index++) {
+ builder.setFadeInVolumeShaperConfigForAudioAttributes(
+ createGenericAudioAttributesForUsage(fadeableUsages.get(index)),
+ DEFAULT_FADEIN_VSHAPE);
+ }
+ }
+
+ return builder.build();
+ }
+
+ private static AudioAttributes createGenericAudioAttributesForUsage(int usage) {
+ if (AudioAttributes.isSystemUsage(usage)) {
+ return new AudioAttributes.Builder().setSystemUsage(usage).build();
+ }
+ return new AudioAttributes.Builder().setUsage(usage).build();
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java b/services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java
index 65059d5..fa94821 100644
--- a/services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java
@@ -23,8 +23,6 @@
import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_JAM_AUDIOTRACK;
import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_UNKNOWN;
-import static org.junit.Assert.assertThrows;
-
import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioManager;
@@ -75,20 +73,11 @@
@Before
public void setUp() {
- mFadeOutManager = new FadeOutManager(new FadeConfigurations());
+ mFadeOutManager = new FadeOutManager();
mContext = ApplicationProvider.getApplicationContext();
}
@Test
- public void constructor_nullFadeConfigurations_fails() {
- Throwable thrown = assertThrows(NullPointerException.class, () -> new FadeOutManager(
- /* FadeConfigurations= */ null));
-
- expect.withMessage("Constructor exception")
- .that(thrown).hasMessageThat().contains("Fade configurations can not be null");
- }
-
- @Test
public void testCanCauseFadeOut_forFaders_returnsTrue() {
FocusRequester winner = createFocusRequester(TEST_MEDIA_AUDIO_ATTRIBUTE, "winning-client",
"unit-test", TEST_UID_USER,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index 3b5cae3..88b2ed4 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -50,14 +50,22 @@
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.biometrics.PromptInfo;
+import android.hardware.biometrics.fingerprint.SensorProps;
+import android.hardware.face.FaceSensorConfigurations;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.IFaceService;
+import android.hardware.fingerprint.FingerprintSensorConfigurations;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintService;
import android.hardware.iris.IIrisService;
import android.os.Binder;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.InstrumentationRegistry;
@@ -89,6 +97,9 @@
@Rule
public MockitoRule mockitorule = MockitoJUnit.rule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -118,6 +129,10 @@
@Captor
private ArgumentCaptor<List<FingerprintSensorPropertiesInternal>> mFingerprintPropsCaptor;
@Captor
+ private ArgumentCaptor<FingerprintSensorConfigurations> mFingerprintSensorConfigurationsCaptor;
+ @Captor
+ private ArgumentCaptor<FaceSensorConfigurations> mFaceSensorConfigurationsCaptor;
+ @Captor
private ArgumentCaptor<List<FaceSensorPropertiesInternal>> mFacePropsCaptor;
@Before
@@ -143,6 +158,9 @@
when(mContext.getResources()).thenReturn(mResources);
when(mInjector.getBiometricService()).thenReturn(mBiometricService);
when(mInjector.getConfiguration(any())).thenReturn(config);
+ when(mInjector.getFaceConfiguration(any())).thenReturn(config);
+ when(mInjector.getFingerprintConfiguration(any())).thenReturn(config);
+ when(mInjector.getIrisConfiguration(any())).thenReturn(config);
when(mInjector.getFingerprintService()).thenReturn(mFingerprintService);
when(mInjector.getFaceService()).thenReturn(mFaceService);
when(mInjector.getIrisService()).thenReturn(mIrisService);
@@ -173,12 +191,13 @@
}
@Test
+ @RequiresFlagsDisabled(com.android.server.biometrics.Flags.FLAG_DE_HIDL)
public void testRegisterAuthenticator_registerAuthenticators() throws Exception {
final int fingerprintId = 0;
final int fingerprintStrength = 15;
final int faceId = 1;
- final int faceStrength = 4095;
+ final int faceStrength = 15;
final String[] config = {
// ID0:Fingerprint:Strong
@@ -206,6 +225,51 @@
Utils.authenticatorStrengthToPropertyStrength(faceStrength));
}
+ @Test
+ @RequiresFlagsEnabled(com.android.server.biometrics.Flags.FLAG_DE_HIDL)
+ public void testRegisterAuthenticator_registerAuthenticatorsLegacy() throws RemoteException {
+ final int fingerprintId = 0;
+ final int fingerprintStrength = 15;
+
+ final int faceId = 1;
+ final int faceStrength = 4095;
+
+ final String[] config = {
+ // ID0:Fingerprint:Strong
+ String.format("%d:2:%d", fingerprintId, fingerprintStrength),
+ // ID2:Face:Convenience
+ String.format("%d:8:%d", faceId, faceStrength)
+ };
+
+ when(mInjector.getFingerprintConfiguration(any())).thenReturn(config);
+ when(mInjector.getFaceConfiguration(any())).thenReturn(config);
+ when(mInjector.getFingerprintAidlInstances()).thenReturn(new String[]{});
+ when(mInjector.getFaceAidlInstances()).thenReturn(new String[]{});
+
+ mAuthService = new AuthService(mContext, mInjector);
+ mAuthService.onStart();
+
+ verify(mFingerprintService).registerAuthenticatorsLegacy(
+ mFingerprintSensorConfigurationsCaptor.capture());
+
+ final SensorProps[] fingerprintProp = mFingerprintSensorConfigurationsCaptor.getValue()
+ .getSensorPairForInstance("defaultHIDL").second;
+
+ assertEquals(fingerprintProp[0].commonProps.sensorId, fingerprintId);
+ assertEquals(fingerprintProp[0].commonProps.sensorStrength,
+ Utils.authenticatorStrengthToPropertyStrength(fingerprintStrength));
+
+ verify(mFaceService).registerAuthenticatorsLegacy(
+ mFaceSensorConfigurationsCaptor.capture());
+
+ final android.hardware.biometrics.face.SensorProps[] faceProp =
+ mFaceSensorConfigurationsCaptor.getValue()
+ .getSensorPairForInstance("defaultHIDL").second;
+
+ assertEquals(faceProp[0].commonProps.sensorId, faceId);
+ assertEquals(faceProp[0].commonProps.sensorStrength,
+ Utils.authenticatorStrengthToPropertyStrength(faceStrength));
+ }
// TODO(b/141025588): Check that an exception is thrown when the userId != callingUserId
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
index 94cb860..74f8f08 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
@@ -29,7 +29,7 @@
import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.filters.SmallTest;
@@ -43,10 +43,10 @@
import java.util.ArrayList;
import java.util.List;
-@RequiresFlagsDisabled(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
@Presubmit
@SmallTest
public class SensorOverlaysTest {
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final int SENSOR_ID = 11;
private static final long REQUEST_ID = 8;
@@ -59,6 +59,7 @@
@Before
public void setup() {
+ mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR);
when(mAcquisitionClient.getRequestId()).thenReturn(REQUEST_ID);
when(mAcquisitionClient.hasRequestId()).thenReturn(true);
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
new file mode 100644
index 0000000..c9e1c4a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.biometrics.sensors.face;
+
+import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
+import static android.hardware.face.FaceSensorProperties.TYPE_UNKNOWN;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.face.SensorProps;
+import android.hardware.face.FaceSensorConfigurations;
+import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.provider.Settings;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
+import com.android.server.biometrics.Flags;
+import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.face.aidl.FaceProvider;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@Presubmit
+@SmallTest
+public class FaceServiceTest {
+ private static final int ID_DEFAULT = 2;
+ private static final int ID_VIRTUAL = 6;
+ private static final String NAME_DEFAULT = "default";
+ private static final String NAME_VIRTUAL = "virtual";
+
+ @Rule
+ public final MockitoRule mMockito = MockitoJUnit.rule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+ @Rule
+ public final TestableContext mContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+ @Rule
+ public final FakeSettingsProviderRule mSettingsRule = FakeSettingsProvider.rule();
+ @Mock
+ FaceProvider mFaceProviderDefault;
+ @Mock
+ FaceProvider mFaceProviderVirtual;
+ @Mock
+ IFace mDefaultFaceDaemon;
+ @Mock
+ IFace mVirtualFaceDaemon;
+ @Mock
+ IBiometricService mIBiometricService;
+
+ private final SensorProps mDefaultSensorProps = new SensorProps();
+ private final SensorProps mVirtualSensorProps = new SensorProps();
+ private FaceService mFaceService;
+ private final FaceSensorPropertiesInternal mSensorPropsDefault =
+ new FaceSensorPropertiesInternal(ID_DEFAULT, STRENGTH_STRONG,
+ 2 /* maxEnrollmentsPerUser */,
+ List.of(),
+ TYPE_UNKNOWN,
+ true /* supportsFaceDetection */,
+ true /* supportsSelfIllumination */,
+ false /* resetLockoutRequiresChallenge */);
+ private final FaceSensorPropertiesInternal mSensorPropsVirtual =
+ new FaceSensorPropertiesInternal(ID_VIRTUAL, STRENGTH_STRONG,
+ 2 /* maxEnrollmentsPerUser */,
+ List.of(),
+ TYPE_UNKNOWN,
+ true /* supportsFaceDetection */,
+ true /* supportsSelfIllumination */,
+ false /* resetLockoutRequiresChallenge */);
+ private FaceSensorConfigurations mFaceSensorConfigurations;
+
+ @Before
+ public void setUp() throws RemoteException {
+ when(mDefaultFaceDaemon.getSensorProps()).thenReturn(
+ new SensorProps[]{mDefaultSensorProps});
+ when(mVirtualFaceDaemon.getSensorProps()).thenReturn(
+ new SensorProps[]{mVirtualSensorProps});
+ when(mFaceProviderDefault.getSensorProperties()).thenReturn(List.of(mSensorPropsDefault));
+ when(mFaceProviderVirtual.getSensorProperties()).thenReturn(List.of(mSensorPropsVirtual));
+
+ mFaceSensorConfigurations = new FaceSensorConfigurations(false);
+ mFaceSensorConfigurations.addAidlConfigs(new String[]{NAME_DEFAULT, NAME_VIRTUAL},
+ (name) -> {
+ if (name.equals(IFace.DESCRIPTOR + "/" + NAME_DEFAULT)) {
+ return mDefaultFaceDaemon;
+ } else if (name.equals(IFace.DESCRIPTOR + "/" + NAME_VIRTUAL)) {
+ return mVirtualFaceDaemon;
+ }
+ return null;
+ });
+ }
+
+ private void initService() {
+ mFaceService = new FaceService(mContext,
+ (filteredSensorProps, resetLockoutRequiresChallenge) -> {
+ if (NAME_DEFAULT.equals(filteredSensorProps.first)) return mFaceProviderDefault;
+ if (NAME_VIRTUAL.equals(filteredSensorProps.first)) return mFaceProviderVirtual;
+ return null;
+ }, () -> mIBiometricService);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+ public void registerAuthenticatorsLegacy_defaultOnly() throws Exception {
+ initService();
+
+ mFaceService.mServiceWrapper.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+ waitForRegistration();
+
+ verify(mIBiometricService).registerAuthenticator(eq(ID_DEFAULT),
+ eq(BiometricAuthenticator.TYPE_FACE),
+ eq(Utils.propertyStrengthToAuthenticatorStrength(STRENGTH_STRONG)),
+ any());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+ public void registerAuthenticatorsLegacy_virtualOnly() throws Exception {
+ initService();
+ Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
+ Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 1);
+
+ mFaceService.mServiceWrapper.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+ waitForRegistration();
+
+ verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL),
+ eq(BiometricAuthenticator.TYPE_FACE),
+ eq(Utils.propertyStrengthToAuthenticatorStrength(STRENGTH_STRONG)), any());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+ public void registerAuthenticatorsLegacy_virtualAlwaysWhenNoOther() throws Exception {
+ mFaceSensorConfigurations = new FaceSensorConfigurations(false);
+ mFaceSensorConfigurations.addAidlConfigs(new String[]{NAME_VIRTUAL},
+ (name) -> {
+ if (name.equals(IFace.DESCRIPTOR + "/" + NAME_DEFAULT)) {
+ return mDefaultFaceDaemon;
+ } else if (name.equals(IFace.DESCRIPTOR + "/" + NAME_VIRTUAL)) {
+ return mVirtualFaceDaemon;
+ }
+ return null;
+ });
+ initService();
+
+ mFaceService.mServiceWrapper.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+ waitForRegistration();
+
+ verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL),
+ eq(BiometricAuthenticator.TYPE_FACE),
+ eq(Utils.propertyStrengthToAuthenticatorStrength(STRENGTH_STRONG)), any());
+ }
+
+ private void waitForRegistration() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ mFaceService.mServiceWrapper.addAuthenticatorsRegisteredCallback(
+ new IFaceAuthenticatorsRegisteredCallback.Stub() {
+ public void onAllAuthenticatorsRegistered(
+ List<FaceSensorPropertiesInternal> sensors) {
+ latch.countDown();
+ }
+ });
+ latch.await(10, TimeUnit.SECONDS);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index f43120d..8b1a291 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -16,6 +16,9 @@
package com.android.server.biometrics.sensors.face.aidl;
+import static android.os.UserHandle.USER_NULL;
+import static android.os.UserHandle.USER_SYSTEM;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
@@ -32,15 +35,19 @@
import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.ISession;
import android.hardware.biometrics.face.SensorProps;
+import android.hardware.face.HidlFaceSensorConfig;
import android.os.RemoteException;
-import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import com.android.internal.R;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
@@ -49,6 +56,7 @@
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -59,6 +67,10 @@
@SmallTest
public class FaceProviderTest {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
private static final String TAG = "FaceProviderTest";
private static final float FRR_THRESHOLD = 0.2f;
@@ -109,7 +121,7 @@
mFaceProvider = new FaceProvider(mContext, mBiometricStateCallback,
mSensorProps, TAG, mLockoutResetDispatcher, mBiometricContext,
- mDaemon);
+ mDaemon, false /* resetLockoutRequiresChallenge */, false /* testHalEnabled */);
}
@Test
@@ -124,10 +136,38 @@
assertThat(currentClient).isInstanceOf(FaceInternalCleanupClient.class);
assertThat(currentClient.getSensorId()).isEqualTo(prop.commonProps.sensorId);
- assertThat(currentClient.getTargetUserId()).isEqualTo(UserHandle.USER_SYSTEM);
+ assertThat(currentClient.getTargetUserId()).isEqualTo(USER_SYSTEM);
}
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+ public void testAddingHidlSensors() {
+ when(mResources.getIntArray(anyInt())).thenReturn(new int[]{});
+ when(mResources.getBoolean(anyInt())).thenReturn(false);
+
+ final int faceId = 0;
+ final int faceStrength = 15;
+ final String config = String.format("%d:8:%d", faceId, faceStrength);
+ final HidlFaceSensorConfig faceSensorConfig = new HidlFaceSensorConfig();
+ faceSensorConfig.parse(config, mContext);
+ final HidlFaceSensorConfig[] hidlFaceSensorConfig =
+ new HidlFaceSensorConfig[]{faceSensorConfig};
+ mFaceProvider = new FaceProvider(mContext,
+ mBiometricStateCallback, hidlFaceSensorConfig, TAG,
+ mLockoutResetDispatcher, mBiometricContext, mDaemon,
+ true /* resetLockoutRequiresChallenge */,
+ true /* testHalEnabled */);
+
+ assertThat(mFaceProvider.mFaceSensors.get(faceId)
+ .getLazySession().get().getUserId()).isEqualTo(USER_NULL);
+
+ waitForIdle();
+
+ assertThat(mFaceProvider.mFaceSensors.get(faceId)
+ .getLazySession().get().getUserId()).isEqualTo(USER_SYSTEM);
+ }
+
@SuppressWarnings("rawtypes")
@Test
public void halServiceDied_resetsAllSchedulers() {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
index 7a293e8..e7f7195 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
@@ -157,7 +157,7 @@
sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */);
final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext,
null /* handler */, internalProp, mLockoutResetDispatcher, mBiometricContext);
-
+ sensor.init(mLockoutResetDispatcher, mFaceProvider);
mScheduler.reset();
assertNull(mScheduler.getCurrentClient());
@@ -185,6 +185,7 @@
sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */);
final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext, null,
internalProp, mLockoutResetDispatcher, mBiometricContext, mCurrentSession);
+ sensor.init(mLockoutResetDispatcher, mFaceProvider);
mScheduler = (UserAwareBiometricScheduler) sensor.getScheduler();
sensor.mCurrentSession = new AidlSession(0, mock(ISession.class),
USER_ID, mHalCallback);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java
new file mode 100644
index 0000000..4e43332
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.biometrics.sensors.face.hidl;
+
+import static com.android.server.biometrics.sensors.face.hidl.HidlToAidlSessionAdapter.ENROLL_TIMEOUT_SEC;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.hardware.biometrics.face.V1_0.OptionalUint64;
+import android.hardware.biometrics.face.V1_0.Status;
+import android.hardware.face.Face;
+import android.hardware.face.HidlFaceSensorConfig;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+import android.testing.TestableContext;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler;
+import com.android.server.biometrics.sensors.face.aidl.FaceEnrollClient;
+import com.android.server.biometrics.sensors.face.aidl.FaceProvider;
+import com.android.server.biometrics.sensors.face.aidl.FaceResetLockoutClient;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+public class HidlToAidlSensorAdapterTest {
+ private static final String TAG = "HidlToAidlSensorAdapterTest";
+ private static final int USER_ID = 2;
+ private static final int SENSOR_ID = 4;
+ private static final byte[] HAT = new byte[69];
+ @Rule
+ public final MockitoRule mMockito = MockitoJUnit.rule();
+
+ @Mock
+ private IBiometricService mBiometricService;
+ @Mock
+ private LockoutResetDispatcher mLockoutResetDispatcherForSensor;
+ @Mock
+ private LockoutResetDispatcher mLockoutResetDispatcherForClient;
+ @Mock
+ private BiometricLogger mLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+ @Mock
+ private AuthSessionCoordinator mAuthSessionCoordinator;
+ @Mock
+ private FaceProvider mFaceProvider;
+ @Mock
+ private Runnable mInternalCleanupAndGetFeatureRunnable;
+ @Mock
+ private IBiometricsFace mDaemon;
+ @Mock
+ AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
+ @Mock
+ BiometricUtils<Face> mBiometricUtils;
+
+ private final TestLooper mLooper = new TestLooper();
+ private HidlToAidlSensorAdapter mHidlToAidlSensorAdapter;
+ private final TestableContext mContext = new TestableContext(
+ ApplicationProvider.getApplicationContext());
+
+ @Before
+ public void setUp() throws RemoteException {
+ final OptionalUint64 result = new OptionalUint64();
+ result.status = Status.OK;
+
+ when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
+ when(mDaemon.setCallback(any())).thenReturn(result);
+ doAnswer((answer) -> {
+ mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback()
+ .onLockoutChanged(0);
+ return null;
+ }).when(mDaemon).resetLockout(any());
+ doAnswer((answer) -> {
+ mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback()
+ .onEnrollmentProgress(1, 0);
+ return null;
+ }).when(mDaemon).enroll(any(), anyInt(), any());
+
+ mContext.getOrCreateTestableResources();
+
+ final String config = String.format("%d:8:15", SENSOR_ID);
+ final BiometricScheduler scheduler = new BiometricScheduler(TAG,
+ new Handler(mLooper.getLooper()),
+ BiometricScheduler.SENSOR_TYPE_FACE,
+ null /* gestureAvailabilityTracker */,
+ mBiometricService, 10 /* recentOperationsLimit */);
+ final HidlFaceSensorConfig faceSensorConfig = new HidlFaceSensorConfig();
+ faceSensorConfig.parse(config, mContext);
+ mHidlToAidlSensorAdapter = new HidlToAidlSensorAdapter(TAG, mFaceProvider,
+ mContext, new Handler(mLooper.getLooper()), faceSensorConfig,
+ mLockoutResetDispatcherForSensor, mBiometricContext,
+ false /* resetLockoutRequiresChallenge */, mInternalCleanupAndGetFeatureRunnable,
+ mAuthSessionCoordinator, mDaemon, mAidlResponseHandlerCallback);
+ mHidlToAidlSensorAdapter.init(mLockoutResetDispatcherForSensor, mFaceProvider);
+ mHidlToAidlSensorAdapter.setScheduler(scheduler);
+ mHidlToAidlSensorAdapter.handleUserChanged(USER_ID);
+ }
+
+ @Test
+ public void lockoutTimedResetViaClient() {
+ setLockoutTimed();
+
+ mHidlToAidlSensorAdapter.getScheduler().scheduleClientMonitor(
+ new FaceResetLockoutClient(mContext, mHidlToAidlSensorAdapter.getLazySession(),
+ USER_ID, TAG, SENSOR_ID, mLogger, mBiometricContext, HAT,
+ mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */),
+ mLockoutResetDispatcherForClient, 0 /* biometricStrength */));
+ mLooper.dispatchAll();
+
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false/* forAuth */)
+ .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+ verify(mLockoutResetDispatcherForClient, never()).notifyLockoutResetCallbacks(SENSOR_ID);
+ verify(mLockoutResetDispatcherForSensor).notifyLockoutResetCallbacks(SENSOR_ID);
+ verify(mAuthSessionCoordinator, never()).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+ }
+
+ @Test
+ public void lockoutTimedResetViaCallback() {
+ setLockoutTimed();
+
+ mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback().onLockoutChanged(0);
+ mLooper.dispatchAll();
+
+ verify(mLockoutResetDispatcherForSensor).notifyLockoutResetCallbacks(eq(
+ SENSOR_ID));
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .getLockoutModeForUser(USER_ID))
+ .isEqualTo(LockoutTracker.LOCKOUT_NONE);
+ verify(mAuthSessionCoordinator, never()).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+ }
+
+ @Test
+ public void lockoutPermanentResetViaCallback() {
+ setLockoutPermanent();
+
+ mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback().onLockoutChanged(0);
+ mLooper.dispatchAll();
+
+ verify(mLockoutResetDispatcherForSensor).notifyLockoutResetCallbacks(eq(
+ SENSOR_ID));
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+ verify(mAuthSessionCoordinator, never()).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+ }
+
+ @Test
+ public void lockoutPermanentResetViaClient() {
+ setLockoutPermanent();
+
+ mHidlToAidlSensorAdapter.getScheduler().scheduleClientMonitor(
+ new FaceResetLockoutClient(mContext,
+ mHidlToAidlSensorAdapter.getLazySession(),
+ USER_ID, TAG, SENSOR_ID, mLogger, mBiometricContext, HAT,
+ mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */),
+ mLockoutResetDispatcherForClient, 0 /* biometricStrength */));
+ mLooper.dispatchAll();
+
+ verify(mLockoutResetDispatcherForClient, never()).notifyLockoutResetCallbacks(SENSOR_ID);
+ verify(mLockoutResetDispatcherForSensor).notifyLockoutResetCallbacks(SENSOR_ID);
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+ verify(mAuthSessionCoordinator, never()).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+ }
+
+ @Test
+ public void verifyOnEnrollSuccessCallback() {
+ mHidlToAidlSensorAdapter.getScheduler().scheduleClientMonitor(new FaceEnrollClient(mContext,
+ mHidlToAidlSensorAdapter.getLazySession(), null /* token */, null /* listener */,
+ USER_ID, HAT, TAG, 1 /* requestId */, mBiometricUtils,
+ new int[]{} /* disabledFeatures */, ENROLL_TIMEOUT_SEC, null /* previewSurface */,
+ SENSOR_ID, mLogger, mBiometricContext, 1 /* maxTemplatesPerUser */,
+ false /* debugConsent */));
+ mLooper.dispatchAll();
+
+ verify(mAidlResponseHandlerCallback).onEnrollSuccess();
+ }
+
+ private void setLockoutTimed() {
+ mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback()
+ .onLockoutChanged(1);
+ mLooper.dispatchAll();
+
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .getLockoutModeForUser(USER_ID))
+ .isEqualTo(LockoutTracker.LOCKOUT_TIMED);
+ }
+
+ private void setLockoutPermanent() {
+ mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback()
+ .onLockoutChanged(-1);
+ mLooper.dispatchAll();
+
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_PERMANENT);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapterTest.java
similarity index 80%
rename from services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java
rename to services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapterTest.java
index 9a40e8a..b9a4fb4 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapterTest.java
@@ -16,7 +16,7 @@
package com.android.server.biometrics.sensors.face.hidl;
-import static com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter.ENROLL_TIMEOUT_SEC;
+import static com.android.server.biometrics.sensors.face.hidl.HidlToAidlSessionAdapter.ENROLL_TIMEOUT_SEC;
import static com.android.server.biometrics.sensors.face.hidl.FaceGenerateChallengeClient.CHALLENGE_TIMEOUT_SEC;
import static com.google.common.truth.Truth.assertThat;
@@ -68,7 +68,7 @@
@Presubmit
@SmallTest
-public class AidlToHidlAdapterTest {
+public class HidlToAidlSessionAdapterTest {
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
@@ -84,25 +84,32 @@
private Clock mClock;
private final long mChallenge = 100L;
- private AidlToHidlAdapter mAidlToHidlAdapter;
+ private HidlToAidlSessionAdapter mHidlToAidlSessionAdapter;
private final Face mFace = new Face("face" /* name */, 1 /* faceId */, 0 /* deviceId */);
private final int mFeature = BiometricFaceConstants.FEATURE_REQUIRE_REQUIRE_DIVERSITY;
private final byte[] mFeatures = new byte[]{Feature.REQUIRE_ATTENTION};
@Before
public void setUp() throws RemoteException {
+ final OptionalUint64 setCallbackResult = new OptionalUint64();
+ setCallbackResult.value = 1;
+
+ when(mSession.setCallback(any())).thenReturn(setCallbackResult);
+
TestableContext testableContext = new TestableContext(
InstrumentationRegistry.getInstrumentation().getContext());
testableContext.addMockSystemService(FaceManager.class, mFaceManager);
- mAidlToHidlAdapter = new AidlToHidlAdapter(testableContext, () -> mSession, 0 /* userId */,
- mAidlResponseHandler, mClock);
+
+ mHidlToAidlSessionAdapter = new HidlToAidlSessionAdapter(testableContext, () -> mSession,
+ 0 /* userId */, mAidlResponseHandler, mClock);
mHardwareAuthToken.timestamp = new Timestamp();
mHardwareAuthToken.mac = new byte[10];
- final OptionalUint64 result = new OptionalUint64();
- result.status = Status.OK;
- result.value = mChallenge;
- when(mSession.generateChallenge(anyInt())).thenReturn(result);
+ final OptionalUint64 generateChallengeResult = new OptionalUint64();
+ generateChallengeResult.status = Status.OK;
+ generateChallengeResult.value = mChallenge;
+
+ when(mSession.generateChallenge(anyInt())).thenReturn(generateChallengeResult);
when(mFaceManager.getEnrolledFaces(anyInt())).thenReturn(List.of(mFace));
}
@@ -112,16 +119,16 @@
final ArgumentCaptor<Long> challengeCaptor = ArgumentCaptor.forClass(Long.class);
- mAidlToHidlAdapter.generateChallenge();
+ mHidlToAidlSessionAdapter.generateChallenge();
verify(mSession).generateChallenge(CHALLENGE_TIMEOUT_SEC);
verify(mAidlResponseHandler).onChallengeGenerated(challengeCaptor.capture());
assertThat(challengeCaptor.getValue()).isEqualTo(mChallenge);
forwardTime(10 /* seconds */);
- mAidlToHidlAdapter.generateChallenge();
+ mHidlToAidlSessionAdapter.generateChallenge();
forwardTime(20 /* seconds */);
- mAidlToHidlAdapter.generateChallenge();
+ mHidlToAidlSessionAdapter.generateChallenge();
//Confirms that the challenge is cached and the hal method is not called again
verifyNoMoreInteractions(mSession);
@@ -129,7 +136,7 @@
.onChallengeGenerated(mChallenge);
forwardTime(60 /* seconds */);
- mAidlToHidlAdapter.generateChallenge();
+ mHidlToAidlSessionAdapter.generateChallenge();
//HAL method called after challenge has timed out
verify(mSession, times(2)).generateChallenge(CHALLENGE_TIMEOUT_SEC);
@@ -138,11 +145,11 @@
@Test
public void testRevokeChallenge_waitsUntilEmpty() throws RemoteException {
for (int i = 0; i < 3; i++) {
- mAidlToHidlAdapter.generateChallenge();
+ mHidlToAidlSessionAdapter.generateChallenge();
forwardTime(10 /* seconds */);
}
for (int i = 0; i < 3; i++) {
- mAidlToHidlAdapter.revokeChallenge(0);
+ mHidlToAidlSessionAdapter.revokeChallenge(0);
forwardTime((i + 1) * 10 /* seconds */);
}
@@ -151,20 +158,19 @@
@Test
public void testRevokeChallenge_timeout() throws RemoteException {
- mAidlToHidlAdapter.generateChallenge();
- mAidlToHidlAdapter.generateChallenge();
+ mHidlToAidlSessionAdapter.generateChallenge();
+ mHidlToAidlSessionAdapter.generateChallenge();
forwardTime(700);
- mAidlToHidlAdapter.generateChallenge();
- mAidlToHidlAdapter.revokeChallenge(0);
+ mHidlToAidlSessionAdapter.generateChallenge();
+ mHidlToAidlSessionAdapter.revokeChallenge(0);
verify(mSession).revokeChallenge();
}
@Test
public void testEnroll() throws RemoteException {
- ICancellationSignal cancellationSignal = mAidlToHidlAdapter.enroll(mHardwareAuthToken,
- EnrollmentType.DEFAULT, mFeatures,
- null /* previewSurface */);
+ ICancellationSignal cancellationSignal = mHidlToAidlSessionAdapter.enroll(
+ mHardwareAuthToken, EnrollmentType.DEFAULT, mFeatures, null /* previewSurface */);
ArgumentCaptor<ArrayList<Integer>> featureCaptor = ArgumentCaptor.forClass(ArrayList.class);
verify(mSession).enroll(any(), eq(ENROLL_TIMEOUT_SEC), featureCaptor.capture());
@@ -182,7 +188,8 @@
@Test
public void testAuthenticate() throws RemoteException {
final int operationId = 2;
- ICancellationSignal cancellationSignal = mAidlToHidlAdapter.authenticate(operationId);
+ ICancellationSignal cancellationSignal = mHidlToAidlSessionAdapter.authenticate(
+ operationId);
verify(mSession).authenticate(operationId);
@@ -193,7 +200,7 @@
@Test
public void testDetectInteraction() throws RemoteException {
- ICancellationSignal cancellationSignal = mAidlToHidlAdapter.detectInteraction();
+ ICancellationSignal cancellationSignal = mHidlToAidlSessionAdapter.detectInteraction();
verify(mSession).authenticate(0);
@@ -204,7 +211,7 @@
@Test
public void testEnumerateEnrollments() throws RemoteException {
- mAidlToHidlAdapter.enumerateEnrollments();
+ mHidlToAidlSessionAdapter.enumerateEnrollments();
verify(mSession).enumerate();
}
@@ -212,7 +219,7 @@
@Test
public void testRemoveEnrollment() throws RemoteException {
final int[] enrollments = new int[]{1};
- mAidlToHidlAdapter.removeEnrollments(enrollments);
+ mHidlToAidlSessionAdapter.removeEnrollments(enrollments);
verify(mSession).remove(enrollments[0]);
}
@@ -226,8 +233,8 @@
when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result);
- mAidlToHidlAdapter.setFeature(mFeature);
- mAidlToHidlAdapter.getFeatures();
+ mHidlToAidlSessionAdapter.setFeature(mFeature);
+ mHidlToAidlSessionAdapter.getFeatures();
verify(mSession).getFeature(eq(mFeature), anyInt());
verify(mAidlResponseHandler).onFeaturesRetrieved(featureRetrieved.capture());
@@ -244,8 +251,8 @@
when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result);
- mAidlToHidlAdapter.setFeature(mFeature);
- mAidlToHidlAdapter.getFeatures();
+ mHidlToAidlSessionAdapter.setFeature(mFeature);
+ mHidlToAidlSessionAdapter.getFeatures();
verify(mSession).getFeature(eq(mFeature), anyInt());
verify(mAidlResponseHandler).onFeaturesRetrieved(featureRetrieved.capture());
@@ -260,8 +267,8 @@
when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result);
- mAidlToHidlAdapter.setFeature(mFeature);
- mAidlToHidlAdapter.getFeatures();
+ mHidlToAidlSessionAdapter.setFeature(mFeature);
+ mHidlToAidlSessionAdapter.getFeatures();
verify(mSession).getFeature(eq(mFeature), anyInt());
verify(mAidlResponseHandler, never()).onFeaturesRetrieved(any());
@@ -270,7 +277,7 @@
@Test
public void testGetFeatures_featureNotSet() throws RemoteException {
- mAidlToHidlAdapter.getFeatures();
+ mHidlToAidlSessionAdapter.getFeatures();
verify(mSession, never()).getFeature(eq(mFeature), anyInt());
verify(mAidlResponseHandler, never()).onFeaturesRetrieved(any());
@@ -283,7 +290,7 @@
when(mSession.setFeature(anyInt(), anyBoolean(), any(), anyInt())).thenReturn(Status.OK);
- mAidlToHidlAdapter.setFeature(mHardwareAuthToken, feature, enabled);
+ mHidlToAidlSessionAdapter.setFeature(mHardwareAuthToken, feature, enabled);
verify(mAidlResponseHandler).onFeatureSet(feature);
}
@@ -296,7 +303,7 @@
when(mSession.setFeature(anyInt(), anyBoolean(), any(), anyInt()))
.thenReturn(Status.INTERNAL_ERROR);
- mAidlToHidlAdapter.setFeature(mHardwareAuthToken, feature, enabled);
+ mHidlToAidlSessionAdapter.setFeature(mHardwareAuthToken, feature, enabled);
verify(mAidlResponseHandler).onError(BiometricFaceConstants.FACE_ERROR_UNKNOWN,
0 /* vendorCode */);
@@ -311,7 +318,7 @@
when(mSession.getAuthenticatorId()).thenReturn(result);
- mAidlToHidlAdapter.getAuthenticatorId();
+ mHidlToAidlSessionAdapter.getAuthenticatorId();
verify(mSession).getAuthenticatorId();
verify(mAidlResponseHandler).onAuthenticatorIdRetrieved(authenticatorId);
@@ -319,7 +326,7 @@
@Test
public void testResetLockout() throws RemoteException {
- mAidlToHidlAdapter.resetLockout(mHardwareAuthToken);
+ mHidlToAidlSessionAdapter.resetLockout(mHardwareAuthToken);
ArgumentCaptor<ArrayList> hatCaptor = ArgumentCaptor.forClass(ArrayList.class);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
index 2aa62d9..f570ba2 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
@@ -42,13 +42,19 @@
import android.app.AppOpsManager;
import android.content.pm.PackageManager;
import android.hardware.biometrics.IBiometricService;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.SensorProps;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
+import android.hardware.fingerprint.FingerprintSensorConfigurations;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.os.Binder;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.testing.TestableContext;
@@ -58,6 +64,7 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.LocalServices;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
@@ -89,6 +96,9 @@
@Rule
public final MockitoRule mMockito = MockitoJUnit.rule();
@Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+ @Rule
public final TestableContext mContext = new TestableContext(
InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
@Rule
@@ -110,6 +120,10 @@
private IBinder mToken;
@Mock
private VirtualDeviceManagerInternal mVdmInternal;
+ @Mock
+ private IFingerprint mDefaultFingerprintDaemon;
+ @Mock
+ private IFingerprint mVirtualFingerprintDaemon;
@Captor
private ArgumentCaptor<FingerprintAuthenticateOptions> mAuthenticateOptionsCaptor;
@@ -126,7 +140,10 @@
List.of(),
TYPE_UDFPS_OPTICAL,
false /* resetLockoutRequiresHardwareAuthToken */);
+ private FingerprintSensorConfigurations mFingerprintSensorConfigurations;
private FingerprintService mService;
+ private final SensorProps mDefaultSensorProps = new SensorProps();
+ private final SensorProps mVirtualSensorProps = new SensorProps();
@Before
public void setup() throws Exception {
@@ -139,6 +156,10 @@
.thenAnswer(i -> i.getArguments()[0].equals(ID_DEFAULT));
when(mFingerprintVirtual.containsSensor(anyInt()))
.thenAnswer(i -> i.getArguments()[0].equals(ID_VIRTUAL));
+ when(mDefaultFingerprintDaemon.getSensorProps()).thenReturn(
+ new SensorProps[]{mDefaultSensorProps});
+ when(mVirtualFingerprintDaemon.getSensorProps()).thenReturn(
+ new SensorProps[]{mVirtualSensorProps});
mContext.addMockSystemService(AppOpsManager.class, mAppOpsManager);
for (int permission : List.of(OP_USE_BIOMETRIC, OP_USE_FINGERPRINT)) {
@@ -150,6 +171,18 @@
mContext.getTestablePermissions().setPermission(
permission, PackageManager.PERMISSION_GRANTED);
}
+
+ mFingerprintSensorConfigurations = new FingerprintSensorConfigurations(
+ true /* resetLockoutRequiresHardwareAuthToken */);
+ mFingerprintSensorConfigurations.addAidlSensors(new String[]{NAME_DEFAULT, NAME_VIRTUAL},
+ (name) -> {
+ if (name.equals(IFingerprint.DESCRIPTOR + "/" + NAME_DEFAULT)) {
+ return mDefaultFingerprintDaemon;
+ } else if (name.equals(IFingerprint.DESCRIPTOR + "/" + NAME_VIRTUAL)) {
+ return mVirtualFingerprintDaemon;
+ }
+ return null;
+ });
}
private void initServiceWith(String... aidlInstances) {
@@ -160,6 +193,10 @@
if (NAME_DEFAULT.equals(name)) return mFingerprintDefault;
if (NAME_VIRTUAL.equals(name)) return mFingerprintVirtual;
return null;
+ }, (sensorPropsPair, resetLockoutRequiresHardwareAuthToken) -> {
+ if (NAME_DEFAULT.equals(sensorPropsPair.first)) return mFingerprintDefault;
+ if (NAME_VIRTUAL.equals(sensorPropsPair.first)) return mFingerprintVirtual;
+ return null;
});
}
@@ -180,6 +217,17 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+ public void registerAuthenticatorsLegacy_defaultOnly() throws Exception {
+ initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
+
+ mService.mServiceWrapper.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
+ waitForRegistration();
+
+ verify(mIBiometricService).registerAuthenticator(eq(ID_DEFAULT), anyInt(), anyInt(), any());
+ }
+
+ @Test
public void registerAuthenticators_virtualOnly() throws Exception {
initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
@@ -192,6 +240,19 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+ public void registerAuthenticatorsLegacy_virtualOnly() throws Exception {
+ initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
+ Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
+ Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 1);
+
+ mService.mServiceWrapper.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
+ waitForRegistration();
+
+ verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
+ }
+
+ @Test
public void registerAuthenticators_virtualAlwaysWhenNoOther() throws Exception {
initServiceWith(NAME_VIRTUAL);
@@ -201,6 +262,28 @@
verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+ public void registerAuthenticatorsLegacy_virtualAlwaysWhenNoOther() throws Exception {
+ mFingerprintSensorConfigurations =
+ new FingerprintSensorConfigurations(true);
+ mFingerprintSensorConfigurations.addAidlSensors(new String[]{NAME_VIRTUAL},
+ (name) -> {
+ if (name.equals(IFingerprint.DESCRIPTOR + "/" + NAME_DEFAULT)) {
+ return mDefaultFingerprintDaemon;
+ } else if (name.equals(IFingerprint.DESCRIPTOR + "/" + NAME_VIRTUAL)) {
+ return mVirtualFingerprintDaemon;
+ }
+ return null;
+ });
+ initServiceWith(NAME_VIRTUAL);
+
+ mService.mServiceWrapper.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
+ waitForRegistration();
+
+ verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
+ }
+
private void waitForRegistration() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
mService.mServiceWrapper.addAuthenticatorsRegisteredCallback(
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index c24227f..774ea5b 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -161,6 +161,7 @@
@Before
public void setup() {
+ mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR);
mContext.addMockSystemService(BiometricManager.class, mBiometricManager);
when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
when(mBiometricLogger.getAmbientLightProbe(anyBoolean())).thenAnswer(i ->
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
index 4cfb83f..bf5986c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
@@ -16,6 +16,9 @@
package com.android.server.biometrics.sensors.fingerprint.aidl;
+import static android.os.UserHandle.USER_NULL;
+import static android.os.UserHandle.USER_SYSTEM;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
@@ -34,17 +37,20 @@
import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.biometrics.fingerprint.SensorLocation;
import android.hardware.biometrics.fingerprint.SensorProps;
+import android.hardware.fingerprint.HidlFingerprintSensorConfig;
import android.os.RemoteException;
-import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.AuthenticationStateListeners;
-import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.HalClientMonitor;
@@ -52,6 +58,7 @@
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -64,6 +71,10 @@
private static final String TAG = "FingerprintProviderTest";
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Mock
private Context mContext;
@Mock
@@ -115,7 +126,8 @@
mFingerprintProvider = new FingerprintProvider(mContext,
mBiometricStateCallback, mAuthenticationStateListeners, mSensorProps, TAG,
mLockoutResetDispatcher, mGestureAvailabilityDispatcher, mBiometricContext,
- mDaemon);
+ mDaemon, false /* resetLockoutRequiresHardwareAuthToken */,
+ true /* testHalEnabled */);
}
@Test
@@ -123,17 +135,42 @@
waitForIdle();
for (SensorProps prop : mSensorProps) {
- final BiometricScheduler scheduler =
- mFingerprintProvider.mFingerprintSensors.get(prop.commonProps.sensorId)
- .getScheduler();
- BaseClientMonitor currentClient = scheduler.getCurrentClient();
-
- assertThat(currentClient).isInstanceOf(FingerprintInternalCleanupClient.class);
- assertThat(currentClient.getSensorId()).isEqualTo(prop.commonProps.sensorId);
- assertThat(currentClient.getTargetUserId()).isEqualTo(UserHandle.USER_SYSTEM);
+ final Sensor sensor =
+ mFingerprintProvider.mFingerprintSensors.get(prop.commonProps.sensorId);
+ assertThat(sensor.getLazySession().get().getUserId()).isEqualTo(USER_SYSTEM);
}
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+ public void testAddingHidlSensors() {
+ when(mResources.getIntArray(anyInt())).thenReturn(new int[]{});
+ when(mResources.getBoolean(anyInt())).thenReturn(false);
+
+ final int fingerprintId = 0;
+ final int fingerprintStrength = 15;
+ final String config = String.format("%d:2:%d", fingerprintId, fingerprintStrength);
+ final HidlFingerprintSensorConfig fingerprintSensorConfig =
+ new HidlFingerprintSensorConfig();
+ fingerprintSensorConfig.parse(config, mContext);
+ HidlFingerprintSensorConfig[] hidlFingerprintSensorConfigs =
+ new HidlFingerprintSensorConfig[]{fingerprintSensorConfig};
+ mFingerprintProvider = new FingerprintProvider(mContext,
+ mBiometricStateCallback, mAuthenticationStateListeners,
+ hidlFingerprintSensorConfigs, TAG, mLockoutResetDispatcher,
+ mGestureAvailabilityDispatcher, mBiometricContext, mDaemon,
+ false /* resetLockoutRequiresHardwareAuthToken */,
+ true /* testHalEnabled */);
+
+ assertThat(mFingerprintProvider.mFingerprintSensors.get(fingerprintId)
+ .getLazySession().get().getUserId()).isEqualTo(USER_NULL);
+
+ waitForIdle();
+
+ assertThat(mFingerprintProvider.mFingerprintSensors.get(fingerprintId)
+ .getLazySession().get().getUserId()).isEqualTo(USER_SYSTEM);
+ }
+
@SuppressWarnings("rawtypes")
@Test
public void halServiceDied_resetsAllSchedulers() {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
index 4102600..126a05e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
@@ -96,7 +96,7 @@
private final TestLooper mLooper = new TestLooper();
private final LockoutCache mLockoutCache = new LockoutCache();
- private UserAwareBiometricScheduler mScheduler;
+ private BiometricScheduler mScheduler;
private AidlResponseHandler mHalCallback;
@Before
@@ -164,7 +164,8 @@
final Sensor sensor = new Sensor("SensorTest", mFingerprintProvider, mContext,
null /* handler */, internalProp, mLockoutResetDispatcher,
mGestureAvailabilityDispatcher, mBiometricContext, mCurrentSession);
- mScheduler = (UserAwareBiometricScheduler) sensor.getScheduler();
+ sensor.init(mGestureAvailabilityDispatcher, mLockoutResetDispatcher);
+ mScheduler = sensor.getScheduler();
sensor.mCurrentSession = new AidlSession(0, mock(ISession.class),
USER_ID, mHalCallback);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java
new file mode 100644
index 0000000..89a4961
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.biometrics.sensors.fingerprint.hidl;
+
+import static android.hardware.fingerprint.FingerprintManager.ENROLL_ENROLL;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AlarmManager;
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.HidlFingerprintSensorConfig;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+
+import androidx.annotation.NonNull;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
+import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.StartUserClient;
+import com.android.server.biometrics.sensors.StopUserClient;
+import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
+import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession;
+import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintEnrollClient;
+import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider;
+import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintResetLockoutClient;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class HidlToAidlSensorAdapterTest {
+
+ private static final String TAG = "HidlToAidlSensorAdapterTest";
+ private static final int USER_ID = 2;
+ private static final int SENSOR_ID = 4;
+ private static final byte[] HAT = new byte[69];
+
+ @Rule
+ public final MockitoRule mMockito = MockitoJUnit.rule();
+
+ @Mock
+ private IBiometricService mBiometricService;
+ @Mock
+ private LockoutResetDispatcher mLockoutResetDispatcherForSensor;
+ @Mock
+ private LockoutResetDispatcher mLockoutResetDispatcherForClient;
+ @Mock
+ private BiometricLogger mLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+ @Mock
+ private AuthSessionCoordinator mAuthSessionCoordinator;
+ @Mock
+ private FingerprintProvider mFingerprintProvider;
+ @Mock
+ private GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
+ @Mock
+ private Runnable mInternalCleanupRunnable;
+ @Mock
+ private AlarmManager mAlarmManager;
+ @Mock
+ private IBiometricsFingerprint mDaemon;
+ @Mock
+ private AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
+ @Mock
+ private BiometricUtils<Fingerprint> mBiometricUtils;
+ @Mock
+ private AuthenticationStateListeners mAuthenticationStateListeners;
+
+ private final TestLooper mLooper = new TestLooper();
+ private HidlToAidlSensorAdapter mHidlToAidlSensorAdapter;
+ private final TestableContext mContext = new TestableContext(
+ ApplicationProvider.getApplicationContext());
+
+ private final UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback =
+ new UserAwareBiometricScheduler.UserSwitchCallback() {
+ @NonNull
+ @Override
+ public StopUserClient<?> getStopUserClient(int userId) {
+ return new StopUserClient<IBiometricsFingerprint>(mContext,
+ mHidlToAidlSensorAdapter::getIBiometricsFingerprint, null, USER_ID,
+ SENSOR_ID, mLogger, mBiometricContext, () -> {}) {
+ @Override
+ protected void startHalOperation() {
+ getCallback().onClientFinished(this, true /* success */);
+ }
+
+ @Override
+ public void unableToStart() {}
+ };
+ }
+
+ @NonNull
+ @Override
+ public StartUserClient<?, ?> getStartUserClient(int newUserId) {
+ return new StartUserClient<IBiometricsFingerprint, AidlSession>(mContext,
+ mHidlToAidlSensorAdapter::getIBiometricsFingerprint, null,
+ USER_ID, SENSOR_ID,
+ mLogger, mBiometricContext,
+ (newUserId1, newUser, halInterfaceVersion) ->
+ mHidlToAidlSensorAdapter.handleUserChanged(newUserId1)) {
+ @Override
+ public void start(@NonNull ClientMonitorCallback callback) {
+ super.start(callback);
+ startHalOperation();
+ }
+
+ @Override
+ protected void startHalOperation() {
+ mUserStartedCallback.onUserStarted(USER_ID, null, 0);
+ getCallback().onClientFinished(this, true /* success */);
+ }
+
+ @Override
+ public void unableToStart() {}
+ };
+ }
+ };;
+
+ @Before
+ public void setUp() throws RemoteException {
+ when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
+ doAnswer((answer) -> {
+ mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback()
+ .onEnrollmentProgress(1 /* enrollmentId */, 0 /* remaining */);
+ return null;
+ }).when(mDaemon).enroll(any(), anyInt(), anyInt());
+
+ mContext.addMockSystemService(AlarmManager.class, mAlarmManager);
+ mContext.getOrCreateTestableResources();
+
+ final String config = String.format("%d:2:15", SENSOR_ID);
+ final UserAwareBiometricScheduler scheduler = new UserAwareBiometricScheduler(TAG,
+ new Handler(mLooper.getLooper()),
+ BiometricScheduler.SENSOR_TYPE_FP_OTHER,
+ null /* gestureAvailabilityDispatcher */,
+ mBiometricService,
+ () -> USER_ID,
+ mUserSwitchCallback);
+ final HidlFingerprintSensorConfig fingerprintSensorConfig =
+ new HidlFingerprintSensorConfig();
+ fingerprintSensorConfig.parse(config, mContext);
+ mHidlToAidlSensorAdapter = new HidlToAidlSensorAdapter(TAG,
+ mFingerprintProvider, mContext, new Handler(mLooper.getLooper()),
+ fingerprintSensorConfig, mLockoutResetDispatcherForSensor,
+ mGestureAvailabilityDispatcher, mBiometricContext,
+ false /* resetLockoutRequiresHardwareAuthToken */,
+ mInternalCleanupRunnable, mAuthSessionCoordinator, mDaemon,
+ mAidlResponseHandlerCallback);
+ mHidlToAidlSensorAdapter.init(mGestureAvailabilityDispatcher,
+ mLockoutResetDispatcherForSensor);
+ mHidlToAidlSensorAdapter.setScheduler(scheduler);
+ mHidlToAidlSensorAdapter.handleUserChanged(USER_ID);
+ }
+
+ @Test
+ public void lockoutTimedResetViaClient() {
+ setLockoutTimed();
+
+ mHidlToAidlSensorAdapter.getScheduler().scheduleClientMonitor(
+ new FingerprintResetLockoutClient(mContext,
+ mHidlToAidlSensorAdapter.getLazySession(),
+ USER_ID, TAG, SENSOR_ID, mLogger, mBiometricContext, HAT,
+ mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */),
+ mLockoutResetDispatcherForClient, 0 /* biometricStrength */));
+ mLooper.dispatchAll();
+
+ verify(mAlarmManager).setExact(anyInt(), anyLong(), any());
+ verify(mLockoutResetDispatcherForClient).notifyLockoutResetCallbacks(SENSOR_ID);
+ verify(mLockoutResetDispatcherForSensor).notifyLockoutResetCallbacks(SENSOR_ID);
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+ verify(mAuthSessionCoordinator).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+ }
+
+ @Test
+ public void lockoutTimedResetViaCallback() {
+ setLockoutTimed();
+
+ mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback().onLockoutCleared();
+ mLooper.dispatchAll();
+
+ verify(mLockoutResetDispatcherForSensor, times(2)).notifyLockoutResetCallbacks(eq(
+ SENSOR_ID));
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+ verify(mAuthSessionCoordinator).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+ }
+
+ @Test
+ public void lockoutPermanentResetViaCallback() {
+ setLockoutPermanent();
+
+ mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback().onLockoutCleared();
+ mLooper.dispatchAll();
+
+ verify(mLockoutResetDispatcherForSensor, times(2)).notifyLockoutResetCallbacks(eq(
+ SENSOR_ID));
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+ verify(mAuthSessionCoordinator).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+ }
+
+ @Test
+ public void lockoutPermanentResetViaClient() {
+ setLockoutPermanent();
+
+ mHidlToAidlSensorAdapter.getScheduler().scheduleClientMonitor(
+ new FingerprintResetLockoutClient(mContext,
+ mHidlToAidlSensorAdapter.getLazySession(),
+ USER_ID, TAG, SENSOR_ID, mLogger, mBiometricContext, HAT,
+ mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */),
+ mLockoutResetDispatcherForClient, 0 /* biometricStrength */));
+ mLooper.dispatchAll();
+
+ verify(mAlarmManager, atLeast(1)).setExact(anyInt(), anyLong(), any());
+ verify(mLockoutResetDispatcherForClient).notifyLockoutResetCallbacks(SENSOR_ID);
+ verify(mLockoutResetDispatcherForSensor).notifyLockoutResetCallbacks(SENSOR_ID);
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+ verify(mAuthSessionCoordinator).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+ }
+
+ @Test
+ public void verifyOnEnrollSuccessCallback() {
+ mHidlToAidlSensorAdapter.getScheduler().scheduleClientMonitor(new FingerprintEnrollClient(
+ mContext, mHidlToAidlSensorAdapter.getLazySession(), null /* token */,
+ 1 /* requestId */, null /* listener */, USER_ID, HAT, TAG, mBiometricUtils,
+ SENSOR_ID, mLogger, mBiometricContext,
+ mHidlToAidlSensorAdapter.getSensorProperties(), null, null,
+ mAuthenticationStateListeners, 5 /* maxTemplatesPerUser */, ENROLL_ENROLL));
+ mLooper.dispatchAll();
+
+ verify(mAidlResponseHandlerCallback).onEnrollSuccess();
+ }
+
+ private void setLockoutPermanent() {
+ for (int i = 0; i < 20; i++) {
+ mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .addFailedAttemptForUser(USER_ID);
+ }
+
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_PERMANENT);
+ }
+
+ private void setLockoutTimed() {
+ for (int i = 0; i < 5; i++) {
+ mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .addFailedAttemptForUser(USER_ID);
+ }
+
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_TIMED);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapterTest.java
similarity index 79%
rename from services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java
rename to services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapterTest.java
index b78ba82..d723e87 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapterTest.java
@@ -41,7 +41,7 @@
@Presubmit
@SmallTest
-public class AidlToHidlAdapterTest {
+public class HidlToAidlSessionAdapterTest {
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
@@ -54,11 +54,11 @@
private final long mChallenge = 100L;
private final int mUserId = 0;
- private AidlToHidlAdapter mAidlToHidlAdapter;
+ private HidlToAidlSessionAdapter mHidlToAidlSessionAdapter;
@Before
public void setUp() {
- mAidlToHidlAdapter = new AidlToHidlAdapter(() -> mSession, mUserId,
+ mHidlToAidlSessionAdapter = new HidlToAidlSessionAdapter(() -> mSession, mUserId,
mAidlResponseHandler);
mHardwareAuthToken.timestamp = new Timestamp();
mHardwareAuthToken.mac = new byte[10];
@@ -67,7 +67,7 @@
@Test
public void testGenerateChallenge() throws RemoteException {
when(mSession.preEnroll()).thenReturn(mChallenge);
- mAidlToHidlAdapter.generateChallenge();
+ mHidlToAidlSessionAdapter.generateChallenge();
verify(mSession).preEnroll();
verify(mAidlResponseHandler).onChallengeGenerated(mChallenge);
@@ -75,7 +75,7 @@
@Test
public void testRevokeChallenge() throws RemoteException {
- mAidlToHidlAdapter.revokeChallenge(mChallenge);
+ mHidlToAidlSessionAdapter.revokeChallenge(mChallenge);
verify(mSession).postEnroll();
verify(mAidlResponseHandler).onChallengeRevoked(0L);
@@ -84,9 +84,9 @@
@Test
public void testEnroll() throws RemoteException {
final ICancellationSignal cancellationSignal =
- mAidlToHidlAdapter.enroll(mHardwareAuthToken);
+ mHidlToAidlSessionAdapter.enroll(mHardwareAuthToken);
- verify(mSession).enroll(any(), anyInt(), eq(AidlToHidlAdapter.ENROLL_TIMEOUT_SEC));
+ verify(mSession).enroll(any(), anyInt(), eq(HidlToAidlSessionAdapter.ENROLL_TIMEOUT_SEC));
cancellationSignal.cancel();
@@ -96,7 +96,8 @@
@Test
public void testAuthenticate() throws RemoteException {
final int operationId = 2;
- final ICancellationSignal cancellationSignal = mAidlToHidlAdapter.authenticate(operationId);
+ final ICancellationSignal cancellationSignal = mHidlToAidlSessionAdapter.authenticate(
+ operationId);
verify(mSession).authenticate(operationId, mUserId);
@@ -107,7 +108,8 @@
@Test
public void testDetectInteraction() throws RemoteException {
- final ICancellationSignal cancellationSignal = mAidlToHidlAdapter.detectInteraction();
+ final ICancellationSignal cancellationSignal = mHidlToAidlSessionAdapter
+ .detectInteraction();
verify(mSession).authenticate(0 /* operationId */, mUserId);
@@ -118,7 +120,7 @@
@Test
public void testEnumerateEnrollments() throws RemoteException {
- mAidlToHidlAdapter.enumerateEnrollments();
+ mHidlToAidlSessionAdapter.enumerateEnrollments();
verify(mSession).enumerate();
}
@@ -126,7 +128,7 @@
@Test
public void testRemoveEnrollment() throws RemoteException {
final int[] enrollmentIds = new int[]{1};
- mAidlToHidlAdapter.removeEnrollments(enrollmentIds);
+ mHidlToAidlSessionAdapter.removeEnrollments(enrollmentIds);
verify(mSession).remove(mUserId, enrollmentIds[0]);
}
@@ -134,14 +136,14 @@
@Test
public void testRemoveMultipleEnrollments() throws RemoteException {
final int[] enrollmentIds = new int[]{1, 2};
- mAidlToHidlAdapter.removeEnrollments(enrollmentIds);
+ mHidlToAidlSessionAdapter.removeEnrollments(enrollmentIds);
verify(mSession).remove(mUserId, 0);
}
@Test
public void testResetLockout() throws RemoteException {
- mAidlToHidlAdapter.resetLockout(mHardwareAuthToken);
+ mHidlToAidlSessionAdapter.resetLockout(mHardwareAuthToken);
verify(mAidlResponseHandler).onLockoutCleared();
}
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 edfe1b4..071d571 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
@@ -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.
@@ -24,10 +24,8 @@
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.companion.virtual.camera.VirtualCameraCallback;
import android.companion.virtual.camera.VirtualCameraConfig;
-import android.companion.virtual.camera.VirtualCameraMetadata;
import android.companion.virtual.camera.VirtualCameraStreamConfig;
import android.companion.virtualcamera.IVirtualCameraService;
import android.companion.virtualcamera.VirtualCameraConfiguration;
@@ -156,10 +154,6 @@
@NonNull VirtualCameraStreamConfig streamConfig) {}
@Override
- public void onProcessCaptureRequest(
- int streamId, long frameId, @Nullable VirtualCameraMetadata metadata) {}
-
- @Override
public void onStreamClosed(int streamId) {}
};
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraStreamConfigTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraStreamConfigTest.java
new file mode 100644
index 0000000..d9a38eb
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraStreamConfigTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.virtual.camera;
+
+import android.companion.virtual.camera.VirtualCameraStreamConfig;
+import android.graphics.ImageFormat;
+import android.graphics.PixelFormat;
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.google.common.testing.EqualsTester;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class VirtualCameraStreamConfigTest {
+
+ private static final int VGA_WIDTH = 640;
+ private static final int VGA_HEIGHT = 480;
+
+ private static final int QVGA_WIDTH = 320;
+ private static final int QVGA_HEIGHT = 240;
+
+ @Test
+ public void testEquals() {
+ VirtualCameraStreamConfig vgaYuvStreamConfig = new VirtualCameraStreamConfig(VGA_WIDTH,
+ VGA_HEIGHT,
+ ImageFormat.YUV_420_888);
+ VirtualCameraStreamConfig qvgaYuvStreamConfig = new VirtualCameraStreamConfig(QVGA_WIDTH,
+ QVGA_HEIGHT, ImageFormat.YUV_420_888);
+ VirtualCameraStreamConfig vgaRgbaStreamConfig = new VirtualCameraStreamConfig(VGA_WIDTH,
+ VGA_HEIGHT, PixelFormat.RGBA_8888);
+
+ new EqualsTester()
+ .addEqualityGroup(vgaYuvStreamConfig, reparcel(vgaYuvStreamConfig))
+ .addEqualityGroup(qvgaYuvStreamConfig, reparcel(qvgaYuvStreamConfig))
+ .addEqualityGroup(vgaRgbaStreamConfig, reparcel(vgaRgbaStreamConfig))
+ .testEquals();
+ }
+
+ private static VirtualCameraStreamConfig reparcel(VirtualCameraStreamConfig config) {
+ Parcel parcel = Parcel.obtain();
+ try {
+ config.writeToParcel(parcel, /* flags= */ 0);
+ parcel.setDataPosition(0);
+ return VirtualCameraStreamConfig.CREATOR.createFromParcel(parcel);
+ } finally {
+ parcel.recycle();
+ }
+ }
+
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
index a63d01b..e7da26e 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
@@ -456,6 +456,14 @@
}
@Test
+ public void avbEnabled_standby_avbDisabled() {
+ enableAbsoluteVolumeBehavior();
+ mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+ assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ }
+
+ @Test
public void avbEnabled_cecVolumeDisabled_avbDisabled() {
enableAbsoluteVolumeBehavior();
diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
index ce15c6d..eb9cce0 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
@@ -67,10 +67,10 @@
import androidx.test.InstrumentationRegistry;
import com.android.internal.R;
+import com.android.internal.pm.parsing.PackageParser2;
import com.android.server.compat.PlatformCompat;
import com.android.server.integrity.engine.RuleEvaluationEngine;
import com.android.server.integrity.model.IntegrityCheckResult;
-import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.parsing.TestPackageParser2;
import com.android.server.testutils.TestUtils;
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
index b4281d63c..101ea6d 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -183,6 +183,13 @@
}
@Test
+ public void testIsUserInitialized_NonExistentUserReturnsFalse() {
+ int nonExistentUserId = UserHandle.USER_NULL;
+ assertThat(mUserManagerService.isUserInitialized(nonExistentUserId))
+ .isFalse();
+ }
+
+ @Test
public void testSetUserRestrictionWithIncorrectID() throws Exception {
int incorrectId = 1;
while (mUserManagerService.userExists(incorrectId)) {
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
index 1bfd43f..25eedf5 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
@@ -37,6 +37,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.servicestests.R;
+import com.android.internal.pm.parsing.PackageParserException;
import com.android.internal.pm.parsing.pkg.ParsedPackage;
import com.android.server.pm.PackageManagerException;
import com.android.server.pm.parsing.TestPackageParser2;
@@ -161,7 +162,8 @@
}
@Test
- public void testParsePackageWithDmFileValid() throws IOException, PackageManagerException {
+ public void testParsePackageWithDmFileValid() throws IOException, PackageManagerException,
+ PackageParserException {
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
createDexMetadataFile("install_split_base.apk");
ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
@@ -178,7 +180,7 @@
@Test
public void testParsePackageSplitsWithDmFileValid()
- throws IOException, PackageManagerException {
+ throws IOException, PackageManagerException, PackageParserException {
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a);
createDexMetadataFile("install_split_base.apk");
@@ -201,7 +203,7 @@
@Test
public void testParsePackageSplitsNoBaseWithDmFileValid()
- throws IOException, PackageManagerException {
+ throws IOException, PackageManagerException, PackageParserException {
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a);
createDexMetadataFile("install_split_feature_a.apk");
@@ -219,7 +221,7 @@
}
@Test
- public void testParsePackageWithDmFileInvalid() throws IOException {
+ public void testParsePackageWithDmFileInvalid() throws IOException, PackageParserException {
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
File invalidDmFile = new File(mTmpDir, "install_split_base.dm");
Files.createFile(invalidDmFile.toPath());
@@ -242,7 +244,7 @@
@Test
public void testParsePackageSplitsWithDmFileInvalid()
- throws IOException, PackageManagerException {
+ throws IOException, PackageParserException {
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a);
createDexMetadataFile("install_split_base.apk");
@@ -268,7 +270,7 @@
@Test
public void testParsePackageWithDmFileInvalidManifest()
- throws IOException, PackageManagerException {
+ throws IOException, PackageParserException {
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
createDexMetadataFile("install_split_base.apk", /*validManifest=*/false);
@@ -283,7 +285,7 @@
@Test
public void testParsePackageWithDmFileEmptyManifest()
- throws IOException, PackageManagerException {
+ throws IOException, PackageParserException {
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
createDexMetadataFile("install_split_base.apk", /*packageName=*/"doesn't matter",
/*versionCode=*/-12345L, /*emptyManifest=*/true, /*validManifest=*/true);
@@ -299,7 +301,7 @@
@Test
public void testParsePackageWithDmFileBadPackageName()
- throws IOException, PackageManagerException {
+ throws IOException, PackageParserException {
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
createDexMetadataFile("install_split_base.apk", /*packageName=*/"bad package name",
DEX_METADATA_VERSION_CODE, /*emptyManifest=*/false, /*validManifest=*/true);
@@ -315,7 +317,7 @@
@Test
public void testParsePackageWithDmFileBadVersionCode()
- throws IOException, PackageManagerException {
+ throws IOException, PackageParserException {
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
createDexMetadataFile("install_split_base.apk", DEX_METADATA_PACKAGE_NAME,
/*versionCode=*/12345L, /*emptyManifest=*/false, /*validManifest=*/true);
@@ -331,7 +333,7 @@
@Test
public void testParsePackageWithDmFileMissingPackageName()
- throws IOException, PackageManagerException {
+ throws IOException, PackageParserException {
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
createDexMetadataFile("install_split_base.apk", /*packageName=*/null,
DEX_METADATA_VERSION_CODE, /*emptyManifest=*/false, /*validManifest=*/true);
@@ -347,7 +349,7 @@
@Test
public void testParsePackageWithDmFileMissingVersionCode()
- throws IOException, PackageManagerException {
+ throws IOException, PackageParserException {
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
createDexMetadataFile("install_split_base.apk", DEX_METADATA_PACKAGE_NAME,
/*versionCode=*/null, /*emptyManifest=*/false, /*validManifest=*/true);
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
index 9b4ca2a..6a088d9 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
@@ -36,7 +36,7 @@
@Postsubmit
class AndroidPackageParsingValidationTest {
companion object {
- private val parser2 = PackageParser2.forParsingFileWithDefaults()
+ private val parser2 = PackageParserUtils.forParsingFileWithDefaults()
private val apks = ((PackageManagerService.SYSTEM_PARTITIONS)
.flatMap {
listOfNotNull(it.privAppFolder, it.appFolder, it.overlayFolder)
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/TestPackageParser2.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/TestPackageParser2.kt
index c44f583..e420e4b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/TestPackageParser2.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/TestPackageParser2.kt
@@ -18,11 +18,12 @@
import android.content.pm.ApplicationInfo
import android.util.ArraySet
+import com.android.internal.pm.parsing.PackageParser2
import java.io.File
class TestPackageParser2(var cacheDir: File? = null) : PackageParser2(
null /* separateProcesses */, null /* displayMetrics */,
- cacheDir /* cacheDir */, object : PackageParser2.Callback() {
+ cacheDir?.let { PackageCacher(cacheDir) }, object : PackageParser2.Callback() {
override fun isChangeEnabled(changeId: Long, appInfo: ApplicationInfo): Boolean {
return true
}
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 8487903..89056cc 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -64,17 +64,18 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.intThat;
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.annotation.DurationMillisLong;
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.usage.AppStandbyInfo;
import android.app.usage.UsageEvents;
+import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManagerInternal;
import android.appwidget.AppWidgetManager;
import android.content.Context;
@@ -99,7 +100,6 @@
import android.view.Display;
import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -110,6 +110,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -125,6 +126,7 @@
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.function.BooleanSupplier;
import java.util.stream.Collectors;
/**
@@ -175,6 +177,9 @@
private static final int ASSERT_RETRY_ATTEMPTS = 20;
private static final int ASSERT_RETRY_DELAY_MILLISECONDS = 500;
+ @DurationMillisLong
+ private static final long FLUSH_TIMEOUT_MILLISECONDS = 5_000;
+
/** Mock variable used in {@link MyInjector#isPackageInstalled(String, int, int)} */
private static boolean isPackageInstalled = true;
@@ -563,6 +568,7 @@
mInjector = new MyInjector(myContext, Looper.getMainLooper());
mController = setupController();
setupInitialUsageHistory();
+ flushHandler(mController);
}
@After
@@ -571,12 +577,9 @@
}
@Test
- @FlakyTest(bugId = 185169504)
public void testBoundWidgetPackageExempt() throws Exception {
assumeTrue(mInjector.getContext().getSystemService(AppWidgetManager.class) != null);
- assertEquals(STANDBY_BUCKET_ACTIVE,
- mController.getAppStandbyBucket(PACKAGE_EXEMPTED_1, USER_ID,
- mInjector.mElapsedRealtime, false));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_EXEMPTED_1);
}
@Test
@@ -654,7 +657,6 @@
}
@Test
- @FlakyTest(bugId = 185169504)
public void testIsAppIdle_Charging() throws Exception {
TestParoleListener paroleListener = new TestParoleListener();
mController.addListener(paroleListener);
@@ -662,7 +664,7 @@
setChargingState(mController, false);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_FORCED_BY_SYSTEM);
- assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
assertFalse(mController.isInParole());
@@ -671,7 +673,7 @@
setChargingState(mController, true);
paroleListener.awaitOnLatch(2000);
assertTrue(paroleListener.getParoleState());
- assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
assertTrue(mController.isInParole());
@@ -680,14 +682,13 @@
setChargingState(mController, false);
paroleListener.awaitOnLatch(2000);
assertFalse(paroleListener.getParoleState());
- assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
assertFalse(mController.isInParole());
}
@Test
- @FlakyTest(bugId = 185169504)
public void testIsAppIdle_Enabled() throws Exception {
setChargingState(mController, false);
TestParoleListener paroleListener = new TestParoleListener();
@@ -696,7 +697,7 @@
setAppIdleEnabled(mController, true);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_FORCED_BY_SYSTEM);
- assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
@@ -711,7 +712,7 @@
setAppIdleEnabled(mController, true);
paroleListener.awaitOnLatch(2000);
assertFalse(paroleListener.getParoleState());
- assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
}
@@ -723,6 +724,7 @@
private void assertTimeout(AppStandbyController controller, long elapsedTime, int bucket,
int userId) {
mInjector.mElapsedRealtime = elapsedTime;
+ flushHandler(controller);
controller.checkIdleStates(userId);
assertEquals(bucket,
controller.getAppStandbyBucket(PACKAGE_1, userId, mInjector.mElapsedRealtime,
@@ -744,50 +746,85 @@
}
private int getStandbyBucket(int userId, AppStandbyController controller, String packageName) {
+ flushHandler(controller);
return controller.getAppStandbyBucket(packageName, userId, mInjector.mElapsedRealtime,
true);
}
+ private List<AppStandbyInfo> getStandbyBuckets(int userId) {
+ flushHandler(mController);
+ return mController.getAppStandbyBuckets(userId);
+ }
+
private int getStandbyBucketReason(String packageName) {
+ flushHandler(mController);
return mController.getAppStandbyBucketReason(packageName, USER_ID,
mInjector.mElapsedRealtime);
}
- private void assertBucket(int bucket) throws InterruptedException {
- assertBucket(bucket, PACKAGE_1);
+ private void waitAndAssertBucket(int bucket, String pkg) {
+ waitAndAssertBucket(mController, bucket, pkg);
}
- private void assertBucket(int bucket, String pkg) throws InterruptedException {
- int retries = 0;
- do {
- if (bucket == getStandbyBucket(mController, pkg)) {
- // Success
- return;
- }
- Thread.sleep(ASSERT_RETRY_DELAY_MILLISECONDS);
- retries++;
- } while(retries < ASSERT_RETRY_ATTEMPTS);
- // try one last time
- assertEquals(bucket, getStandbyBucket(mController, pkg));
+ private void waitAndAssertBucket(AppStandbyController controller, int bucket, String pkg) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(pkg);
+ sb.append(" was not in the ");
+ sb.append(UsageStatsManager.standbyBucketToString(bucket));
+ sb.append(" (");
+ sb.append(bucket);
+ sb.append(") bucket.");
+ waitAndAssertBucket(sb.toString(), controller, bucket, pkg);
}
- private void assertNotBucket(int bucket) throws InterruptedException {
- final String pkg = PACKAGE_1;
+ private void waitAndAssertBucket(String msg, int bucket, String pkg) {
+ waitAndAssertBucket(msg, mController, bucket, pkg);
+ }
+
+ private void waitAndAssertBucket(String msg, AppStandbyController controller, int bucket,
+ String pkg) {
+ waitAndAssertBucket(msg, controller, bucket, USER_ID, pkg);
+ }
+
+ private void waitAndAssertBucket(String msg, AppStandbyController controller, int bucket,
+ int userId,
+ String pkg) {
+ waitUntil(() -> bucket == getStandbyBucket(userId, controller, pkg));
+ assertEquals(msg, bucket, getStandbyBucket(userId, controller, pkg));
+ }
+
+ private void waitAndAssertNotBucket(int bucket, String pkg) {
+ waitAndAssertNotBucket(mController, bucket, pkg);
+ }
+
+ private void waitAndAssertNotBucket(AppStandbyController controller, int bucket, String pkg) {
+ waitUntil(() -> bucket != getStandbyBucket(controller, pkg));
+ assertNotEquals(bucket, getStandbyBucket(controller, pkg));
+ }
+
+ private void waitAndAssertLastNoteEvent(int event) {
+ waitUntil(() -> {
+ flushHandler(mController);
+ return event == mInjector.mLastNoteEvent;
+ });
+ assertEquals(event, mInjector.mLastNoteEvent);
+ }
+
+ // Waits until condition is true or times out.
+ private void waitUntil(BooleanSupplier resultSupplier) {
int retries = 0;
do {
- if (bucket != getStandbyBucket(mController, pkg)) {
- // Success
- return;
+ if (resultSupplier.getAsBoolean()) return;
+ try {
+ Thread.sleep(ASSERT_RETRY_DELAY_MILLISECONDS);
+ } catch (InterruptedException ie) {
+ // Do nothing
}
- Thread.sleep(ASSERT_RETRY_DELAY_MILLISECONDS);
retries++;
- } while(retries < ASSERT_RETRY_ATTEMPTS);
- // try one last time
- assertNotEquals(bucket, getStandbyBucket(mController, pkg));
+ } while (retries < ASSERT_RETRY_ATTEMPTS);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testBuckets() throws Exception {
assertTimeout(mController, 0, STANDBY_BUCKET_NEVER);
@@ -820,14 +857,13 @@
}
@Test
- @FlakyTest(bugId = 185169504)
public void testSetAppStandbyBucket() throws Exception {
// For a known package, standby bucket should be set properly
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
mInjector.mElapsedRealtime = HOUR_MS;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_TIMEOUT);
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// For an unknown package, standby bucket should not be set, hence NEVER is returned
// Ensure the unknown package is not already in history by removing it
@@ -836,21 +872,20 @@
mController.setAppStandbyBucket(PACKAGE_UNKNOWN, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_TIMEOUT);
isPackageInstalled = true; // Reset mocked variable for other tests
- assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_UNKNOWN));
+ waitAndAssertBucket(STANDBY_BUCKET_NEVER, PACKAGE_UNKNOWN);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testAppStandbyBucketOnInstallAndUninstall() throws Exception {
// On package install, standby bucket should be ACTIVE
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_UNKNOWN);
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_UNKNOWN));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_UNKNOWN);
// On uninstall, package should not exist in history and should return a NEVER bucket
mController.clearAppIdleForPackage(PACKAGE_UNKNOWN, USER_ID);
- assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_UNKNOWN));
+ waitAndAssertBucket(STANDBY_BUCKET_NEVER, PACKAGE_UNKNOWN);
// Ensure uninstalled app is not in history
- List<AppStandbyInfo> buckets = mController.getAppStandbyBuckets(USER_ID);
+ List<AppStandbyInfo> buckets = getStandbyBuckets(USER_ID);
for(AppStandbyInfo bucket : buckets) {
if (bucket.mPackageName.equals(PACKAGE_UNKNOWN)) {
fail("packageName found in app idle history after uninstall.");
@@ -859,7 +894,6 @@
}
@Test
- @FlakyTest(bugId = 185169504)
public void testScreenTimeAndBuckets() throws Exception {
mInjector.setDisplayOn(false);
@@ -876,22 +910,21 @@
// RARE bucket, should fail because the screen wasn't ON.
mInjector.mElapsedRealtime = RARE_THRESHOLD + 1;
mController.checkIdleStates(USER_ID);
- assertNotEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertNotBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
mInjector.setDisplayOn(true);
assertTimeout(mController, RARE_THRESHOLD + 2 * HOUR_MS + 1, STANDBY_BUCKET_RARE);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testForcedIdle() throws Exception {
mController.forceIdleState(PACKAGE_1, USER_ID, true);
- assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
mController.forceIdleState(PACKAGE_1, USER_ID, false);
- assertEquals(STANDBY_BUCKET_ACTIVE, mController.getAppStandbyBucket(PACKAGE_1, USER_ID, 0,
- true));
+
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
}
@@ -901,15 +934,15 @@
.onPropertiesChanged(mInjector.getDeviceConfigProperties());
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime = 1;
rearmQuotaBumpLatch(PACKAGE_1, USER_ID);
reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1);
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mController.forceIdleState(PACKAGE_1, USER_ID, true);
reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1);
- assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
assertFalse(mQuotaBumpLatch.await(1, TimeUnit.SECONDS));
}
@@ -917,9 +950,10 @@
public void testNotificationEvent_bucketPromotion_changePromotedBucket() throws Exception {
mInjector.mPropertiesChangedListener
.onPropertiesChanged(mInjector.getDeviceConfigProperties());
+ mInjector.mElapsedRealtime += RARE_THRESHOLD + 1;
mController.forceIdleState(PACKAGE_1, USER_ID, true);
reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1);
- assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
// TODO: Avoid hardcoding these string constants.
mInjector.mSettingsBuilder.setInt("notification_seen_promoted_bucket",
@@ -928,11 +962,10 @@
mInjector.getDeviceConfigProperties());
mController.forceIdleState(PACKAGE_1, USER_ID, true);
reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1);
- assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testNotificationEvent_quotaBump() throws Exception {
mInjector.mSettingsBuilder
.setBoolean("trigger_quota_bump_on_notification_seen", true);
@@ -942,7 +975,7 @@
.onPropertiesChanged(mInjector.getDeviceConfigProperties());
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime = RARE_THRESHOLD * 2;
setAndAssertBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_FORCED_BY_SYSTEM);
@@ -951,83 +984,80 @@
reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1);
assertTrue(mQuotaBumpLatch.await(1, TimeUnit.SECONDS));
- assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testSlicePinnedEvent() throws Exception {
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime = 1;
reportEvent(mController, SLICE_PINNED, mInjector.mElapsedRealtime, PACKAGE_1);
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mController.forceIdleState(PACKAGE_1, USER_ID, true);
reportEvent(mController, SLICE_PINNED, mInjector.mElapsedRealtime, PACKAGE_1);
- assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testSlicePinnedPrivEvent() throws Exception {
mController.forceIdleState(PACKAGE_1, USER_ID, true);
reportEvent(mController, SLICE_PINNED_PRIV, mInjector.mElapsedRealtime, PACKAGE_1);
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testPredictionTimedOut() throws Exception {
// Set it to timeout or usage, so that prediction can override it
mInjector.mElapsedRealtime = HOUR_MS;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_TIMEOUT);
- assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_PREDICTED);
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Fast forward 12 hours
mInjector.mElapsedRealtime += WORKING_SET_THRESHOLD;
mController.checkIdleStates(USER_ID);
// Should still be in predicted bucket, since prediction timeout is 1 day since prediction
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Fast forward two more hours
mInjector.mElapsedRealtime += 2 * HOUR_MS;
mController.checkIdleStates(USER_ID);
// Should have now applied prediction timeout
- assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
// Fast forward RARE bucket
mInjector.mElapsedRealtime += RARE_THRESHOLD;
mController.checkIdleStates(USER_ID);
// Should continue to apply prediction timeout
- assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
+ @Ignore("b/317086276")
public void testOverrides() throws Exception {
// Can force to NEVER
mInjector.mElapsedRealtime = HOUR_MS;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_NEVER, PACKAGE_1);
// Prediction can't override FORCED reasons
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_FORCED_BY_SYSTEM);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_PREDICTED);
- assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
REASON_MAIN_FORCED_BY_USER);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_PREDICTED);
- assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1);
// Prediction can't override NEVER
mInjector.mElapsedRealtime = 2 * HOUR_MS;
@@ -1035,115 +1065,114 @@
REASON_MAIN_DEFAULT);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_PREDICTED);
- assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_NEVER, PACKAGE_1);
// Prediction can't set to NEVER
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_USAGE);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
REASON_MAIN_PREDICTED);
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Prediction can't remove from RESTRICTED
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_USER);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_PREDICTED);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_PREDICTED);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Force from user can remove from RESTRICTED
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_USER);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
// Force from system can remove from RESTRICTED if it was put it in due to system
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_FORCED_BY_SYSTEM);
- assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_USER);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_FORCED_BY_SYSTEM);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_PREDICTED);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_FORCED_BY_SYSTEM);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Non-user usage can't remove from RESTRICTED
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_USAGE);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_USAGE | REASON_SUB_USAGE_SYSTEM_INTERACTION);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_USAGE | REASON_SUB_USAGE_SYNC_ADAPTER);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_USAGE | REASON_SUB_USAGE_EXEMPTED_SYNC_SCHEDULED_NON_DOZE);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Explicit user usage can remove from RESTRICTED
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_USER);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_USAGE | REASON_SUB_USAGE_USER_INTERACTION);
- assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_USAGE | REASON_SUB_USAGE_MOVE_TO_FOREGROUND);
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testTimeout() throws Exception {
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime = 2000;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// bucketing works after timeout
mInjector.mElapsedRealtime = mController.mPredictionTimeoutMillis - 100;
mController.checkIdleStates(USER_ID);
// Use recent prediction
- assertBucket(STANDBY_BUCKET_FREQUENT);
+ waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1);
+ waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1);
// Way past prediction timeout, use system thresholds
mInjector.mElapsedRealtime = RARE_THRESHOLD;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RARE);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
}
/** Test that timeouts still work properly even if invalid configuration values are set. */
@Test
- @FlakyTest(bugId = 185169504)
public void testTimeout_InvalidThresholds() throws Exception {
mInjector.mSettingsBuilder
.setLong("screen_threshold_active", -1)
@@ -1161,19 +1190,19 @@
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime = HOUR_MS;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_FREQUENT);
+ waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1);
mInjector.mElapsedRealtime = 2 * HOUR_MS;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RARE);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
mInjector.mElapsedRealtime = 4 * HOUR_MS;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
}
/**
@@ -1181,74 +1210,72 @@
* timeout has passed.
*/
@Test
- @FlakyTest(bugId = 185169504)
+ @Ignore("b/317086276")
public void testTimeoutBeforeRestricted() throws Exception {
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime += WORKING_SET_THRESHOLD;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
// Bucket shouldn't change
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// bucketing works after timeout
mInjector.mElapsedRealtime += DAY_MS;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Way past all timeouts. Make sure timeout processing doesn't raise bucket.
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
}
/**
* Test that an app is put into the RESTRICTED bucket after enough time has passed.
*/
@Test
- @FlakyTest(bugId = 185169504)
public void testRestrictedDelay() throws Exception {
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime += mInjector.getAutoRestrictedBucketDelayMs() - 5000;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
// Bucket shouldn't change
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// bucketing works after timeout
mInjector.mElapsedRealtime += 6000;
Thread.sleep(6000);
// Enough time has passed. The app should automatically be put into the RESTRICTED bucket.
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
}
/**
* Test that an app is put into the RESTRICTED bucket after enough time has passed.
*/
@Test
- @FlakyTest(bugId = 185169504)
public void testRestrictedDelay_DelayChange() throws Exception {
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mAutoRestrictedBucketDelayMs = 2 * HOUR_MS;
mInjector.mElapsedRealtime += 2 * HOUR_MS - 5000;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
// Bucket shouldn't change
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// bucketing works after timeout
mInjector.mElapsedRealtime += 6000;
Thread.sleep(6000);
// Enough time has passed. The app should automatically be put into the RESTRICTED bucket.
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
}
/**
@@ -1256,36 +1283,35 @@
* a low bucket after the RESTRICTED timeout.
*/
@Test
- @FlakyTest(bugId = 185169504)
public void testRestrictedTimeoutOverridesRestoredLowBucketPrediction() throws Exception {
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Predict to RARE Not long enough to time out into RESTRICTED.
mInjector.mElapsedRealtime += RARE_THRESHOLD;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_RARE);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
// Add a short timeout event
mInjector.mElapsedRealtime += 1000;
reportEvent(mController, SYSTEM_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime += 1000;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Long enough that it could have timed out into RESTRICTED. Instead of reverting to
// predicted RARE, should go into RESTRICTED
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Ensure that prediction can still raise it out despite this override.
mInjector.mElapsedRealtime += 1;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
}
/**
@@ -1293,7 +1319,6 @@
* a low bucket after the RESTRICTED timeout.
*/
@Test
- @FlakyTest(bugId = 185169504)
public void testRestrictedTimeoutOverridesPredictionLowBucket() throws Exception {
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
@@ -1301,7 +1326,7 @@
mInjector.mElapsedRealtime += RARE_THRESHOLD;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_RARE);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
mInjector.mElapsedRealtime += 1;
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
@@ -1310,10 +1335,10 @@
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
}
/**
@@ -1321,261 +1346,250 @@
* interaction.
*/
@Test
- @FlakyTest(bugId = 185169504)
public void testSystemInteractionOverridesRestrictedTimeout() throws Exception {
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Long enough that it could have timed out into RESTRICTED.
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Report system interaction.
mInjector.mElapsedRealtime += 1000;
reportEvent(mController, SYSTEM_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
// Ensure that it's raised out of RESTRICTED for the system interaction elevation duration.
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime += 1000;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Elevation duration over. Should fall back down.
mInjector.mElapsedRealtime += 10 * MINUTE_MS;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testPredictionRaiseFromRestrictedTimeout_highBucket() throws Exception {
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
// Way past all timeouts. App times out into RESTRICTED bucket.
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Since the app timed out into RESTRICTED, prediction should be able to remove from the
// bucket.
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testPredictionRaiseFromRestrictedTimeout_lowBucket() throws Exception {
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
// Way past all timeouts. App times out into RESTRICTED bucket.
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Prediction into a low bucket means no expectation of the app being used, so we shouldn't
// elevate the app from RESTRICTED.
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testCascadingTimeouts() throws Exception {
mInjector.mPropertiesChangedListener
.onPropertiesChanged(mInjector.getDeviceConfigProperties());
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
reportEvent(mController, NOTIFICATION_SEEN, 1000, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime = 2000 + mController.mStrongUsageTimeoutMillis;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_WORKING_SET);
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
mInjector.mElapsedRealtime = 2000 + mController.mNotificationSeenTimeoutMillis;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_FREQUENT);
+ waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testOverlappingTimeouts() throws Exception {
mInjector.mPropertiesChangedListener
.onPropertiesChanged(mInjector.getDeviceConfigProperties());
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
reportEvent(mController, NOTIFICATION_SEEN, 1000, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Overlapping USER_INTERACTION before previous one times out
reportEvent(mController, USER_INTERACTION, mController.mStrongUsageTimeoutMillis - 1000,
PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Still in ACTIVE after first USER_INTERACTION times out
mInjector.mElapsedRealtime = mController.mStrongUsageTimeoutMillis + 1000;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Both timed out, so NOTIFICATION_SEEN timeout should be effective
mInjector.mElapsedRealtime = mController.mStrongUsageTimeoutMillis * 2 + 2000;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_WORKING_SET);
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
mInjector.mElapsedRealtime = mController.mNotificationSeenTimeoutMillis + 2000;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_RARE);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testSystemInteractionTimeout() throws Exception {
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
// Fast forward to RARE
mInjector.mElapsedRealtime = RARE_THRESHOLD + 100;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RARE);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
// Trigger a SYSTEM_INTERACTION and verify bucket
reportEvent(mController, SYSTEM_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Verify it's still in ACTIVE close to end of timeout
mInjector.mElapsedRealtime += mController.mSystemInteractionTimeoutMillis - 100;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Verify bucket moves to RARE after timeout
mInjector.mElapsedRealtime += 200;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RARE);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testInitialForegroundServiceTimeout() throws Exception {
mInjector.mElapsedRealtime = 1 * RARE_THRESHOLD + 100;
// Make sure app is in NEVER bucket
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
REASON_MAIN_FORCED_BY_USER);
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_NEVER);
+ waitAndAssertBucket(STANDBY_BUCKET_NEVER, PACKAGE_1);
mInjector.mElapsedRealtime += 100;
// Trigger a FOREGROUND_SERVICE_START and verify bucket
reportEvent(mController, FOREGROUND_SERVICE_START, mInjector.mElapsedRealtime, PACKAGE_1);
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Verify it's still in ACTIVE close to end of timeout
mInjector.mElapsedRealtime += mController.mInitialForegroundServiceStartTimeoutMillis - 100;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Verify bucket moves to RARE after timeout
mInjector.mElapsedRealtime += 200;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RARE);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
// Trigger a FOREGROUND_SERVICE_START again
reportEvent(mController, FOREGROUND_SERVICE_START, mInjector.mElapsedRealtime, PACKAGE_1);
mController.checkIdleStates(USER_ID);
// Bucket should not be immediately elevated on subsequent service starts
- assertBucket(STANDBY_BUCKET_RARE);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testPredictionNotOverridden() throws Exception {
mInjector.mPropertiesChangedListener
.onPropertiesChanged(mInjector.getDeviceConfigProperties());
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime = WORKING_SET_THRESHOLD - 1000;
reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Falls back to WORKING_SET
mInjector.mElapsedRealtime += 5000;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_WORKING_SET);
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
// Predict to ACTIVE
mInjector.mElapsedRealtime += 1000;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// CheckIdleStates should not change the prediction
mInjector.mElapsedRealtime += 1000;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testPredictionStrikesBack() throws Exception {
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Predict to FREQUENT
mInjector.mElapsedRealtime = RARE_THRESHOLD;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_FREQUENT);
+ waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1);
// Add a short timeout event
mInjector.mElapsedRealtime += 1000;
reportEvent(mController, SYSTEM_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime += 1000;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Verify it reverted to predicted
mInjector.mElapsedRealtime += WORKING_SET_THRESHOLD / 2;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_FREQUENT);
+ waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testSystemForcedFlags_NotAddedForUserForce() throws Exception {
final int expectedReason = REASON_MAIN_FORCED_BY_USER;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
assertEquals(expectedReason, getStandbyBucketReason(PACKAGE_1));
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
assertEquals(expectedReason, getStandbyBucketReason(PACKAGE_1));
}
@Test
- @FlakyTest(bugId = 185169504)
public void testSystemForcedFlags_AddedForSystemForce() throws Exception {
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_DEFAULT);
@@ -1584,13 +1598,13 @@
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM
| REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
assertEquals(REASON_MAIN_FORCED_BY_SYSTEM
| REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE,
getStandbyBucketReason(PACKAGE_1));
mController.restrictApp(PACKAGE_1, USER_ID, REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Flags should be combined
assertEquals(REASON_MAIN_FORCED_BY_SYSTEM
| REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE
@@ -1598,7 +1612,6 @@
}
@Test
- @FlakyTest(bugId = 185169504)
public void testSystemForcedFlags_SystemForceChangesBuckets() throws Exception {
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_DEFAULT);
@@ -1607,14 +1620,14 @@
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
REASON_MAIN_FORCED_BY_SYSTEM
| REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE);
- assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1);
assertEquals(REASON_MAIN_FORCED_BY_SYSTEM
| REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE,
getStandbyBucketReason(PACKAGE_1));
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE);
- assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1);
// Flags should be combined
assertEquals(REASON_MAIN_FORCED_BY_SYSTEM
| REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE
@@ -1623,20 +1636,19 @@
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY);
- assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
// Flags should be combined
assertEquals(REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY,
getStandbyBucketReason(PACKAGE_1));
mController.restrictApp(PACKAGE_1, USER_ID, REASON_SUB_FORCED_SYSTEM_FLAG_UNDEFINED);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Flags should not be combined since the bucket changed.
assertEquals(REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_UNDEFINED,
getStandbyBucketReason(PACKAGE_1));
}
@Test
- @FlakyTest(bugId = 185169504)
public void testRestrictApp_MainReason() throws Exception {
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_DEFAULT);
@@ -1644,11 +1656,11 @@
mController.restrictApp(PACKAGE_1, USER_ID, REASON_MAIN_PREDICTED, 0);
// Call should be ignored.
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mController.restrictApp(PACKAGE_1, USER_ID, REASON_MAIN_FORCED_BY_USER, 0);
// Call should go through
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
}
@Test
@@ -1724,15 +1736,15 @@
}
@Test
- @FlakyTest(bugId = 185169504)
public void testUserInteraction_CrossProfile() throws Exception {
mInjector.mRunningUsers = new int[] {USER_ID, USER_ID2, USER_ID3};
mInjector.mCrossProfileTargets = Arrays.asList(USER_HANDLE_USER2);
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertEquals("Cross profile connected package bucket should be elevated on usage",
- STANDBY_BUCKET_ACTIVE, getStandbyBucket(USER_ID2, mController, PACKAGE_1));
- assertEquals("Not Cross profile connected package bucket should not be elevated on usage",
- STANDBY_BUCKET_NEVER, getStandbyBucket(USER_ID3, mController, PACKAGE_1));
+ waitAndAssertBucket("Cross profile connected package bucket should be elevated on usage",
+ mController, STANDBY_BUCKET_ACTIVE, USER_ID2, PACKAGE_1);
+ waitAndAssertBucket(
+ "Not Cross profile connected package bucket should not be elevated on usage",
+ mController, STANDBY_BUCKET_NEVER, USER_ID3, PACKAGE_1);
assertTimeout(mController, WORKING_SET_THRESHOLD - 1, STANDBY_BUCKET_ACTIVE, USER_ID);
assertTimeout(mController, WORKING_SET_THRESHOLD - 1, STANDBY_BUCKET_ACTIVE, USER_ID2);
@@ -1742,51 +1754,50 @@
mInjector.mCrossProfileTargets = Collections.emptyList();
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertEquals("No longer cross profile connected package bucket should not be "
- + "elevated on usage",
- STANDBY_BUCKET_WORKING_SET, getStandbyBucket(USER_ID2, mController, PACKAGE_1));
+ waitAndAssertBucket("No longer cross profile connected package bucket should not be "
+ + "elevated on usage", mController, STANDBY_BUCKET_WORKING_SET, USER_ID2,
+ PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testUnexemptedSyncScheduled() throws Exception {
rearmLatch(PACKAGE_1);
mController.addListener(mListener);
- assertEquals("Test package did not start in the Never bucket", STANDBY_BUCKET_NEVER,
- getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket("Test package did not start in the Never bucket", STANDBY_BUCKET_NEVER,
+ PACKAGE_1);
mController.postReportSyncScheduled(PACKAGE_1, USER_ID, false);
mStateChangedLatch.await(1000, TimeUnit.MILLISECONDS);
- assertEquals("Unexempted sync scheduled should bring the package out of the Never bucket",
- STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(
+ "Unexempted sync scheduled should bring the package out of the Never bucket",
+ STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
setAndAssertBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_FORCED_BY_SYSTEM);
rearmLatch(PACKAGE_1);
mController.postReportSyncScheduled(PACKAGE_1, USER_ID, false);
mStateChangedLatch.await(1000, TimeUnit.MILLISECONDS);
- assertEquals("Unexempted sync scheduled should not elevate a non Never bucket",
- STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket("Unexempted sync scheduled should not elevate a non Never bucket",
+ STANDBY_BUCKET_RARE, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testExemptedSyncScheduled() throws Exception {
setAndAssertBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_FORCED_BY_SYSTEM);
mInjector.mDeviceIdleMode = true;
rearmLatch(PACKAGE_1);
mController.postReportSyncScheduled(PACKAGE_1, USER_ID, true);
mStateChangedLatch.await(1000, TimeUnit.MILLISECONDS);
- assertEquals("Exempted sync scheduled in doze should set bucket to working set",
- STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket("Exempted sync scheduled in doze should set bucket to working set",
+ STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
setAndAssertBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_FORCED_BY_SYSTEM);
mInjector.mDeviceIdleMode = false;
rearmLatch(PACKAGE_1);
mController.postReportSyncScheduled(PACKAGE_1, USER_ID, true);
mStateChangedLatch.await(1000, TimeUnit.MILLISECONDS);
- assertEquals("Exempted sync scheduled while not in doze should set bucket to active",
- STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket("Exempted sync scheduled while not in doze should set bucket to active",
+ STANDBY_BUCKET_ACTIVE, PACKAGE_1);
}
@Test
@@ -1796,14 +1807,14 @@
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID2);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.maybeUnrestrictBuggyApp("com.random.package", USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Updates shouldn't change bucket if the app was forced by the system for a non-buggy
// reason.
@@ -1814,11 +1825,11 @@
| REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE);
mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID2);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.maybeUnrestrictBuggyApp("com.random.package", USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Updates should change bucket if the app was forced by the system for a buggy reason.
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
@@ -1827,11 +1838,11 @@
REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY);
mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID2);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.maybeUnrestrictBuggyApp("com.random.package", USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID);
- assertNotEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertNotBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Updates shouldn't change bucket if the app was forced by the system for more than just
// a buggy reason.
@@ -1842,13 +1853,13 @@
| REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY);
mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
assertEquals(REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE,
getStandbyBucketReason(PACKAGE_1));
mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID2);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.maybeUnrestrictBuggyApp("com.random.package", USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Updates shouldn't change bucket if the app was forced by the user.
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
@@ -1857,11 +1868,11 @@
REASON_MAIN_FORCED_BY_USER);
mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID2);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.maybeUnrestrictBuggyApp("com.random.package", USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
}
@Test
@@ -1876,37 +1887,37 @@
mController.setAppStandbyBucket(PACKAGE_SYSTEM_HEADFULL, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_TIMEOUT);
- assertBucket(STANDBY_BUCKET_RARE, PACKAGE_SYSTEM_HEADFULL);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_SYSTEM_HEADFULL);
// Make sure headless system apps don't get lowered.
mController.setAppStandbyBucket(PACKAGE_SYSTEM_HEADLESS, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_TIMEOUT);
- assertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_SYSTEM_HEADLESS);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_SYSTEM_HEADLESS);
// Package 1 doesn't have activities and is headless, but is not a system app, so it can
// be lowered.
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_TIMEOUT);
- assertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
}
@Test
public void testWellbeingAppElevated() throws Exception {
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_WELLBEING);
- assertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_WELLBEING);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_WELLBEING);
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD;
// Make sure the default wellbeing app does not get lowered below WORKING_SET.
mController.setAppStandbyBucket(PACKAGE_WELLBEING, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_TIMEOUT);
- assertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_WELLBEING);
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_WELLBEING);
// A non default wellbeing app should be able to fall lower than WORKING_SET.
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_TIMEOUT);
- assertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
}
@Test
@@ -1914,22 +1925,22 @@
mInjector.mClockApps.add(Pair.create(PACKAGE_1, UID_1));
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_2);
- assertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_2);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_2);
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD;
// Make sure a clock app does not get lowered below WORKING_SET.
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_TIMEOUT);
- assertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
// A non clock app should be able to fall lower than WORKING_SET.
mController.setAppStandbyBucket(PACKAGE_2, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_TIMEOUT);
- assertBucket(STANDBY_BUCKET_RARE, PACKAGE_2);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_2);
}
@Test
@@ -2067,13 +2078,13 @@
public void testBackgroundLocationBucket() throws Exception {
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime,
PACKAGE_BACKGROUND_LOCATION);
- assertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_BACKGROUND_LOCATION);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_BACKGROUND_LOCATION);
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD;
// Make sure PACKAGE_BACKGROUND_LOCATION does not get lowered than STANDBY_BUCKET_FREQUENT.
mController.setAppStandbyBucket(PACKAGE_BACKGROUND_LOCATION, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_TIMEOUT);
- assertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_BACKGROUND_LOCATION);
+ waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_BACKGROUND_LOCATION);
}
@Test
@@ -2083,41 +2094,41 @@
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE, mInjector.mLastNoteEvent);
+ waitAndAssertLastNoteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE, mInjector.mLastNoteEvent);
+ waitAndAssertLastNoteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE);
// Since we're staying on the PACKAGE_ACTIVE side, noteEvent shouldn't be called.
// Reset the last event to confirm the method isn't called.
mInjector.mLastNoteEvent = BatteryStats.HistoryItem.EVENT_NONE;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(BatteryStats.HistoryItem.EVENT_NONE, mInjector.mLastNoteEvent);
+ waitAndAssertLastNoteEvent(BatteryStats.HistoryItem.EVENT_NONE);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE, mInjector.mLastNoteEvent);
+ waitAndAssertLastNoteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE);
// Since we're staying on the PACKAGE_ACTIVE side, noteEvent shouldn't be called.
// Reset the last event to confirm the method isn't called.
mInjector.mLastNoteEvent = BatteryStats.HistoryItem.EVENT_NONE;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(BatteryStats.HistoryItem.EVENT_NONE, mInjector.mLastNoteEvent);
+ waitAndAssertLastNoteEvent(BatteryStats.HistoryItem.EVENT_NONE);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE, mInjector.mLastNoteEvent);
+ waitAndAssertLastNoteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE, mInjector.mLastNoteEvent);
+ waitAndAssertLastNoteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_EXEMPTED,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE, mInjector.mLastNoteEvent);
+ waitAndAssertLastNoteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE);
}
private String getAdminAppsStr(int userId) {
@@ -2187,8 +2198,7 @@
rearmLatch(pkg);
mController.setAppStandbyBucket(pkg, user, bucket, reason);
mStateChangedLatch.await(1, TimeUnit.SECONDS);
- assertEquals("Failed to set package bucket", bucket,
- getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket("Failed to set package bucket", bucket, PACKAGE_1);
}
private void rearmLatch(String pkgName) {
@@ -2205,4 +2215,12 @@
mLatchUserId = userId;
mQuotaBumpLatch = new CountDownLatch(1);
}
+
+ private void flushHandler(AppStandbyController controller) {
+ assertTrue("Failed to flush handler!", controller.flushHandler(FLUSH_TIMEOUT_MILLISECONDS));
+ // Some AppStandbyController handler messages queue another handler message. Flush again
+ // to catch those as well.
+ assertTrue("Failed to flush handler (the second time)!",
+ controller.flushHandler(FLUSH_TIMEOUT_MILLISECONDS));
+ }
}
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 260ee396..30843d2 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
@@ -245,8 +245,8 @@
}
@Test
- @TestParameters({"{origin: ORIGIN_USER}", "{origin: ORIGIN_INIT}", "{origin: ORIGIN_INIT_USER}",
- "{origin: ORIGIN_SYSTEM_OR_SYSTEMUI}"})
+ @TestParameters({"{origin: ORIGIN_USER}", "{origin: ORIGIN_INIT}",
+ "{origin: ORIGIN_INIT_USER}"})
public void apply_nightModeWithScreenOn_appliedImmediatelyBasedOnOrigin(ChangeOrigin origin) {
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
@@ -263,7 +263,7 @@
@Test
@TestParameters({"{origin: ORIGIN_APP}", "{origin: ORIGIN_RESTORE_BACKUP}",
- "{origin: ORIGIN_UNKNOWN}"})
+ "{origin: ORIGIN_SYSTEM_OR_SYSTEMUI}", "{origin: ORIGIN_UNKNOWN}"})
public void apply_nightModeWithScreenOn_willBeAppliedLaterBasedOnOrigin(ChangeOrigin origin) {
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
index 2868b7e..ae36839 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
@@ -68,10 +68,7 @@
import android.os.Parcel;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.INotificationListener;
import android.service.notification.NotificationListenerFilter;
import android.service.notification.NotificationListenerService;
@@ -111,7 +108,7 @@
public class NotificationListenersTest extends UiServiceTestCase {
@Rule
- public CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+ public SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock
private PackageManager mPm;
@@ -696,8 +693,8 @@
}
@Test
- @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testListenerTrusted_withPermission() throws RemoteException {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
when(mNm.mPackageManager.checkUidPermission(RECEIVE_SENSITIVE_NOTIFICATIONS, mUid1))
.thenReturn(PERMISSION_GRANTED);
ManagedServices.ManagedServiceInfo info = getMockServiceInfo();
@@ -706,8 +703,8 @@
}
@Test
- @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testListenerTrusted_withSystemSignature() {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
when(mNm.mPackageManagerInternal.isPlatformSigned(mCn1.getPackageName())).thenReturn(true);
ManagedServices.ManagedServiceInfo info = getMockServiceInfo();
mListeners.onServiceAdded(info);
@@ -715,8 +712,8 @@
}
@Test
- @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testListenerTrusted_withCdmAssociation() throws Exception {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
mNm.mCompanionManager = mock(ICompanionDeviceManager.class);
AssociationInfo assocInfo = mock(AssociationInfo.class);
when(assocInfo.isRevoked()).thenReturn(false);
@@ -731,16 +728,16 @@
}
@Test
- @RequiresFlagsDisabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testListenerTrusted_ifFlagDisabled() {
+ mSetFlagsRule.disableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
ManagedServices.ManagedServiceInfo info = getMockServiceInfo();
mListeners.onServiceAdded(info);
assertTrue(mListeners.isUidTrusted(mUid1));
}
@Test
- @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testRedaction_whenPosted() {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
ArrayList<ManagedServices.ManagedServiceInfo> infos = new ArrayList<>();
infos.add(getMockServiceInfo());
doReturn(infos).when(mListeners).getServices();
@@ -762,13 +759,11 @@
mListeners.notifyPostedLocked(r, old);
verify(mListeners, atLeast(1)).redactStatusBarNotification(eq(sbn));
verify(mListeners, never()).redactStatusBarNotification(eq(oldSbn));
-
-
}
@Test
- @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testRedaction_whenPosted_oldRemoved() {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
ArrayList<ManagedServices.ManagedServiceInfo> infos = new ArrayList<>();
infos.add(getMockServiceInfo());
doReturn(infos).when(mListeners).getServices();
@@ -795,8 +790,8 @@
}
@Test
- @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testRedaction_whenRemoved() {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
doReturn(mock(StatusBarNotification.class))
.when(mListeners).redactStatusBarNotification(any());
ArrayList<ManagedServices.ManagedServiceInfo> infos = new ArrayList<>();
@@ -816,8 +811,8 @@
}
@Test
- @RequiresFlagsDisabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testRedaction_noneIfFlagDisabled() {
+ mSetFlagsRule.disableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
ArrayList<ManagedServices.ManagedServiceInfo> infos = new ArrayList<>();
infos.add(getMockServiceInfo());
doReturn(infos).when(mListeners).getServices();
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 56c75b5..9408a8b 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -77,9 +77,14 @@
import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+import static android.service.notification.Adjustment.KEY_CONTEXTUAL_ACTIONS;
import static android.service.notification.Adjustment.KEY_IMPORTANCE;
+import static android.service.notification.Adjustment.KEY_TEXT_REPLIES;
import static android.service.notification.Adjustment.KEY_USER_SENTIMENT;
import static android.service.notification.Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS;
+import static android.service.notification.Condition.SOURCE_CONTEXT;
+import static android.service.notification.Condition.SOURCE_USER_ACTION;
+import static android.service.notification.Condition.STATE_TRUE;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
@@ -213,9 +218,6 @@
import android.os.WorkSource;
import android.permission.PermissionManager;
import android.platform.test.annotations.EnableFlags;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
import android.platform.test.rule.DeniedDevices;
import android.platform.test.rule.DeviceProduct;
@@ -224,6 +226,7 @@
import android.provider.MediaStore;
import android.provider.Settings;
import android.service.notification.Adjustment;
+import android.service.notification.Condition;
import android.service.notification.ConversationChannelWrapper;
import android.service.notification.DeviceEffectsApplier;
import android.service.notification.INotificationListener;
@@ -293,6 +296,7 @@
import org.junit.Assert;
import org.junit.Before;
import org.junit.ClassRule;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
@@ -342,6 +346,11 @@
private static final String SCHEME_TIMEOUT = "timeout";
private static final String REDACTED_TEXT = "redacted text";
+ private static final AutomaticZenRule SOME_ZEN_RULE =
+ new AutomaticZenRule.Builder("rule", Uri.parse("uri"))
+ .setOwner(new ComponentName("pkg", "cls"))
+ .build();
+
private final int mUid = Binder.getCallingUid();
private final @UserIdInt int mUserId = UserHandle.getUserId(mUid);
@@ -351,9 +360,6 @@
@Rule
public TestRule compatChangeRule = new PlatformCompatChangeRule();
- @Rule
- public CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
private TestableNotificationManagerService mService;
private INotificationManager mBinderService;
private NotificationManagerInternal mInternalService;
@@ -9048,7 +9054,7 @@
zenPolicy, NotificationManager.INTERRUPTION_FILTER_NONE, isEnabled);
try {
- mBinderService.addAutomaticZenRule(rule, mContext.getPackageName());
+ mBinderService.addAutomaticZenRule(rule, mContext.getPackageName(), false);
fail("Zen policy only applies to priority only mode");
} catch (IllegalArgumentException e) {
// yay
@@ -9056,11 +9062,11 @@
rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled);
- mBinderService.addAutomaticZenRule(rule, mContext.getPackageName());
+ mBinderService.addAutomaticZenRule(rule, mContext.getPackageName(), false);
rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
null, NotificationManager.INTERRUPTION_FILTER_NONE, isEnabled);
- mBinderService.addAutomaticZenRule(rule, mContext.getPackageName());
+ mBinderService.addAutomaticZenRule(rule, mContext.getPackageName(), false);
}
@Test
@@ -9075,7 +9081,7 @@
boolean isEnabled = true;
AutomaticZenRule rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled);
- mBinderService.addAutomaticZenRule(rule, "com.android.settings");
+ mBinderService.addAutomaticZenRule(rule, "com.android.settings", false);
// verify that zen mode helper gets passed in a package name of "android"
verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule),
@@ -9097,7 +9103,7 @@
boolean isEnabled = true;
AutomaticZenRule rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled);
- mBinderService.addAutomaticZenRule(rule, "com.android.settings");
+ mBinderService.addAutomaticZenRule(rule, "com.android.settings", false);
// verify that zen mode helper gets passed in a package name of "android"
verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule),
@@ -9117,7 +9123,7 @@
boolean isEnabled = true;
AutomaticZenRule rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled);
- mBinderService.addAutomaticZenRule(rule, "another.package");
+ mBinderService.addAutomaticZenRule(rule, "another.package", false);
// verify that zen mode helper gets passed in the package name from the arg, not the owner
verify(mockZenModeHelper).addAutomaticZenRule(
@@ -9128,10 +9134,8 @@
@Test
@EnableFlags(android.app.Flags.FLAG_MODES_API)
public void testAddAutomaticZenRule_typeManagedCanBeUsedByDeviceOwners() throws Exception {
+ ZenModeHelper zenModeHelper = setUpMockZenTest();
mService.setCallerIsNormalPackage();
- mService.setZenHelper(mock(ZenModeHelper.class));
- when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
- .thenReturn(true);
AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", Uri.parse("uri"))
.setType(AutomaticZenRule.TYPE_MANAGED)
@@ -9139,8 +9143,9 @@
.build();
when(mDevicePolicyManager.isActiveDeviceOwner(anyInt())).thenReturn(true);
- mBinderService.addAutomaticZenRule(rule, "pkg");
- // No exception!
+ mBinderService.addAutomaticZenRule(rule, "pkg", /* fromUser= */ false);
+
+ verify(zenModeHelper).addAutomaticZenRule(eq("pkg"), eq(rule), anyInt(), any(), anyInt());
}
@Test
@@ -9158,7 +9163,144 @@
when(mDevicePolicyManager.isActiveDeviceOwner(anyInt())).thenReturn(false);
assertThrows(IllegalArgumentException.class,
- () -> mBinderService.addAutomaticZenRule(rule, "pkg"));
+ () -> mBinderService.addAutomaticZenRule(rule, "pkg", /* fromUser= */ false));
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void addAutomaticZenRule_fromUser_mappedToOriginUser() throws Exception {
+ ZenModeHelper zenModeHelper = setUpMockZenTest();
+ mService.isSystemUid = true;
+
+ mBinderService.addAutomaticZenRule(SOME_ZEN_RULE, "pkg", /* fromUser= */ true);
+
+ verify(zenModeHelper).addAutomaticZenRule(eq("pkg"), eq(SOME_ZEN_RULE),
+ eq(ZenModeConfig.UPDATE_ORIGIN_USER), anyString(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void addAutomaticZenRule_fromSystemNotUser_mappedToOriginSystem() throws Exception {
+ ZenModeHelper zenModeHelper = setUpMockZenTest();
+ mService.isSystemUid = true;
+
+ mBinderService.addAutomaticZenRule(SOME_ZEN_RULE, "pkg", /* fromUser= */ false);
+
+ verify(zenModeHelper).addAutomaticZenRule(eq("pkg"), eq(SOME_ZEN_RULE),
+ eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyString(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void addAutomaticZenRule_fromApp_mappedToOriginApp() throws Exception {
+ ZenModeHelper zenModeHelper = setUpMockZenTest();
+ mService.setCallerIsNormalPackage();
+
+ mBinderService.addAutomaticZenRule(SOME_ZEN_RULE, "pkg", /* fromUser= */ false);
+
+ verify(zenModeHelper).addAutomaticZenRule(eq("pkg"), eq(SOME_ZEN_RULE),
+ eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyString(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void addAutomaticZenRule_fromAppFromUser_blocked() throws Exception {
+ setUpMockZenTest();
+ mService.setCallerIsNormalPackage();
+
+ assertThrows(SecurityException.class, () ->
+ mBinderService.addAutomaticZenRule(SOME_ZEN_RULE, "pkg", /* fromUser= */ true));
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void updateAutomaticZenRule_fromUserFromSystem_allowed() throws Exception {
+ ZenModeHelper zenModeHelper = setUpMockZenTest();
+ mService.isSystemUid = true;
+
+ mBinderService.updateAutomaticZenRule("id", SOME_ZEN_RULE, /* fromUser= */ true);
+
+ verify(zenModeHelper).updateAutomaticZenRule(eq("id"), eq(SOME_ZEN_RULE),
+ eq(ZenModeConfig.UPDATE_ORIGIN_USER), anyString(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void updateAutomaticZenRule_fromUserFromApp_blocked() throws Exception {
+ setUpMockZenTest();
+ mService.setCallerIsNormalPackage();
+
+ assertThrows(SecurityException.class, () ->
+ mBinderService.addAutomaticZenRule(SOME_ZEN_RULE, "pkg", /* fromUser= */ true));
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void removeAutomaticZenRule_fromUserFromSystem_allowed() throws Exception {
+ ZenModeHelper zenModeHelper = setUpMockZenTest();
+ mService.isSystemUid = true;
+
+ mBinderService.removeAutomaticZenRule("id", /* fromUser= */ true);
+
+ verify(zenModeHelper).removeAutomaticZenRule(eq("id"),
+ eq(ZenModeConfig.UPDATE_ORIGIN_USER), anyString(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void removeAutomaticZenRule_fromUserFromApp_blocked() throws Exception {
+ setUpMockZenTest();
+ mService.setCallerIsNormalPackage();
+
+ assertThrows(SecurityException.class, () ->
+ mBinderService.removeAutomaticZenRule("id", /* fromUser= */ true));
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void setAutomaticZenRuleState_fromUserMatchesConditionSource_okay() throws Exception {
+ ZenModeHelper zenModeHelper = setUpMockZenTest();
+ mService.setCallerIsNormalPackage();
+
+ Condition withSourceContext = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
+ SOURCE_CONTEXT);
+ mBinderService.setAutomaticZenRuleState("id", withSourceContext, /* fromUser= */ false);
+ verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceContext),
+ eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyInt());
+
+ Condition withSourceUser = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
+ SOURCE_USER_ACTION);
+ mBinderService.setAutomaticZenRuleState("id", withSourceUser, /* fromUser= */ true);
+ verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceUser),
+ eq(ZenModeConfig.UPDATE_ORIGIN_USER), anyInt());
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void setAutomaticZenRuleState_fromUserDoesNotMatchConditionSource_blocked()
+ throws Exception {
+ ZenModeHelper zenModeHelper = setUpMockZenTest();
+ mService.setCallerIsNormalPackage();
+
+ Condition withSourceContext = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
+ SOURCE_CONTEXT);
+ assertThrows(IllegalArgumentException.class,
+ () -> mBinderService.setAutomaticZenRuleState("id", withSourceContext,
+ /* fromUser= */ true));
+
+ Condition withSourceUser = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
+ SOURCE_USER_ACTION);
+ assertThrows(IllegalArgumentException.class,
+ () -> mBinderService.setAutomaticZenRuleState("id", withSourceUser,
+ /* fromUser= */ false));
+ }
+
+ private ZenModeHelper setUpMockZenTest() {
+ ZenModeHelper zenModeHelper = mock(ZenModeHelper.class);
+ mService.setZenHelper(zenModeHelper);
+ when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
+ .thenReturn(true);
+ return zenModeHelper;
}
@Test
@@ -9184,7 +9326,7 @@
});
mService.getBinderService().setZenMode(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS, null,
- "testing!");
+ "testing!", false);
waitForIdle();
InOrder inOrder = inOrder(mContext);
@@ -11632,8 +11774,8 @@
}
@Test
- @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testGetActiveNotificationsFromListener_redactNotification() throws Exception {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
NotificationRecord r =
generateNotificationRecord(mTestNotificationChannel, 0, 0);
mService.addNotification(r);
@@ -11662,12 +11804,11 @@
}
@Test
- @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testGetSnoozedNotificationsFromListener_redactNotification() throws Exception {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
NotificationRecord r =
generateNotificationRecord(mTestNotificationChannel, 0, 0);
- mService.addNotification(r);
- mService.snoozeNotificationInt(r.getKey(), 1000, null, mListener);
+ when(mSnoozeHelper.getSnoozed()).thenReturn(List.of(r));
when(mListeners.isUidTrusted(anyInt())).thenReturn(false);
when(mListeners.hasSensitiveContent(any())).thenReturn(true);
StatusBarNotification redacted = generateRedactedSbn(mTestNotificationChannel, 1, 1);
@@ -11847,6 +11988,97 @@
}
@Test
+ public void testMakeRankingUpdate_redactsIfRecordSensitiveAndServiceUntrusted() {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
+ when(mListeners.isUidTrusted(anyInt())).thenReturn(false);
+ when(mListeners.hasSensitiveContent(any())).thenReturn(true);
+ NotificationRecord pkgA = new NotificationRecord(mContext,
+ generateSbn("a", 1000, 9, 0), mTestNotificationChannel);
+ addSmartActionsAndReplies(pkgA);
+ mService.addNotification(pkgA);
+ NotificationRecord pkgB = new NotificationRecord(mContext,
+ generateSbn("b", 1001, 9, 0), mTestNotificationChannel);
+ addSmartActionsAndReplies(pkgB);
+ mService.addNotification(pkgB);
+
+ ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
+ when(info.enabledAndUserMatches(anyInt())).thenReturn(true);
+ when(info.isSameUser(anyInt())).thenReturn(true);
+ NotificationRankingUpdate nru = mService.makeRankingUpdateLocked(info);
+ NotificationListenerService.Ranking ranking =
+ nru.getRankingMap().getRawRankingObject(pkgA.getSbn().getKey());
+ assertEquals(0, ranking.getSmartActions().size());
+ assertEquals(0, ranking.getSmartReplies().size());
+ NotificationListenerService.Ranking ranking2 =
+ nru.getRankingMap().getRawRankingObject(pkgB.getSbn().getKey());
+ assertEquals(0, ranking2.getSmartActions().size());
+ assertEquals(0, ranking2.getSmartReplies().size());
+ }
+
+ @Test
+ public void testMakeRankingUpdate_doestntRedactIfFlagDisabled() {
+ mSetFlagsRule.disableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
+ when(mListeners.isUidTrusted(anyInt())).thenReturn(false);
+ when(mListeners.hasSensitiveContent(any())).thenReturn(true);
+ NotificationRecord pkgA = new NotificationRecord(mContext,
+ generateSbn("a", 1000, 9, 0), mTestNotificationChannel);
+ addSmartActionsAndReplies(pkgA);
+
+ mService.addNotification(pkgA);
+ ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
+ when(info.enabledAndUserMatches(anyInt())).thenReturn(true);
+ when(info.isSameUser(anyInt())).thenReturn(true);
+ NotificationRankingUpdate nru = mService.makeRankingUpdateLocked(info);
+ NotificationListenerService.Ranking ranking =
+ nru.getRankingMap().getRawRankingObject(pkgA.getSbn().getKey());
+ assertEquals(1, ranking.getSmartActions().size());
+ assertEquals(1, ranking.getSmartReplies().size());
+ }
+
+ @Test
+ public void testMakeRankingUpdate_doesntRedactIfNotSensitiveOrServiceTrusted() {
+ mSetFlagsRule.disableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
+ NotificationRecord pkgA = new NotificationRecord(mContext,
+ generateSbn("a", 1000, 9, 0), mTestNotificationChannel);
+ addSmartActionsAndReplies(pkgA);
+
+ mService.addNotification(pkgA);
+ ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
+ when(info.enabledAndUserMatches(anyInt())).thenReturn(true);
+ when(info.isSameUser(anyInt())).thenReturn(true);
+
+ // No sensitive content, no redaction
+ when(mListeners.isUidTrusted(eq(1000))).thenReturn(false);
+ when(mListeners.hasSensitiveContent(any())).thenReturn(false);
+ NotificationRankingUpdate nru = mService.makeRankingUpdateLocked(info);
+ NotificationListenerService.Ranking ranking =
+ nru.getRankingMap().getRawRankingObject(pkgA.getSbn().getKey());
+ assertEquals(1, ranking.getSmartActions().size());
+ assertEquals(1, ranking.getSmartReplies().size());
+
+ // trusted listener, no redaction
+ when(mListeners.isUidTrusted(eq(1000))).thenReturn(true);
+ when(mListeners.hasSensitiveContent(any())).thenReturn(true);
+ nru = mService.makeRankingUpdateLocked(info);
+ ranking = nru.getRankingMap().getRawRankingObject(pkgA.getSbn().getKey());
+ assertEquals(1, ranking.getSmartActions().size());
+ assertEquals(1, ranking.getSmartReplies().size());
+ }
+
+ private void addSmartActionsAndReplies(NotificationRecord record) {
+ Bundle b = new Bundle();
+ ArrayList<Notification.Action> actions = new ArrayList<>();
+ actions.add(new Notification.Action(0, "", null));
+ b.putParcelableArrayList(KEY_CONTEXTUAL_ACTIONS, actions);
+ ArrayList<CharSequence> replies = new ArrayList<>(List.of("test"));
+ b.putCharSequenceArrayList(KEY_TEXT_REPLIES, replies);
+ Adjustment a = new Adjustment(record.getSbn().getPackageName(), record.getSbn().getKey(),
+ b, "", record.getUserId());
+ record.addAdjustment(a);
+ record.applyAdjustments();
+ }
+
+ @Test
public void testMaybeShowReviewPermissionsNotification_flagOff() {
mService.setShowReviewPermissionsNotification(false);
reset(mMockNm);
@@ -12157,7 +12389,9 @@
/* isImageBitmap= */ true,
/* isExpired= */ true);
addRecordAndRemoveBitmaps(record);
- assertThat(record.getNotification().extras.containsKey(EXTRA_PICTURE)).isFalse();
+ assertThat(record.getNotification().extras.containsKey(EXTRA_PICTURE)).isTrue();
+ final Parcelable picture = record.getNotification().extras.getParcelable(EXTRA_PICTURE);
+ assertThat(picture).isNull();
}
@Test
@@ -12191,7 +12425,10 @@
/* isImageBitmap= */ false,
/* isExpired= */ true);
addRecordAndRemoveBitmaps(record);
- assertThat(record.getNotification().extras.containsKey(EXTRA_PICTURE_ICON)).isFalse();
+ assertThat(record.getNotification().extras.containsKey(EXTRA_PICTURE_ICON)).isTrue();
+ final Parcelable pictureIcon =
+ record.getNotification().extras.getParcelable(EXTRA_PICTURE_ICON);
+ assertThat(pictureIcon).isNull();
}
@Test
@@ -13441,9 +13678,10 @@
.thenReturn(true);
NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
- mBinderService.setNotificationPolicy("package", policy);
+ mBinderService.setNotificationPolicy("package", policy, false);
- verify(zenHelper).applyGlobalPolicyAsImplicitZenRule(eq("package"), anyInt(), eq(policy));
+ verify(zenHelper).applyGlobalPolicyAsImplicitZenRule(eq("package"), anyInt(), eq(policy),
+ eq(ZenModeConfig.UPDATE_ORIGIN_APP));
}
@Test
@@ -13457,14 +13695,38 @@
mService.isSystemUid = true;
NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
- mBinderService.setNotificationPolicy("package", policy);
+ mBinderService.setNotificationPolicy("package", policy, false);
verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
}
@Test
@EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
- public void setNotificationPolicy_watchCompanionApp_setsGlobalPolicy() throws RemoteException {
+ public void setNotificationPolicy_watchCompanionApp_setsGlobalPolicy()
+ throws RemoteException {
+ setNotificationPolicy_dependingOnCompanionAppDevice_maySetGlobalPolicy(
+ AssociationRequest.DEVICE_PROFILE_WATCH, true);
+ }
+
+ @Test
+ @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
+ public void setNotificationPolicy_autoCompanionApp_setsGlobalPolicy()
+ throws RemoteException {
+ setNotificationPolicy_dependingOnCompanionAppDevice_maySetGlobalPolicy(
+ AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION, true);
+ }
+
+ @Test
+ @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
+ public void setNotificationPolicy_otherCompanionApp_doesNotSetGlobalPolicy()
+ throws RemoteException {
+ setNotificationPolicy_dependingOnCompanionAppDevice_maySetGlobalPolicy(
+ AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, false);
+ }
+
+ private void setNotificationPolicy_dependingOnCompanionAppDevice_maySetGlobalPolicy(
+ @AssociationRequest.DeviceProfile String deviceProfile, boolean canSetGlobalPolicy)
+ throws RemoteException {
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mService.setCallerIsNormalPackage();
ZenModeHelper zenModeHelper = mock(ZenModeHelper.class);
@@ -13474,14 +13736,19 @@
when(mCompanionMgr.getAssociations(anyString(), anyInt()))
.thenReturn(ImmutableList.of(
new AssociationInfo.Builder(1, mUserId, "package")
- .setDisplayName("My watch")
- .setDeviceProfile(AssociationRequest.DEVICE_PROFILE_WATCH)
+ .setDisplayName("My connected device")
+ .setDeviceProfile(deviceProfile)
.build()));
NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
- mBinderService.setNotificationPolicy("package", policy);
+ mBinderService.setNotificationPolicy("package", policy, false);
- verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
+ if (canSetGlobalPolicy) {
+ verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
+ } else {
+ verify(zenModeHelper).applyGlobalPolicyAsImplicitZenRule(anyString(), anyInt(),
+ eq(policy), anyInt());
+ }
}
@Test
@@ -13495,7 +13762,7 @@
.thenReturn(true);
NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
- mBinderService.setNotificationPolicy("package", policy);
+ mBinderService.setNotificationPolicy("package", policy, false);
verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
}
@@ -13525,7 +13792,7 @@
when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
.thenReturn(true);
- mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY);
+ mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY, false);
verify(zenHelper).applyGlobalZenModeAsImplicitZenRule(eq("package"), anyInt(),
eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS));
@@ -13542,7 +13809,7 @@
.thenReturn(true);
mService.isSystemUid = true;
- mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY);
+ mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY, false);
verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null),
eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyString(), eq("package"),
@@ -13551,7 +13818,29 @@
@Test
@EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
- public void setInterruptionFilter_watchCompanionApp_setsGlobalPolicy() throws RemoteException {
+ public void setInterruptionFilter_watchCompanionApp_setsGlobalZen() throws RemoteException {
+ setInterruptionFilter_dependingOnCompanionAppDevice_maySetGlobalZen(
+ AssociationRequest.DEVICE_PROFILE_WATCH, true);
+ }
+
+ @Test
+ @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
+ public void setInterruptionFilter_autoCompanionApp_setsGlobalZen() throws RemoteException {
+ setInterruptionFilter_dependingOnCompanionAppDevice_maySetGlobalZen(
+ AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION, true);
+ }
+
+ @Test
+ @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
+ public void setInterruptionFilter_otherCompanionApp_doesNotSetGlobalZen()
+ throws RemoteException {
+ setInterruptionFilter_dependingOnCompanionAppDevice_maySetGlobalZen(
+ AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, false);
+ }
+
+ private void setInterruptionFilter_dependingOnCompanionAppDevice_maySetGlobalZen(
+ @AssociationRequest.DeviceProfile String deviceProfile, boolean canSetGlobalPolicy)
+ throws RemoteException {
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
ZenModeHelper zenModeHelper = mock(ZenModeHelper.class);
mService.mZenModeHelper = zenModeHelper;
@@ -13561,14 +13850,19 @@
when(mCompanionMgr.getAssociations(anyString(), anyInt()))
.thenReturn(ImmutableList.of(
new AssociationInfo.Builder(1, mUserId, "package")
- .setDisplayName("My watch")
- .setDeviceProfile(AssociationRequest.DEVICE_PROFILE_WATCH)
+ .setDisplayName("My connected device")
+ .setDeviceProfile(deviceProfile)
.build()));
- mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY);
+ mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY, false);
- verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null),
- eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyString(), eq("package"), anyInt());
+ if (canSetGlobalPolicy) {
+ verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null),
+ eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyString(), eq("package"), anyInt());
+ } else {
+ verify(zenModeHelper).applyGlobalZenModeAsImplicitZenRule(anyString(), anyInt(),
+ eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS));
+ }
}
@Test
@@ -13586,6 +13880,7 @@
}
@Test
+ @Ignore("b/316989461")
public void cancelNotificationsFromListener_rapidClear_oldNew_cancelOne()
throws RemoteException {
mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
@@ -13615,6 +13910,7 @@
}
@Test
+ @Ignore("b/316989461")
public void cancelNotificationsFromListener_rapidClear_old_cancelOne() throws RemoteException {
mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
.FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
@@ -13642,6 +13938,7 @@
}
@Test
+ @Ignore("b/316989461")
public void cancelNotificationsFromListener_rapidClear_oldNew_cancelOne_flagDisabled()
throws RemoteException {
mSetFlagsRule.disableFlags(android.view.contentprotection.flags.Flags
@@ -13672,6 +13969,7 @@
}
@Test
+ @Ignore("b/316989461")
public void cancelNotificationsFromListener_rapidClear_oldNew_cancelAll()
throws RemoteException {
mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
@@ -13700,6 +13998,7 @@
}
@Test
+ @Ignore("b/316989461")
public void cancelNotificationsFromListener_rapidClear_old_cancelAll() throws RemoteException {
mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
.FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
@@ -13726,6 +14025,7 @@
}
@Test
+ @Ignore("b/316989461")
public void cancelNotificationsFromListener_rapidClear_oldNew_cancelAll_flagDisabled()
throws RemoteException {
mSetFlagsRule.disableFlags(android.view.contentprotection.flags.Flags
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java
index 4a1435f..1fcee06 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java
@@ -99,7 +99,7 @@
public boolean getFromSystemOrSystemUi(int i) throws IllegalArgumentException {
// While this isn't a logged output value, it's still helpful to check in tests.
checkInRange(i);
- return mChanges.get(i).mFromSystemOrSystemUi;
+ return mChanges.get(i).isFromSystemOrSystemUi();
}
public boolean getIsUserAction(int i) throws IllegalArgumentException {
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 1aea56c..44f0894 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -43,6 +43,8 @@
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_OFF;
+import static android.service.notification.Condition.SOURCE_SCHEDULE;
+import static android.service.notification.Condition.SOURCE_USER_ACTION;
import static android.service.notification.Condition.STATE_FALSE;
import static android.service.notification.Condition.STATE_TRUE;
import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_APP;
@@ -112,6 +114,7 @@
import android.os.Parcel;
import android.os.Process;
import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.provider.Settings.Global;
@@ -119,12 +122,13 @@
import android.service.notification.DeviceEffectsApplier;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
import android.service.notification.ZenModeConfig.ScheduleInfo;
import android.service.notification.ZenModeConfig.ZenRule;
import android.service.notification.ZenModeDiff;
import android.service.notification.ZenPolicy;
import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
+import android.testing.TestWithLooperRule;
import android.testing.TestableLooper;
import android.util.ArrayMap;
import android.util.Log;
@@ -147,6 +151,8 @@
import com.google.common.collect.ImmutableList;
import com.google.common.truth.Correspondence;
import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
import org.junit.Before;
import org.junit.Rule;
@@ -172,7 +178,7 @@
@SmallTest
@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
-@RunWith(AndroidTestingRunner.class)
+@RunWith(TestParameterInjector.class)
@TestableLooper.RunWithLooper
public class ZenModeHelperTest extends UiServiceTestCase {
@@ -211,7 +217,11 @@
private static final ZenDeviceEffects NO_EFFECTS = new ZenDeviceEffects.Builder().build();
@Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(
+ SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT);
+
+ @Rule(order = Integer.MAX_VALUE) // set the highest order so it's the innermost rule
+ public TestWithLooperRule mLooperRule = new TestWithLooperRule();
ConditionProviders mConditionProviders;
@Mock NotificationManager mNotificationManager;
@@ -2339,15 +2349,38 @@
assertEquals(ZEN_MODE_OFF, mZenModeHelper.mZenMode);
}
+ private enum ModesApiFlag {
+ ENABLED(true, /* originForUserActionInSystemUi= */ UPDATE_ORIGIN_USER),
+ DISABLED(false, /* originForUserActionInSystemUi= */ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI);
+
+ private final boolean mEnabled;
+ @ConfigChangeOrigin private final int mOriginForUserActionInSystemUi;
+
+ ModesApiFlag(boolean enabled, @ConfigChangeOrigin int originForUserActionInSystemUi) {
+ this.mEnabled = enabled;
+ this.mOriginForUserActionInSystemUi = originForUserActionInSystemUi;
+ }
+
+ void applyFlag(SetFlagsRule setFlagsRule) {
+ if (mEnabled) {
+ setFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+ } else {
+ setFlagsRule.disableFlags(Flags.FLAG_MODES_API);
+ }
+ }
+ }
+
@Test
- public void testZenModeEventLog_setManualZenMode() throws IllegalArgumentException {
+ public void testZenModeEventLog_setManualZenMode(@TestParameter ModesApiFlag modesApiFlag)
+ throws IllegalArgumentException {
+ modesApiFlag.applyFlag(mSetFlagsRule);
mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
setupZenConfig();
// Turn zen mode on (to important_interruptions)
// Need to additionally call the looper in order to finish the post-apply-config process
mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID);
+ modesApiFlag.mOriginForUserActionInSystemUi, "", null, Process.SYSTEM_UID);
// Now turn zen mode off, but via a different package UID -- this should get registered as
// "not an action by the user" because some other app is changing zen mode
@@ -2374,7 +2407,8 @@
assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeEventLogger.getNewZenMode(0));
assertEquals(DNDProtoEnums.MANUAL_RULE, mZenModeEventLogger.getChangedRuleType(0));
assertEquals(1, mZenModeEventLogger.getNumRulesActive(0));
- assertTrue(mZenModeEventLogger.getFromSystemOrSystemUi(0));
+ assertThat(mZenModeEventLogger.getFromSystemOrSystemUi(0)).isEqualTo(
+ modesApiFlag == ModesApiFlag.DISABLED);
assertTrue(mZenModeEventLogger.getIsUserAction(0));
assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(0));
checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(0));
@@ -2399,7 +2433,9 @@
}
@Test
- public void testZenModeEventLog_automaticRules() throws IllegalArgumentException {
+ public void testZenModeEventLog_automaticRules(@TestParameter ModesApiFlag modesApiFlag)
+ throws IllegalArgumentException {
+ modesApiFlag.applyFlag(mSetFlagsRule);
mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
setupZenConfig();
@@ -2421,8 +2457,8 @@
// Event 2: "User" turns off the automatic rule (sets it to not enabled)
zenRule.setEnabled(false);
- mZenModeHelper.updateAutomaticZenRule(id, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
- Process.SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(id, zenRule,
+ modesApiFlag.mOriginForUserActionInSystemUi, "", Process.SYSTEM_UID);
// Add a new system rule
AutomaticZenRule systemRule = new AutomaticZenRule("systemRule",
@@ -2440,8 +2476,8 @@
UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
// Event 4: "User" deletes the rule
- mZenModeHelper.removeAutomaticZenRule(systemId, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
- Process.SYSTEM_UID);
+ mZenModeHelper.removeAutomaticZenRule(systemId, modesApiFlag.mOriginForUserActionInSystemUi,
+ "", Process.SYSTEM_UID);
// In total, this represents 4 events
assertEquals(4, mZenModeEventLogger.numLoggedChanges());
@@ -2497,20 +2533,109 @@
}
@Test
- public void testZenModeEventLog_policyChanges() throws IllegalArgumentException {
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void testZenModeEventLog_automaticRuleActivatedFromAppByAppAndUser()
+ throws IllegalArgumentException {
+ mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
+ setupZenConfig();
+
+ // Ann app adds an automatic zen rule
+ AutomaticZenRule zenRule = new AutomaticZenRule("name",
+ null,
+ new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+ ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+ null,
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+ String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
+ UPDATE_ORIGIN_APP, "test", CUSTOM_PKG_UID);
+
+ // Event 1: Mimic the rule coming on manually when the user turns it on in the app
+ // ("Turn on bedtime now" because user goes to bed earlier).
+ mZenModeHelper.setAutomaticZenRuleState(id,
+ new Condition(zenRule.getConditionId(), "", STATE_TRUE, SOURCE_USER_ACTION),
+ UPDATE_ORIGIN_USER, CUSTOM_PKG_UID);
+
+ // Event 2: App deactivates the rule automatically (it's 8 AM, bedtime schedule ends)
+ mZenModeHelper.setAutomaticZenRuleState(id,
+ new Condition(zenRule.getConditionId(), "", STATE_FALSE, SOURCE_SCHEDULE),
+ UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+
+ // Event 3: App activates the rule automatically (it's now 11 PM, bedtime schedule starts)
+ mZenModeHelper.setAutomaticZenRuleState(id,
+ new Condition(zenRule.getConditionId(), "", STATE_TRUE, SOURCE_SCHEDULE),
+ UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+
+ // Event 4: User deactivates the rule manually (they get up before 8 AM on the next day)
+ mZenModeHelper.setAutomaticZenRuleState(id,
+ new Condition(zenRule.getConditionId(), "", STATE_FALSE, SOURCE_USER_ACTION),
+ UPDATE_ORIGIN_USER, CUSTOM_PKG_UID);
+
+ // In total, this represents 4 events
+ assertEquals(4, mZenModeEventLogger.numLoggedChanges());
+
+ // Automatic rule turning on manually:
+ // - event ID: DND_TURNED_ON
+ // - 1 rule (newly) active
+ // - is a user action
+ // - package UID is the calling package
+ assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_ON.getId(),
+ mZenModeEventLogger.getEventId(0));
+ assertEquals(1, mZenModeEventLogger.getNumRulesActive(0));
+ assertTrue(mZenModeEventLogger.getIsUserAction(0));
+ assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(0));
+
+ // Automatic rule turned off automatically by app:
+ // - event ID: DND_TURNED_OFF
+ // - 0 rules active
+ // - is not a user action
+ // - package UID is the calling package
+ assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_OFF.getId(),
+ mZenModeEventLogger.getEventId(1));
+ assertEquals(0, mZenModeEventLogger.getNumRulesActive(1));
+ assertFalse(mZenModeEventLogger.getIsUserAction(1));
+ assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(1));
+
+ // Automatic rule turned on automatically by app:
+ // - event ID: DND_TURNED_ON
+ // - 1 rule (newly) active
+ // - is not a user action
+ // - package UID is the calling package
+ assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_ON.getId(),
+ mZenModeEventLogger.getEventId(2));
+ assertEquals(DNDProtoEnums.AUTOMATIC_RULE, mZenModeEventLogger.getChangedRuleType(2));
+ assertEquals(1, mZenModeEventLogger.getNumRulesActive(2));
+ assertFalse(mZenModeEventLogger.getIsUserAction(2));
+ assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(2));
+
+ // Automatic rule turned off automatically by the user:
+ // - event ID: DND_TURNED_ON
+ // - 0 rules active
+ // - is a user action
+ // - package UID is the calling package
+ assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_OFF.getId(),
+ mZenModeEventLogger.getEventId(3));
+ assertEquals(0, mZenModeEventLogger.getNumRulesActive(3));
+ assertTrue(mZenModeEventLogger.getIsUserAction(3));
+ assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(3));
+ }
+
+ @Test
+ public void testZenModeEventLog_policyChanges(@TestParameter ModesApiFlag modesApiFlag)
+ throws IllegalArgumentException {
+ modesApiFlag.applyFlag(mSetFlagsRule);
mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
setupZenConfig();
// First just turn zen mode on
mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID);
+ modesApiFlag.mOriginForUserActionInSystemUi, "", null, Process.SYSTEM_UID);
// Now change the policy slightly; want to confirm that this'll be reflected in the logs
ZenModeConfig newConfig = mZenModeHelper.mConfig.copy();
newConfig.allowAlarms = true;
newConfig.allowRepeatCallers = false;
mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ modesApiFlag.mOriginForUserActionInSystemUi, Process.SYSTEM_UID);
// Turn zen mode off; we want to make sure policy changes do not get logged when zen mode
// is off.
@@ -2521,7 +2646,7 @@
newConfig.allowMessages = false;
newConfig.allowRepeatCallers = true;
mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ modesApiFlag.mOriginForUserActionInSystemUi, Process.SYSTEM_UID);
// Total events: we only expect ones for turning on, changing policy, and turning off
assertEquals(3, mZenModeEventLogger.numLoggedChanges());
@@ -2554,7 +2679,9 @@
}
@Test
- public void testZenModeEventLog_ruleCounts() throws IllegalArgumentException {
+ public void testZenModeEventLog_ruleCounts(@TestParameter ModesApiFlag modesApiFlag)
+ throws IllegalArgumentException {
+ modesApiFlag.applyFlag(mSetFlagsRule);
mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
setupZenConfig();
@@ -2657,8 +2784,10 @@
}
@Test
- public void testZenModeEventLog_noLogWithNoConfigChange() throws IllegalArgumentException {
+ public void testZenModeEventLog_noLogWithNoConfigChange(
+ @TestParameter ModesApiFlag modesApiFlag) throws IllegalArgumentException {
// If evaluateZenMode is called independently of a config change, don't log.
+ modesApiFlag.applyFlag(mSetFlagsRule);
mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
setupZenConfig();
@@ -2675,9 +2804,11 @@
}
@Test
- public void testZenModeEventLog_reassignUid() throws IllegalArgumentException {
+ public void testZenModeEventLog_reassignUid(@TestParameter ModesApiFlag modesApiFlag)
+ throws IllegalArgumentException {
// Test that, only in specific cases, we reassign the calling UID to one associated with
// the automatic rule owner.
+ modesApiFlag.applyFlag(mSetFlagsRule);
mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
setupZenConfig();
@@ -2689,7 +2820,7 @@
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+ UPDATE_ORIGIN_APP, "test", Process.SYSTEM_UID);
// Rule 2, same as rule 1 but owned by the system
AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
@@ -2699,11 +2830,11 @@
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+ modesApiFlag.mOriginForUserActionInSystemUi, "test", Process.SYSTEM_UID);
// Turn on rule 1; call looks like it's from the system. Because setting a condition is
// typically an automatic (non-user-initiated) action, expect the calling UID to be
- // re-evaluated to the one associat.d with CUSTOM_PKG_NAME.
+ // re-evaluated to the one associated with CUSTOM_PKG_NAME.
mZenModeHelper.setAutomaticZenRuleState(id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
@@ -2717,8 +2848,8 @@
// Disable rule 1. Because this looks like a user action, the UID should not be modified
// from the system-provided one.
zenRule.setEnabled(false);
- mZenModeHelper.updateAutomaticZenRule(id, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
- Process.SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(id, zenRule,
+ modesApiFlag.mOriginForUserActionInSystemUi, "", Process.SYSTEM_UID);
// Add a manual rule. Any manual rule changes should not get calling uids reassigned.
mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, UPDATE_ORIGIN_APP,
@@ -2775,8 +2906,10 @@
}
@Test
- public void testZenModeEventLog_channelsBypassingChanges() {
+ public void testZenModeEventLog_channelsBypassingChanges(
+ @TestParameter ModesApiFlag modesApiFlag) {
// Verify that the right thing happens when the canBypassDnd value changes.
+ modesApiFlag.applyFlag(mSetFlagsRule);
mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
setupZenConfig();
@@ -2872,14 +3005,11 @@
// Second message where we change the policy:
// - DND_POLICY_CHANGED (indicates only the policy changed and nothing else)
// - rule type: unknown (it's a policy change, not a rule change)
- // - user action (because it comes from a "system" uid)
// - change is in allow channels, and final policy
assertThat(mZenModeEventLogger.getEventId(1))
.isEqualTo(ZenModeEventLogger.ZenStateChangedEvent.DND_POLICY_CHANGED.getId());
assertThat(mZenModeEventLogger.getChangedRuleType(1))
.isEqualTo(DNDProtoEnums.UNKNOWN_RULE);
- assertThat(mZenModeEventLogger.getIsUserAction(1)).isTrue();
- assertThat(mZenModeEventLogger.getPackageUid(1)).isEqualTo(Process.SYSTEM_UID);
DNDPolicyProto dndProto = mZenModeEventLogger.getPolicyProto(1);
assertThat(dndProto.getAllowChannels().getNumber())
.isEqualTo(DNDProtoEnums.CHANNEL_TYPE_NONE);
@@ -3372,6 +3502,29 @@
}
@Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void removeAutomaticZenRule_propagatesOriginToEffectsApplier() {
+ mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+ reset(mDeviceEffectsApplier);
+
+ String ruleId = addRuleWithEffects(new ZenDeviceEffects.Builder()
+ .setShouldSuppressAmbientDisplay(true)
+ .setShouldDimWallpaper(true)
+ .build());
+ mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ CUSTOM_PKG_UID);
+ mTestableLooper.processAllMessages();
+ verify(mDeviceEffectsApplier).apply(any(), eq(UPDATE_ORIGIN_APP));
+
+ // Now delete the (currently active!) rule. For example, assume this is done from settings.
+ mZenModeHelper.removeAutomaticZenRule(ruleId, UPDATE_ORIGIN_USER, "remove",
+ Process.SYSTEM_UID);
+ mTestableLooper.processAllMessages();
+
+ verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_USER));
+ }
+
+ @Test
public void testDeviceEffects_applied() {
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
@@ -3511,8 +3664,8 @@
AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
.setDeviceEffects(effects)
.build();
- return mZenModeHelper.addAutomaticZenRule("pkg", rule, UPDATE_ORIGIN_APP, "",
- CUSTOM_PKG_UID);
+ return mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
+ UPDATE_ORIGIN_APP, "reasons", CUSTOM_PKG_UID);
}
@Test
@@ -3619,7 +3772,8 @@
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);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, policy,
+ UPDATE_ORIGIN_APP);
ZenPolicy expectedZenPolicy = new ZenPolicy.Builder()
.disallowAllSounds()
@@ -3643,13 +3797,14 @@
PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED,
Policy.getAllSuppressedVisualEffects(), CONVERSATION_SENDERS_IMPORTANT);
mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
- original);
+ original, UPDATE_ORIGIN_APP);
// 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);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, updated,
+ UPDATE_ORIGIN_APP);
ZenPolicy expectedZenPolicy = new ZenPolicy.Builder()
.disallowAllSounds()
@@ -3671,7 +3826,7 @@
withoutWtfCrash(
() -> mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME,
- CUSTOM_PKG_UID, new Policy(0, 0, 0)));
+ CUSTOM_PKG_UID, new Policy(0, 0, 0), UPDATE_ORIGIN_APP));
assertThat(mZenModeHelper.mConfig.automaticRules).isEmpty();
}
@@ -3684,7 +3839,7 @@
Policy.getAllSuppressedVisualEffects(), STATE_FALSE,
CONVERSATION_SENDERS_IMPORTANT);
mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
- writtenPolicy);
+ writtenPolicy, UPDATE_ORIGIN_APP);
Policy readPolicy = mZenModeHelper.getNotificationPolicyFromImplicitZenRule(
CUSTOM_PKG_NAME);
diff --git a/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java b/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java
index d2ef180..ca3787e 100644
--- a/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java
@@ -95,6 +95,26 @@
assertFalse(action.executed);
}
+ @Test
+ public void queueKeyAction_beforeAndAfterCancelQueuedActions_onlyActionsAfterCancelExecuted() {
+ TestAction action1 = new TestAction();
+ TestAction action2 = new TestAction();
+ TestAction action3 = new TestAction();
+ mKeyActionExecutor.queueKeyAction(
+ KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1, action1);
+ mKeyActionExecutor.queueKeyAction(
+ KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1, action2);
+ mKeyActionExecutor.cancelQueuedAction(KeyEvent.KEYCODE_STEM_PRIMARY);
+ mKeyActionExecutor.queueKeyAction(
+ KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1, action3);
+
+ mKeyActionExecutor.setActionsExecutable(KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1);
+
+ assertFalse(action1.executed);
+ assertFalse(action2.executed);
+ assertTrue(action3.executed);
+ }
+
static class TestAction implements Runnable {
public boolean executed;
diff --git a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
index f7ad2a8..50d37ec 100644
--- a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
@@ -19,14 +19,18 @@
import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_DOUBLE_PRESS;
import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_LONG_PRESS;
import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_SHORT_PRESS;
+import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_TRIPLE_PRESS;
import static android.view.KeyEvent.KEYCODE_STEM_PRIMARY;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.server.policy.PhoneWindowManager.DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP;
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT;
import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS;
import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_PRIMARY_LAUNCH_TARGET_ACTIVITY;
+import static com.android.server.policy.PhoneWindowManager.TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY;
import android.app.ActivityManager.RecentTaskInfo;
+import android.app.ActivityTaskManager.RootTaskInfo;
import android.content.ComponentName;
import android.os.RemoteException;
import android.provider.Settings;
@@ -50,6 +54,7 @@
public void stemSingleKey_duringSetup_doNothing() {
overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
+ mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false);
mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
mPhoneWindowManager.overrideIsUserSetupComplete(false);
@@ -65,6 +70,7 @@
public void stemSingleKey_AfterSetup_openAllApp() {
overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
+ mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false);
mPhoneWindowManager.overrideStartActivity();
mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
mPhoneWindowManager.overrideIsUserSetupComplete(true);
@@ -83,6 +89,7 @@
STEM_PRIMARY_BUTTON_SHORT_PRESS,
SHORT_PRESS_PRIMARY_LAUNCH_TARGET_ACTIVITY);
setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
+ mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false);
mPhoneWindowManager.overrideStartActivity();
mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
mPhoneWindowManager.overrideIsUserSetupComplete(true);
@@ -104,6 +111,7 @@
mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
mPhoneWindowManager.overrideIsUserSetupComplete(true);
mPhoneWindowManager.overrideFocusedWindowButtonOverridePermission(true);
+
setDispatchedKeyHandler(keyEvent -> true);
sendKey(KEYCODE_STEM_PRIMARY);
@@ -131,6 +139,7 @@
STEM_PRIMARY_BUTTON_LONG_PRESS,
LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT);
setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
+ mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false);
mPhoneWindowManager.setupAssistForLaunch();
mPhoneWindowManager.overrideIsUserSetupComplete(true);
@@ -144,6 +153,7 @@
STEM_PRIMARY_BUTTON_LONG_PRESS,
LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT);
setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
+ mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false);
mPhoneWindowManager.setupAssistForLaunch();
mPhoneWindowManager.overrideSearchManager(null);
mPhoneWindowManager.overrideStatusBarManagerInternal();
@@ -156,7 +166,8 @@
@Test
public void stemDoubleKey_EarlyShortPress_AllAppsThenSwitchToMostRecent()
throws RemoteException {
- overrideBehavior(STEM_PRIMARY_BUTTON_DOUBLE_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
+ overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
+ overrideBehavior(STEM_PRIMARY_BUTTON_DOUBLE_PRESS, DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP);
setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(true);
mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
@@ -171,14 +182,47 @@
sendKey(KEYCODE_STEM_PRIMARY);
mPhoneWindowManager.assertOpenAllAppView();
- mPhoneWindowManager.assertSwitchToRecent(referenceId);
+ mPhoneWindowManager.assertSwitchToTask(referenceId);
}
@Test
- public void stemDoubleKey_NoEarlyShortPress_SwitchToMostRecent() throws RemoteException {
+ public void stemTripleKey_EarlyShortPress_AllAppsThenBackToOriginalThenToggleA11y()
+ throws RemoteException {
+ overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
+ overrideBehavior(
+ STEM_PRIMARY_BUTTON_TRIPLE_PRESS, TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY);
+ setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
+ mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(true);
+ mPhoneWindowManager.overrideTalkbackShortcutGestureEnabled(true);
+ mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
+ mPhoneWindowManager.overrideIsUserSetupComplete(true);
+ RootTaskInfo allAppsTask = new RootTaskInfo();
+ int referenceId = 777;
+ allAppsTask.taskId = referenceId;
+ doReturn(allAppsTask)
+ .when(mPhoneWindowManager.mActivityManagerService)
+ .getFocusedRootTaskInfo();
+
+ mPhoneWindowManager.assertTalkBack(/* expectEnabled= */ false);
+
+ sendKey(KEYCODE_STEM_PRIMARY);
+ sendKey(KEYCODE_STEM_PRIMARY);
+ sendKey(KEYCODE_STEM_PRIMARY);
+
+ mPhoneWindowManager.assertOpenAllAppView();
+ mPhoneWindowManager.assertSwitchToTask(referenceId);
+ mPhoneWindowManager.assertTalkBack(/* expectEnabled= */ true);
+ }
+
+ @Test
+ public void stemMultiKey_NoEarlyPress_NoOpenAllApp() throws RemoteException {
+ overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
overrideBehavior(STEM_PRIMARY_BUTTON_DOUBLE_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
+ overrideBehavior(
+ STEM_PRIMARY_BUTTON_TRIPLE_PRESS, TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY);
setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false);
+ mPhoneWindowManager.overrideTalkbackShortcutGestureEnabled(true);
mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
mPhoneWindowManager.overrideIsUserSetupComplete(true);
RecentTaskInfo recentTaskInfo = new RecentTaskInfo();
@@ -189,9 +233,16 @@
sendKey(KEYCODE_STEM_PRIMARY);
sendKey(KEYCODE_STEM_PRIMARY);
+ sendKey(KEYCODE_STEM_PRIMARY);
mPhoneWindowManager.assertNotOpenAllAppView();
- mPhoneWindowManager.assertSwitchToRecent(referenceId);
+ mPhoneWindowManager.assertTalkBack(/* expectEnabled= */ true);
+
+ sendKey(KEYCODE_STEM_PRIMARY);
+ sendKey(KEYCODE_STEM_PRIMARY);
+
+ mPhoneWindowManager.assertNotOpenAllAppView();
+ mPhoneWindowManager.assertSwitchToTask(referenceId);
}
@Test
@@ -215,7 +266,7 @@
sendKey(KEYCODE_STEM_PRIMARY);
mPhoneWindowManager.assertNotOpenAllAppView();
- mPhoneWindowManager.assertSwitchToRecent(referenceId);
+ mPhoneWindowManager.assertSwitchToTask(referenceId);
}
private void overrideBehavior(String key, int expectedBehavior) {
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 0678210..7c2f7ee 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -73,6 +73,7 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.Looper;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.RemoteException;
@@ -176,8 +177,9 @@
private Handler mHandler;
private boolean mIsTalkBackEnabled;
+ private boolean mIsTalkBackShortcutGestureEnabled;
- class TestTalkbackShortcutController extends TalkbackShortcutController {
+ private class TestTalkbackShortcutController extends TalkbackShortcutController {
TestTalkbackShortcutController(Context context) {
super(context);
}
@@ -190,13 +192,18 @@
@Override
boolean isTalkBackShortcutGestureEnabled() {
- return true;
+ return mIsTalkBackShortcutGestureEnabled;
}
}
private class TestInjector extends PhoneWindowManager.Injector {
TestInjector(Context context, WindowManagerPolicy.WindowManagerFuncs funcs) {
- super(context, funcs, mTestLooper.getLooper());
+ super(context, funcs);
+ }
+
+ @Override
+ Looper getLooper() {
+ return mTestLooper.getLooper();
}
AccessibilityShortcutController getAccessibilityShortcutController(
@@ -410,6 +417,10 @@
mPhoneWindowManager.mShouldEarlyShortPressOnStemPrimary = shouldEarlyShortPress;
}
+ void overrideTalkbackShortcutGestureEnabled(boolean enabled) {
+ mIsTalkBackShortcutGestureEnabled = enabled;
+ }
+
// Override assist perform function.
void overrideLongPressOnPower(int behavior) {
mPhoneWindowManager.mLongPressOnPowerBehavior = behavior;
@@ -714,7 +725,7 @@
}
void assertOpenAllAppView() {
- mTestLooper.dispatchAll();
+ moveTimeForward(TEST_SINGLE_KEY_DELAY_MILLIS);
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(mContext, timeout(TEST_SINGLE_KEY_DELAY_MILLIS))
.startActivityAsUser(intentCaptor.capture(), isNull(), any(UserHandle.class));
@@ -728,7 +739,7 @@
}
void assertActivityTargetLaunched(ComponentName targetActivity) {
- mTestLooper.dispatchAll();
+ moveTimeForward(TEST_SINGLE_KEY_DELAY_MILLIS);
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(mContext, timeout(TEST_SINGLE_KEY_DELAY_MILLIS))
.startActivityAsUser(intentCaptor.capture(), isNull(), any(UserHandle.class));
@@ -743,10 +754,15 @@
expectedModifierState, deviceBus), description(errorMsg));
}
- void assertSwitchToRecent(int persistentId) throws RemoteException {
+ void assertSwitchToTask(int persistentId) throws RemoteException {
mTestLooper.dispatchAll();
verify(mActivityManagerService,
timeout(TEST_SINGLE_KEY_DELAY_MILLIS)).startActivityFromRecents(eq(persistentId),
isNull());
}
+
+ void assertTalkBack(boolean expectEnabled) {
+ mTestLooper.dispatchAll();
+ Assert.assertEquals(expectEnabled, mIsTalkBackEnabled);
+ }
}
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 85c6f9e..718c598 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -202,8 +202,7 @@
}
private TestStartingWindowOrganizer registerTestStartingWindowOrganizer() {
- return new TestStartingWindowOrganizer(mAtm,
- mSystemServicesTestRule.getPowerManagerWrapper());
+ return new TestStartingWindowOrganizer(mAtm);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/CompatModePackagesTests.java b/services/tests/wmtests/src/com/android/server/wm/CompatModePackagesTests.java
index 1f7b65e..c187263 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CompatModePackagesTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CompatModePackagesTests.java
@@ -20,10 +20,13 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import android.app.GameManagerInternal;
+import android.content.pm.ApplicationInfo;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
@@ -90,6 +93,14 @@
public void testGetCompatScale_noGameManager() {
assertEquals(mAtm.mCompatModePackages.getCompatScale(TEST_PACKAGE, TEST_USER_ID), 1f,
0.01f);
- }
+ final ApplicationInfo info = new ApplicationInfo();
+ // Any non-zero value without FLAG_SUPPORTS_*_SCREENS.
+ info.flags = ApplicationInfo.FLAG_HAS_CODE;
+ info.packageName = info.sourceDir = "legacy.app";
+ mAtm.mCompatModePackages.compatibilityInfoForPackageLocked(info);
+ assertTrue(mAtm.mCompatModePackages.useLegacyScreenCompatMode(info.packageName));
+ mAtm.mCompatModePackages.handlePackageUninstalledLocked(info.packageName);
+ assertFalse(mAtm.mCompatModePackages.useLegacyScreenCompatMode(info.packageName));
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index c6796dc..985be42 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -19,6 +19,7 @@
import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP;
import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP;
import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION;
+import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION_TO_USER;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
@@ -673,6 +674,47 @@
}
@Test
+ @EnableCompatChanges({OVERRIDE_ANY_ORIENTATION_TO_USER})
+ public void testOverrideOrientationIfNeeded_fullscreenOverrideEnabled_returnsUser()
+ throws Exception {
+ mDisplayContent.setIgnoreOrientationRequest(true);
+ assertEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ANY_ORIENTATION_TO_USER})
+ public void testOverrideOrientationIfNeeded_fullscreenOverrideEnabled_optOut_returnsUnchanged()
+ throws Exception {
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE, /* value */ false);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+ mDisplayContent.setIgnoreOrientationRequest(true);
+
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ANY_ORIENTATION_TO_USER})
+ public void testOverrideOrientationIfNeeded_fullscreenOverrideEnabled_returnsUnchanged()
+ throws Exception {
+ mDisplayContent.setIgnoreOrientationRequest(false);
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ANY_ORIENTATION_TO_USER})
+ public void testOverrideOrientationIfNeeded_fullscreenAndUserOverrideEnabled_returnsUnchanged()
+ throws Exception {
+ prepareActivityThatShouldApplyUserMinAspectRatioOverride();
+
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
+ }
+
+ @Test
@EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT})
public void testOverrideOrientationIfNeeded_portraitOverrideEnabled_returnsPortrait()
throws Exception {
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java
index ef427bb..a8b2178 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java
@@ -16,7 +16,7 @@
package com.android.server.wm;
-import static android.server.wm.CtsWindowInfoUtils.dumpWindowsOnScreen;
+import static android.server.wm.CtsWindowInfoUtils.assertAndDumpWindowState;
import static android.server.wm.CtsWindowInfoUtils.waitForWindowFocus;
import static android.server.wm.CtsWindowInfoUtils.waitForWindowVisible;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -129,37 +129,22 @@
mScvh2.setView(mView2, lp2);
});
- boolean wasVisible = waitForWindowVisible(mView1);
- if (!wasVisible) {
- dumpWindowsOnScreen(TAG, "requestFocusWithMultipleWindows");
- }
- assertTrue("Failed to wait for view1", wasVisible);
-
- wasVisible = waitForWindowVisible(mView2);
- if (!wasVisible) {
- dumpWindowsOnScreen(TAG, "requestFocusWithMultipleWindows-not visible");
- }
- assertTrue("Failed to wait for view2", wasVisible);
+ assertAndDumpWindowState(TAG, "Failed to wait for view1", waitForWindowVisible(mView1));
+ assertAndDumpWindowState(TAG, "Failed to wait for view2", waitForWindowVisible(mView2));
IWindow window = IWindow.Stub.asInterface(mSurfaceView.getWindowToken());
WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(window,
mScvh1.getInputTransferToken(), true);
- boolean gainedFocus = waitForWindowFocus(mView1, true);
- if (!gainedFocus) {
- dumpWindowsOnScreen(TAG, "requestFocusWithMultipleWindows-view1 not focus");
- }
- assertTrue("Failed to gain focus for view1", gainedFocus);
+ assertAndDumpWindowState(TAG, "Failed to wait for view1 focus",
+ waitForWindowFocus(mView1, true));
WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(window,
mScvh2.getInputTransferToken(), true);
- gainedFocus = waitForWindowFocus(mView2, true);
- if (!gainedFocus) {
- dumpWindowsOnScreen(TAG, "requestFocusWithMultipleWindows-view2 not focus");
- }
- assertTrue("Failed to gain focus for view2", gainedFocus);
+ assertAndDumpWindowState(TAG, "Failed to wait for view2 focus",
+ waitForWindowFocus(mView2, true));
}
private static class TestWindowlessWindowManager extends WindowlessWindowManager {
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 51f0404..8cd9ff3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -134,7 +134,6 @@
private StaticMockitoSession mMockitoSession;
private ActivityTaskManagerService mAtmService;
private WindowManagerService mWmService;
- private WindowState.PowerManagerWrapper mPowerManagerWrapper;
private InputManagerService mImService;
private InputChannel mInputChannel;
private Runnable mOnBeforeServicesCreated;
@@ -360,7 +359,6 @@
}
private void setUpWindowManagerService() {
- mPowerManagerWrapper = mock(WindowState.PowerManagerWrapper.class);
TestWindowManagerPolicy wmPolicy = new TestWindowManagerPolicy();
TestDisplayWindowSettingsProvider testDisplayWindowSettingsProvider =
new TestDisplayWindowSettingsProvider();
@@ -485,10 +483,6 @@
return mAtmService;
}
- WindowState.PowerManagerWrapper getPowerManagerWrapper() {
- return mPowerManagerWrapper;
- }
-
/** Creates a no-op wakelock object. */
PowerManager.WakeLock createStubbedWakeLock(boolean needVerification) {
if (needVerification) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java b/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java
index ac49839..6a15b05 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.server.wm.CtsWindowInfoUtils.assertAndDumpWindowState;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
@@ -138,11 +139,8 @@
return false;
}, TIMEOUT_S, TimeUnit.SECONDS);
- if (!foundTrusted[0]) {
- CtsWindowInfoUtils.dumpWindowsOnScreen(TAG, mName.getMethodName());
- }
-
- assertTrue("Failed to find window or was not marked trusted", foundTrusted[0]);
+ assertAndDumpWindowState(TAG, "Failed to find window or was not marked trusted",
+ foundTrusted[0]);
}
private void testTrustedOverlayChildHelper(boolean expectedTrustedChild)
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 2007f68..75e252f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -52,7 +52,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doThrow;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -368,28 +367,26 @@
firstWindow.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
secondWindow.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
- final WindowState.PowerManagerWrapper powerManagerWrapper =
- mSystemServicesTestRule.getPowerManagerWrapper();
- reset(powerManagerWrapper);
+ final var powerManager = mWm.mPowerManager;
+ clearInvocations(powerManager);
firstWindow.prepareWindowToDisplayDuringRelayout(false /*wasVisible*/);
- verify(powerManagerWrapper).wakeUp(anyLong(), anyInt(), anyString());
+ verify(powerManager).wakeUp(anyLong(), anyInt(), anyString());
- reset(powerManagerWrapper);
+ clearInvocations(powerManager);
secondWindow.prepareWindowToDisplayDuringRelayout(false /*wasVisible*/);
- verify(powerManagerWrapper).wakeUp(anyLong(), anyInt(), anyString());
+ verify(powerManager).wakeUp(anyLong(), anyInt(), anyString());
}
private void testPrepareWindowToDisplayDuringRelayout(WindowState appWindow,
boolean expectedWakeupCalled, boolean expectedCurrentLaunchCanTurnScreenOn) {
- final WindowState.PowerManagerWrapper powerManagerWrapper =
- mSystemServicesTestRule.getPowerManagerWrapper();
- reset(powerManagerWrapper);
+ final var powerManager = mWm.mPowerManager;
+ clearInvocations(powerManager);
appWindow.prepareWindowToDisplayDuringRelayout(false /* wasVisible */);
if (expectedWakeupCalled) {
- verify(powerManagerWrapper).wakeUp(anyLong(), anyInt(), anyString());
+ verify(powerManager).wakeUp(anyLong(), anyInt(), anyString());
} else {
- verify(powerManagerWrapper, never()).wakeUp(anyLong(), anyInt(), anyString());
+ verify(powerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
}
// If wakeup is expected to be called, the currentLaunchCanTurnScreenOn should be false
// because the state will be consumed.
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 df4af11..a5f6190f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -294,7 +294,7 @@
*/
static void suppressInsetsAnimation(InsetsControlTarget target) {
spyOn(target);
- Mockito.doNothing().when(target).notifyInsetsControlChanged();
+ Mockito.doNothing().when(target).notifyInsetsControlChanged(anyInt());
}
@After
@@ -637,14 +637,12 @@
WindowState createWindow(WindowState parent, int type, WindowToken token, String name,
int ownerId, boolean ownerCanAddInternalSystemWindow, IWindow iwindow) {
return createWindow(parent, type, token, name, ownerId, UserHandle.getUserId(ownerId),
- ownerCanAddInternalSystemWindow, mWm, getTestSession(token), iwindow,
- mSystemServicesTestRule.getPowerManagerWrapper());
+ ownerCanAddInternalSystemWindow, mWm, getTestSession(token), iwindow);
}
static WindowState createWindow(WindowState parent, int type, WindowToken token,
String name, int ownerId, int userId, boolean ownerCanAddInternalSystemWindow,
- WindowManagerService service, Session session, IWindow iWindow,
- WindowState.PowerManagerWrapper powerManagerWrapper) {
+ WindowManagerService service, Session session, IWindow iWindow) {
SystemServicesTestRule.checkHoldsLock(service.mGlobalLock);
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type);
@@ -652,9 +650,7 @@
attrs.packageName = "test";
final WindowState w = new WindowState(service, session, iWindow, token, parent,
- OP_NONE, attrs, VISIBLE, ownerId, userId,
- ownerCanAddInternalSystemWindow,
- powerManagerWrapper);
+ OP_NONE, attrs, VISIBLE, ownerId, userId, ownerCanAddInternalSystemWindow);
// TODO: Probably better to make this call in the WindowState ctor to avoid errors with
// adding it to the token...
token.addWindow(w);
@@ -1738,17 +1734,14 @@
static class TestStartingWindowOrganizer extends WindowOrganizerTests.StubOrganizer {
private final ActivityTaskManagerService mAtm;
private final WindowManagerService mWMService;
- private final WindowState.PowerManagerWrapper mPowerManagerWrapper;
private Runnable mRunnableWhenAddingSplashScreen;
private final SparseArray<IBinder> mTaskAppMap = new SparseArray<>();
private final HashMap<IBinder, WindowState> mAppWindowMap = new HashMap<>();
- TestStartingWindowOrganizer(ActivityTaskManagerService service,
- WindowState.PowerManagerWrapper powerManagerWrapper) {
+ TestStartingWindowOrganizer(ActivityTaskManagerService service) {
mAtm = service;
mWMService = mAtm.mWindowManager;
- mPowerManagerWrapper = powerManagerWrapper;
mAtm.mTaskOrganizerController.setDeferTaskOrgCallbacksConsumer(Runnable::run);
mAtm.mTaskOrganizerController.registerTaskOrganizer(this);
}
@@ -1767,8 +1760,7 @@
final WindowState window = WindowTestsBase.createWindow(null,
TYPE_APPLICATION_STARTING, activity,
"Starting window", 0 /* ownerId */, 0 /* userId*/,
- false /* internalWindows */, mWMService, createTestSession(mAtm), iWindow,
- mPowerManagerWrapper);
+ false /* internalWindows */, mWMService, createTestSession(mAtm), iWindow);
activity.mStartingWindow = window;
mAppWindowMap.put(info.appToken, window);
mTaskAppMap.put(info.taskInfo.taskId, info.appToken);
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index 4c978ad..2445f51 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -28,6 +28,7 @@
import android.annotation.UserIdInt;
import android.app.AppOpsManager;
import android.app.usage.ExternalStorageStats;
+import android.app.usage.Flags;
import android.app.usage.IStorageStatsManager;
import android.app.usage.StorageStats;
import android.app.usage.UsageStatsManagerInternal;
@@ -434,6 +435,7 @@
final long[] ceDataInodes = new long[packageNames.length];
String[] codePaths = new String[0];
+ final PackageStats stats = new PackageStats(TAG);
for (int i = 0; i < packageNames.length; i++) {
try {
final ApplicationInfo appInfo = mPackage.getApplicationInfoAsUser(packageNames[i],
@@ -443,7 +445,11 @@
} else {
if (appInfo.getCodePath() != null) {
codePaths = ArrayUtils.appendElement(String.class, codePaths,
- appInfo.getCodePath());
+ appInfo.getCodePath());
+ }
+ if (Flags.getAppBytesByDataTypeApi()) {
+ computeAppStatsByDataTypes(
+ stats, appInfo.sourceDir);
}
}
} catch (NameNotFoundException e) {
@@ -451,7 +457,6 @@
}
}
- final PackageStats stats = new PackageStats(TAG);
try {
mInstaller.getAppSize(volumeUuid, packageNames, userId, getDefaultFlags(),
appId, ceDataInodes, codePaths, stats);
@@ -587,6 +592,9 @@
res.codeBytes = stats.codeSize + stats.externalCodeSize;
res.dataBytes = stats.dataSize + stats.externalDataSize;
res.cacheBytes = stats.cacheSize + stats.externalCacheSize;
+ res.apkBytes = stats.apkSize;
+ res.libBytes = stats.libSize;
+ res.dmBytes = stats.dmSize;
res.externalCacheBytes = stats.externalCacheSize;
return res;
}
@@ -894,4 +902,61 @@
mStorageStatsAugmenters.add(Pair.create(tag, storageStatsAugmenter));
}
}
+
+ private long getDirBytes(File dir) {
+ if (!dir.isDirectory()) {
+ return 0;
+ }
+
+ long size = 0;
+ try {
+ for (File file : dir.listFiles()) {
+ if (file.isFile()) {
+ size += file.length();
+ continue;
+ }
+ if (file.isDirectory()) {
+ size += getDirBytes(file);
+ }
+ }
+ } catch (NullPointerException e) {
+ Slog.w(TAG, "Failed to list directory " + dir.getName());
+ }
+
+ return size;
+ }
+
+ private long getFileBytesInDir(File dir, String suffix) {
+ if (!dir.isDirectory()) {
+ return 0;
+ }
+
+ long size = 0;
+ try {
+ for (File file : dir.listFiles()) {
+ if (file.isFile() && file.getName().endsWith(suffix)) {
+ size += file.length();
+ }
+ }
+ } catch (NullPointerException e) {
+ Slog.w(TAG, "Failed to list directory " + dir.getName());
+ }
+
+ return size;
+ }
+
+ private void computeAppStatsByDataTypes(
+ PackageStats stats, String sourceDirName) {
+
+ // Get apk, lib, dm file sizes.
+ File srcDir = new File(sourceDirName);
+ if (srcDir.isFile()) {
+ sourceDirName = srcDir.getParent();
+ srcDir = new File(sourceDirName);
+ }
+
+ stats.apkSize += getFileBytesInDir(srcDir, ".apk");
+ stats.dmSize += getFileBytesInDir(srcDir, ".dm");
+ stats.libSize += getDirBytes(new File(sourceDirName + "/lib/"));
+ }
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 72db7fe..08f719e 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -1121,13 +1121,8 @@
switch (event.mEventType) {
case Event.ACTIVITY_RESUMED:
- FrameworkStatsLog.write(
- FrameworkStatsLog.APP_USAGE_EVENT_OCCURRED,
- uid,
- event.mPackage,
- "",
- FrameworkStatsLog
- .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__MOVE_TO_FOREGROUND);
+ logAppUsageEventReportedAtomLocked(Event.ACTIVITY_RESUMED, uid, event.mPackage);
+
// check if this activity has already been resumed
if (mVisibleActivities.get(event.mInstanceId) != null) break;
final String usageSourcePackage = getUsageSourcePackage(event);
@@ -1172,13 +1167,8 @@
usageSourcePackage2);
mVisibleActivities.put(event.mInstanceId, pausedData);
} else {
- FrameworkStatsLog.write(
- FrameworkStatsLog.APP_USAGE_EVENT_OCCURRED,
- uid,
- event.mPackage,
- "",
- FrameworkStatsLog
- .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__MOVE_TO_BACKGROUND);
+ logAppUsageEventReportedAtomLocked(Event.ACTIVITY_PAUSED, uid,
+ event.mPackage);
}
pausedData.lastEvent = Event.ACTIVITY_PAUSED;
@@ -1203,13 +1193,8 @@
}
if (prevData.lastEvent != Event.ACTIVITY_PAUSED) {
- FrameworkStatsLog.write(
- FrameworkStatsLog.APP_USAGE_EVENT_OCCURRED,
- uid,
- event.mPackage,
- "",
- FrameworkStatsLog
- .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__MOVE_TO_BACKGROUND);
+ logAppUsageEventReportedAtomLocked(Event.ACTIVITY_PAUSED, uid,
+ event.mPackage);
}
ArraySet<String> tokens;
@@ -1244,11 +1229,19 @@
}
break;
case Event.USER_INTERACTION:
- // Fall through
+ logAppUsageEventReportedAtomLocked(Event.USER_INTERACTION, uid, event.mPackage);
+ // Fall through.
case Event.APP_COMPONENT_USED:
convertToSystemTimeLocked(event);
mLastTimeComponentUsedGlobal.put(event.mPackage, event.mTimeStamp);
break;
+ case Event.SHORTCUT_INVOCATION:
+ case Event.CHOOSER_ACTION:
+ case Event.STANDBY_BUCKET_CHANGED:
+ case Event.FOREGROUND_SERVICE_START:
+ case Event.FOREGROUND_SERVICE_STOP:
+ logAppUsageEventReportedAtomLocked(event.mEventType, uid, event.mPackage);
+ break;
}
final UserUsageStatsService service = getUserUsageStatsServiceLocked(userId);
@@ -1261,6 +1254,45 @@
mIoHandler.obtainMessage(MSG_NOTIFY_USAGE_EVENT_LISTENER, userId, 0, event).sendToTarget();
}
+ @GuardedBy("mLock")
+ private void logAppUsageEventReportedAtomLocked(int eventType, int uid, String packageName) {
+ FrameworkStatsLog.write(FrameworkStatsLog.APP_USAGE_EVENT_OCCURRED, uid, packageName,
+ "", getAppUsageEventOccurredAtomEventType(eventType));
+ }
+
+ /** Make sure align with the EventType defined in the AppUsageEventOccurred atom. */
+ private int getAppUsageEventOccurredAtomEventType(int eventType) {
+ switch (eventType) {
+ case Event.ACTIVITY_RESUMED:
+ return FrameworkStatsLog
+ .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__MOVE_TO_FOREGROUND;
+ case Event.ACTIVITY_PAUSED:
+ return FrameworkStatsLog
+ .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__MOVE_TO_BACKGROUND;
+ case Event.USER_INTERACTION:
+ return FrameworkStatsLog
+ .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__USER_INTERACTION;
+ case Event.SHORTCUT_INVOCATION:
+ return FrameworkStatsLog
+ .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__SHORTCUT_INVOCATION;
+ case Event.CHOOSER_ACTION:
+ return FrameworkStatsLog
+ .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__CHOOSER_ACTION;
+ case Event.STANDBY_BUCKET_CHANGED:
+ return FrameworkStatsLog
+ .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__STANDBY_BUCKET_CHANGED;
+ case Event.FOREGROUND_SERVICE_START:
+ return FrameworkStatsLog
+ .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__FOREGROUND_SERVICE_START;
+ case Event.FOREGROUND_SERVICE_STOP:
+ return FrameworkStatsLog
+ .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__FOREGROUND_SERVICE_STOP;
+ default:
+ Slog.w(TAG, "Unsupported usage event logging: " + eventType);
+ return -1;
+ }
+ }
+
private String getUsageSourcePackage(Event event) {
switch(mUsageSource) {
case USAGE_SOURCE_CURRENT_ACTIVITY:
@@ -2395,7 +2427,19 @@
return null;
}
- return queryEventsHelper(UserHandle.getCallingUserId(), query.getBeginTimeMillis(),
+ final int callingUserId = UserHandle.getCallingUserId();
+ int userId = query.getUserId();
+ if (userId == UserHandle.USER_NULL) {
+ // Convert userId to actual user Id if not specified in the query object.
+ userId = callingUserId;
+ }
+ if (userId != callingUserId) {
+ getContext().enforceCallingPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ "No permission to query usage stats for user " + userId);
+ }
+
+ return queryEventsHelper(userId, query.getBeginTimeMillis(),
query.getEndTimeMillis(), callingPackage, query.getEventTypeFilter());
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
index 4720d27..f9fa9b7 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
@@ -106,96 +106,104 @@
@Override
public void onAttentionGained() {
Slog.v(TAG, "BinderCallback#onAttentionGained");
- mEgressingData = true;
- if (mAttentionListener == null) {
- return;
- }
- try {
- mAttentionListener.onAttentionGained();
- } catch (RemoteException e) {
- Slog.e(TAG, "Error delivering attention gained event.", e);
- try {
- callback.onVisualQueryDetectionServiceFailure(
- new VisualQueryDetectionServiceFailure(
- ERROR_CODE_ILLEGAL_ATTENTION_STATE,
- "Attention listener failed to switch to GAINED state."));
- } catch (RemoteException ex) {
- Slog.v(TAG, "Fail to call onVisualQueryDetectionServiceFailure");
+ synchronized (mLock) {
+ mEgressingData = true;
+ if (mAttentionListener == null) {
+ return;
}
- return;
+ try {
+ mAttentionListener.onAttentionGained();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error delivering attention gained event.", e);
+ try {
+ callback.onVisualQueryDetectionServiceFailure(
+ new VisualQueryDetectionServiceFailure(
+ ERROR_CODE_ILLEGAL_ATTENTION_STATE,
+ "Attention listener fails to switch to GAINED state."));
+ } catch (RemoteException ex) {
+ Slog.v(TAG, "Fail to call onVisualQueryDetectionServiceFailure");
+ }
+ }
}
}
@Override
public void onAttentionLost() {
Slog.v(TAG, "BinderCallback#onAttentionLost");
- mEgressingData = false;
- if (mAttentionListener == null) {
- return;
- }
- try {
- mAttentionListener.onAttentionLost();
- } catch (RemoteException e) {
- Slog.e(TAG, "Error delivering attention lost event.", e);
- try {
- callback.onVisualQueryDetectionServiceFailure(
- new VisualQueryDetectionServiceFailure(
- ERROR_CODE_ILLEGAL_ATTENTION_STATE,
- "Attention listener failed to switch to LOST state."));
- } catch (RemoteException ex) {
- Slog.v(TAG, "Fail to call onVisualQueryDetectionServiceFailure");
+ synchronized (mLock) {
+ mEgressingData = false;
+ if (mAttentionListener == null) {
+ return;
}
- return;
+ try {
+ mAttentionListener.onAttentionLost();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error delivering attention lost event.", e);
+ try {
+ callback.onVisualQueryDetectionServiceFailure(
+ new VisualQueryDetectionServiceFailure(
+ ERROR_CODE_ILLEGAL_ATTENTION_STATE,
+ "Attention listener fails to switch to LOST state."));
+ } catch (RemoteException ex) {
+ Slog.v(TAG, "Fail to call onVisualQueryDetectionServiceFailure");
+ }
+ }
}
}
@Override
public void onQueryDetected(@NonNull String partialQuery) throws RemoteException {
- Objects.requireNonNull(partialQuery);
Slog.v(TAG, "BinderCallback#onQueryDetected");
- if (!mEgressingData) {
- Slog.v(TAG, "Query should not be egressed within the unattention state.");
- callback.onVisualQueryDetectionServiceFailure(
- new VisualQueryDetectionServiceFailure(
- ERROR_CODE_ILLEGAL_STREAMING_STATE,
- "Cannot stream queries without attention signals."));
- return;
+ synchronized (mLock) {
+ Objects.requireNonNull(partialQuery);
+ if (!mEgressingData) {
+ Slog.v(TAG, "Query should not be egressed within the unattention state.");
+ callback.onVisualQueryDetectionServiceFailure(
+ new VisualQueryDetectionServiceFailure(
+ ERROR_CODE_ILLEGAL_STREAMING_STATE,
+ "Cannot stream queries without attention signals."));
+ return;
+ }
+ mQueryStreaming = true;
+ callback.onQueryDetected(partialQuery);
+ Slog.i(TAG, "Egressed from visual query detection process.");
}
- mQueryStreaming = true;
- callback.onQueryDetected(partialQuery);
- Slog.i(TAG, "Egressed from visual query detection process.");
}
@Override
public void onQueryFinished() throws RemoteException {
Slog.v(TAG, "BinderCallback#onQueryFinished");
- if (!mQueryStreaming) {
- Slog.v(TAG, "Query streaming state signal FINISHED is block since there is"
- + " no active query being streamed.");
- callback.onVisualQueryDetectionServiceFailure(
- new VisualQueryDetectionServiceFailure(
- ERROR_CODE_ILLEGAL_STREAMING_STATE,
- "Cannot send FINISHED signal with no query streamed."));
- return;
+ synchronized (mLock) {
+ if (!mQueryStreaming) {
+ Slog.v(TAG, "Query streaming state signal FINISHED is block since there is"
+ + " no active query being streamed.");
+ callback.onVisualQueryDetectionServiceFailure(
+ new VisualQueryDetectionServiceFailure(
+ ERROR_CODE_ILLEGAL_STREAMING_STATE,
+ "Cannot send FINISHED signal with no query streamed."));
+ return;
+ }
+ callback.onQueryFinished();
+ mQueryStreaming = false;
}
- callback.onQueryFinished();
- mQueryStreaming = false;
}
@Override
public void onQueryRejected() throws RemoteException {
Slog.v(TAG, "BinderCallback#onQueryRejected");
- if (!mQueryStreaming) {
- Slog.v(TAG, "Query streaming state signal REJECTED is block since there is"
- + " no active query being streamed.");
- callback.onVisualQueryDetectionServiceFailure(
- new VisualQueryDetectionServiceFailure(
- ERROR_CODE_ILLEGAL_STREAMING_STATE,
- "Cannot send REJECTED signal with no query streamed."));
- return;
+ synchronized (mLock) {
+ if (!mQueryStreaming) {
+ Slog.v(TAG, "Query streaming state signal REJECTED is block since there is"
+ + " no active query being streamed.");
+ callback.onVisualQueryDetectionServiceFailure(
+ new VisualQueryDetectionServiceFailure(
+ ERROR_CODE_ILLEGAL_STREAMING_STATE,
+ "Cannot send REJECTED signal with no query streamed."));
+ return;
+ }
+ callback.onQueryRejected();
+ mQueryStreaming = false;
}
- callback.onQueryRejected();
- mQueryStreaming = false;
}
};
return mRemoteDetectionService.run(
diff --git a/telecomm/java/android/telecom/AuthenticatorService.java b/telecomm/java/android/telecom/AuthenticatorService.java
deleted file mode 100644
index 1e43c71..0000000
--- a/telecomm/java/android/telecom/AuthenticatorService.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.telecom;
-import android.accounts.AbstractAccountAuthenticator;
-import android.accounts.Account;
-import android.accounts.AccountAuthenticatorResponse;
-import android.accounts.NetworkErrorException;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.IBinder;
-
-/**
- * A generic stub account authenticator service often used for sync adapters that do not directly
- * involve accounts.
- *
- * @hide
- */
-public class AuthenticatorService extends Service {
- private static Authenticator mAuthenticator;
-
- @Override
- public void onCreate() {
- mAuthenticator = new Authenticator(this);
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return mAuthenticator.getIBinder();
- }
-
- /**
- * Stub account authenticator. All methods either return null or throw an exception.
- */
- public class Authenticator extends AbstractAccountAuthenticator {
- public Authenticator(Context context) {
- super(context);
- }
-
- @Override
- public Bundle editProperties(AccountAuthenticatorResponse accountAuthenticatorResponse,
- String s) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public Bundle addAccount(AccountAuthenticatorResponse accountAuthenticatorResponse,
- String s, String s2, String[] strings, Bundle bundle)
- throws NetworkErrorException {
- return null;
- }
-
- @Override
- public Bundle confirmCredentials(AccountAuthenticatorResponse accountAuthenticatorResponse,
- Account account, Bundle bundle)
- throws NetworkErrorException {
- return null;
- }
-
- @Override
- public Bundle getAuthToken(AccountAuthenticatorResponse accountAuthenticatorResponse,
- Account account, String s, Bundle bundle)
- throws NetworkErrorException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String getAuthTokenLabel(String s) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public Bundle updateCredentials(AccountAuthenticatorResponse accountAuthenticatorResponse,
- Account account, String s, Bundle bundle)
- throws NetworkErrorException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public Bundle hasFeatures(AccountAuthenticatorResponse accountAuthenticatorResponse,
- Account account, String[] strings)
- throws NetworkErrorException {
- throw new UnsupportedOperationException();
- }
- }
-}
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index def52a5..874c10c 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -210,100 +210,6 @@
"android.telecom.extra.SILENT_RINGING_REQUESTED";
/**
- * Call event sent from a {@link Call} via {@link #sendCallEvent(String, Bundle)} to inform
- * Telecom that the user has requested that the current {@link Call} should be handed over
- * to another {@link ConnectionService}.
- * <p>
- * The caller must specify the {@link #EXTRA_HANDOVER_PHONE_ACCOUNT_HANDLE} to indicate to
- * Telecom which {@link PhoneAccountHandle} the {@link Call} should be handed over to.
- * @hide
- * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated
- * APIs instead.
- */
- public static final String EVENT_REQUEST_HANDOVER =
- "android.telecom.event.REQUEST_HANDOVER";
-
- /**
- * Extra key used with the {@link #EVENT_REQUEST_HANDOVER} call event. Specifies the
- * {@link PhoneAccountHandle} to which a call should be handed over to.
- * @hide
- * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated
- * APIs instead.
- */
- public static final String EXTRA_HANDOVER_PHONE_ACCOUNT_HANDLE =
- "android.telecom.extra.HANDOVER_PHONE_ACCOUNT_HANDLE";
-
- /**
- * Integer extra key used with the {@link #EVENT_REQUEST_HANDOVER} call event. Specifies the
- * video state of the call when it is handed over to the new {@link PhoneAccount}.
- * <p>
- * Valid values: {@link VideoProfile#STATE_AUDIO_ONLY},
- * {@link VideoProfile#STATE_BIDIRECTIONAL}, {@link VideoProfile#STATE_RX_ENABLED}, and
- * {@link VideoProfile#STATE_TX_ENABLED}.
- * @hide
- * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated
- * APIs instead.
- */
- public static final String EXTRA_HANDOVER_VIDEO_STATE =
- "android.telecom.extra.HANDOVER_VIDEO_STATE";
-
- /**
- * Extra key used with the {@link #EVENT_REQUEST_HANDOVER} call event. Used by the
- * {@link InCallService} initiating a handover to provide a {@link Bundle} with extra
- * information to the handover {@link ConnectionService} specified by
- * {@link #EXTRA_HANDOVER_PHONE_ACCOUNT_HANDLE}.
- * <p>
- * This {@link Bundle} is not interpreted by Telecom, but passed as-is to the
- * {@link ConnectionService} via the request extras when
- * {@link ConnectionService#onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}
- * is called to initate the handover.
- * @hide
- * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated
- * APIs instead.
- */
- public static final String EXTRA_HANDOVER_EXTRAS = "android.telecom.extra.HANDOVER_EXTRAS";
-
- /**
- * Call event sent from Telecom to the handover {@link ConnectionService} via
- * {@link Connection#onCallEvent(String, Bundle)} to inform a {@link Connection} that a handover
- * to the {@link ConnectionService} has completed successfully.
- * <p>
- * A handover is initiated with the {@link #EVENT_REQUEST_HANDOVER} call event.
- * @hide
- * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated
- * APIs instead.
- */
- public static final String EVENT_HANDOVER_COMPLETE =
- "android.telecom.event.HANDOVER_COMPLETE";
-
- /**
- * Call event sent from Telecom to the handover destination {@link ConnectionService} via
- * {@link Connection#onCallEvent(String, Bundle)} to inform the handover destination that the
- * source connection has disconnected. The {@link Bundle} parameter for the call event will be
- * {@code null}.
- * <p>
- * A handover is initiated with the {@link #EVENT_REQUEST_HANDOVER} call event.
- * @hide
- * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated
- * APIs instead.
- */
- public static final String EVENT_HANDOVER_SOURCE_DISCONNECTED =
- "android.telecom.event.HANDOVER_SOURCE_DISCONNECTED";
-
- /**
- * Call event sent from Telecom to the handover {@link ConnectionService} via
- * {@link Connection#onCallEvent(String, Bundle)} to inform a {@link Connection} that a handover
- * to the {@link ConnectionService} has failed.
- * <p>
- * A handover is initiated with the {@link #EVENT_REQUEST_HANDOVER} call event.
- * @hide
- * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated
- * APIs instead.
- */
- public static final String EVENT_HANDOVER_FAILED =
- "android.telecom.event.HANDOVER_FAILED";
-
- /**
* Event reported from the Telecom stack to report an in-call diagnostic message which the
* dialer app may opt to display to the user. A diagnostic message is used to communicate
* scenarios the device has detected which may impact the quality of the ongoing call.
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 4a541da..a2105b0 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -961,28 +961,6 @@
"android.telecom.event.CALL_REMOTELY_UNHELD";
/**
- * Connection event used to inform an {@link InCallService} which initiated a call handover via
- * {@link Call#EVENT_REQUEST_HANDOVER} that the handover from this {@link Connection} has
- * successfully completed.
- * @hide
- * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated
- * APIs instead.
- */
- public static final String EVENT_HANDOVER_COMPLETE =
- "android.telecom.event.HANDOVER_COMPLETE";
-
- /**
- * Connection event used to inform an {@link InCallService} which initiated a call handover via
- * {@link Call#EVENT_REQUEST_HANDOVER} that the handover from this {@link Connection} has failed
- * to complete.
- * @hide
- * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated
- * APIs instead.
- */
- public static final String EVENT_HANDOVER_FAILED =
- "android.telecom.event.HANDOVER_FAILED";
-
- /**
* String Connection extra key used to store SIP invite fields for an incoming call for IMS call
*/
public static final String EXTRA_SIP_INVITE = "android.telecom.extra.SIP_INVITE";
@@ -3362,15 +3340,6 @@
public void onDisconnect() {}
/**
- * Notifies this Connection of a request to disconnect a participant of the conference managed
- * by the connection.
- *
- * @param endpoint the {@link Uri} of the participant to disconnect.
- * @hide
- */
- public void onDisconnectConferenceParticipant(Uri endpoint) {}
-
- /**
* Notifies this Connection of a request to separate from its parent conference.
*/
public void onSeparate() {}
diff --git a/telephony/java/android/telephony/AnomalyReporter.java b/telephony/java/android/telephony/AnomalyReporter.java
index db38f88..575ec27 100644
--- a/telephony/java/android/telephony/AnomalyReporter.java
+++ b/telephony/java/android/telephony/AnomalyReporter.java
@@ -187,14 +187,15 @@
}
for (ResolveInfo r : packages) {
- if (r.activityInfo == null
- || pm.checkPermission(
+ if (r.activityInfo == null) {
+ Rlog.w(TAG, "Found package without activity");
+ continue;
+ } else if (pm.checkPermission(
android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
r.activityInfo.packageName)
- != PackageManager.PERMISSION_GRANTED) {
- Rlog.w(TAG,
- "Found package without proper permissions or no activity"
- + r.activityInfo.packageName);
+ != PackageManager.PERMISSION_GRANTED) {
+ Rlog.w(TAG, "Found package without proper permissions"
+ + r.activityInfo.packageName);
continue;
}
Rlog.d(TAG, "Found a valid package " + r.activityInfo.packageName);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index bcd9929..c7b84a3 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -525,6 +525,12 @@
public static final String KEY_PREFER_2G_BOOL = "prefer_2g_bool";
/**
+ * Used in the Preferred Network Types menu to determine if the 3G option is displayed.
+ */
+ @FlaggedApi(Flags.FLAG_HIDE_PREFER_3G_ITEM)
+ public static final String KEY_PREFER_3G_VISIBILITY_BOOL = "prefer_3g_visibility_bool";
+
+ /**
* Used in Cellular Network Settings for preferred network type to show 4G only mode.
* @hide
*/
@@ -3715,19 +3721,19 @@
* This configuration allows the system UI to display different 5G icons for different 5G
* scenarios.
*
- * There are five 5G scenarios:
- * 1. connected_mmwave: device currently connected to 5G cell as the secondary cell and using
- * millimeter wave.
- * 2. connected: device currently connected to 5G cell as the secondary cell but not using
- * millimeter wave.
- * 3. not_restricted_rrc_idle: device camped on a network that has 5G capability(not necessary
- * to connect a 5G cell as a secondary cell) and the use of 5G is not restricted and RRC
- * currently in IDLE state.
- * 4. not_restricted_rrc_con: device camped on a network that has 5G capability(not necessary
- * to connect a 5G cell as a secondary cell) and the use of 5G is not restricted and RRC
- * currently in CONNECTED state.
- * 5. restricted: device camped on a network that has 5G capability(not necessary to connect a
- * 5G cell as a secondary cell) but the use of 5G is restricted.
+ * There are six 5G scenarios for icon configuration:
+ * 1. connected_mmwave: device currently connected to 5G cell as the primary or secondary cell
+ * and considered NR advanced.
+ * 2. connected: device currently connected to 5G cell as the primary or secondary cell but not
+ * considered NR advanced.
+ * 3. connected_rrc_idle: device currently connected to 5G cell as the primary or secondary cell
+ * and RRC currently in IDLE state.
+ * 4. not_restricted_rrc_idle: device camped on a network that has 5G capability and the use of
+ * 5G is not restricted and RRC currently in IDLE state.
+ * 5. not_restricted_rrc_con: device camped on a network that has 5G capability and the use of
+ * 5G is not restricted and RRC currently in CONNECTED state.
+ * 6. restricted: device camped on a network that has 5G capability but the use of 5G is
+ * restricted.
*
* The configured string contains multiple key-value pairs separated by comma. For each pair,
* the key and value are separated by a colon. The key corresponds to a 5G status above and
@@ -3748,21 +3754,21 @@
* This configuration allows the system UI to determine how long to continue to display 5G icons
* when the device switches between different 5G scenarios.
*
- * There are seven 5G scenarios:
- * 1. connected_mmwave: device currently connected to 5G cell as the secondary cell and using
- * millimeter wave.
- * 2. connected: device currently connected to 5G cell as the secondary cell but not using
- * millimeter wave.
- * 3. not_restricted_rrc_idle: device camped on a network that has 5G capability (not necessary
- * to connect a 5G cell as a secondary cell) and the use of 5G is not restricted and RRC
- * currently in IDLE state.
- * 4. not_restricted_rrc_con: device camped on a network that has 5G capability (not necessary
- * to connect a 5G cell as a secondary cell) and the use of 5G is not restricted and RRC
- * currently in CONNECTED state.
- * 5. restricted: device camped on a network that has 5G capability (not necessary to connect a
- * 5G cell as a secondary cell) but the use of 5G is restricted.
- * 6. legacy: device is not camped on a network that has 5G capability
- * 7. any: any of the above scenarios
+ * There are eight 5G scenarios:
+ * 1. connected_mmwave: device currently connected to 5G cell as the primary or secondary cell
+ * and considered NR advanced.
+ * 2. connected: device currently connected to 5G cell as the primary or secondary cell but not
+ * considered NR advanced.
+ * 3. connected_rrc_idle: device currently connected to 5G cell as the primary or secondary cell
+ * and RRC currently in IDLE state.
+ * 4. not_restricted_rrc_idle: device camped on a network that has 5G capability and the use of
+ * 5G is not restricted and RRC currently in IDLE state.
+ * 5. not_restricted_rrc_con: device camped on a network that has 5G capability and the use of
+ * 5G is not restricted and RRC currently in CONNECTED state.
+ * 6. restricted: device camped on a network that has 5G capability but the use of 5G is
+ * restricted.
+ * 7. legacy: device is not camped on a network that has 5G capability
+ * 8. any: any of the above scenarios
*
* The configured string contains various timer rules separated by a semicolon.
* Each rule will have three items: prior 5G scenario, current 5G scenario, and grace period
@@ -3770,8 +3776,8 @@
* 5G scenario, the system UI will continue to show the icon for the prior 5G scenario (defined
* in {@link #KEY_5G_ICON_CONFIGURATION_STRING}) for the amount of time specified by the grace
* period. If the prior 5G scenario is reestablished, the timer will reset and start again if
- * the UE changes 5G scenarios again. Defined states (5G scenarios #1-5) take precedence over
- * 'any' (5G scenario #6), and unspecified transitions have a default grace period of 0.
+ * the UE changes 5G scenarios again. Defined states (5G scenarios #1-7) take precedence over
+ * 'any' (5G scenario #8), and unspecified transitions have a default grace period of 0.
* The order of rules in the configuration determines the priority (the first applicable timer
* rule will be used).
*
@@ -3794,21 +3800,21 @@
* This configuration extends {@link #KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING} to allow the
* system UI to continue displaying 5G icons after the initial timer expires.
*
- * There are seven 5G scenarios:
- * 1. connected_mmwave: device currently connected to 5G cell as the secondary cell and using
- * millimeter wave.
- * 2. connected: device currently connected to 5G cell as the secondary cell but not using
- * millimeter wave.
- * 3. not_restricted_rrc_idle: device camped on a network that has 5G capability (not necessary
- * to connect a 5G cell as a secondary cell) and the use of 5G is not restricted and RRC
- * currently in IDLE state.
- * 4. not_restricted_rrc_con: device camped on a network that has 5G capability (not necessary
- * to connect a 5G cell as a secondary cell) and the use of 5G is not restricted and RRC
- * currently in CONNECTED state.
- * 5. restricted: device camped on a network that has 5G capability (not necessary to connect a
- * 5G cell as a secondary cell) but the use of 5G is restricted.
- * 6. legacy: device is not camped on a network that has 5G capability
- * 7. any: any of the above scenarios
+ * There are eight 5G scenarios:
+ * 1. connected_mmwave: device currently connected to 5G cell as the primary or secondary cell
+ * and considered NR advanced.
+ * 2. connected: device currently connected to 5G cell as the primary or secondary cell but not
+ * considered NR advanced.
+ * 3. connected_rrc_idle: device currently connected to 5G cell as the primary or secondary cell
+ * and RRC currently in IDLE state.
+ * 4. not_restricted_rrc_idle: device camped on a network that has 5G capability and the use of
+ * 5G is not restricted and RRC currently in IDLE state.
+ * 5. not_restricted_rrc_con: device camped on a network that has 5G capability and the use of
+ * 5G is not restricted and RRC currently in CONNECTED state.
+ * 6. restricted: device camped on a network that has 5G capability but the use of 5G is
+ * restricted.
+ * 7. legacy: device is not camped on a network that has 5G capability
+ * 8. any: any of the above scenarios
*
* The configured string contains various timer rules separated by a semicolon.
* Each rule will have three items: primary 5G scenario, secondary 5G scenario, and
@@ -3818,7 +3824,7 @@
* period. If the primary 5G scenario is reestablished, the timers will reset and the system UI
* will continue to display the icon for the primary 5G scenario without interruption. If the
* secondary 5G scenario is lost, the timer will reset and the icon will reflect the true state.
- * Defined states (5G scenarios #1-5) take precedence over 'any' (5G scenario #6), and
+ * Defined states (5G scenarios #1-7) take precedence over 'any' (5G scenario #8), and
* unspecified transitions have a default grace period of 0. The order of rules in the
* configuration determines the priority (the first applicable timer rule will be used).
*
@@ -8885,18 +8891,18 @@
KEY_PREFIX + "epdg_static_address_roaming_string";
/**
- * Controls if the multiple SA proposals allowed for IKE session to include
- * all the 3GPP TS 33.210 and RFC 8221 supported cipher suites in multiple
- * IKE SA proposals as per RFC 7296.
+ * Enables the use of multiple IKE SA proposals, encompassing both carrier-preferred
+ * ciphers and all supported ciphers from 3GPP TS 33.210 and RFC 8221,
+ * as defined in RFC 7296.
*/
@FlaggedApi(Flags.FLAG_ENABLE_MULTIPLE_SA_PROPOSALS)
public static final String KEY_SUPPORTS_IKE_SESSION_MULTIPLE_SA_PROPOSALS_BOOL =
KEY_PREFIX + "supports_ike_session_multiple_sa_proposals_bool";
/**
- * Controls if the multiple SA proposals allowed for Child session to include
- * all the 3GPP TS 33.210 and RFC 8221 supported cipher suites in multiple
- * Child SA proposals as per RFC 7296.
+ * Enables the use of multiple Child SA proposals, encompassing both carrier-preferred
+ * ciphers and all supported ciphers from 3GPP TS 33.210 and RFC 8221,
+ * as defined in RFC 7296.
*/
@FlaggedApi(Flags.FLAG_ENABLE_MULTIPLE_SA_PROPOSALS)
public static final String KEY_SUPPORTS_CHILD_SESSION_MULTIPLE_SA_PROPOSALS_BOOL =
@@ -10044,6 +10050,49 @@
public static final String KEY_AUTO_DATA_SWITCH_RAT_SIGNAL_SCORE_BUNDLE =
"auto_data_switch_rat_signal_score_string_bundle";
+ // TODO(b/316183370): replace @code with @link in javadoc after feature is released
+ /**
+ * An array of cellular services supported by a subscription.
+ *
+ * <p>Permissible values include:
+ * <ul>
+ * <li>{@code SubscriptionManager#SERVICE_CAPABILITY_VOICE} for voice services</li>
+ * <li>{@code SubscriptionManager#SERVICE_CAPABILITY_SMS} for SMS services</li>
+ * <li>{@code SubscriptionManager#SERVICE_CAPABILITY_DATA} for data services</li>
+ * </ul>
+ *
+ * <p>Carrier-specific factors may influence how these services are supported. Therefore,
+ * modifying this carrier configuration might not always enable the specified services. These
+ * capability bitmasks should be considered as indicators of a carrier's preferred services
+ * to enhance user experience, rather than as absolute platform guarantees.
+ *
+ * <p>Device-level service capabilities, defined by
+ * {@code TelephonyManager#isDeviceVoiceCapable} and
+ * {@code TelephonyManager#isDeviceSmsCapable}, take precedence over these subscription-level
+ * settings. For instance, a device where {@code TelephonyManager#isDeviceVoiceCapable} returns
+ * false may not be able to make voice calls, even if subscribed to a service marked as
+ * voice-capable.
+ *
+ * <p>To determine a subscription's cellular service capabilities, use
+ * {@code SubscriptionInfo#getServiceCapabilities()}. To track changes in services, register
+ * a {@link SubscriptionManager.OnSubscriptionsChangedListener} and invoke the
+ * same method in its callback.
+ *
+ * <p>Emergency service availability may not depend on the cellular service capabilities.
+ * For example, emergency calls might be possible on a subscription even if it lacks
+ * {@code SubscriptionManager#SERVICE_CAPABILITY_VOICE}.
+ *
+ * <p>If unset, the default value is “[1, 2, 3]” (supports all cellular services).
+ *
+ * @see TelephonyManager#isDeviceVoiceCapable
+ * @see TelephonyManager#isDeviceSmsCapable
+ * @see SubscriptionInfo#getServiceCapabilities()
+ * @see SubscriptionManager.OnSubscriptionsChangedListener
+ */
+ @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
+ public static final String KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY =
+ "cellular_service_capabilities_int_array";
+
/** The default value for every variable. */
private static final PersistableBundle sDefaults;
@@ -10144,6 +10193,7 @@
sDefaults.putBoolean(KEY_MDN_IS_ADDITIONAL_VOICEMAIL_NUMBER_BOOL, false);
sDefaults.putBoolean(KEY_OPERATOR_SELECTION_EXPAND_BOOL, true);
sDefaults.putBoolean(KEY_PREFER_2G_BOOL, false);
+ sDefaults.putBoolean(KEY_PREFER_3G_VISIBILITY_BOOL, true);
sDefaults.putBoolean(KEY_4G_ONLY_BOOL, false);
sDefaults.putBoolean(KEY_SHOW_APN_SETTING_CDMA_BOOL, false);
sDefaults.putBoolean(KEY_SHOW_CDMA_CHOICES_BOOL, false);
@@ -10558,7 +10608,7 @@
sDefaults.putBoolean(KEY_USE_CALL_WAITING_USSD_BOOL, false);
sDefaults.putInt(KEY_CALL_WAITING_SERVICE_CLASS_INT, 1 /* SERVICE_CLASS_VOICE */);
sDefaults.putString(KEY_5G_ICON_CONFIGURATION_STRING,
- "connected_mmwave:5G,connected:5G,not_restricted_rrc_idle:5G,"
+ "connected_mmwave:5G,connected:5G,connected_rrc_idle:5G,not_restricted_rrc_idle:5G,"
+ "not_restricted_rrc_con:5G");
sDefaults.putString(KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING, "");
sDefaults.putString(KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING, "");
@@ -10830,6 +10880,7 @@
new boolean[] {false, false, true, false, false});
sDefaults.putStringArray(KEY_CARRIER_SERVICE_NAME_STRING_ARRAY, new String[0]);
sDefaults.putStringArray(KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY, new String[0]);
+ sDefaults.putIntArray(KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY, new int[]{1, 2, 3});
}
/**
diff --git a/telephony/java/android/telephony/SecurityAlgorithmUpdate.java b/telephony/java/android/telephony/SecurityAlgorithmUpdate.java
new file mode 100644
index 0000000..61d7ead
--- /dev/null
+++ b/telephony/java/android/telephony/SecurityAlgorithmUpdate.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 android.telephony;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * A single occurrence capturing a notable change to previously reported
+ * cryptography algorithms for a given network and network event.
+ *
+ * @hide
+ */
+public final class SecurityAlgorithmUpdate implements Parcelable {
+ private static final String TAG = "SecurityAlgorithmUpdate";
+
+ private @ConnectionEvent int mConnectionEvent;
+ private @SecurityAlgorithm int mEncryption;
+ private @SecurityAlgorithm int mIntegrity;
+ private boolean mIsUnprotectedEmergency;
+
+ public SecurityAlgorithmUpdate(@ConnectionEvent int connectionEvent,
+ @SecurityAlgorithm int encryption, @SecurityAlgorithm int integrity,
+ boolean isUnprotectedEmergency) {
+ mConnectionEvent = connectionEvent;
+ mEncryption = encryption;
+ mIntegrity = integrity;
+ mIsUnprotectedEmergency = isUnprotectedEmergency;
+ }
+
+ private SecurityAlgorithmUpdate(Parcel in) {
+ readFromParcel(in);
+ }
+
+ public @ConnectionEvent int getConnectionEvent() {
+ return mConnectionEvent;
+ }
+
+ public @SecurityAlgorithm int getEncryption() {
+ return mEncryption;
+ }
+
+ public @SecurityAlgorithm int getIntegrity() {
+ return mIntegrity;
+ }
+
+ public boolean isUnprotectedEmergency() {
+ return mIsUnprotectedEmergency;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mConnectionEvent);
+ out.writeInt(mEncryption);
+ out.writeInt(mIntegrity);
+ out.writeBoolean(mIsUnprotectedEmergency);
+ }
+
+ private void readFromParcel(@NonNull Parcel in) {
+ mConnectionEvent = in.readInt();
+ mEncryption = in.readInt();
+ mIntegrity = in.readInt();
+ mIsUnprotectedEmergency = in.readBoolean();
+ }
+
+ public static final Parcelable.Creator<SecurityAlgorithmUpdate> CREATOR =
+ new Parcelable.Creator<SecurityAlgorithmUpdate>() {
+ public SecurityAlgorithmUpdate createFromParcel(Parcel in) {
+ return new SecurityAlgorithmUpdate(in);
+ }
+
+ public SecurityAlgorithmUpdate[] newArray(int size) {
+ return new SecurityAlgorithmUpdate[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return TAG + ":{ mConnectionEvent = " + mConnectionEvent + " mEncryption = " + mEncryption
+ + " mIntegrity = " + mIntegrity + " mIsUnprotectedEmergency = "
+ + mIsUnprotectedEmergency;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SecurityAlgorithmUpdate)) return false;
+ SecurityAlgorithmUpdate that = (SecurityAlgorithmUpdate) o;
+ return mConnectionEvent == that.mConnectionEvent
+ && mEncryption == that.mEncryption
+ && mIntegrity == that.mIntegrity
+ && mIsUnprotectedEmergency == that.mIsUnprotectedEmergency;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mConnectionEvent, mEncryption, mIntegrity, mIsUnprotectedEmergency);
+ }
+
+ public static final int CONNECTION_EVENT_CS_SIGNALLING_GSM = 0;
+ public static final int CONNECTION_EVENT_PS_SIGNALLING_GPRS = 1;
+ public static final int CONNECTION_EVENT_CS_SIGNALLING_3G = 2;
+ public static final int CONNECTION_EVENT_PS_SIGNALLING_3G = 3;
+ 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;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"CONNECTION_EVENT_"}, value = {CONNECTION_EVENT_CS_SIGNALLING_GSM,
+ 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_AS_SIGNALLING_5G, CONNECTION_EVENT_VONR_SIP,
+ CONNECTION_EVENT_VONR_RTP})
+ public @interface ConnectionEvent {
+ }
+
+ public static final int SECURITY_ALGORITHM_A50 = 0;
+ public static final int SECURITY_ALGORITHM_A51 = 1;
+ public static final int SECURITY_ALGORITHM_A52 = 2;
+ public static final int SECURITY_ALGORITHM_A53 = 3;
+ public static final int SECURITY_ALGORITHM_A54 = 4;
+ public static final int SECURITY_ALGORITHM_GEA0 = 14;
+ public static final int SECURITY_ALGORITHM_GEA1 = 15;
+ public static final int SECURITY_ALGORITHM_GEA2 = 16;
+ public static final int SECURITY_ALGORITHM_GEA3 = 17;
+ public static final int SECURITY_ALGORITHM_GEA4 = 18;
+ public static final int SECURITY_ALGORITHM_GEA5 = 19;
+ public static final int SECURITY_ALGORITHM_UEA0 = 29;
+ public static final int SECURITY_ALGORITHM_UEA1 = 30;
+ public static final int SECURITY_ALGORITHM_UEA2 = 31;
+ public static final int SECURITY_ALGORITHM_EEA0 = 41;
+ public static final int SECURITY_ALGORITHM_EEA1 = 42;
+ public static final int SECURITY_ALGORITHM_EEA2 = 43;
+ public static final int SECURITY_ALGORITHM_EEA3 = 44;
+ public static final int SECURITY_ALGORITHM_NEA0 = 55;
+ 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_NULL = 68;
+ public static final int SECURITY_ALGORITHM_AES_GCM = 69;
+ public static final int SECURITY_ALGORITHM_AES_GMAC = 70;
+ public static final int SECURITY_ALGORITHM_AES_CBC = 71;
+ public static final int SECURITY_ALGORITHM_DES_EDE3_CBC = 72;
+ 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_SHA1_96_NULL = 75;
+ public static final int SECURITY_ALGORITHM_HMAC_MD5_96 = 76;
+ public static final int SECURITY_ALGORITHM_HMAC_MD5_96_NULL = 77;
+ public static final int SECURITY_ALGORITHM_SRTP_AES_COUNTER = 87;
+ public static final int SECURITY_ALGORITHM_SRTP_AES_F8 = 88;
+ public static final int SECURITY_ALGORITHM_SRTP_HMAC_SHA1 = 89;
+ public static final int SECURITY_ALGORITHM_ENCR_AES_GCM_16 = 99;
+ public static final int SECURITY_ALGORITHM_ENCR_AES_CBC = 100;
+ public static final int SECURITY_ALGORITHM_AUTH_HMAC_SHA2_256_128 = 101;
+ public static final int SECURITY_ALGORITHM_UNKNOWN = 113;
+ public static final int SECURITY_ALGORITHM_OTHER = 114;
+ public static final int SECURITY_ALGORITHM_ORYX = 124;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"CONNECTION_EVENT_"}, value = {SECURITY_ALGORITHM_A50, SECURITY_ALGORITHM_A51,
+ SECURITY_ALGORITHM_A52, SECURITY_ALGORITHM_A53,
+ SECURITY_ALGORITHM_A54, SECURITY_ALGORITHM_GEA0, SECURITY_ALGORITHM_GEA1,
+ SECURITY_ALGORITHM_GEA2, SECURITY_ALGORITHM_GEA3, SECURITY_ALGORITHM_GEA4,
+ SECURITY_ALGORITHM_GEA5, SECURITY_ALGORITHM_UEA0, SECURITY_ALGORITHM_UEA1,
+ 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_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_SHA1_96_NULL,
+ SECURITY_ALGORITHM_HMAC_MD5_96, SECURITY_ALGORITHM_HMAC_MD5_96_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/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index 6ebf3be..a188581 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -54,6 +54,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
/**
* A Parcelable class for Subscription Information.
@@ -262,6 +263,11 @@
private final boolean mIsOnlyNonTerrestrialNetwork;
/**
+ * The service capabilities (in the form of bitmask combination) the subscription supports.
+ */
+ private final int mServiceCapabilities;
+
+ /**
* @hide
*
* @deprecated Use {@link SubscriptionInfo.Builder}.
@@ -386,6 +392,7 @@
this.mPortIndex = portIndex;
this.mUsageSetting = usageSetting;
this.mIsOnlyNonTerrestrialNetwork = false;
+ this.mServiceCapabilities = 0;
}
/**
@@ -425,6 +432,7 @@
this.mPortIndex = builder.mPortIndex;
this.mUsageSetting = builder.mUsageSetting;
this.mIsOnlyNonTerrestrialNetwork = builder.mIsOnlyNonTerrestrialNetwork;
+ this.mServiceCapabilities = builder.mServiceCapabilities;
}
/**
@@ -882,6 +890,44 @@
return mIsOnlyNonTerrestrialNetwork;
}
+ // TODO(b/316183370): replace @code with @link in javadoc after feature is released
+ /**
+ * Retrieves the service capabilities for the current subscription.
+ *
+ * <p>These capabilities are hint to system components and applications, allowing them to
+ * enhance user experience. For instance, a Dialer application can inform the user that the
+ * current subscription is incapable of making voice calls if the voice service is not
+ * available.
+ *
+ * <p>Correct usage of these service capabilities must also consider the device's overall
+ * service capabilities. For example, even if the subscription supports voice calls, a voice
+ * call might not be feasible on a device that only supports data services. To determine the
+ * device's capabilities for voice and SMS services, refer to
+ * {@code TelephonyManager#isDeviceVoiceCapable()} and
+ * {@code TelephonyManager#isDeviceSmsCapable()}.
+ *
+ * <p>Emergency service availability may not directly correlate with the subscription or
+ * device's general service capabilities. In some cases, emergency calls might be possible
+ * even if the subscription or device does not typically support voice services.
+ *
+ * @return A set of integer representing the subscription's service capabilities,
+ * defined by {@code SubscriptionManager#SERVICE_CAPABILITY_VOICE},
+ * {@code SubscriptionManager#SERVICE_CAPABILITY_SMS}
+ * and {@code SubscriptionManager#SERVICE_CAPABILITY_DATA}.
+ *
+ * @see TelephonyManager#isDeviceVoiceCapable()
+ * @see TelephonyManager#isDeviceSmsCapable()
+ * @see CarrierConfigManager#KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY
+ * @see SubscriptionManager#SERVICE_CAPABILITY_VOICE
+ * @see SubscriptionManager#SERVICE_CAPABILITY_SMS
+ * @see SubscriptionManager#SERVICE_CAPABILITY_DATA
+ */
+ @NonNull
+ @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
+ public @SubscriptionManager.ServiceCapability Set<Integer> getServiceCapabilities() {
+ return SubscriptionManager.getServiceCapabilitiesSet(mServiceCapabilities);
+ }
+
@NonNull
public static final Parcelable.Creator<SubscriptionInfo> CREATOR =
new Parcelable.Creator<SubscriptionInfo>() {
@@ -919,6 +965,8 @@
.setUiccApplicationsEnabled(source.readBoolean())
.setUsageSetting(source.readInt())
.setOnlyNonTerrestrialNetwork(source.readBoolean())
+ .setServiceCapabilities(
+ SubscriptionManager.getServiceCapabilitiesSet(source.readInt()))
.build();
}
@@ -961,6 +1009,7 @@
dest.writeBoolean(mAreUiccApplicationsEnabled);
dest.writeInt(mUsageSetting);
dest.writeBoolean(mIsOnlyNonTerrestrialNetwork);
+ dest.writeInt(mServiceCapabilities);
}
@Override
@@ -1024,6 +1073,8 @@
+ " areUiccApplicationsEnabled=" + mAreUiccApplicationsEnabled
+ " usageSetting=" + SubscriptionManager.usageSettingToString(mUsageSetting)
+ " isOnlyNonTerrestrialNetwork=" + mIsOnlyNonTerrestrialNetwork
+ + " serviceCapabilities=" + SubscriptionManager.getServiceCapabilitiesSet(
+ mServiceCapabilities).toString()
+ "]";
}
@@ -1049,7 +1100,8 @@
that.mNativeAccessRules) && Arrays.equals(mCarrierConfigAccessRules,
that.mCarrierConfigAccessRules) && Objects.equals(mGroupUuid, that.mGroupUuid)
&& mCountryIso.equals(that.mCountryIso) && mGroupOwner.equals(that.mGroupOwner)
- && mIsOnlyNonTerrestrialNetwork == that.mIsOnlyNonTerrestrialNetwork;
+ && mIsOnlyNonTerrestrialNetwork == that.mIsOnlyNonTerrestrialNetwork
+ && mServiceCapabilities == that.mServiceCapabilities;
}
@Override
@@ -1058,7 +1110,7 @@
mDisplayNameSource, mIconTint, mNumber, mDataRoaming, mMcc, mMnc, mIsEmbedded,
mCardString, mIsOpportunistic, mGroupUuid, mCountryIso, mCarrierId, mProfileClass,
mType, mGroupOwner, mAreUiccApplicationsEnabled, mPortIndex, mUsageSetting, mCardId,
- mIsGroupDisabled, mIsOnlyNonTerrestrialNetwork);
+ mIsGroupDisabled, mIsOnlyNonTerrestrialNetwork, mServiceCapabilities);
result = 31 * result + Arrays.hashCode(mEhplmns);
result = 31 * result + Arrays.hashCode(mHplmns);
result = 31 * result + Arrays.hashCode(mNativeAccessRules);
@@ -1263,6 +1315,11 @@
private boolean mIsOnlyNonTerrestrialNetwork = false;
/**
+ * Service capabilities bitmasks the subscription supports.
+ */
+ private int mServiceCapabilities = 0;
+
+ /**
* Default constructor.
*/
public Builder() {
@@ -1305,6 +1362,7 @@
mPortIndex = info.mPortIndex;
mUsageSetting = info.mUsageSetting;
mIsOnlyNonTerrestrialNetwork = info.mIsOnlyNonTerrestrialNetwork;
+ mServiceCapabilities = info.mServiceCapabilities;
}
/**
@@ -1703,6 +1761,32 @@
}
/**
+ * Set the service capabilities that the subscription supports.
+ *
+ * @param capabilities Bitmask combination of SubscriptionManager
+ * .SERVICE_CAPABILITY_XXX.
+ * @return The builder.
+ *
+ * @throws IllegalArgumentException when any capability is not supported.
+ */
+ @NonNull
+ @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
+ public Builder setServiceCapabilities(
+ @NonNull @SubscriptionManager.ServiceCapability Set<Integer> capabilities) {
+ int combinedCapabilities = 0;
+ for (int capability : capabilities) {
+ if (capability < SubscriptionManager.SERVICE_CAPABILITY_VOICE
+ || capability > SubscriptionManager.SERVICE_CAPABILITY_MAX) {
+ throw new IllegalArgumentException(
+ "Invalid service capability value: " + capability);
+ }
+ combinedCapabilities |= SubscriptionManager.serviceCapabilityToBitmask(capability);
+ }
+ mServiceCapabilities = combinedCapabilities;
+ return this;
+ }
+
+ /**
* Build the {@link SubscriptionInfo}.
*
* @return The {@link SubscriptionInfo} instance.
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 326b6f5..6c8663a 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -88,10 +88,12 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -1138,6 +1140,14 @@
*/
public static final String IS_NTN = SimInfo.COLUMN_IS_NTN;
+ /**
+ * TelephonyProvider column name to identify service capabilities.
+ * Disabled by default.
+ * <P>Type: INTEGER (int)</P>
+ * @hide
+ */
+ public static final String SERVICE_CAPABILITIES = SimInfo.COLUMN_SERVICE_CAPABILITIES;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"USAGE_SETTING_"},
@@ -1347,6 +1357,86 @@
})
public @interface PhoneNumberSource {}
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"SERVICE_CAPABILITY"},
+ value = {
+ SERVICE_CAPABILITY_VOICE,
+ SERVICE_CAPABILITY_SMS,
+ SERVICE_CAPABILITY_DATA,
+ })
+ public @interface ServiceCapability {
+ }
+
+ /**
+ * Represents a value indicating the voice calling capabilities of a subscription.
+ *
+ * <p>This value identifies whether the subscription supports various voice calling services.
+ * These services can include circuit-switched (CS) calling, packet-switched (PS) IMS (IP
+ * Multimedia Subsystem) calling, and over-the-top (OTT) calling options.
+ *
+ * <p>Note: The availability of emergency calling services is not solely dependent on this
+ * voice capability. Emergency services may be accessible even if the subscription lacks
+ * standard voice capabilities. However, the device's ability to support emergency calls
+ * can be influenced by its inherent voice capabilities, as determined by
+ * {@link TelephonyManager#isDeviceVoiceCapable()}.
+ *
+ * @see TelephonyManager#isDeviceVoiceCapable()
+ */
+ @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
+ public static final int SERVICE_CAPABILITY_VOICE = 1;
+
+ /**
+ * Represents a value indicating the SMS capabilities of a subscription.
+ *
+ * <p>This value identifies whether the subscription supports various sms services.
+ * These services can include circuit-switched (CS) SMS, packet-switched (PS) IMS (IP
+ * Multimedia Subsystem) SMS, and over-the-top (OTT) SMS options.
+ *
+ * <p>Note: The availability of emergency SMS services is not solely dependent on this
+ * sms capability. Emergency services may be accessible even if the subscription lacks
+ * standard sms capabilities. However, the device's ability to support emergency sms
+ * can be influenced by its inherent sms capabilities, as determined by
+ * {@link TelephonyManager#isDeviceSmsCapable()}.
+ *
+ * @see TelephonyManager#isDeviceSmsCapable()
+ */
+ @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
+ public static final int SERVICE_CAPABILITY_SMS = 2;
+
+ /**
+ * Represents a value indicating the data calling capabilities of a subscription.
+ */
+ @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
+ public static final int SERVICE_CAPABILITY_DATA = 3;
+
+ /**
+ * Maximum value of service capabilities supported so far.
+ * @hide
+ */
+ public static final int SERVICE_CAPABILITY_MAX = SERVICE_CAPABILITY_DATA;
+
+ /**
+ * Bitmask for {@code SERVICE_CAPABILITY_VOICE}.
+ * @hide
+ */
+ public static final int SERVICE_CAPABILITY_VOICE_BITMASK =
+ serviceCapabilityToBitmask(SERVICE_CAPABILITY_VOICE);
+
+ /**
+ * Bitmask for {@code SERVICE_CAPABILITY_SMS}.
+ * @hide
+ */
+ public static final int SERVICE_CAPABILITY_SMS_BITMASK =
+ serviceCapabilityToBitmask(SERVICE_CAPABILITY_SMS);
+
+ /**
+ * Bitmask for {@code SERVICE_CAPABILITY_DATA}.
+ * @hide
+ */
+ public static final int SERVICE_CAPABILITY_DATA_BITMASK =
+ serviceCapabilityToBitmask(SERVICE_CAPABILITY_DATA);
+
private final Context mContext;
/**
@@ -4484,4 +4574,38 @@
}
return new ArrayList<>();
}
+
+ /**
+ * @return the bitmasks combination of all service capabilities.
+ * @hide
+ */
+ public static int getAllServiceCapabilityBitmasks() {
+ return SERVICE_CAPABILITY_VOICE_BITMASK | SERVICE_CAPABILITY_SMS_BITMASK
+ | SERVICE_CAPABILITY_DATA_BITMASK;
+ }
+
+ /**
+ * @return The set of service capability from a bitmask combined one.
+ * @hide
+ */
+ @NonNull
+ @ServiceCapability
+ public static Set<Integer> getServiceCapabilitiesSet(int combinedServiceCapabilities) {
+ Set<Integer> capabilities = new HashSet<>();
+ for (int i = SERVICE_CAPABILITY_VOICE; i <= SERVICE_CAPABILITY_MAX; i++) {
+ final int capabilityBitmask = serviceCapabilityToBitmask(i);
+ if ((combinedServiceCapabilities & capabilityBitmask) == capabilityBitmask) {
+ capabilities.add(i);
+ }
+ }
+ return Collections.unmodifiableSet(capabilities);
+ }
+
+ /**
+ * @return The service capability bitmask from a {@link ServiceCapability} value.
+ * @hide
+ */
+ public static int serviceCapabilityToBitmask(@ServiceCapability int capability) {
+ return 1 << (capability - 1);
+ }
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index f8166e5..22a5cd7 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -6683,8 +6683,10 @@
* PackageManager.FEATURE_TELEPHONY system feature, which is available
* on any device with a telephony radio, even if the device is
* data-only.
+ * @deprecated Replaced by {@link #isDeviceVoiceCapable()}
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
+ @Deprecated
public boolean isVoiceCapable() {
if (mContext == null) return true;
return mContext.getResources().getBoolean(
@@ -6692,6 +6694,30 @@
}
/**
+ * @return true if the current device is "voice capable".
+ * <p>
+ * "Voice capable" means that this device supports circuit-switched or IMS packet switched
+ * (i.e. voice) phone calls over the telephony network, and is allowed to display the in-call
+ * UI while a cellular voice call is active. This will be false on "data only" devices which
+ * can't make voice calls and don't support any in-call UI.
+ * <p>
+ * Note: the meaning of this flag is subtly different from the PackageManager
+ * .FEATURE_TELEPHONY system feature, which is available on any device with a telephony
+ * radio, even if the device is data-only.
+ * <p>
+ * To check if a subscription is "voice capable", call method
+ * {@link SubscriptionInfo#getServiceCapabilities()} and compare the result with
+ * bitmask {@link SubscriptionManager#SERVICE_CAPABILITY_VOICE}.
+ *
+ * @see SubscriptionInfo#getServiceCapabilities()
+ */
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
+ @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
+ public boolean isDeviceVoiceCapable() {
+ return isVoiceCapable();
+ }
+
+ /**
* @return true if the current device supports sms service.
* <p>
* If true, this means that the device supports both sending and
@@ -6699,6 +6725,7 @@
* <p>
* Note: Voicemail waiting sms, cell broadcasting sms, and MMS are
* disabled when device doesn't support sms.
+ * @deprecated Replaced by {@link #isDeviceSmsCapable()}
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
public boolean isSmsCapable() {
@@ -6708,6 +6735,27 @@
}
/**
+ * @return true if the current device supports SMS service.
+ * <p>
+ * If true, this means that the device supports both sending and
+ * receiving SMS via the telephony network.
+ * <p>
+ * Note: Voicemail waiting SMS, cell broadcasting SMS, and MMS are
+ * disabled when device doesn't support SMS.
+ * <p>
+ * To check if a subscription is "SMS capable", call method
+ * {@link SubscriptionInfo#getServiceCapabilities()} and compare result with
+ * bitmask {@link SubscriptionManager#SERVICE_CAPABILITY_SMS}.
+ *
+ * @see SubscriptionInfo#getServiceCapabilities()
+ */
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
+ @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
+ public boolean isDeviceSmsCapable() {
+ return isSmsCapable();
+ }
+
+ /**
* Requests all available cell information from all radios on the device including the
* camped/registered, serving, and neighboring cells.
*
diff --git a/tests/ActivityManagerPerfTests/tests/Android.bp b/tests/ActivityManagerPerfTests/tests/Android.bp
index e5813ae..cce40f3a 100644
--- a/tests/ActivityManagerPerfTests/tests/Android.bp
+++ b/tests/ActivityManagerPerfTests/tests/Android.bp
@@ -30,6 +30,12 @@
"ActivityManagerPerfTestsUtils",
"collector-device-lib-platform",
],
+ data: [
+ ":ActivityManagerPerfTestsTestApp",
+ ":ActivityManagerPerfTestsStubApp1",
+ ":ActivityManagerPerfTestsStubApp2",
+ ":ActivityManagerPerfTestsStubApp3",
+ ],
platform_apis: true,
min_sdk_version: "25",
// For android.permission.FORCE_STOP_PACKAGES permission
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
index c49f8fe..be47ec7 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
@@ -19,12 +19,12 @@
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
+import android.tools.common.flicker.subject.layers.LayersTraceSubject.Companion.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import android.tools.device.traces.parsers.toFlickerComponent
-import androidx.test.filters.FlakyTest
import com.android.server.wm.flicker.BaseTest
import com.android.server.wm.flicker.helpers.ImeAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
@@ -58,9 +58,10 @@
}
transitions {
broadcastActionTrigger.doAction(ACTION_FINISH_ACTIVITY)
- wmHelper.StateSyncBuilder()
- .withActivityRemoved(ActivityOptions.Ime.Default.COMPONENT.toFlickerComponent())
- .waitForAndVerify()
+ wmHelper
+ .StateSyncBuilder()
+ .withActivityRemoved(ActivityOptions.Ime.Default.COMPONENT.toFlickerComponent())
+ .waitForAndVerify()
}
teardown { simpleApp.exit(wmHelper) }
}
@@ -69,10 +70,16 @@
@Presubmit @Test fun imeLayerBecomesInvisible() = flicker.imeLayerBecomesInvisible()
- @FlakyTest(bugId = 246284124)
+ @Presubmit
@Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ flicker.assertLayers {
+ this.visibleLayersShownMoreThanOneConsecutiveEntry(
+ VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS.toMutableList().also {
+ it.add(simpleApp.componentMatcher)
+ }
+ )
+ }
}
@Presubmit
diff --git a/tests/testables/src/android/testing/TestWithLooperRule.java b/tests/testables/src/android/testing/TestWithLooperRule.java
index 99b303e..37b39c3 100644
--- a/tests/testables/src/android/testing/TestWithLooperRule.java
+++ b/tests/testables/src/android/testing/TestWithLooperRule.java
@@ -19,7 +19,6 @@
import android.testing.TestableLooper.LooperFrameworkMethod;
import android.testing.TestableLooper.RunWithLooper;
-import org.junit.internal.runners.statements.InvokeMethod;
import org.junit.rules.MethodRule;
import org.junit.runner.RunWith;
import org.junit.runners.model.FrameworkMethod;
@@ -79,13 +78,11 @@
while (next != null) {
switch (next.getClass().getSimpleName()) {
case "RunAfters":
- this.<List<FrameworkMethod>>wrapFieldMethodFor(next,
- next.getClass(), "afters", method, target);
+ this.wrapFieldMethodFor(next, "afters", method, target);
next = getNextStatement(next, "next");
break;
case "RunBefores":
- this.<List<FrameworkMethod>>wrapFieldMethodFor(next,
- next.getClass(), "befores", method, target);
+ this.wrapFieldMethodFor(next, "befores", method, target);
next = getNextStatement(next, "next");
break;
case "FailOnTimeout":
@@ -95,8 +92,10 @@
next = getNextStatement(next, "originalStatement");
break;
case "InvokeMethod":
- this.<FrameworkMethod>wrapFieldMethodFor(next,
- InvokeMethod.class, "testMethod", method, target);
+ this.wrapFieldMethodFor(next, "testMethod", method, target);
+ return;
+ case "InvokeParameterizedMethod":
+ this.wrapFieldMethodFor(next, "frameworkMethod", method, target);
return;
default:
throw new Exception(
@@ -112,12 +111,11 @@
// Wrapping the befores, afters, and InvokeMethods with LooperFrameworkMethod
// within the statement.
- private <T> void wrapFieldMethodFor(Statement base, Class<?> targetClass, String fieldStr,
- FrameworkMethod method, Object target)
- throws NoSuchFieldException, IllegalAccessException {
- Field field = targetClass.getDeclaredField(fieldStr);
+ private void wrapFieldMethodFor(Statement base, String fieldStr, FrameworkMethod method,
+ Object target) throws NoSuchFieldException, IllegalAccessException {
+ Field field = base.getClass().getDeclaredField(fieldStr);
field.setAccessible(true);
- T fieldInstance = (T) field.get(base);
+ Object fieldInstance = field.get(base);
if (fieldInstance instanceof FrameworkMethod) {
field.set(base, looperWrap(method, target, (FrameworkMethod) fieldInstance));
} else {
diff --git a/tools/hoststubgen/README.md b/tools/hoststubgen/README.md
index 3455b0a..1a895dc 100644
--- a/tools/hoststubgen/README.md
+++ b/tools/hoststubgen/README.md
@@ -34,11 +34,6 @@
- `test-tiny-framework/` See `README.md` in it.
- - `test-framework`
- This directory was used during the prototype phase, but now that we have real ravenwood tests,
- this directory is obsolete and should be deleted.
-
-
- `scripts`
- `dump-jar.sh`
diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp
index 4eac361..57bcc04 100644
--- a/tools/hoststubgen/hoststubgen/Android.bp
+++ b/tools/hoststubgen/hoststubgen/Android.bp
@@ -9,7 +9,7 @@
// Visibility only for ravenwood prototype uses.
genrule_defaults {
- name: "hoststubgen-for-prototype-only-genrule",
+ name: "ravenwood-internal-only-visibility-genrule",
visibility: [
":__subpackages__",
"//frameworks/base",
@@ -19,7 +19,7 @@
// Visibility only for ravenwood prototype uses.
java_defaults {
- name: "hoststubgen-for-prototype-only-java",
+ name: "ravenwood-internal-only-visibility-java",
visibility: [
":__subpackages__",
"//frameworks/base",
@@ -29,7 +29,7 @@
// Visibility only for ravenwood prototype uses.
filegroup_defaults {
- name: "hoststubgen-for-prototype-only-filegroup",
+ name: "ravenwood-internal-only-visibility-filegroup",
visibility: [
":__subpackages__",
"//frameworks/base",
@@ -41,7 +41,7 @@
// This is only for the prototype. The productionized version is "ravenwood-annotations".
java_library {
name: "hoststubgen-annotations",
- defaults: ["hoststubgen-for-prototype-only-java"],
+ defaults: ["ravenwood-internal-only-visibility-java"],
srcs: [
"annotations-src/**/*.java",
],
@@ -115,7 +115,7 @@
// This is only for the prototype. The productionized version is "ravenwood-standard-options".
filegroup {
name: "hoststubgen-standard-options",
- defaults: ["hoststubgen-for-prototype-only-filegroup"],
+ defaults: ["ravenwood-internal-only-visibility-filegroup"],
srcs: [
"hoststubgen-standard-options.txt",
],
@@ -153,149 +153,25 @@
],
}
-// Generate the stub/impl from framework-all, with hidden APIs.
-java_genrule_host {
- name: "framework-all-hidden-api-host",
- defaults: ["hoststubgen-command-defaults"],
- cmd: hoststubgen_common_options +
- "--in-jar $(location :framework-all) " +
- "--policy-override-file $(location framework-policy-override.txt) ",
- srcs: [
- ":framework-all",
- "framework-policy-override.txt",
- ],
- visibility: ["//visibility:private"],
-}
-
-// Extract the stub jar from "framework-all-host" for subsequent build rules.
-// This is only for the prototype. Do not use it in "productionized" build rules.
-java_genrule_host {
- name: "framework-all-hidden-api-host-stub",
- defaults: ["hoststubgen-for-prototype-only-genrule"],
- cmd: "cp $(in) $(out)",
- srcs: [
- ":framework-all-hidden-api-host{host_stub.jar}",
- ],
- out: [
- "host_stub.jar",
- ],
-}
-
-// Extract the impl jar from "framework-all-host" for subsequent build rules.
-// This is only for the prototype. Do not use it in "productionized" build rules.
-java_genrule_host {
- name: "framework-all-hidden-api-host-impl",
- defaults: ["hoststubgen-for-prototype-only-genrule"],
- cmd: "cp $(in) $(out)",
- srcs: [
- ":framework-all-hidden-api-host{host_impl.jar}",
- ],
- out: [
- "host_impl.jar",
- ],
-}
-
-// Generate the stub/impl from framework-all, with only public/system/test APIs, without
-// hidden APIs.
-// This is only for the prototype. Do not use it in "productionized" build rules.
-java_genrule_host {
- name: "framework-all-test-api-host",
- defaults: ["hoststubgen-command-defaults"],
- cmd: hoststubgen_common_options +
- "--intersect-stub-jar $(location :android_test_stubs_current{.jar}) " +
- "--in-jar $(location :framework-all) " +
- "--policy-override-file $(location framework-policy-override.txt) ",
- srcs: [
- ":framework-all",
- ":android_test_stubs_current{.jar}",
- "framework-policy-override.txt",
- ],
- visibility: ["//visibility:private"],
-}
-
-// Extract the stub jar from "framework-all-test-api-host" for subsequent build rules.
-// This is only for the prototype. Do not use it in "productionized" build rules.
-java_genrule_host {
- name: "framework-all-test-api-host-stub",
- defaults: ["hoststubgen-for-prototype-only-genrule"],
- cmd: "cp $(in) $(out)",
- srcs: [
- ":framework-all-test-api-host{host_stub.jar}",
- ],
- out: [
- "host_stub.jar",
- ],
-}
-
-// Extract the impl jar from "framework-all-test-api-host" for subsequent build rules.
-// This is only for the prototype. Do not use it in "productionized" build rules.
-java_genrule_host {
- name: "framework-all-test-api-host-impl",
- defaults: ["hoststubgen-for-prototype-only-genrule"],
- cmd: "cp $(in) $(out)",
- srcs: [
- ":framework-all-test-api-host{host_impl.jar}",
- ],
- out: [
- "host_impl.jar",
- ],
-}
-
-// This library contains helper classes to build hostside tests/targets.
-// This essentially contains dependencies from tests that we can't actually use the real ones.
-// For example, the actual AndroidTestCase and AndroidJUnit4 don't run on the host side (yet),
-// so we pup "fake" implementations here.
-// Ideally this library should be empty.
-java_library_host {
- name: "hoststubgen-helper-framework-buildtime",
- defaults: ["hoststubgen-for-prototype-only-java"],
- srcs: [
- "helper-framework-buildtime-src/**/*.java",
- ],
- libs: [
- // We need it to pull in some of the framework classes used in this library,
- // such as Context.java.
- "framework-all-hidden-api-host-impl",
- "junit",
- ],
-}
-
-// This module contains "fake" libcore/dalvik classes, framework native substitution, etc,
-// that are needed at runtime.
-java_library_host {
- name: "hoststubgen-helper-framework-runtime",
- defaults: ["hoststubgen-for-prototype-only-java"],
- srcs: [
- "helper-framework-runtime-src/**/*.java",
- ],
- exclude_srcs: [
- "helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/CursorWindow_host.java",
- ],
- libs: [
- "hoststubgen-helper-runtime",
- "framework-all-hidden-api-host-impl",
- ],
-}
-
java_library_host {
name: "hoststubgen-helper-libcore-runtime",
- defaults: ["hoststubgen-for-prototype-only-java"],
srcs: [
"helper-framework-runtime-src/libcore-fake/**/*.java",
],
+ visibility: ["//visibility:private"],
}
java_host_for_device {
name: "hoststubgen-helper-libcore-runtime.ravenwood",
- defaults: ["hoststubgen-for-prototype-only-java"],
libs: [
"hoststubgen-helper-libcore-runtime",
],
+ visibility: ["//visibility:private"],
}
java_library {
name: "hoststubgen-helper-framework-runtime.ravenwood",
- defaults: ["hoststubgen-for-prototype-only-java"],
+ defaults: ["ravenwood-internal-only-visibility-java"],
srcs: [
"helper-framework-runtime-src/framework/**/*.java",
],
@@ -308,88 +184,3 @@
"hoststubgen-helper-libcore-runtime.ravenwood",
],
}
-
-// Defaults for host side test modules.
-// We need two rules for each test.
-// 1. A "-test-lib" jar, which compiles the test against the stub jar.
-// This one is only used by the second rule, so it should be "private.
-// 2. A "-test" jar, which includes 1 + the runtime (impl) jars.
-
-// This and next ones are for tests using framework-app, with hidden APIs.
-java_defaults {
- name: "hosttest-with-framework-all-hidden-api-test-lib-defaults",
- installable: false,
- libs: [
- "framework-all-hidden-api-host-stub",
- ],
- static_libs: [
- "hoststubgen-helper-framework-buildtime",
- "framework-annotations-lib",
- ],
- visibility: ["//visibility:private"],
-}
-
-// Default rules to include `libandroid_runtime`. For now, it's empty, but we'll use it
-// once we start using JNI.
-java_defaults {
- name: "hosttest-with-libandroid_runtime",
- jni_libs: [
- // "libandroid_runtime",
-
- // TODO: Figure out how to build them automatically.
- // Following ones are depended by libandroid_runtime.
- // Without listing them here, not only we won't get them under
- // $ANDROID_HOST_OUT/testcases/*/lib64, but also not under
- // $ANDROID_HOST_OUT/lib64, so we'd fail to load them at runtime.
- // ($ANDROID_HOST_OUT/lib/ gets all of them though.)
- // "libcutils",
- // "libharfbuzz_ng",
- // "libminikin",
- // "libz",
- // "libbinder",
- // "libhidlbase",
- // "libvintf",
- // "libicu",
- // "libutils",
- // "libtinyxml2",
- ],
-}
-
-java_defaults {
- name: "hosttest-with-framework-all-hidden-api-test-defaults",
- defaults: ["hosttest-with-libandroid_runtime"],
- installable: false,
- test_config: "AndroidTest-host.xml",
- static_libs: [
- "hoststubgen-helper-runtime",
- "hoststubgen-helper-framework-runtime",
- "framework-all-hidden-api-host-impl",
- ],
-}
-
-// This and next ones are for tests using framework-app, with public/system/test APIs,
-// without hidden APIs.
-java_defaults {
- name: "hosttest-with-framework-all-test-api-test-lib-defaults",
- installable: false,
- libs: [
- "framework-all-test-api-host-stub",
- ],
- static_libs: [
- "hoststubgen-helper-framework-buildtime",
- "framework-annotations-lib",
- ],
- visibility: ["//visibility:private"],
-}
-
-java_defaults {
- name: "hosttest-with-framework-all-test-api-test-defaults",
- defaults: ["hosttest-with-libandroid_runtime"],
- installable: false,
- test_config: "AndroidTest-host.xml",
- static_libs: [
- "hoststubgen-helper-runtime",
- "hoststubgen-helper-framework-runtime",
- "framework-all-test-api-host-impl",
- ],
-}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/android/test/AndroidTestCase.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/android/test/AndroidTestCase.java
deleted file mode 100644
index e6d3866..0000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/android/test/AndroidTestCase.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.test;
-
-import android.content.Context;
-
-import junit.framework.TestCase;
-
-public class AndroidTestCase extends TestCase {
- protected Context mContext;
- public Context getContext() {
- throw new RuntimeException("[ravenwood] Class Context is not supported yet.");
- }
-}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/annotation/NonNull.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/annotation/NonNull.java
deleted file mode 100644
index 51c5d9a..0000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/annotation/NonNull.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.annotation;
-
-// [ravenwood] TODO: Find the actual androidx jar containing it.s
-
-import static java.lang.annotation.ElementType.FIELD;
-import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.ElementType.PARAMETER;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
-
-/**
- * Denotes that a parameter, field or method return value can never be null.
- * <p>
- * This is a marker annotation and it has no specific attributes.
- *
- * @paramDoc This value cannot be {@code null}.
- * @returnDoc This value cannot be {@code null}.
- * @hide
- */
-@Retention(SOURCE)
-@Target({METHOD, PARAMETER, FIELD})
-public @interface NonNull {
-}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/annotation/Nullable.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/annotation/Nullable.java
deleted file mode 100644
index f1f0e8b..0000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/annotation/Nullable.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.annotation;
-
-// [ravenwood] TODO: Find the actual androidx jar containing it.s
-
-import static java.lang.annotation.ElementType.FIELD;
-import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.ElementType.PARAMETER;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
-
-/**
- * Denotes that a parameter, field or method return value can be null.
- * <p>
- * When decorating a method call parameter, this denotes that the parameter can
- * legitimately be null and the method will gracefully deal with it. Typically
- * used on optional parameters.
- * <p>
- * When decorating a method, this denotes the method might legitimately return
- * null.
- * <p>
- * This is a marker annotation and it has no specific attributes.
- *
- * @paramDoc This value may be {@code null}.
- * @returnDoc This value may be {@code null}.
- * @hide
- */
-@Retention(SOURCE)
-@Target({METHOD, PARAMETER, FIELD})
-public @interface Nullable {
-}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/ext/junit/runners/AndroidJUnit4.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/ext/junit/runners/AndroidJUnit4.java
deleted file mode 100644
index 0c82e4e..0000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/ext/junit/runners/AndroidJUnit4.java
+++ /dev/null
@@ -1,30 +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 androidx.test.ext.junit.runners;
-
-import org.junit.runners.BlockJUnit4ClassRunner;
-import org.junit.runners.model.InitializationError;
-
-// TODO: We need to simulate the androidx test runner.
-// https://source.corp.google.com/piper///depot/google3/third_party/android/androidx_test/ext/junit/java/androidx/test/ext/junit/runners/AndroidJUnit4.java
-// https://source.corp.google.com/piper///depot/google3/third_party/android/androidx_test/runner/android_junit_runner/java/androidx/test/internal/runner/junit4/AndroidJUnit4ClassRunner.java
-
-public class AndroidJUnit4 extends BlockJUnit4ClassRunner {
- public AndroidJUnit4(Class<?> testClass) throws InitializationError {
- super(testClass);
- }
-}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/FlakyTest.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/FlakyTest.java
deleted file mode 100644
index 2470d839..0000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/FlakyTest.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.test.filters;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Designates a test as being flaky (non-deterministic).
- *
- * <p>Can then be used to filter tests on execution using -e annotation or -e notAnnotation as
- * desired.
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.METHOD, ElementType.TYPE})
-public @interface FlakyTest {
- /**
- * An optional bug number associated with the test. -1 Means that no bug number is associated with
- * the flaky annotation.
- *
- * @return int
- */
- int bugId() default -1;
-
- /**
- * Details, such as the reason of why the test is flaky.
- *
- * @return String
- */
- String detail() default "";
-}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/LargeTest.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/LargeTest.java
deleted file mode 100644
index 578d7dc..0000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/LargeTest.java
+++ /dev/null
@@ -1,44 +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 androidx.test.filters;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Annotation to assign a large test size qualifier to a test. This annotation can be used at a
- * method or class level.
- *
- * <p>Test size qualifiers are a great way to structure test code and are used to assign a test to a
- * test suite of similar run time.
- *
- * <p>Execution time: >1000ms
- *
- * <p>Large tests should be focused on testing integration of all application components. These
- * tests fully participate in the system and may make use of all resources such as databases, file
- * systems and network. As a rule of thumb most functional UI tests are large tests.
- *
- * <p>Note: This class replaces the deprecated Android platform size qualifier <a
- * href="{@docRoot}reference/android/test/suitebuilder/annotation/LargeTest.html"><code>
- * android.test.suitebuilder.annotation.LargeTest</code></a> and is the recommended way to annotate
- * tests written with the AndroidX Test Library.
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.METHOD, ElementType.TYPE})
-public @interface LargeTest {}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/MediumTest.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/MediumTest.java
deleted file mode 100644
index dfdaa53..0000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/MediumTest.java
+++ /dev/null
@@ -1,45 +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 androidx.test.filters;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Annotation to assign a medium test size qualifier to a test. This annotation can be used at a
- * method or class level.
- *
- * <p>Test size qualifiers are a great way to structure test code and are used to assign a test to a
- * test suite of similar run time.
- *
- * <p>Execution time: <1000ms
- *
- * <p>Medium tests should be focused on a very limited subset of components or a single component.
- * Resource access to the file system through well defined interfaces like databases,
- * ContentProviders, or Context is permitted. Network access should be restricted, (long-running)
- * blocking operations should be avoided and use mock objects instead.
- *
- * <p>Note: This class replaces the deprecated Android platform size qualifier <a
- * href="{@docRoot}reference/android/test/suitebuilder/annotation/MediumTest.html"><code>
- * android.test.suitebuilder.annotation.MediumTest</code></a> and is the recommended way to annotate
- * tests written with the AndroidX Test Library.
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.METHOD, ElementType.TYPE})
-public @interface MediumTest {}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/RequiresDevice.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/RequiresDevice.java
deleted file mode 100644
index 3d3ee33..0000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/RequiresDevice.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.test.filters;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Indicates that a specific test should not be run on emulator.
- *
- * <p>It will be executed only if the test is running on the physical android device.
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-public @interface RequiresDevice {}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/SdkSuppress.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/SdkSuppress.java
deleted file mode 100644
index dd65ddb..0000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/SdkSuppress.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.test.filters;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Indicates that a specific test or class requires a minimum or maximum API Level to execute.
- *
- * <p>Test(s) will be skipped when executed on android platforms less/more than specified level
- * (inclusive).
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-public @interface SdkSuppress {
- /** The minimum API level to execute (inclusive) */
- int minSdkVersion() default 1;
- /** The maximum API level to execute (inclusive) */
- int maxSdkVersion() default Integer.MAX_VALUE;
- /**
- * The {@link android.os.Build.VERSION.CODENAME} to execute on. This is intended to be used to run
- * on a pre-release SDK, where the {@link android.os.Build.VERSION.SDK_INT} has not yet been
- * finalized. This is treated as an OR operation with respect to the minSdkVersion and
- * maxSdkVersion attributes.
- *
- * <p>For example, to filter a test so it runs on only the prerelease R SDK: <code>
- * {@literal @}SdkSuppress(minSdkVersion = Build.VERSION_CODES.R, codeName = "R")
- * </code>
- */
- String codeName() default "unset";
-}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/SmallTest.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/SmallTest.java
deleted file mode 100644
index dd32df4..0000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/SmallTest.java
+++ /dev/null
@@ -1,46 +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 androidx.test.filters;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Annotation to assign a small test size qualifier to a test. This annotation can be used at a
- * method or class level.
- *
- * <p>Test size qualifiers are a great way to structure test code and are used to assign a test to a
- * test suite of similar run time.
- *
- * <p>Execution time: <200ms
- *
- * <p>Small tests should be run very frequently. Focused on units of code to verify specific logical
- * conditions. These tests should runs in an isolated environment and use mock objects for external
- * dependencies. Resource access (such as file system, network, or databases) are not permitted.
- * Tests that interact with hardware, make binder calls, or that facilitate android instrumentation
- * should not use this annotation.
- *
- * <p>Note: This class replaces the deprecated Android platform size qualifier <a
- * href="http://developer.android.com/reference/android/test/suitebuilder/annotation/SmallTest.html">
- * android.test.suitebuilder.annotation.SmallTest</a> and is the recommended way to annotate tests
- * written with the AndroidX Test Library.
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.METHOD, ElementType.TYPE})
-public @interface SmallTest {}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/Suppress.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/Suppress.java
deleted file mode 100644
index 88e636c..0000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/Suppress.java
+++ /dev/null
@@ -1,36 +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 androidx.test.filters;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Use this annotation on test classes or test methods that should not be included in a test suite.
- * If the annotation appears on the class then no tests in that class will be included. If the
- * annotation appears only on a test method then only that method will be excluded.
- *
- * <p>Note: This class replaces the deprecated Android platform annotation <a
- * href="http://developer.android.com/reference/android/test/suitebuilder/annotation/Suppress.html">
- * android.test.suitebuilder.annotation.Suppress</a> and is the recommended way to suppress tests
- * written with the AndroidX Test Library.
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.METHOD, ElementType.TYPE})
-public @interface Suppress {}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/runner/AndroidJUnit4.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/runner/AndroidJUnit4.java
deleted file mode 100644
index e137939..0000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/runner/AndroidJUnit4.java
+++ /dev/null
@@ -1,24 +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 androidx.test.runner;
-
-import org.junit.runners.model.InitializationError;
-
-public class AndroidJUnit4 extends androidx.test.ext.junit.runners.AndroidJUnit4 {
- public AndroidJUnit4(Class<?> testClass) throws InitializationError {
- super(testClass);
- }
-}
diff --git a/tools/hoststubgen/hoststubgen/test-framework/Android.bp b/tools/hoststubgen/hoststubgen/test-framework/Android.bp
deleted file mode 100644
index 2b91cc1..0000000
--- a/tools/hoststubgen/hoststubgen/test-framework/Android.bp
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright (C) 2023 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-build = ["AndroidHostTest.bp"]
diff --git a/tools/hoststubgen/hoststubgen/test-framework/AndroidHostTest.bp b/tools/hoststubgen/hoststubgen/test-framework/AndroidHostTest.bp
deleted file mode 100644
index 1f8382a..0000000
--- a/tools/hoststubgen/hoststubgen/test-framework/AndroidHostTest.bp
+++ /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.
-
-// Add `build = ["AndroidHostTest.bp"]` to Android.bp to include this file.
-
-// Compile the test jar, using 2 rules.
-// 1. Build the test against the stub.
-java_library_host {
- name: "HostStubGenTest-framework-test-host-test-lib",
- defaults: ["hosttest-with-framework-all-hidden-api-test-lib-defaults"],
- srcs: [
- "src/**/*.java",
- ],
- static_libs: [
- "junit",
- "truth",
- "mockito",
-
- // http://cs/h/googleplex-android/platform/superproject/main/+/main:platform_testing/libraries/annotations/src/android/platform/test/annotations/
- "platform-test-annotations",
- "hoststubgen-annotations",
- ],
-}
-
-// 2. Link the above module with necessary runtime dependencies, so it can be executed stand-alone.
-java_test_host {
- name: "HostStubGenTest-framework-all-test-host-test",
- defaults: ["hosttest-with-framework-all-hidden-api-test-defaults"],
- static_libs: [
- "HostStubGenTest-framework-test-host-test-lib",
- ],
- test_suites: ["general-tests"],
-}
-
-// "Productionized" build rule.
-android_ravenwood_test {
- name: "HostStubGenTest-framework-test",
- srcs: [
- "src/**/*.java",
- ],
- static_libs: [
- "junit",
- "truth",
- "mockito",
- ],
-}
diff --git a/tools/hoststubgen/hoststubgen/test-framework/AndroidTest-host.xml b/tools/hoststubgen/hoststubgen/test-framework/AndroidTest-host.xml
deleted file mode 100644
index f35dcf6..0000000
--- a/tools/hoststubgen/hoststubgen/test-framework/AndroidTest-host.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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.
--->
-
-<!-- [Ravenwood] Copied from $ANDROID_BUILD_TOP/cts/hostsidetests/devicepolicy/AndroidTest.xml -->
-<configuration description="CtsContentTestCases host-side test">
- <option name="test-suite-tag" value="ravenwood" />
- <option name="config-descriptor:metadata" key="component" value="framework" />
- <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
- <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
- <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
- <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
-
- <test class="com.android.tradefed.testtype.IsolatedHostTest" >
- <option name="jar" value="HostStubGenTest-framework-all-test-host-test.jar" />
- </test>
-</configuration>
diff --git a/tools/hoststubgen/hoststubgen/test-framework/README.md b/tools/hoststubgen/hoststubgen/test-framework/README.md
deleted file mode 100644
index 26a9ad1..0000000
--- a/tools/hoststubgen/hoststubgen/test-framework/README.md
+++ /dev/null
@@ -1,22 +0,0 @@
-# HostStubGen: (obsolete) real framework test
-
-This directory contains tests against the actual framework.jar code. The tests were
-copied from somewhere else in the android tree. We use this directory to quickly run existing
-tests.
-
-This directory was used during the prototype phase, but now that we have real ravenwood tests,
-this directory is obsolete and should be deleted.
-
-## How to run
-
-- With `atest`. This is the proper way to run it, but it may fail due to atest's known problems.
-
-```
-$ atest HostStubGenTest-framework-all-test-host-test
-```
-
-- Advanced option: `run-test-without-atest.sh` runs the test without using `atest`
-
-```
-$ ./run-test-without-atest.sh
-```
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/test-framework/run-test-without-atest.sh b/tools/hoststubgen/hoststubgen/test-framework/run-test-without-atest.sh
deleted file mode 100755
index cfc06a1..0000000
--- a/tools/hoststubgen/hoststubgen/test-framework/run-test-without-atest.sh
+++ /dev/null
@@ -1,85 +0,0 @@
-#!/bin/bash
-# Copyright (C) 2023 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Run HostStubGenTest-framework-test-host-test directly with JUnit.
-# (without using atest.)
-
-source "${0%/*}"/../../common.sh
-
-
-# Options:
-# -v enable verbose log
-# -d enable debugger
-
-verbose=0
-debug=0
-while getopts "vd" opt; do
- case "$opt" in
- v) verbose=1 ;;
- d) debug=1 ;;
- esac
-done
-shift $(($OPTIND - 1))
-
-
-if (( $verbose )) ; then
- JAVA_OPTS="$JAVA_OPTS -verbose:class"
-fi
-
-if (( $debug )) ; then
- JAVA_OPTS="$JAVA_OPTS -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8700"
-fi
-
-#=======================================
-module=HostStubGenTest-framework-all-test-host-test
-module_jar=$ANDROID_BUILD_TOP/out/host/linux-x86/testcases/$module/$module.jar
-run m $module
-
-out=out
-
-rm -fr $out
-mkdir -p $out
-
-
-# Copy and extract the relevant jar files so we can look into them.
-run cp \
- $module_jar \
- $SOONG_INT/frameworks/base/tools/hoststubgen/hoststubgen/framework-all-hidden-api-host/linux_glibc_common/gen/*.jar \
- $out
-
-run extract $out/*.jar
-
-# Result is the number of failed tests.
-result=0
-
-
-# This suite runs all tests in the JAR.
-tests=(com.android.hoststubgen.hosthelper.HostTestSuite)
-
-# Uncomment this to run a specific test.
-# tests=(com.android.hoststubgen.frameworktest.LogTest)
-
-
-for class in ${tests[@]} ; do
- echo "Running $class ..."
-
- run cd "${module_jar%/*}"
- run $JAVA $JAVA_OPTS \
- -cp $module_jar \
- org.junit.runner.JUnitCore \
- $class || result=$(( $result + 1 ))
-done
-
-exit $result
diff --git a/tools/hoststubgen/hoststubgen/test-framework/src/com/android/hoststubgen/frameworktest/ArrayMapTest.java b/tools/hoststubgen/hoststubgen/test-framework/src/com/android/hoststubgen/frameworktest/ArrayMapTest.java
deleted file mode 100644
index 2c5949c..0000000
--- a/tools/hoststubgen/hoststubgen/test-framework/src/com/android/hoststubgen/frameworktest/ArrayMapTest.java
+++ /dev/null
@@ -1,472 +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.hoststubgen.frameworktest;
-
-// [ravewnwood] Copied from cts/, and commented out unsupported stuff.
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.util.ArrayMap;
-import android.util.Log;
-
-import org.junit.Test;
-
-import java.util.AbstractMap;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.NoSuchElementException;
-import java.util.Set;
-import java.util.function.BiFunction;
-
-/**
- * Some basic tests for {@link android.util.ArrayMap}.
- */
-public class ArrayMapTest {
- static final boolean DEBUG = false;
-
- private static boolean compare(Object v1, Object v2) {
- if (v1 == null) {
- return v2 == null;
- }
- if (v2 == null) {
- return false;
- }
- return v1.equals(v2);
- }
-
- private static void compareMaps(HashMap map, ArrayMap array) {
- if (map.size() != array.size()) {
- fail("Bad size: expected " + map.size() + ", got " + array.size());
- }
-
- Set<Entry> mapSet = map.entrySet();
- for (Map.Entry entry : mapSet) {
- Object expValue = entry.getValue();
- Object gotValue = array.get(entry.getKey());
- if (!compare(expValue, gotValue)) {
- fail("Bad value: expected " + expValue + ", got " + gotValue
- + " at key " + entry.getKey());
- }
- }
-
- for (int i = 0; i < array.size(); i++) {
- Object gotValue = array.valueAt(i);
- Object key = array.keyAt(i);
- Object expValue = map.get(key);
- if (!compare(expValue, gotValue)) {
- fail("Bad value: expected " + expValue + ", got " + gotValue
- + " at key " + key);
- }
- }
-
- if (map.entrySet().hashCode() != array.entrySet().hashCode()) {
- fail("Entry set hash codes differ: map=0x"
- + Integer.toHexString(map.entrySet().hashCode()) + " array=0x"
- + Integer.toHexString(array.entrySet().hashCode()));
- }
-
- if (!map.entrySet().equals(array.entrySet())) {
- fail("Failed calling equals on map entry set against array set");
- }
-
- if (!array.entrySet().equals(map.entrySet())) {
- fail("Failed calling equals on array entry set against map set");
- }
-
- if (map.keySet().hashCode() != array.keySet().hashCode()) {
- fail("Key set hash codes differ: map=0x"
- + Integer.toHexString(map.keySet().hashCode()) + " array=0x"
- + Integer.toHexString(array.keySet().hashCode()));
- }
-
- if (!map.keySet().equals(array.keySet())) {
- fail("Failed calling equals on map key set against array set");
- }
-
- if (!array.keySet().equals(map.keySet())) {
- fail("Failed calling equals on array key set against map set");
- }
-
- if (!map.keySet().containsAll(array.keySet())) {
- fail("Failed map key set contains all of array key set");
- }
-
- if (!array.keySet().containsAll(map.keySet())) {
- fail("Failed array key set contains all of map key set");
- }
-
- if (!array.containsAll(map.keySet())) {
- fail("Failed array contains all of map key set");
- }
-
- if (!map.entrySet().containsAll(array.entrySet())) {
- fail("Failed map entry set contains all of array entry set");
- }
-
- if (!array.entrySet().containsAll(map.entrySet())) {
- fail("Failed array entry set contains all of map entry set");
- }
- }
-
- private static void validateArrayMap(ArrayMap array) {
- Set<Map.Entry> entrySet = array.entrySet();
- int index = 0;
- Iterator<Entry> entryIt = entrySet.iterator();
- while (entryIt.hasNext()) {
- Map.Entry entry = entryIt.next();
- Object value = entry.getKey();
- Object realValue = array.keyAt(index);
- if (!compare(realValue, value)) {
- fail("Bad array map entry set: expected key " + realValue
- + ", got " + value + " at index " + index);
- }
- value = entry.getValue();
- realValue = array.valueAt(index);
- if (!compare(realValue, value)) {
- fail("Bad array map entry set: expected value " + realValue
- + ", got " + value + " at index " + index);
- }
- index++;
- }
-
- index = 0;
- Set keySet = array.keySet();
- Iterator keyIt = keySet.iterator();
- while (keyIt.hasNext()) {
- Object value = keyIt.next();
- Object realValue = array.keyAt(index);
- if (!compare(realValue, value)) {
- fail("Bad array map key set: expected key " + realValue
- + ", got " + value + " at index " + index);
- }
- index++;
- }
-
- index = 0;
- Collection valueCol = array.values();
- Iterator valueIt = valueCol.iterator();
- while (valueIt.hasNext()) {
- Object value = valueIt.next();
- Object realValue = array.valueAt(index);
- if (!compare(realValue, value)) {
- fail("Bad array map value col: expected value " + realValue
- + ", got " + value + " at index " + index);
- }
- index++;
- }
- }
-
- private static void dump(Map map, ArrayMap array) {
- Log.e("test", "HashMap of " + map.size() + " entries:");
- Set<Map.Entry> mapSet = map.entrySet();
- for (Map.Entry entry : mapSet) {
- Log.e("test", " " + entry.getKey() + " -> " + entry.getValue());
- }
- Log.e("test", "ArrayMap of " + array.size() + " entries:");
- for (int i = 0; i < array.size(); i++) {
- Log.e("test", " " + array.keyAt(i) + " -> " + array.valueAt(i));
- }
- }
-
- private static void dump(ArrayMap map1, ArrayMap map2) {
- Log.e("test", "ArrayMap of " + map1.size() + " entries:");
- for (int i = 0; i < map1.size(); i++) {
- Log.e("test", " " + map1.keyAt(i) + " -> " + map1.valueAt(i));
- }
- Log.e("test", "ArrayMap of " + map2.size() + " entries:");
- for (int i = 0; i < map2.size(); i++) {
- Log.e("test", " " + map2.keyAt(i) + " -> " + map2.valueAt(i));
- }
- }
-
- @Test
- public void testCopyArrayMap() {
- // map copy constructor test
- ArrayMap newMap = new ArrayMap<Integer, String>();
- for (int i = 0; i < 10; ++i) {
- newMap.put(i, String.valueOf(i));
- }
- ArrayMap mapCopy = new ArrayMap(newMap);
- if (!compare(mapCopy, newMap)) {
- String msg = "ArrayMap copy constructor failure: expected " +
- newMap + ", got " + mapCopy;
- Log.e("test", msg);
- dump(newMap, mapCopy);
- fail(msg);
- return;
- }
- }
-
- @Test
- public void testEqualsArrayMap() {
- ArrayMap<Integer, String> map1 = new ArrayMap<>();
- ArrayMap<Integer, String> map2 = new ArrayMap<>();
- HashMap<Integer, String> map3 = new HashMap<>();
- if (!compare(map1, map2) || !compare(map1, map3) || !compare(map3, map2)) {
- fail("ArrayMap equals failure for empty maps " + map1 + ", " +
- map2 + ", " + map3);
- }
-
- for (int i = 0; i < 10; ++i) {
- String value = String.valueOf(i);
- map1.put(i, value);
- map2.put(i, value);
- map3.put(i, value);
- }
- if (!compare(map1, map2) || !compare(map1, map3) || !compare(map3, map2)) {
- fail("ArrayMap equals failure for populated maps " + map1 + ", " +
- map2 + ", " + map3);
- }
-
- map1.remove(0);
- if (compare(map1, map2) || compare(map1, map3) || compare(map3, map1)) {
- fail("ArrayMap equals failure for map size " + map1 + ", " +
- map2 + ", " + map3);
- }
-
- map1.put(0, "-1");
- if (compare(map1, map2) || compare(map1, map3) || compare(map3, map1)) {
- fail("ArrayMap equals failure for map contents " + map1 + ", " +
- map2 + ", " + map3);
- }
- }
-
- private static void checkEntrySetToArray(ArrayMap<?, ?> testMap) {
- try {
- testMap.entrySet().toArray();
- fail();
- } catch (UnsupportedOperationException expected) {
- }
-
- try {
- Map.Entry<?, ?>[] entries = new Map.Entry[20];
- testMap.entrySet().toArray(entries);
- fail();
- } catch (UnsupportedOperationException expected) {
- }
- }
-
- // http://b/32294038, Test ArrayMap.entrySet().toArray()
- @Test
- public void testEntrySetArray() {
- // Create
- ArrayMap<Integer, String> testMap = new ArrayMap<>();
-
- // Test empty
- checkEntrySetToArray(testMap);
-
- // Test non-empty
- for (int i = 0; i < 10; ++i) {
- testMap.put(i, String.valueOf(i));
- }
- checkEntrySetToArray(testMap);
- }
-
- @Test
- public void testCanNotIteratePastEnd_entrySetIterator() {
- Map<String, String> map = new ArrayMap<>();
- map.put("key 1", "value 1");
- map.put("key 2", "value 2");
- Set<Map.Entry<String, String>> expectedEntriesToIterate = new HashSet<>(Arrays.asList(
- entryOf("key 1", "value 1"),
- entryOf("key 2", "value 2")
- ));
- Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
-
- // Assert iteration over the expected two entries in any order
- assertTrue(iterator.hasNext());
- Map.Entry<String, String> firstEntry = copyOf(iterator.next());
- assertTrue(expectedEntriesToIterate.remove(firstEntry));
-
- assertTrue(iterator.hasNext());
- Map.Entry<String, String> secondEntry = copyOf(iterator.next());
- assertTrue(expectedEntriesToIterate.remove(secondEntry));
-
- assertFalse(iterator.hasNext());
-
- try {
- iterator.next();
- fail();
- } catch (NoSuchElementException expected) {
- }
- }
-
- private static <K, V> Map.Entry<K, V> entryOf(K key, V value) {
- return new AbstractMap.SimpleEntry<>(key, value);
- }
-
- private static <K, V> Map.Entry<K, V> copyOf(Map.Entry<K, V> entry) {
- return entryOf(entry.getKey(), entry.getValue());
- }
-
- @Test
- public void testCanNotIteratePastEnd_keySetIterator() {
- Map<String, String> map = new ArrayMap<>();
- map.put("key 1", "value 1");
- map.put("key 2", "value 2");
- Set<String> expectedKeysToIterate = new HashSet<>(Arrays.asList("key 1", "key 2"));
- Iterator<String> iterator = map.keySet().iterator();
-
- // Assert iteration over the expected two keys in any order
- assertTrue(iterator.hasNext());
- String firstKey = iterator.next();
- assertTrue(expectedKeysToIterate.remove(firstKey));
-
- assertTrue(iterator.hasNext());
- String secondKey = iterator.next();
- assertTrue(expectedKeysToIterate.remove(secondKey));
-
- assertFalse(iterator.hasNext());
-
- try {
- iterator.next();
- fail();
- } catch (NoSuchElementException expected) {
- }
- }
-
- @Test
- public void testCanNotIteratePastEnd_valuesIterator() {
- Map<String, String> map = new ArrayMap<>();
- map.put("key 1", "value 1");
- map.put("key 2", "value 2");
- Set<String> expectedValuesToIterate = new HashSet<>(Arrays.asList("value 1", "value 2"));
- Iterator<String> iterator = map.values().iterator();
-
- // Assert iteration over the expected two values in any order
- assertTrue(iterator.hasNext());
- String firstValue = iterator.next();
- assertTrue(expectedValuesToIterate.remove(firstValue));
-
- assertTrue(iterator.hasNext());
- String secondValue = iterator.next();
- assertTrue(expectedValuesToIterate.remove(secondValue));
-
- assertFalse(iterator.hasNext());
-
- try {
- iterator.next();
- fail();
- } catch (NoSuchElementException expected) {
- }
- }
-
- @Test
- public void testForEach() {
- ArrayMap<String, Integer> map = new ArrayMap<>();
-
- for (int i = 0; i < 50; ++i) {
- map.put(Integer.toString(i), i * 10);
- }
-
- // Make sure forEach goes through all of the elements.
- HashMap<String, Integer> seen = new HashMap<>();
- map.forEach(seen::put);
- compareMaps(seen, map);
- }
-
- /**
- * The entrySet Iterator returns itself from each call to {@code next()}. This is unusual
- * behavior for {@link Iterator#next()}; this test ensures that any future change to this
- * behavior is deliberate.
- */
- @Test
- public void testUnusualBehavior_eachEntryIsSameAsIterator_entrySetIterator() {
- Map<String, String> map = new ArrayMap<>();
- map.put("key 1", "value 1");
- map.put("key 2", "value 2");
- Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
-
- assertSame(iterator, iterator.next());
- assertSame(iterator, iterator.next());
- }
-
- @SuppressWarnings("SelfEquals")
- @Test
- public void testUnusualBehavior_equalsThrowsAfterRemove_entrySetIterator() {
- Map<String, String> map = new ArrayMap<>();
- map.put("key 1", "value 1");
- map.put("key 2", "value 2");
- Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
- iterator.next();
- iterator.remove();
- try {
- iterator.equals(iterator);
- fail();
- } catch (IllegalStateException expected) {
- }
- }
-
- private static <T> void assertEqualsBothWays(T a, T b) {
- assertEquals(a, b);
- assertEquals(b, a);
- assertEquals(a.hashCode(), b.hashCode());
- }
-
- @Test
- public void testRemoveAll() {
- final ArrayMap<Integer, String> map = new ArrayMap<>();
- for (Integer i : Arrays.asList(0, 1, 2, 3, 4, 5)) {
- map.put(i, i.toString());
- }
-
- final ArrayMap<Integer, String> expectedMap = new ArrayMap<>();
- for (Integer i : Arrays.asList(2, 4)) {
- expectedMap.put(i, String.valueOf(i));
- }
- map.removeAll(Arrays.asList(0, 1, 3, 5, 6));
- if (!compare(map, expectedMap)) {
- fail("ArrayMap removeAll failure, expect " + expectedMap + ", but " + map);
- }
-
- map.removeAll(Collections.emptyList());
- if (!compare(map, expectedMap)) {
- fail("ArrayMap removeAll failure for empty maps, expect " + expectedMap + ", but " +
- map);
- }
-
- map.removeAll(Arrays.asList(2, 4));
- if (!map.isEmpty()) {
- fail("ArrayMap removeAll failure, expect empty, but " + map);
- }
- }
-
- @Test
- public void testReplaceAll() {
- final ArrayMap<Integer, Integer> map = new ArrayMap<>();
- final ArrayMap<Integer, Integer> expectedMap = new ArrayMap<>();
- final BiFunction<Integer, Integer, Integer> function = (k, v) -> 2 * v;
- for (Integer i : Arrays.asList(0, 1, 2, 3, 4, 5)) {
- map.put(i, i);
- expectedMap.put(i, 2 * i);
- }
-
- map.replaceAll(function);
- if (!compare(map, expectedMap)) {
- fail("ArrayMap replaceAll failure, expect " + expectedMap + ", but " + map);
- }
- }
-}
diff --git a/tools/hoststubgen/hoststubgen/test-framework/src/com/android/hoststubgen/frameworktest/LogTest.java b/tools/hoststubgen/hoststubgen/test-framework/src/com/android/hoststubgen/frameworktest/LogTest.java
deleted file mode 100644
index 3e33b54..0000000
--- a/tools/hoststubgen/hoststubgen/test-framework/src/com/android/hoststubgen/frameworktest/LogTest.java
+++ /dev/null
@@ -1,41 +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.hoststubgen.frameworktest;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.util.Log;
-
-import org.junit.Test;
-
-/**
- * Some basic tests for {@link android.util.Log}.
- */
-public class LogTest {
- @Test
- public void testBasicLogging() {
- Log.v("TAG", "Test v log");
- Log.d("TAG", "Test d log");
- Log.i("TAG", "Test i log");
- Log.w("TAG", "Test w log");
- Log.e("TAG", "Test e log");
- }
-
- @Test
- public void testNativeMethods() {
- assertThat(Log.isLoggable("mytag", Log.INFO)).isTrue();
- }
-}
diff --git a/tools/hoststubgen/scripts/build-framework-hostside-jars-and-extract.sh b/tools/hoststubgen/scripts/build-framework-hostside-jars-and-extract.sh
deleted file mode 100755
index 7268123..0000000
--- a/tools/hoststubgen/scripts/build-framework-hostside-jars-and-extract.sh
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/bin/bash
-# Copyright (C) 2023 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-# Script to build `framework-host-stub` and `framework-host-impl`, and copy the
-# generated jars to $out, and unzip them. Useful for looking into the generated files.
-
-source "${0%/*}"/../common.sh
-
-out=framework-all-stub-out
-
-rm -fr $out
-mkdir -p $out
-
-# Build the jars with `m`.
-run m framework-all-hidden-api-host
-
-# Copy the jar to out/ and extract them.
-run cp \
- $SOONG_INT/frameworks/base/tools/hoststubgen/hoststubgen/framework-all-hidden-api-host/linux_glibc_common/gen/* \
- $out
-
-extract $out/*.jar
diff --git a/tools/hoststubgen/scripts/run-all-tests.sh b/tools/hoststubgen/scripts/run-all-tests.sh
index 222c874..a6847ae 100755
--- a/tools/hoststubgen/scripts/run-all-tests.sh
+++ b/tools/hoststubgen/scripts/run-all-tests.sh
@@ -22,7 +22,6 @@
# These tests are known to pass.
READY_TEST_MODULES=(
- HostStubGenTest-framework-all-test-host-test
hoststubgen-test-tiny-test
CtsUtilTestCasesRavenwood
CtsOsTestCasesRavenwood # This one uses native sustitution, so let's run it too.
@@ -30,7 +29,6 @@
MUST_BUILD_MODULES=(
"${NOT_READY_TEST_MODULES[*]}"
- HostStubGenTest-framework-test
)
# First, build all the test / etc modules. This shouldn't fail.
@@ -44,11 +42,8 @@
# files, and they may fail when something changes in the build system.
run ./hoststubgen/test-tiny-framework/diff-and-update-golden.sh
-run ./hoststubgen/test-framework/run-test-without-atest.sh
-
run ./hoststubgen/test-tiny-framework/run-test-manually.sh
run atest $ATEST_ARGS tiny-framework-dump-test
-run ./scripts/build-framework-hostside-jars-and-extract.sh
# This script is already broken on goog/master
# run ./scripts/build-framework-hostside-jars-without-genrules.sh