Merge "Fix missing viewer config dump" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 6f8a189..c768121 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -100,7 +100,6 @@
"framework-jobscheduler-job.flags-aconfig-java",
"framework_graphics_flags_java_lib",
"hwui_flags_java_lib",
- "interaction_jank_monitor_flags_lib",
"libcore_exported_aconfig_flags_lib",
"libgui_flags_java_lib",
"power_flags_lib",
@@ -1579,17 +1578,3 @@
aconfig_declarations: "dropbox_flags",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
-
-// Zero Jank
-aconfig_declarations {
- name: "interaction_jank_monitor_flags",
- package: "com.android.internal.jank",
- container: "system",
- srcs: ["core/java/com/android/internal/jank/flags.aconfig"],
-}
-
-java_aconfig_library {
- name: "interaction_jank_monitor_flags_lib",
- aconfig_declarations: "interaction_jank_monitor_flags",
- defaults: ["framework-minus-apex-aconfig-java-defaults"],
-}
diff --git a/apct-tests/perftests/protolog/Android.bp b/apct-tests/perftests/tracing/Android.bp
similarity index 100%
rename from apct-tests/perftests/protolog/Android.bp
rename to apct-tests/perftests/tracing/Android.bp
diff --git a/apct-tests/perftests/protolog/AndroidManifest.xml b/apct-tests/perftests/tracing/AndroidManifest.xml
similarity index 100%
rename from apct-tests/perftests/protolog/AndroidManifest.xml
rename to apct-tests/perftests/tracing/AndroidManifest.xml
diff --git a/apct-tests/perftests/protolog/AndroidTest.xml b/apct-tests/perftests/tracing/AndroidTest.xml
similarity index 100%
rename from apct-tests/perftests/protolog/AndroidTest.xml
rename to apct-tests/perftests/tracing/AndroidTest.xml
diff --git a/apct-tests/perftests/protolog/OWNERS b/apct-tests/perftests/tracing/OWNERS
similarity index 100%
rename from apct-tests/perftests/protolog/OWNERS
rename to apct-tests/perftests/tracing/OWNERS
diff --git a/apct-tests/perftests/protolog/src/com/android/internal/protolog/ProtoLogPerfTest.java b/apct-tests/perftests/tracing/src/com/android/internal/protolog/ProtoLogPerfTest.java
similarity index 60%
rename from apct-tests/perftests/protolog/src/com/android/internal/protolog/ProtoLogPerfTest.java
rename to apct-tests/perftests/tracing/src/com/android/internal/protolog/ProtoLogPerfTest.java
index 92dd9be..f4759b8 100644
--- a/apct-tests/perftests/protolog/src/com/android/internal/protolog/ProtoLogPerfTest.java
+++ b/apct-tests/perftests/tracing/src/com/android/internal/protolog/ProtoLogPerfTest.java
@@ -15,43 +15,54 @@
*/
package com.android.internal.protolog;
-import android.perftests.utils.BenchmarkState;
-import android.perftests.utils.PerfStatusReporter;
+import android.app.Activity;
+import android.os.Bundle;
+import android.perftests.utils.Stats;
+
+import androidx.test.InstrumentationRegistry;
import com.android.internal.protolog.common.IProtoLogGroup;
import com.android.internal.protolog.common.LogLevel;
import org.junit.Before;
import org.junit.BeforeClass;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
-import java.util.Arrays;
+import java.util.ArrayList;
import java.util.Collection;
@RunWith(Parameterized.class)
public class ProtoLogPerfTest {
- @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
-
- @Parameters(name="logToProto_{0}_logToLogcat_{1}")
- public static Collection<Object[]> params() {
- return Arrays.asList(new Object[][] {
- { true, true },
- { true, false },
- { false, true },
- { false, false }
- });
- }
-
private final boolean mLogToProto;
private final boolean mLogToLogcat;
- public ProtoLogPerfTest(boolean logToProto, boolean logToLogcat) {
- mLogToProto = logToProto;
- mLogToLogcat = logToLogcat;
+ /**
+ * Generates the parameters used for this test class
+ */
+ @Parameters(name = "LOG_TO_{0}")
+ public static Collection<Object[]> params() {
+ var params = new ArrayList<Object[]>();
+
+ for (LogTo logTo : LogTo.values()) {
+ params.add(new Object[] { logTo });
+ }
+
+ return params;
+ }
+
+ public ProtoLogPerfTest(LogTo logTo) {
+ mLogToProto = switch (logTo) {
+ case ALL, PROTO_ONLY -> true;
+ case LOGCAT_ONLY, NONE -> false;
+ };
+
+ mLogToLogcat = switch (logTo) {
+ case ALL, LOGCAT_ONLY -> true;
+ case PROTO_ONLY, NONE -> false;
+ };
}
@BeforeClass
@@ -66,11 +77,11 @@
}
@Test
- public void logProcessedProtoLogMessageWithoutArgs() {
+ public void log_Processed_NoArgs() {
final var protoLog = ProtoLog.getSingleInstance();
+ final var perfMonitor = new PerfMonitor();
- BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- while (state.keepRunning()) {
+ while (perfMonitor.keepRunning()) {
protoLog.log(
LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 123,
0, (Object[]) null);
@@ -78,11 +89,11 @@
}
@Test
- public void logProcessedProtoLogMessageWithArgs() {
+ public void log_Processed_WithArgs() {
final var protoLog = ProtoLog.getSingleInstance();
+ final var perfMonitor = new PerfMonitor();
- BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- while (state.keepRunning()) {
+ while (perfMonitor.keepRunning()) {
protoLog.log(
LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 123,
0b1110101001010100,
@@ -91,18 +102,58 @@
}
@Test
- public void logNonProcessedProtoLogMessageWithNoArgs() {
- BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- while (state.keepRunning()) {
+ public void log_Unprocessed_NoArgs() {
+ final var perfMonitor = new PerfMonitor();
+
+ while (perfMonitor.keepRunning()) {
ProtoLog.d(TestProtoLogGroup.TEST_GROUP, "Test message");
}
}
@Test
- public void logNonProcessedProtoLogMessageWithArgs() {
- BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- while (state.keepRunning()) {
- ProtoLog.d(TestProtoLogGroup.TEST_GROUP, "Test messag %s, %d, %b", "arg1", 2, true);
+ public void log_Unprocessed_WithArgs() {
+ final var perfMonitor = new PerfMonitor();
+
+ while (perfMonitor.keepRunning()) {
+ ProtoLog.d(TestProtoLogGroup.TEST_GROUP, "Test message %s, %d, %b", "arg1", 2, true);
+ }
+ }
+
+ private class PerfMonitor {
+ private int mIteration = 0;
+
+ private static final int WARM_UP_ITERATIONS = 10;
+ private static final int ITERATIONS = 1000;
+
+ private final ArrayList<Long> mResults = new ArrayList<>();
+
+ private long mStartTimeNs;
+
+ public boolean keepRunning() {
+ final long currentTime = System.nanoTime();
+
+ mIteration++;
+
+ if (mIteration > ITERATIONS) {
+ reportResults();
+ return false;
+ }
+
+ if (mIteration > WARM_UP_ITERATIONS) {
+ mResults.add(currentTime - mStartTimeNs);
+ }
+
+ mStartTimeNs = System.nanoTime();
+ return true;
+ }
+
+ public void reportResults() {
+ final var stats = new Stats(mResults);
+
+ Bundle status = new Bundle();
+ status.putLong("protologging_time_mean_ns", (long) stats.getMean());
+ status.putLong("protologging_time_median_ns", (long) stats.getMedian());
+ InstrumentationRegistry.getInstrumentation().sendStatus(Activity.RESULT_OK, status);
}
}
@@ -168,4 +219,11 @@
return ordinal();
}
}
+
+ private enum LogTo {
+ PROTO_ONLY,
+ LOGCAT_ONLY,
+ ALL,
+ NONE,
+ }
}
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index 613b784..e5389b4 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -65,3 +65,13 @@
description: "Remove started user if user will be stopped due to user switch"
bug: "321598070"
}
+
+flag {
+ name: "use_correct_process_state_for_logging"
+ namespace: "backstage_power"
+ description: "Use correct process state for statsd logging"
+ bug: "361308212"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
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 d65a66c..be8e304 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -473,6 +473,14 @@
mInitialDownloadedBytesFromCalling = TrafficStats.getUidRxBytes(job.getUid());
mInitialUploadedBytesFromCalling = TrafficStats.getUidTxBytes(job.getUid());
+ int procState = mService.getUidProcState(job.getUid());
+ if (Flags.useCorrectProcessStateForLogging()
+ && procState > ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND) {
+ // Try to get the latest proc state from AMS, there might be some delay
+ // for the proc states worse than TRANSIENT_BACKGROUND.
+ procState = mActivityManagerInternal.getUidProcessState(job.getUid());
+ }
+
FrameworkStatsLog.write(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED,
job.isProxyJob() ? new int[]{sourceUid, job.getUid()} : new int[]{sourceUid},
// Given that the source tag is set by the calling app, it should be connected
@@ -517,7 +525,7 @@
job.getEstimatedNetworkDownloadBytes(),
job.getEstimatedNetworkUploadBytes(),
job.getWorkCount(),
- ActivityManager.processStateAmToProto(mService.getUidProcState(job.getUid())),
+ ActivityManager.processStateAmToProto(procState),
job.getNamespaceHash(),
/* system_measured_source_download_bytes */ 0,
/* system_measured_source_upload_bytes */ 0,
@@ -1528,6 +1536,13 @@
mJobPackageTracker.noteInactive(completedJob,
loggingInternalStopReason, loggingDebugReason);
final int sourceUid = completedJob.getSourceUid();
+ int procState = mService.getUidProcState(completedJob.getUid());
+ if (Flags.useCorrectProcessStateForLogging()
+ && procState > ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND) {
+ // Try to get the latest proc state from AMS, there might be some delay
+ // for the proc states worse than TRANSIENT_BACKGROUND.
+ procState = mActivityManagerInternal.getUidProcessState(completedJob.getUid());
+ }
FrameworkStatsLog.write(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED,
completedJob.isProxyJob()
? new int[]{sourceUid, completedJob.getUid()} : new int[]{sourceUid},
@@ -1573,7 +1588,7 @@
completedJob.getEstimatedNetworkUploadBytes(),
completedJob.getWorkCount(),
ActivityManager
- .processStateAmToProto(mService.getUidProcState(completedJob.getUid())),
+ .processStateAmToProto(procState),
completedJob.getNamespaceHash(),
TrafficStats.getUidRxBytes(completedJob.getSourceUid())
- mInitialDownloadedBytesFromSource,
diff --git a/api/Android.bp b/api/Android.bp
index 341be3d53..533f9f6 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -87,6 +87,7 @@
"framework-permission",
"framework-permission-s",
"framework-profiling",
+ "framework-photopicker",
"framework-scheduling",
"framework-sdkextensions",
"framework-statsd",
diff --git a/core/api/current.txt b/core/api/current.txt
index 2565d6f..4e6dacf 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -141,7 +141,7 @@
field public static final String MANAGE_DEVICE_POLICY_APPS_CONTROL = "android.permission.MANAGE_DEVICE_POLICY_APPS_CONTROL";
field public static final String MANAGE_DEVICE_POLICY_APP_RESTRICTIONS = "android.permission.MANAGE_DEVICE_POLICY_APP_RESTRICTIONS";
field public static final String MANAGE_DEVICE_POLICY_APP_USER_DATA = "android.permission.MANAGE_DEVICE_POLICY_APP_USER_DATA";
- field @FlaggedApi("android.app.admin.flags.assist_content_user_restriction_enabled") public static final String MANAGE_DEVICE_POLICY_ASSIST_CONTENT = "android.permission.MANAGE_DEVICE_POLICY_ASSIST_CONTENT";
+ field public static final String MANAGE_DEVICE_POLICY_ASSIST_CONTENT = "android.permission.MANAGE_DEVICE_POLICY_ASSIST_CONTENT";
field public static final String MANAGE_DEVICE_POLICY_AUDIO_OUTPUT = "android.permission.MANAGE_DEVICE_POLICY_AUDIO_OUTPUT";
field public static final String MANAGE_DEVICE_POLICY_AUTOFILL = "android.permission.MANAGE_DEVICE_POLICY_AUTOFILL";
field public static final String MANAGE_DEVICE_POLICY_BACKUP_SERVICE = "android.permission.MANAGE_DEVICE_POLICY_BACKUP_SERVICE";
@@ -169,7 +169,7 @@
field public static final String MANAGE_DEVICE_POLICY_LOCK = "android.permission.MANAGE_DEVICE_POLICY_LOCK";
field public static final String MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS = "android.permission.MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS";
field public static final String MANAGE_DEVICE_POLICY_LOCK_TASK = "android.permission.MANAGE_DEVICE_POLICY_LOCK_TASK";
- field @FlaggedApi("android.app.admin.flags.esim_management_enabled") public static final String MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS = "android.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS";
+ field public static final String MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS = "android.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS";
field public static final String MANAGE_DEVICE_POLICY_METERED_DATA = "android.permission.MANAGE_DEVICE_POLICY_METERED_DATA";
field public static final String MANAGE_DEVICE_POLICY_MICROPHONE = "android.permission.MANAGE_DEVICE_POLICY_MICROPHONE";
field @FlaggedApi("android.app.admin.flags.dedicated_device_control_api_enabled") public static final String MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE = "android.permission.MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE";
@@ -8081,7 +8081,7 @@
method public CharSequence getStartUserSessionMessage(@NonNull android.content.ComponentName);
method @Deprecated public boolean getStorageEncryption(@Nullable android.content.ComponentName);
method public int getStorageEncryptionStatus();
- method @FlaggedApi("android.app.admin.flags.esim_management_enabled") @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS) public java.util.Set<java.lang.Integer> getSubscriptionIds();
+ method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS) public java.util.Set<java.lang.Integer> getSubscriptionIds();
method @Nullable public android.app.admin.SystemUpdatePolicy getSystemUpdatePolicy();
method @Nullable public android.os.PersistableBundle getTransferOwnershipBundle();
method @Nullable public java.util.List<android.os.PersistableBundle> getTrustAgentConfiguration(@Nullable android.content.ComponentName, @NonNull android.content.ComponentName);
@@ -8756,6 +8756,8 @@
method public int getResultCode();
method @NonNull public android.app.appsearch.GenericDocument getResultDocument();
method public boolean isSuccess();
+ method @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") @NonNull public static android.app.appfunctions.ExecuteAppFunctionResponse newFailure(int, @Nullable String, @Nullable android.os.Bundle);
+ method @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") @NonNull public static android.app.appfunctions.ExecuteAppFunctionResponse newSuccess(@NonNull android.app.appsearch.GenericDocument, @Nullable android.os.Bundle);
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.appfunctions.ExecuteAppFunctionResponse> CREATOR;
field public static final String PROPERTY_RETURN_VALUE = "returnValue";
@@ -8767,13 +8769,6 @@
field public static final int RESULT_TIMED_OUT = 5; // 0x5
}
- @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public static final class ExecuteAppFunctionResponse.Builder {
- ctor public ExecuteAppFunctionResponse.Builder(@NonNull android.app.appsearch.GenericDocument);
- ctor public ExecuteAppFunctionResponse.Builder(int, @NonNull String);
- method @NonNull public android.app.appfunctions.ExecuteAppFunctionResponse build();
- method @NonNull public android.app.appfunctions.ExecuteAppFunctionResponse.Builder setExtras(@NonNull android.os.Bundle);
- }
-
}
package android.app.assist {
@@ -34116,7 +34111,7 @@
field public static final String DISALLOW_AIRPLANE_MODE = "no_airplane_mode";
field public static final String DISALLOW_AMBIENT_DISPLAY = "no_ambient_display";
field public static final String DISALLOW_APPS_CONTROL = "no_control_apps";
- field @FlaggedApi("android.app.admin.flags.assist_content_user_restriction_enabled") public static final String DISALLOW_ASSIST_CONTENT = "no_assist_content";
+ field public static final String DISALLOW_ASSIST_CONTENT = "no_assist_content";
field public static final String DISALLOW_AUTOFILL = "no_autofill";
field public static final String DISALLOW_BLUETOOTH = "no_bluetooth";
field public static final String DISALLOW_BLUETOOTH_SHARING = "no_bluetooth_sharing";
@@ -34166,7 +34161,7 @@
field public static final String DISALLOW_SHARE_INTO_MANAGED_PROFILE = "no_sharing_into_profile";
field public static final String DISALLOW_SHARE_LOCATION = "no_share_location";
field public static final String DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI = "no_sharing_admin_configured_wifi";
- field @FlaggedApi("android.app.admin.flags.esim_management_enabled") public static final String DISALLOW_SIM_GLOBALLY = "no_sim_globally";
+ field public static final String DISALLOW_SIM_GLOBALLY = "no_sim_globally";
field public static final String DISALLOW_SMS = "no_sms";
field public static final String DISALLOW_SYSTEM_ERROR_DIALOGS = "no_system_error_dialogs";
field @FlaggedApi("com.android.net.thread.platform.flags.thread_user_restriction_enabled") public static final String DISALLOW_THREAD_NETWORK = "no_thread_network";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index f26522b..3637ca7 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -6746,6 +6746,14 @@
field public static final int STATUS_OK = 0; // 0x0
}
+ @FlaggedApi("android.media.soundtrigger.generic_model_api") public static final class SoundTrigger.GenericSoundModel extends android.hardware.soundtrigger.SoundTrigger.SoundModel implements android.os.Parcelable {
+ ctor public SoundTrigger.GenericSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], int);
+ ctor public SoundTrigger.GenericSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[]);
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.GenericSoundModel> CREATOR;
+ }
+
public static final class SoundTrigger.Keyphrase implements android.os.Parcelable {
ctor public SoundTrigger.Keyphrase(int, int, @NonNull java.util.Locale, @NonNull String, @Nullable int[]);
method public int describeContents();
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 21396a1..8fd3326 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -7459,15 +7459,15 @@
}
/**
- * Similar to {@link #onOpChanged(String, String, int)} but includes the device for which
- * the op mode has changed.
+ * Similar to {@link #onOpChanged(String, String)} but includes user and the device for
+ * which the op mode has changed.
*
* <p> Implement this method if callbacks are required on all devices.
* If not implemented explicitly, the default implementation will notify for op changes
- * on the default device {@link VirtualDeviceManager#PERSISTENT_DEVICE_ID_DEFAULT} only.
+ * on the default device only.
*
- * <p> If implemented, {@link #onOpChanged(String, String, int)}
- * will not be called automatically.
+ * <p> If implemented, {@link #onOpChanged(String, String)} will not be called
+ * automatically.
*
* @param op The Op that changed.
* @param packageName Package of the app whose Op changed.
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 8b3ee24..e44e776 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -31,6 +31,7 @@
import android.app.ambientcontext.AmbientContextManager;
import android.app.ambientcontext.IAmbientContextManager;
import android.app.appfunctions.AppFunctionManager;
+import android.app.appfunctions.AppFunctionManagerConfiguration;
import android.app.appfunctions.IAppFunctionManager;
import android.app.appsearch.AppSearchManagerFrameworkInitializer;
import android.app.blob.BlobStoreManagerFrameworkInitializer;
@@ -937,8 +938,10 @@
@Override
public AppFunctionManager createService(ContextImpl ctx)
throws ServiceNotFoundException {
+ if (!AppFunctionManagerConfiguration.isSupported(ctx)) {
+ return null;
+ }
IAppFunctionManager service;
- //TODO(b/357551503): If the feature not present avoid look up every time
service = IAppFunctionManager.Stub.asInterface(
ServiceManager.getServiceOrThrow(Context.APP_FUNCTION_SERVICE));
return new AppFunctionManager(service, ctx.getOuterContext());
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 0fc77f0..d31d8f2 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -56,7 +56,6 @@
import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
import static android.app.admin.flags.Flags.FLAG_DEVICE_POLICY_SIZE_TRACKING_INTERNAL_BUG_FIX_ENABLED;
import static android.app.admin.flags.Flags.FLAG_DEVICE_THEFT_API_ENABLED;
-import static android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED;
import static android.app.admin.flags.Flags.FLAG_DEVICE_POLICY_SIZE_TRACKING_ENABLED;
import static android.app.admin.flags.Flags.FLAG_HEADLESS_DEVICE_OWNER_PROVISIONING_FIX_ENABLED;
import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
@@ -17742,7 +17741,6 @@
* @throws SecurityException if the caller is not authorized to call this method.
* @return ids of all managed subscriptions currently downloaded by an admin on the device.
*/
- @FlaggedApi(FLAG_ESIM_MANAGEMENT_ENABLED)
@RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS)
@NonNull
public Set<Integer> getSubscriptionIds() {
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index f2861fe..edbbd5b 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -105,6 +105,14 @@
bug: "289520697"
}
+flag {
+ name: "coexistence_migration_for_supervision_enabled"
+ is_exported: true
+ namespace: "enterprise"
+ description: "Migrate existing APIs that are used by supervision (Kids Module) to be coexistable."
+ bug: "356894721"
+}
+
# Fully rolled out and must not be used.
flag {
name: "security_log_v2_enabled"
diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java
index b6240a7..0ee9026 100644
--- a/core/java/android/app/appfunctions/AppFunctionManager.java
+++ b/core/java/android/app/appfunctions/AppFunctionManager.java
@@ -50,8 +50,7 @@
* Creates an instance.
*
* @param mService An interface to the backing service.
- * @param context A {@link Context}.
- *
+ * @param context A {@link Context}.
* @hide
*/
public AppFunctionManager(IAppFunctionManager service, Context context) {
@@ -108,8 +107,8 @@
} catch (RuntimeException e) {
// Ideally shouldn't happen since errors are wrapped into the
// response, but we catch it here for additional safety.
- callback.accept(new ExecuteAppFunctionResponse.Builder(
- getResultCode(e), e.getMessage()).build());
+ callback.accept(ExecuteAppFunctionResponse.newFailure(
+ getResultCode(e), e.getMessage(), /*extras=*/ null));
}
}
});
diff --git a/core/java/android/app/appfunctions/AppFunctionManagerConfiguration.java b/core/java/android/app/appfunctions/AppFunctionManagerConfiguration.java
new file mode 100644
index 0000000..e4784b4
--- /dev/null
+++ b/core/java/android/app/appfunctions/AppFunctionManagerConfiguration.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appfunctions;
+
+import static android.app.appfunctions.flags.Flags.enableAppFunctionManager;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+/**
+ * Represents the system configuration of support for the {@code AppFunctionManager} and
+ * associated systems.
+ *
+ * @hide
+ */
+public class AppFunctionManagerConfiguration {
+ private final Context mContext;
+
+ /**
+ * Constructs a new instance of {@code AppFunctionManagerConfiguration}.
+ * @param context context
+ */
+ public AppFunctionManagerConfiguration(@NonNull final Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Indicates whether the current target is intended to support {@code AppFunctionManager}.
+ * @return {@code true} if supported; otherwise {@code false}
+ */
+ public boolean isSupported() {
+ return enableAppFunctionManager() && !isWatch();
+
+ }
+
+ /**
+ * Indicates whether the current target is intended to support {@code AppFunctionManager}.
+ * @param context context
+ * @return {@code true} if supported; otherwise {@code false}
+ */
+ public static boolean isSupported(@NonNull final Context context) {
+ return new AppFunctionManagerConfiguration(context).isSupported();
+ }
+
+ private boolean isWatch() {
+ return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
+ }
+}
diff --git a/core/java/android/app/appfunctions/AppFunctionManagerHelper.java b/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
new file mode 100644
index 0000000..3169f0e
--- /dev/null
+++ b/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appfunctions;
+
+import static android.app.appfunctions.AppFunctionRuntimeMetadata.PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID;
+import static android.app.appfunctions.AppFunctionRuntimeMetadata.PROPERTY_ENABLED;
+import static android.app.appfunctions.AppFunctionStaticMetadataHelper.APP_FUNCTION_INDEXER_PACKAGE;
+import static android.app.appfunctions.AppFunctionStaticMetadataHelper.STATIC_PROPERTY_ENABLED_BY_DEFAULT;
+import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.GlobalSearchSession;
+import android.app.appsearch.JoinSpec;
+import android.app.appsearch.SearchResult;
+import android.app.appsearch.SearchResults;
+import android.app.appsearch.SearchSpec;
+import android.os.OutcomeReceiver;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Helper class containing utilities for {@link AppFunctionManager}.
+ *
+ * @hide
+ */
+@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
+public class AppFunctionManagerHelper {
+
+ /**
+ * Returns (through a callback) a boolean indicating whether the app function is enabled.
+ * <p>
+ * This method can only check app functions that are owned by the caller owned by packages
+ * visible to the caller.
+ * <p>
+ * If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors:
+ * <ul>
+ * <li>{@link IllegalArgumentException}, if the function is not found</li>
+ * <li>{@link SecurityException}, if the caller does not have permission to query the
+ * target package
+ * </li>
+ * </ul>
+ *
+ * @param functionIdentifier the identifier of the app function to check (unique within the
+ * target package) and in most cases, these are automatically
+ * generated by the AppFunctions SDK
+ * @param targetPackage the package name of the app function's owner
+ * @param appSearchExecutor the executor to run the metadata search mechanism through AppSearch
+ * @param callbackExecutor the executor to run the callback
+ * @param callback the callback to receive the function enabled check result
+ * @hide
+ */
+ public static void isAppFunctionEnabled(
+ @NonNull String functionIdentifier,
+ @NonNull String targetPackage,
+ @NonNull AppSearchManager appSearchManager,
+ @NonNull Executor appSearchExecutor,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull OutcomeReceiver<Boolean, Exception> callback
+ ) {
+ Objects.requireNonNull(functionIdentifier);
+ Objects.requireNonNull(targetPackage);
+ Objects.requireNonNull(appSearchManager);
+ Objects.requireNonNull(appSearchExecutor);
+ Objects.requireNonNull(callbackExecutor);
+ Objects.requireNonNull(callback);
+
+ appSearchManager.createGlobalSearchSession(appSearchExecutor,
+ (searchSessionResult) -> {
+ if (!searchSessionResult.isSuccess()) {
+ callbackExecutor.execute(() ->
+ callback.onError(failedResultToException(searchSessionResult)));
+ return;
+ }
+ try (GlobalSearchSession searchSession = searchSessionResult.getResultValue()) {
+ SearchResults results = searchJoinedStaticWithRuntimeAppFunctions(
+ searchSession, targetPackage, functionIdentifier);
+ results.getNextPage(appSearchExecutor,
+ listAppSearchResult -> callbackExecutor.execute(() -> {
+ if (listAppSearchResult.isSuccess()) {
+ callback.onResult(getEnabledStateFromSearchResults(
+ Objects.requireNonNull(
+ listAppSearchResult.getResultValue())));
+ } else {
+ callback.onError(
+ failedResultToException(listAppSearchResult));
+ }
+ }));
+ } catch (Exception e) {
+ callbackExecutor.execute(() -> callback.onError(e));
+ }
+ });
+ }
+
+ /**
+ * Searches joined app function static and runtime metadata using the function Id and the
+ * package.
+ *
+ * @hide
+ */
+ private static @NonNull SearchResults searchJoinedStaticWithRuntimeAppFunctions(
+ @NonNull GlobalSearchSession session,
+ @NonNull String targetPackage,
+ @NonNull String functionIdentifier) {
+ SearchSpec runtimeSearchSpec = getAppFunctionRuntimeMetadataSearchSpecByFunctionId(
+ targetPackage);
+ JoinSpec joinSpec = new JoinSpec.Builder(
+ PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID)
+ .setNestedSearch(
+ functionIdentifier,
+ runtimeSearchSpec).build();
+ SearchSpec joinedStaticWithRuntimeSearchSpec = new SearchSpec.Builder()
+ .setJoinSpec(joinSpec)
+ .addFilterPackageNames(APP_FUNCTION_INDEXER_PACKAGE)
+ .addFilterSchemas(
+ AppFunctionStaticMetadataHelper.getStaticSchemaNameForPackage(
+ targetPackage))
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build();
+ return session.search(functionIdentifier, joinedStaticWithRuntimeSearchSpec);
+ }
+
+ /**
+ * Finds whether the function is enabled or not from the search results returned by
+ * {@link #searchJoinedStaticWithRuntimeAppFunctions}.
+ *
+ * @throws IllegalArgumentException if the function is not found in the results
+ * @hide
+ */
+ private static boolean getEnabledStateFromSearchResults(
+ @NonNull List<SearchResult> joinedStaticRuntimeResults) {
+ if (joinedStaticRuntimeResults.isEmpty()) {
+ // Function not found.
+ throw new IllegalArgumentException("App function not found.");
+ } else {
+ List<SearchResult> runtimeMetadataResults =
+ joinedStaticRuntimeResults.getFirst().getJoinedResults();
+ if (!runtimeMetadataResults.isEmpty()) {
+ Boolean result = (Boolean) runtimeMetadataResults
+ .getFirst().getGenericDocument()
+ .getProperty(PROPERTY_ENABLED);
+ if (result != null) {
+ return result;
+ }
+ }
+ // Runtime metadata not found. Using the default value in the static metadata.
+ return joinedStaticRuntimeResults.getFirst().getGenericDocument()
+ .getPropertyBoolean(STATIC_PROPERTY_ENABLED_BY_DEFAULT);
+ }
+ }
+
+ /**
+ * Returns search spec that queries app function metadata for a specific package name by its
+ * function identifier.
+ *
+ * @hide
+ */
+ public static @NonNull SearchSpec getAppFunctionRuntimeMetadataSearchSpecByFunctionId(
+ @NonNull String targetPackage) {
+ return new SearchSpec.Builder()
+ .addFilterPackageNames(APP_FUNCTION_INDEXER_PACKAGE)
+ .addFilterSchemas(
+ AppFunctionRuntimeMetadata.getRuntimeSchemaNameForPackage(
+ targetPackage))
+ .addFilterProperties(
+ AppFunctionRuntimeMetadata.getRuntimeSchemaNameForPackage(
+ targetPackage),
+ List.of(AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID))
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build();
+ }
+
+ /**
+ * Converts a failed app search result codes into an exception.
+ *
+ * @hide
+ */
+ public static @NonNull Exception failedResultToException(
+ @NonNull AppSearchResult appSearchResult) {
+ return switch (appSearchResult.getResultCode()) {
+ case AppSearchResult.RESULT_INVALID_ARGUMENT -> new IllegalArgumentException(
+ appSearchResult.getErrorMessage());
+ case AppSearchResult.RESULT_IO_ERROR -> new IOException(
+ appSearchResult.getErrorMessage());
+ case AppSearchResult.RESULT_SECURITY_ERROR -> new SecurityException(
+ appSearchResult.getErrorMessage());
+ default -> new IllegalStateException(appSearchResult.getErrorMessage());
+ };
+ }
+}
diff --git a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
new file mode 100644
index 0000000..fdd12b0
--- /dev/null
+++ b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appfunctions;
+
+import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.appsearch.AppSearchSchema;
+import android.app.appsearch.GenericDocument;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Objects;
+
+/**
+ * Represents runtime function metadata of an app function.
+ *
+ * <p>This is a temporary solution for app function indexing, as later we would like to index the
+ * actual function signature entity class shape instead of just the schema info.
+ *
+ * @hide
+ */
+// TODO(b/357551503): Link to canonical docs rather than duplicating once they
+// are available.
+@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
+public class AppFunctionRuntimeMetadata extends GenericDocument {
+ public static final String RUNTIME_SCHEMA_TYPE = "AppFunctionRuntimeMetadata";
+ public static final String APP_FUNCTION_INDEXER_PACKAGE = "android";
+ public static final String APP_FUNCTION_RUNTIME_METADATA_DB = "appfunctions-db";
+ public static final String APP_FUNCTION_RUNTIME_NAMESPACE = "app_functions_runtime";
+ public static final String PROPERTY_FUNCTION_ID = "functionId";
+ public static final String PROPERTY_PACKAGE_NAME = "packageName";
+ public static final String PROPERTY_ENABLED = "enabled";
+ public static final String PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID =
+ "appFunctionStaticMetadataQualifiedId";
+ private static final String TAG = "AppSearchAppFunction";
+ private static final String RUNTIME_SCHEMA_TYPE_SEPARATOR = "-";
+
+ public AppFunctionRuntimeMetadata(@NonNull GenericDocument genericDocument) {
+ super(genericDocument);
+ }
+
+ /**
+ * Returns a per-app runtime metadata schema name, to store all functions for that package.
+ */
+ public static String getRuntimeSchemaNameForPackage(@NonNull String pkg) {
+ return RUNTIME_SCHEMA_TYPE + RUNTIME_SCHEMA_TYPE_SEPARATOR + Objects.requireNonNull(pkg);
+ }
+
+ /**
+ * Returns the document id for an app function's runtime metadata.
+ */
+ public static String getDocumentIdForAppFunction(
+ @NonNull String pkg, @NonNull String functionId) {
+ return pkg + "/" + functionId;
+ }
+
+ /**
+ * Different packages have different visibility requirements. To allow for different visibility,
+ * we need to have per-package app function schemas.
+ * <p>This schema should be set visible to callers from the package owner itself and for callers
+ * with {@link android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or {@link
+ * android.permission.EXECUTE_APP_FUNCTIONS} permissions.
+ *
+ * @param packageName The package name to create a schema for.
+ */
+ @NonNull
+ public static AppSearchSchema createAppFunctionRuntimeSchema(@NonNull String packageName) {
+ return new AppSearchSchema.Builder(getRuntimeSchemaNameForPackage(packageName))
+ .addProperty(
+ new AppSearchSchema.StringPropertyConfig.Builder(PROPERTY_FUNCTION_ID)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(
+ AppSearchSchema.StringPropertyConfig
+ .INDEXING_TYPE_EXACT_TERMS)
+ .setTokenizerType(
+ AppSearchSchema.StringPropertyConfig
+ .TOKENIZER_TYPE_VERBATIM)
+ .build())
+ .addProperty(
+ new AppSearchSchema.StringPropertyConfig.Builder(PROPERTY_PACKAGE_NAME)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(
+ AppSearchSchema.StringPropertyConfig
+ .INDEXING_TYPE_EXACT_TERMS)
+ .setTokenizerType(
+ AppSearchSchema.StringPropertyConfig
+ .TOKENIZER_TYPE_VERBATIM)
+ .build())
+ .addProperty(
+ new AppSearchSchema.BooleanPropertyConfig.Builder(PROPERTY_ENABLED)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .build())
+ .addProperty(
+ new AppSearchSchema.StringPropertyConfig.Builder(
+ PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .setJoinableValueType(
+ AppSearchSchema.StringPropertyConfig
+ .JOINABLE_VALUE_TYPE_QUALIFIED_ID)
+ .build())
+ .addParentType(RUNTIME_SCHEMA_TYPE)
+ .build();
+ }
+
+ /**
+ * Returns the function id. This might look like "com.example.message#send_message".
+ */
+ @NonNull
+ public String getFunctionId() {
+ return Objects.requireNonNull(getPropertyString(PROPERTY_FUNCTION_ID));
+ }
+
+ /**
+ * Returns the package name of the package that owns this function.
+ */
+ @NonNull
+ public String getPackageName() {
+ return Objects.requireNonNull(getPropertyString(PROPERTY_PACKAGE_NAME));
+ }
+
+ /**
+ * Returns if the function is set to be enabled or not. If not set, the {@link
+ * AppFunctionStaticMetadataHelper#STATIC_PROPERTY_ENABLED_BY_DEFAULT} value would be used.
+ */
+ @Nullable
+ public Boolean getEnabled() {
+ return (Boolean) getProperty(PROPERTY_ENABLED);
+ }
+
+ /**
+ * Returns the qualified id linking to the static metadata of the app function.
+ */
+ @Nullable
+ @VisibleForTesting
+ public String getAppFunctionStaticMetadataQualifiedId() {
+ return getPropertyString(PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID);
+ }
+
+ public static final class Builder extends GenericDocument.Builder<Builder> {
+ /**
+ * Creates a Builder for a {@link AppFunctionRuntimeMetadata}.
+ *
+ * @param packageName the name of the package that owns the function.
+ * @param functionId the id of the function.
+ * @param staticMetadataQualifiedId the qualified static metadata id that this runtime
+ * metadata refers to.
+ */
+ public Builder(
+ @NonNull String packageName,
+ @NonNull String functionId,
+ @NonNull String staticMetadataQualifiedId) {
+ super(
+ APP_FUNCTION_RUNTIME_NAMESPACE,
+ getDocumentIdForAppFunction(
+ Objects.requireNonNull(packageName),
+ Objects.requireNonNull(functionId)),
+ getRuntimeSchemaNameForPackage(packageName));
+ setPropertyString(PROPERTY_PACKAGE_NAME, packageName);
+ setPropertyString(PROPERTY_FUNCTION_ID, functionId);
+
+ // Set qualified id automatically
+ setPropertyString(
+ PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID,
+ staticMetadataQualifiedId);
+ }
+
+
+ /**
+ * Sets an indicator specifying if the function is enabled or not. This would override the
+ * default enabled state in the static metadata ({@link
+ * AppFunctionStaticMetadataHelper#STATIC_PROPERTY_ENABLED_BY_DEFAULT}).
+ */
+ @NonNull
+ public Builder setEnabled(boolean enabled) {
+ setPropertyBoolean(PROPERTY_ENABLED, enabled);
+ return this;
+ }
+
+ /**
+ * Creates the {@link AppFunctionRuntimeMetadata} GenericDocument.
+ */
+ @NonNull
+ public AppFunctionRuntimeMetadata build() {
+ return new AppFunctionRuntimeMetadata(super.build());
+ }
+ }
+}
diff --git a/core/java/android/app/appfunctions/AppFunctionService.java b/core/java/android/app/appfunctions/AppFunctionService.java
index 6259d16..22bc908 100644
--- a/core/java/android/app/appfunctions/AppFunctionService.java
+++ b/core/java/android/app/appfunctions/AppFunctionService.java
@@ -81,8 +81,9 @@
// Apps should handle exceptions. But if they don't, report the error on
// behalf of them.
safeCallback.onResult(
- new ExecuteAppFunctionResponse.Builder(
- getResultCode(ex), getExceptionMessage(ex)).build());
+ ExecuteAppFunctionResponse.newFailure(
+ getResultCode(ex),
+ ex.getMessage(), /*extras=*/ null));
}
}
};
@@ -117,8 +118,4 @@
public abstract void onExecuteFunction(
@NonNull ExecuteAppFunctionRequest request,
@NonNull Consumer<ExecuteAppFunctionResponse> callback);
-
- private String getExceptionMessage(Exception exception) {
- return exception.getMessage() == null ? "" : exception.getMessage();
- }
}
diff --git a/core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java b/core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java
new file mode 100644
index 0000000..6d4172a
--- /dev/null
+++ b/core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appfunctions;
+
+import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.app.appsearch.util.DocumentIdUtil;
+
+import java.util.Objects;
+
+/**
+ * Contains constants and helper related to static metadata represented with {@code
+ * com.android.server.appsearch.appsindexer.appsearchtypes.AppFunctionStaticMetadata}.
+ * <p>
+ * The constants listed here **must not change** and be kept consistent with the canonical
+ * static metadata class.
+ *
+ * @hide
+ */
+@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
+public class AppFunctionStaticMetadataHelper {
+ public static final String STATIC_SCHEMA_TYPE = "AppFunctionStaticMetadata";
+ public static final String STATIC_PROPERTY_ENABLED_BY_DEFAULT = "enabledByDefault";
+
+ public static final String APP_FUNCTION_STATIC_NAMESPACE = "app_functions";
+
+ // These are constants that has to be kept the same with {@code
+ // com.android.server.appsearch.appsindexer.appsearchtypes.AppSearchHelper}.
+ public static final String APP_FUNCTION_STATIC_METADATA_DB = "apps-db";
+ public static final String APP_FUNCTION_INDEXER_PACKAGE = "android";
+
+ /**
+ * Returns a per-app static metadata schema name, to store all functions for that package.
+ */
+ public static String getStaticSchemaNameForPackage(@NonNull String pkg) {
+ return STATIC_SCHEMA_TYPE + "-" + Objects.requireNonNull(pkg);
+ }
+
+ /** Returns the document id for an app function's static metadata. */
+ public static String getDocumentIdForAppFunction(
+ @NonNull String pkg, @NonNull String functionId) {
+ return pkg + "/" + functionId;
+ }
+
+ /**
+ * Returns the fully qualified Id used in AppSearch for the given package and function id
+ * app function static metadata.
+ */
+ public static String getStaticMetadataQualifiedId(String packageName, String functionId) {
+ return DocumentIdUtil.createQualifiedId(
+ APP_FUNCTION_INDEXER_PACKAGE,
+ APP_FUNCTION_STATIC_METADATA_DB,
+ APP_FUNCTION_STATIC_NAMESPACE,
+ getDocumentIdForAppFunction(packageName, functionId));
+ }
+}
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
index 9fb3375..58d4088 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
@@ -162,6 +162,55 @@
}
/**
+ * Returns a successful response.
+ *
+ * @param resultDocument The return value of the executed function.
+ * @param extras The additional metadata data relevant to this function execution
+ * response.
+ */
+ @NonNull
+ @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
+ public static ExecuteAppFunctionResponse newSuccess(@NonNull GenericDocument resultDocument,
+ @Nullable Bundle extras) {
+ Objects.requireNonNull(resultDocument);
+ Bundle actualExtras = getActualExtras(extras);
+ GenericDocumentWrapper resultDocumentWrapper = new GenericDocumentWrapper(resultDocument);
+
+ return new ExecuteAppFunctionResponse(
+ resultDocumentWrapper, actualExtras, RESULT_OK, /*errorMessage=*/null);
+ }
+
+ /**
+ * Returns a failure response.
+ *
+ * @param resultCode The result code of the app function execution.
+ * @param extras The additional metadata data relevant to this function execution
+ * response.
+ * @param errorMessage The error message associated with the result, if any.
+ */
+ @NonNull
+ @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
+ public static ExecuteAppFunctionResponse newFailure(@ResultCode int resultCode,
+ @Nullable String errorMessage,
+ @Nullable Bundle extras) {
+ if (resultCode == RESULT_OK) {
+ throw new IllegalArgumentException("resultCode must not be RESULT_OK");
+ }
+ Bundle actualExtras = getActualExtras(extras);
+ GenericDocumentWrapper emptyWrapper = new GenericDocumentWrapper(
+ new GenericDocument.Builder<>("", "", "").build());
+ return new ExecuteAppFunctionResponse(
+ emptyWrapper, actualExtras, resultCode, errorMessage);
+ }
+
+ private static Bundle getActualExtras(@Nullable Bundle extras) {
+ if (extras == null) {
+ return Bundle.EMPTY;
+ }
+ return extras;
+ }
+
+ /**
* Returns a generic document containing the return value of the executed function.
*
* <p>The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value.</p>
@@ -250,64 +299,4 @@
@Retention(RetentionPolicy.SOURCE)
public @interface ResultCode {
}
-
- /**
- * The builder for creating {@link ExecuteAppFunctionResponse} instances.
- */
- @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
- public static final class Builder {
- @NonNull
- private GenericDocument mResultDocument =
- new GenericDocument.Builder<>("", "", "").build();
- @NonNull
- private Bundle mExtras = Bundle.EMPTY;
- private int mResultCode;
- @Nullable
- private String mErrorMessage;
-
- /**
- * Creates a new builder for {@link ExecuteAppFunctionResponse}.
- */
- private Builder() {
- }
-
- /**
- * Creates a new builder for {@link ExecuteAppFunctionResponse} to build a success response
- * with a result code of {@link #RESULT_OK} and a resultDocument.
- */
- public Builder(@NonNull GenericDocument resultDocument) {
- Objects.requireNonNull(resultDocument);
- mResultDocument = resultDocument;
- mResultCode = RESULT_OK;
- }
-
- /**
- * Creates a new builder for {@link ExecuteAppFunctionResponse} to build an error response
- * with a result code and an error message.
- */
- public Builder(@ResultCode int resultCode,
- @NonNull String errorMessage) {
- mResultCode = resultCode;
- mErrorMessage = Objects.requireNonNull(errorMessage);
- }
-
- /**
- * Sets the extras of the app function execution.
- */
- @NonNull
- public Builder setExtras(@NonNull Bundle extras) {
- mExtras = Objects.requireNonNull(extras);
- return this;
- }
-
- /**
- * Builds the {@link ExecuteAppFunctionResponse} instance.
- */
- @NonNull
- public ExecuteAppFunctionResponse build() {
- return new ExecuteAppFunctionResponse(
- new GenericDocumentWrapper(mResultDocument),
- mExtras, mResultCode, mErrorMessage);
- }
- }
}
diff --git a/core/java/android/app/supervision/OWNERS b/core/java/android/app/supervision/OWNERS
new file mode 100644
index 0000000..afc5495
--- /dev/null
+++ b/core/java/android/app/supervision/OWNERS
@@ -0,0 +1,2 @@
+jparks@google.com
+romkal@google.com
diff --git a/core/java/android/companion/flags.aconfig b/core/java/android/companion/flags.aconfig
index ee9114f..93d62cf 100644
--- a/core/java/android/companion/flags.aconfig
+++ b/core/java/android/companion/flags.aconfig
@@ -10,13 +10,6 @@
}
flag {
- name: "companion_transport_apis"
- namespace: "companion"
- description: "Grants access to the companion transport apis."
- bug: "288297505"
-}
-
-flag {
name: "association_tag"
is_exported: true
namespace: "companion"
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index d7a517a..ffed536 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -629,11 +629,10 @@
* A builder for {@link AttributionSource}
*/
public static final class Builder {
+ private boolean mHasBeenUsed;
private @NonNull final AttributionSourceState mAttributionSourceState =
new AttributionSourceState();
- private long mBuilderFieldsSet = 0L;
-
/**
* Creates a new Builder.
*
@@ -642,8 +641,17 @@
*/
public Builder(int uid) {
mAttributionSourceState.uid = uid;
+ mAttributionSourceState.pid = Process.INVALID_PID;
+ mAttributionSourceState.deviceId = Context.DEVICE_ID_DEFAULT;
+ mAttributionSourceState.token = sDefaultToken;
}
+ /**
+ * Creates a builder that is ready to build a new {@link AttributionSource} where
+ * all fields (primitive, immutable data, pointers) are copied from the given
+ * {@link AttributionSource}. Builder methods can still be used to mutate fields further.
+ * @param current The source to copy fields from.
+ */
public Builder(@NonNull AttributionSource current) {
if (current == null) {
throw new IllegalArgumentException("current AttributionSource can not be null");
@@ -652,10 +660,11 @@
mAttributionSourceState.pid = current.getPid();
mAttributionSourceState.packageName = current.getPackageName();
mAttributionSourceState.attributionTag = current.getAttributionTag();
- mAttributionSourceState.token = current.getToken();
mAttributionSourceState.renouncedPermissions =
current.mAttributionSourceState.renouncedPermissions;
- mBuilderFieldsSet |= 0x2 | 0x4 | 0x8 | 0x10;
+ mAttributionSourceState.deviceId = current.getDeviceId();
+ mAttributionSourceState.next = current.mAttributionSourceState.next;
+ mAttributionSourceState.token = current.getToken();
}
/**
@@ -667,7 +676,6 @@
*/
public @NonNull Builder setPid(int value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x2;
mAttributionSourceState.pid = value;
return this;
}
@@ -677,7 +685,6 @@
*/
public @NonNull Builder setPackageName(@Nullable String value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x4;
mAttributionSourceState.packageName = value;
return this;
}
@@ -687,7 +694,6 @@
*/
public @NonNull Builder setAttributionTag(@Nullable String value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x8;
mAttributionSourceState.attributionTag = value;
return this;
}
@@ -718,11 +724,11 @@
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS)
- public @NonNull Builder setRenouncedPermissions(@Nullable Set<String> value) {
+ public @NonNull Builder setRenouncedPermissions(
+ @Nullable Set<String> renouncedPermissions) {
checkNotUsed();
- mBuilderFieldsSet |= 0x10;
- mAttributionSourceState.renouncedPermissions = (value != null)
- ? value.toArray(new String[0]) : null;
+ mAttributionSourceState.renouncedPermissions = (renouncedPermissions != null)
+ ? renouncedPermissions.toArray(new String[0]) : null;
return this;
}
@@ -735,7 +741,6 @@
@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
public @NonNull Builder setDeviceId(int deviceId) {
checkNotUsed();
- mBuilderFieldsSet |= 0x20;
mAttributionSourceState.deviceId = deviceId;
return this;
}
@@ -745,7 +750,6 @@
*/
public @NonNull Builder setNext(@Nullable AttributionSource value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x40;
mAttributionSourceState.next = (value != null) ? new AttributionSourceState[]
{value.mAttributionSourceState} : mAttributionSourceState.next;
return this;
@@ -760,7 +764,6 @@
if (value == null) {
throw new IllegalArgumentException("Null AttributionSource not permitted.");
}
- mBuilderFieldsSet |= 0x40;
mAttributionSourceState.next =
new AttributionSourceState[]{value.mAttributionSourceState};
return this;
@@ -769,28 +772,7 @@
/** Builds the instance. This builder should not be touched after calling this! */
public @NonNull AttributionSource build() {
checkNotUsed();
- mBuilderFieldsSet |= 0x80; // Mark builder used
-
- if ((mBuilderFieldsSet & 0x2) == 0) {
- mAttributionSourceState.pid = Process.INVALID_PID;
- }
- if ((mBuilderFieldsSet & 0x4) == 0) {
- mAttributionSourceState.packageName = null;
- }
- if ((mBuilderFieldsSet & 0x8) == 0) {
- mAttributionSourceState.attributionTag = null;
- }
- if ((mBuilderFieldsSet & 0x10) == 0) {
- mAttributionSourceState.renouncedPermissions = null;
- }
- if ((mBuilderFieldsSet & 0x20) == 0) {
- mAttributionSourceState.deviceId = Context.DEVICE_ID_DEFAULT;
- }
- if ((mBuilderFieldsSet & 0x40) == 0) {
- mAttributionSourceState.next = null;
- }
-
- mAttributionSourceState.token = sDefaultToken;
+ mHasBeenUsed = true;
if (mAttributionSourceState.next == null) {
// The NDK aidl backend doesn't support null parcelable arrays.
@@ -800,7 +782,7 @@
}
private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x80) != 0) {
+ if (mHasBeenUsed) {
throw new IllegalStateException(
"This Builder should not be reused. Use a new Builder instance instead");
}
diff --git a/core/java/android/hardware/display/BrightnessInfo.java b/core/java/android/hardware/display/BrightnessInfo.java
index 109b0a8..6a96a54 100644
--- a/core/java/android/hardware/display/BrightnessInfo.java
+++ b/core/java/android/hardware/display/BrightnessInfo.java
@@ -158,6 +158,8 @@
return "thermal";
case BRIGHTNESS_MAX_REASON_POWER_IC:
return "power IC";
+ case BRIGHTNESS_MAX_REASON_WEAR_BEDTIME_MODE:
+ return "wear bedtime";
}
return "invalid";
}
diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index 03cf7c5..2a36238 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -567,7 +567,7 @@
Objects.requireNonNull(listener, "listener must not be null");
synchronized (mOnTabletModeChangedListeners) {
- if (mOnTabletModeChangedListeners == null) {
+ if (mOnTabletModeChangedListeners.isEmpty()) {
initializeTabletModeListenerLocked();
}
int idx = findOnTabletModeChangedListenerLocked(listener);
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 0ddb3e2..83c4de3 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -99,11 +99,3 @@
description: "Refactor ModifierShortcutManager internal representation of shortcuts."
bug: "358603902"
}
-
-flag {
- namespace: "input_native"
- name: "manage_key_gestures"
- description: "Manage key gestures through Input APIs"
- is_exported: true
- bug: "358569822"
-}
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index bfff4db..9f3e3ad 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -29,6 +29,7 @@
import static java.util.Objects.requireNonNull;
import android.annotation.ElapsedRealtimeLong;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -874,10 +875,9 @@
/*****************************************************************************
* A GenericSoundModel is a specialized {@link SoundModel} for non-voice sound
* patterns.
- *
- * @hide
****************************************************************************/
- public static class GenericSoundModel extends SoundModel implements Parcelable {
+ @FlaggedApi(android.media.soundtrigger.Flags.FLAG_GENERIC_MODEL_API)
+ public static final class GenericSoundModel extends SoundModel implements Parcelable {
public static final @android.annotation.NonNull Parcelable.Creator<GenericSoundModel> CREATOR
= new Parcelable.Creator<GenericSoundModel>() {
@@ -890,11 +890,26 @@
}
};
+ /**
+ * Constructor for {@link GenericSoundModel} with version.
+ *
+ * @param uuid Unique identifier for this sound model.
+ * @param vendorUuid Unique vendor identifier for this sound model.
+ * @param data Opaque data for this sound model.
+ * @param version Vendor-specific version number of this sound model.
+ */
public GenericSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid,
@Nullable byte[] data, int version) {
super(uuid, vendorUuid, TYPE_GENERIC_SOUND, data, version);
}
+ /**
+ * Constructor for {@link GenericSoundModel} without version. The version is set to -1.
+ *
+ * @param uuid Unique identifier for this sound model.
+ * @param vendorUuid Unique vendor identifier for this sound model.
+ * @param data Opaque data for this sound model.
+ */
@UnsupportedAppUsage
public GenericSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid,
@Nullable byte[] data) {
@@ -919,7 +934,7 @@
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(getUuid().toString());
if (getVendorUuid() == null) {
dest.writeInt(-1);
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
index 92b630f..80f39bf 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -46,13 +46,13 @@
* a {@link Message} object containing a bundle of data that will be
* processed by the Handler's {@link #handleMessage} method (requiring that
* you implement a subclass of Handler).
- *
+ *
* <p>When posting or sending to a Handler, you can either
* allow the item to be processed as soon as the message queue is ready
* to do so, or specify a delay before it gets processed or absolute time for
* it to be processed. The latter two allow you to implement timeouts,
* ticks, and other timing-based behavior.
- *
+ *
* <p>When a
* process is created for your application, its main thread is dedicated to
* running a message queue that takes care of managing the top-level
@@ -85,13 +85,13 @@
*/
boolean handleMessage(@NonNull Message msg);
}
-
+
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(@NonNull Message msg) {
}
-
+
/**
* Handle system messages here.
*/
@@ -343,8 +343,8 @@
* The default implementation will either return the class name of the
* message callback if any, or the hexadecimal representation of the
* message "what" field.
- *
- * @param message The message whose name is being queried
+ *
+ * @param message The message whose name is being queried
*/
@NonNull
public String getMessageName(@NonNull Message message) {
@@ -367,7 +367,7 @@
/**
* Same as {@link #obtainMessage()}, except that it also sets the what member of the returned Message.
- *
+ *
* @param what Value to assign to the returned Message.what field.
* @return A Message from the global message pool.
*/
@@ -376,12 +376,12 @@
{
return Message.obtain(this, what);
}
-
+
/**
- *
- * Same as {@link #obtainMessage()}, except that it also sets the what and obj members
+ *
+ * Same as {@link #obtainMessage()}, except that it also sets the what and obj members
* of the returned Message.
- *
+ *
* @param what Value to assign to the returned Message.what field.
* @param obj Value to assign to the returned Message.obj field.
* @return A Message from the global message pool.
@@ -392,7 +392,7 @@
}
/**
- *
+ *
* Same as {@link #obtainMessage()}, except that it also sets the what, arg1 and arg2 members of the returned
* Message.
* @param what Value to assign to the returned Message.what field.
@@ -405,10 +405,10 @@
{
return Message.obtain(this, what, arg1, arg2);
}
-
+
/**
- *
- * Same as {@link #obtainMessage()}, except that it also sets the what, obj, arg1,and arg2 values on the
+ *
+ * Same as {@link #obtainMessage()}, except that it also sets the what, obj, arg1,and arg2 values on the
* returned Message.
* @param what Value to assign to the returned Message.what field.
* @param arg1 Value to assign to the returned Message.arg1 field.
@@ -423,19 +423,19 @@
/**
* Causes the Runnable r to be added to the message queue.
- * The runnable will be run on the thread to which this handler is
- * attached.
- *
+ * The runnable will be run on the thread to which this handler is
+ * attached.
+ *
* @param r The Runnable that will be executed.
- *
- * @return Returns true if the Runnable was successfully placed in to the
+ *
+ * @return Returns true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
-
+
/**
* Causes the Runnable r to be added to the message queue, to be run
* at a specific time given by <var>uptimeMillis</var>.
@@ -446,8 +446,8 @@
* @param r The Runnable that will be executed.
* @param uptimeMillis The absolute time at which the callback should run,
* using the {@link android.os.SystemClock#uptimeMillis} time-base.
- *
- * @return Returns true if the Runnable was successfully placed in to the
+ *
+ * @return Returns true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting. Note that a
* result of true does not mean the Runnable will be processed -- if
@@ -457,7 +457,7 @@
public final boolean postAtTime(@NonNull Runnable r, long uptimeMillis) {
return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}
-
+
/**
* Causes the Runnable r to be added to the message queue, to be run
* at a specific time given by <var>uptimeMillis</var>.
@@ -470,21 +470,21 @@
* {@link #removeCallbacksAndMessages}.
* @param uptimeMillis The absolute time at which the callback should run,
* using the {@link android.os.SystemClock#uptimeMillis} time-base.
- *
- * @return Returns true if the Runnable was successfully placed in to the
+ *
+ * @return Returns true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting. Note that a
* result of true does not mean the Runnable will be processed -- if
* the looper is quit before the delivery time of the message
* occurs then the message will be dropped.
- *
+ *
* @see android.os.SystemClock#uptimeMillis
*/
public final boolean postAtTime(
@NonNull Runnable r, @Nullable Object token, long uptimeMillis) {
return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
}
-
+
/**
* Causes the Runnable r to be added to the message queue, to be run
* after the specified amount of time elapses.
@@ -492,12 +492,12 @@
* is attached.
* <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
* Time spent in deep sleep will add an additional delay to execution.
- *
+ *
* @param r The Runnable that will be executed.
* @param delayMillis The delay (in milliseconds) until the Runnable
* will be executed.
- *
- * @return Returns true if the Runnable was successfully placed in to the
+ *
+ * @return Returns true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting. Note that a
* result of true does not mean the Runnable will be processed --
@@ -507,7 +507,7 @@
public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
-
+
/** @hide */
public final boolean postDelayed(Runnable r, int what, long delayMillis) {
return sendMessageDelayed(getPostMessage(r).setWhat(what), delayMillis);
@@ -547,10 +547,10 @@
* <b>This method is only for use in very special circumstances -- it
* can easily starve the message queue, cause ordering problems, or have
* other unexpected side-effects.</b>
- *
+ *
* @param r The Runnable that will be executed.
- *
- * @return Returns true if the message was successfully placed in to the
+ *
+ * @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
@@ -635,8 +635,8 @@
* Pushes a message onto the end of the message queue after all pending messages
* before the current time. It will be received in {@link #handleMessage},
* in the thread attached to this handler.
- *
- * @return Returns true if the message was successfully placed in to the
+ *
+ * @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
@@ -646,8 +646,8 @@
/**
* Sends a Message containing only the what value.
- *
- * @return Returns true if the message was successfully placed in to the
+ *
+ * @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
@@ -659,9 +659,9 @@
/**
* Sends a Message containing only the what value, to be delivered
* after the specified amount of time elapses.
- * @see #sendMessageDelayed(android.os.Message, long)
- *
- * @return Returns true if the message was successfully placed in to the
+ * @see #sendMessageDelayed(android.os.Message, long)
+ *
+ * @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
@@ -672,11 +672,11 @@
}
/**
- * Sends a Message containing only the what value, to be delivered
+ * Sends a Message containing only the what value, to be delivered
* at a specific time.
* @see #sendMessageAtTime(android.os.Message, long)
- *
- * @return Returns true if the message was successfully placed in to the
+ *
+ * @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
@@ -691,8 +691,8 @@
* Enqueue a message into the message queue after all pending messages
* before (current time + delayMillis). You will receive it in
* {@link #handleMessage}, in the thread attached to this handler.
- *
- * @return Returns true if the message was successfully placed in to the
+ *
+ * @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting. Note that a
* result of true does not mean the message will be processed -- if
@@ -713,12 +713,12 @@
* Time spent in deep sleep will add an additional delay to execution.
* You will receive it in {@link #handleMessage}, in the thread attached
* to this handler.
- *
+ *
* @param uptimeMillis The absolute time at which the message should be
* delivered, using the
* {@link android.os.SystemClock#uptimeMillis} time-base.
- *
- * @return Returns true if the message was successfully placed in to the
+ *
+ * @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting. Note that a
* result of true does not mean the message will be processed -- if
@@ -743,8 +743,8 @@
* <b>This method is only for use in very special circumstances -- it
* can easily starve the message queue, cause ordering problems, or have
* other unexpected side-effects.</b>
- *
- * @return Returns true if the message was successfully placed in to the
+ *
+ * @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
@@ -798,6 +798,12 @@
/**
* Remove any pending posts of messages with code 'what' that are in the
* message queue.
+ *
+ * Note that `Message#what` is 0 unless otherwise set.
+ * When calling `postMessage(Runnable)` or `postAtTime(Runnable, long)`,
+ * the `Runnable` is internally wrapped with a `Message` whose `what` is 0.
+ * Calling `removeMessages(0)` will remove all messages without a `what`,
+ * including posted `Runnable`s.
*/
public final void removeMessages(int what) {
mQueue.removeMessages(this, what, null);
@@ -889,7 +895,7 @@
}
// if we can get rid of this method, the handler need not remember its loop
- // we could instead export a getMessageQueue() method...
+ // we could instead export a getMessageQueue() method...
@NonNull
public final Looper getLooper() {
return mLooper;
diff --git a/core/java/android/os/Message.java b/core/java/android/os/Message.java
index a1db9be..702fdc2 100644
--- a/core/java/android/os/Message.java
+++ b/core/java/android/os/Message.java
@@ -41,6 +41,9 @@
* what this message is about. Each {@link Handler} has its own name-space
* for message codes, so you do not need to worry about yours conflicting
* with other handlers.
+ *
+ * If not specified, this value is 0.
+ * Use values other than 0 to indicate custom message codes.
*/
public int what;
diff --git a/core/java/android/os/SharedMemory.java b/core/java/android/os/SharedMemory.java
index c801fabf..46ae9d8 100644
--- a/core/java/android/os/SharedMemory.java
+++ b/core/java/android/os/SharedMemory.java
@@ -379,6 +379,14 @@
private int mReferenceCount;
private MemoryRegistration(int size) {
+ // Round up to the nearest page size
+ final int PAGE_SIZE = OsConstants._SC_PAGE_SIZE;
+ if (PAGE_SIZE > 0) {
+ final int remainder = size % PAGE_SIZE;
+ if (remainder != 0) {
+ size += PAGE_SIZE - remainder;
+ }
+ }
mSize = size;
mReferenceCount = 1;
VMRuntime.getRuntime().registerNativeAllocation(mSize);
diff --git a/core/java/android/os/TransactionTooLargeException.java b/core/java/android/os/TransactionTooLargeException.java
index 79892e0..6b7cb33 100644
--- a/core/java/android/os/TransactionTooLargeException.java
+++ b/core/java/android/os/TransactionTooLargeException.java
@@ -47,7 +47,7 @@
* If possible, try to break up big requests into smaller pieces.
* </p><p>
* If you are implementing a service, it may help to impose size or complexity
- * contraints on the queries that clients can perform. For example, if the result set
+ * constraints on the queries that clients can perform. For example, if the result set
* could become large, then don't allow the client to request more than a few records
* at a time. Alternately, instead of returning all of the available data all at once,
* return the essential information first and make the client ask for additional information
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 536eca6..f1ec0e4e 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1978,7 +1978,6 @@
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
- @FlaggedApi(android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED)
public static final String DISALLOW_SIM_GLOBALLY =
"no_sim_globally";
@@ -1999,7 +1998,6 @@
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
- @FlaggedApi(android.app.admin.flags.Flags.FLAG_ASSIST_CONTENT_USER_RESTRICTION_ENABLED)
public static final String DISALLOW_ASSIST_CONTENT = "no_assist_content";
/**
diff --git a/core/java/android/util/Half.java b/core/java/android/util/Half.java
index fe536a6..22583ac 100644
--- a/core/java/android/util/Half.java
+++ b/core/java/android/util/Half.java
@@ -19,6 +19,7 @@
import android.annotation.HalfFloat;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
import libcore.util.FP16;
@@ -92,6 +93,7 @@
* <p>This table shows that numbers higher than 1024 lose all fractional precision.</p>
*/
@SuppressWarnings("SimplifiableIfStatement")
+@RavenwoodKeepWholeClass
public final class Half extends Number implements Comparable<Half> {
/**
* The number of bits used to represent a half-precision float value.
diff --git a/core/java/android/view/ImeBackAnimationController.java b/core/java/android/view/ImeBackAnimationController.java
index c7e93c1..b801465 100644
--- a/core/java/android/view/ImeBackAnimationController.java
+++ b/core/java/android/view/ImeBackAnimationController.java
@@ -149,15 +149,17 @@
private void setPreCommitProgress(float progress) {
if (isHideAnimationInProgress()) return;
+ setInterpolatedProgress(BACK_GESTURE.getInterpolation(progress) * PEEK_FRACTION);
+ }
+
+ private void setInterpolatedProgress(float progress) {
if (mWindowInsetsAnimationController != null) {
float hiddenY = mWindowInsetsAnimationController.getHiddenStateInsets().bottom;
float shownY = mWindowInsetsAnimationController.getShownStateInsets().bottom;
float imeHeight = shownY - hiddenY;
- float interpolatedProgress = BACK_GESTURE.getInterpolation(progress);
- int newY = (int) (imeHeight - interpolatedProgress * (imeHeight * PEEK_FRACTION));
+ int newY = (int) (imeHeight - progress * imeHeight);
if (mStartRootScrollY != 0) {
- mViewRoot.setScrollY(
- (int) (mStartRootScrollY * (1 - interpolatedProgress * PEEK_FRACTION)));
+ mViewRoot.setScrollY((int) (mStartRootScrollY * (1 - progress)));
}
mWindowInsetsAnimationController.setInsetsAndAlpha(Insets.of(0, 0, 0, newY), 1f,
progress);
@@ -171,21 +173,14 @@
return;
}
mTriggerBack = triggerBack;
- int currentBottomInset = mWindowInsetsAnimationController.getCurrentInsets().bottom;
- int targetBottomInset;
- if (triggerBack) {
- targetBottomInset = mWindowInsetsAnimationController.getHiddenStateInsets().bottom;
- } else {
- targetBottomInset = mWindowInsetsAnimationController.getShownStateInsets().bottom;
- }
- mPostCommitAnimator = ValueAnimator.ofFloat(currentBottomInset, targetBottomInset);
+ float targetProgress = triggerBack ? 1f : 0f;
+ mPostCommitAnimator = ValueAnimator.ofFloat(
+ BACK_GESTURE.getInterpolation(mLastProgress) * PEEK_FRACTION, targetProgress);
mPostCommitAnimator.setInterpolator(
triggerBack ? STANDARD_ACCELERATE : EMPHASIZED_DECELERATE);
mPostCommitAnimator.addUpdateListener(animation -> {
- int bottomInset = (int) ((float) animation.getAnimatedValue());
if (mWindowInsetsAnimationController != null) {
- mWindowInsetsAnimationController.setInsetsAndAlpha(Insets.of(0, 0, 0, bottomInset),
- 1f, animation.getAnimatedFraction());
+ setInterpolatedProgress((float) animation.getAnimatedValue());
} else {
reset();
}
@@ -213,14 +208,8 @@
notifyHideIme();
// requesting IME as invisible during post-commit
mInsetsController.setRequestedVisibleTypes(0, ime());
- // Changes the animation state. This also notifies RootView of changed insets, which
- // causes it to reset its scrollY to 0f (animated) if it was panned
mInsetsController.onAnimationStateChanged(ime(), /*running*/ true);
}
- if (mStartRootScrollY != 0 && !triggerBack) {
- // This causes RootView to update its scroll back to the panned position
- mInsetsController.getHost().notifyInsetsChanged();
- }
}
private void notifyHideIme() {
@@ -282,6 +271,10 @@
return mPostCommitAnimator != null && mTriggerBack;
}
+ boolean isAnimationInProgress() {
+ return mIsPreCommitAnimationInProgress || mWindowInsetsAnimationController != null;
+ }
+
/**
* Dump information about this ImeBackAnimationController
*
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index 6343313..e90b1c0 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -70,7 +70,14 @@
"ImeInsetsSourceConsumer#onAnimationFinished",
mController.getHost().getInputMethodManager(), null /* icProto */);
}
- boolean insetsChanged = super.onAnimationStateChanged(running);
+ boolean insetsChanged = false;
+ if (Flags.predictiveBackIme() && !running && isShowRequested()
+ && mAnimationState == ANIMATION_STATE_HIDE) {
+ // A user controlled hide animation may have ended in the shown state (e.g.
+ // cancelled predictive back animation) -> Insets need to be reset to shown.
+ insetsChanged |= applyLocalVisibilityOverride();
+ }
+ insetsChanged |= super.onAnimationStateChanged(running);
if (running && !isShowRequested()
&& mController.isPredictiveBackImeHideAnimInProgress()) {
// IME predictive back animation switched from pre-commit to post-commit.
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 7c8cd93..8fdf91a 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -1197,7 +1197,8 @@
pendingRequest.listener, null /* frame */, true /* fromIme */,
pendingRequest.mInsetsAnimationSpec,
pendingRequest.animationType, pendingRequest.layoutInsetsDuringAnimation,
- pendingRequest.useInsetsAnimationThread, statsToken);
+ pendingRequest.useInsetsAnimationThread, statsToken,
+ false /* fromPredictiveBack */);
}
@Override
@@ -1307,7 +1308,10 @@
WindowInsetsAnimationControlListener listener,
boolean fromIme, long durationMs, @Nullable Interpolator interpolator,
@AnimationType int animationType, boolean fromPredictiveBack) {
- if ((mState.calculateUncontrollableInsetsFromFrame(mFrame) & types) != 0) {
+ if ((mState.calculateUncontrollableInsetsFromFrame(mFrame) & types) != 0
+ || (fromPredictiveBack && ((mRequestedVisibleTypes & ime()) == 0))) {
+ // abort if insets are uncontrollable or if control request is from predictive back but
+ // there is already a hide anim in progress
listener.onCancelled(null);
return;
}
@@ -1330,7 +1334,7 @@
// TODO(b/342111149): Create statsToken here once ImeTracker#onStart becomes async.
controlAnimationUnchecked(types, cancellationSignal, listener, mFrame, fromIme, spec,
animationType, getLayoutInsetsDuringAnimationMode(types, fromPredictiveBack),
- false /* useInsetsAnimationThread */, null);
+ false /* useInsetsAnimationThread */, null, fromPredictiveBack);
}
private void controlAnimationUnchecked(@InsetsType int types,
@@ -1338,7 +1342,8 @@
WindowInsetsAnimationControlListener listener, @Nullable Rect frame, boolean fromIme,
InsetsAnimationSpec insetsAnimationSpec, @AnimationType int animationType,
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
- boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken) {
+ boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken,
+ boolean fromPredictiveBack) {
final boolean visible = layoutInsetsDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
// Basically, we accept the requested visibilities from the upstream callers...
@@ -1348,7 +1353,7 @@
// rejecting showing IME.
controlAnimationUncheckedInner(types, cancellationSignal, listener, frame, fromIme,
insetsAnimationSpec, animationType, layoutInsetsDuringAnimation,
- useInsetsAnimationThread, statsToken);
+ useInsetsAnimationThread, statsToken, fromPredictiveBack);
// We are finishing setting the requested visible types. Report them to the server
// and/or the app.
@@ -1360,7 +1365,8 @@
WindowInsetsAnimationControlListener listener, @Nullable Rect frame, boolean fromIme,
InsetsAnimationSpec insetsAnimationSpec, @AnimationType int animationType,
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
- boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken) {
+ boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken,
+ boolean fromPredictiveBack) {
if ((types & mTypesBeingCancelled) != 0) {
final boolean monitoredAnimation =
animationType == ANIMATION_TYPE_SHOW || animationType == ANIMATION_TYPE_HIDE;
@@ -1446,7 +1452,7 @@
}
} else {
Pair<Integer, Boolean> typesReadyPair = collectSourceControls(
- fromIme, types, controls, animationType, statsToken);
+ fromIme, types, controls, animationType, statsToken, fromPredictiveBack);
typesReady = typesReadyPair.first;
boolean imeReady = typesReadyPair.second;
if (DEBUG) {
@@ -1582,7 +1588,7 @@
*/
private Pair<Integer, Boolean> collectSourceControls(boolean fromIme, @InsetsType int types,
SparseArray<InsetsSourceControl> controls, @AnimationType int animationType,
- @Nullable ImeTracker.Token statsToken) {
+ @Nullable ImeTracker.Token statsToken, boolean fromPredictiveBack) {
ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_CLIENT_COLLECT_SOURCE_CONTROLS);
@@ -1594,7 +1600,8 @@
continue;
}
boolean show = animationType == ANIMATION_TYPE_SHOW
- || animationType == ANIMATION_TYPE_USER;
+ || (animationType == ANIMATION_TYPE_USER
+ && (!fromPredictiveBack || !mHost.hasAnimationCallbacks()));
boolean canRun = true;
if (show) {
// Show request
@@ -1617,7 +1624,8 @@
break;
}
} else {
- consumer.requestHide(fromIme, statsToken);
+ consumer.requestHide(fromIme
+ || (fromPredictiveBack && mHost.hasAnimationCallbacks()), statsToken);
}
if (!canRun) {
if (WARN) Log.w(TAG, String.format(
@@ -1672,9 +1680,10 @@
private @LayoutInsetsDuringAnimation int getLayoutInsetsDuringAnimationMode(
@InsetsType int types, boolean fromPredictiveBack) {
- if (fromPredictiveBack) {
- // When insets are animated by predictive back, we want insets to be shown to prevent a
- // jump cut from shown to hidden at the start of the predictive back animation
+ if (fromPredictiveBack && !mHost.hasAnimationCallbacks()) {
+ // When insets are animated by predictive back and the app does not have an animation
+ // callback, we want insets to be shown to prevent a jump cut from shown to hidden at
+ // the start of the predictive back animation
return LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
}
// Generally, we want to layout the opposite of the current state. This is to make animation
@@ -2021,7 +2030,8 @@
listener /* insetsAnimationSpec */,
show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE,
show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN,
- !hasAnimationCallbacks /* useInsetsAnimationThread */, statsToken);
+ !hasAnimationCallbacks /* useInsetsAnimationThread */, statsToken,
+ false /* fromPredictiveBack */);
}
/**
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 2dda835..9e4b27d 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -445,20 +445,16 @@
// Jank due to unknown reasons.
public static final int UNKNOWN = 0x80;
- public JankData(long frameVsyncId, @JankType int jankType, long frameIntervalNs,
- long scheduledAppFrameTimeNs, long actualAppFrameTimeNs) {
+ public JankData(long frameVsyncId, @JankType int jankType, long frameIntervalNs) {
this.frameVsyncId = frameVsyncId;
this.jankType = jankType;
this.frameIntervalNs = frameIntervalNs;
- this.scheduledAppFrameTimeNs = scheduledAppFrameTimeNs;
- this.actualAppFrameTimeNs = actualAppFrameTimeNs;
+
}
public final long frameVsyncId;
public final @JankType int jankType;
public final long frameIntervalNs;
- public final long scheduledAppFrameTimeNs;
- public final long actualAppFrameTimeNs;
}
/**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index e81f32e..523ff38 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -141,7 +141,6 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
-import android.os.Vibrator;
import android.service.credentials.CredentialProviderService;
import android.sysprop.DisplayProperties;
import android.text.InputType;
@@ -34156,7 +34155,8 @@
* REQUESTED_FRAME_RATE_CATEGORY_NORMAL, REQUESTED_FRAME_RATE_CATEGORY_HIGH.
* Keep in mind that the preferred frame rate affects the frame rate for the next frame,
* so use this method carefully. It's important to note that the preference is valid as
- * long as the View is invalidated.
+ * long as the View is invalidated. Please also be aware that the requested frame rate
+ * will not propagate to child views when this API is used on a ViewGroup.
*
* @param frameRate the preferred frame rate of the view.
*/
@@ -34175,6 +34175,8 @@
* REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE, REQUESTED_FRAME_RATE_CATEGORY_LOW,
* REQUESTED_FRAME_RATE_CATEGORY_NORMAL, and REQUESTED_FRAME_RATE_CATEGORY_HIGH.
* Note that the frame rate value is valid as long as the View is invalidated.
+ * Please also be aware that the requested frame rate will not propagate to
+ * child views when this API is used on a ViewGroup.
*
* @return REQUESTED_FRAME_RATE_CATEGORY_DEFAULT by default,
* or value passed to {@link #setRequestedFrameRate(float)}.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 0e1625a..f021bdf 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -6094,6 +6094,12 @@
}
boolean scrollToRectOrFocus(Rect rectangle, boolean immediate) {
+ if (mImeBackAnimationController.isAnimationInProgress()) {
+ // IME predictive back animation is currently in progress which means that scrollY is
+ // currently controlled by ImeBackAnimationController.
+ return false;
+ }
+
final Rect ci = mAttachInfo.mContentInsets;
final Rect vi = mAttachInfo.mVisibleInsets;
int scrollY = 0;
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index a4cea33..ab29df3 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -1051,6 +1051,52 @@
}
/**
+ * Registers callback for when user initialization has completed.
+ * Does nothing if the same callback is already registered.
+ *
+ * @param callback The callback to be registered
+ * @hide
+ */
+ public void registerUserInitializationCompleteCallback(
+ @NonNull IUserInitializationCompleteCallback callback) {
+ IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
+ }
+ }
+ try {
+ service.registerUserInitializationCompleteCallback(callback);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while registering userInitializationCompleteCallback. ", re);
+ }
+ }
+
+ /**
+ * Unregisters callback for when user initialization has completed.
+ *
+ * @param callback The callback to be unregistered
+ * @hide
+ */
+ public void unregisterUserInitializationCompleteCallback(
+ @NonNull IUserInitializationCompleteCallback callback) {
+ IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
+ }
+ }
+ try {
+ service.unregisterUserInitializationCompleteCallback(callback);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG,
+ "Error while unregistering userInitializationCompleteCallback. ", re);
+ }
+ }
+
+ /**
* Whether the current accessibility request comes from an
* {@link AccessibilityService} with the {@link AccessibilityServiceInfo#isAccessibilityTool}
* property set to true.
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 72a1fe4..bf79a2c 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -29,6 +29,7 @@
import android.view.accessibility.IAccessibilityManagerClient;
import android.view.accessibility.AccessibilityWindowAttributes;
import android.view.accessibility.IMagnificationConnection;
+import android.view.accessibility.IUserInitializationCompleteCallback;
import android.view.InputEvent;
import android.view.IWindow;
import android.view.MagnificationSpec;
@@ -192,4 +193,10 @@
@EnforcePermission("MANAGE_ACCESSIBILITY")
Bundle getA11yFeatureToTileMap(int userId);
+
+ @RequiresNoPermission
+ void registerUserInitializationCompleteCallback(IUserInitializationCompleteCallback callback);
+
+ @RequiresNoPermission
+ void unregisterUserInitializationCompleteCallback(IUserInitializationCompleteCallback callback);
}
diff --git a/core/java/android/view/accessibility/IUserInitializationCompleteCallback.aidl b/core/java/android/view/accessibility/IUserInitializationCompleteCallback.aidl
new file mode 100644
index 0000000..fe6c8e2
--- /dev/null
+++ b/core/java/android/view/accessibility/IUserInitializationCompleteCallback.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.accessibility;
+
+/**
+ * A callback for when a new user finishes initializing
+ * NOTE: Must remain a oneway interface, as it is called from system_server while holding a lock.
+ * oneway allows it to return immediately and not hold the lock for longer than is necessary.
+ * @hide
+ */
+
+oneway interface IUserInitializationCompleteCallback {
+
+ /**
+ * Called when a user initialization completes.
+ *
+ * @param userId the id of the initialized user
+ */
+ @RequiresNoPermission
+ void onUserInitializationComplete(int userId);
+}
diff --git a/core/java/android/webkit/flags.aconfig b/core/java/android/webkit/flags.aconfig
index defe61e..b21a490 100644
--- a/core/java/android/webkit/flags.aconfig
+++ b/core/java/android/webkit/flags.aconfig
@@ -9,3 +9,12 @@
bug: "319292658"
is_fixed_read_only: true
}
+
+flag {
+ name: "mainline_apis"
+ is_exported: true
+ namespace: "webview"
+ description: "New APIs required by WebViewBootstrap mainline module"
+ bug: "310653407"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl
index 58b5757..b8a11cf0 100644
--- a/core/java/android/window/ITaskFragmentOrganizerController.aidl
+++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl
@@ -47,6 +47,19 @@
void unregisterOrganizer(in ITaskFragmentOrganizer organizer);
/**
+ * Registers remote animations per transition type for the organizer. It will override the
+ * animations if the transition only contains windows that belong to the organized
+ * TaskFragments in the given Task.
+ */
+ void registerRemoteAnimations(in ITaskFragmentOrganizer organizer,
+ in RemoteAnimationDefinition definition);
+
+ /**
+ * Unregisters remote animations per transition type for the organizer.
+ */
+ void unregisterRemoteAnimations(in ITaskFragmentOrganizer organizer);
+
+ /**
* Saves the state in the system, where the state can be restored if the process of
* the TaskFragmentOrganizer is restarted.
*/
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index 027d323..4cc0d8a 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -34,6 +34,7 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
+import android.view.RemoteAnimationDefinition;
import android.view.WindowManager;
import com.android.window.flags.Flags;
@@ -225,6 +226,34 @@
}
/**
+ * Registers remote animations per transition type for the organizer. It will override the
+ * animations if the transition only contains windows that belong to the organized
+ * TaskFragments, and at least one of the transition window is embedded (not filling the Task).
+ * @hide
+ */
+ @CallSuper
+ public void registerRemoteAnimations(@NonNull RemoteAnimationDefinition definition) {
+ try {
+ getController().registerRemoteAnimations(mInterface, definition);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Unregisters remote animations per transition type for the organizer.
+ * @hide
+ */
+ @CallSuper
+ public void unregisterRemoteAnimations() {
+ try {
+ getController().unregisterRemoteAnimations(mInterface);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Saves the state in the system, where the state can be restored if the process of
* the TaskFragmentOrganizer is restarted.
*
diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig
index 6ce9725..cd31850 100644
--- a/core/java/android/window/flags/responsible_apis.aconfig
+++ b/core/java/android/window/flags/responsible_apis.aconfig
@@ -71,3 +71,11 @@
bug: "339720406"
}
+flag {
+ name: "bal_reduce_grace_period"
+ namespace: "responsible_apis"
+ description: "Changes to reduce or ideally remove the grace period exemption."
+ bug: "362575865"
+}
+
+
diff --git a/core/java/android/window/flags/wallpaper_manager.aconfig b/core/java/android/window/flags/wallpaper_manager.aconfig
index 8c6721a..efacc34 100644
--- a/core/java/android/window/flags/wallpaper_manager.aconfig
+++ b/core/java/android/window/flags/wallpaper_manager.aconfig
@@ -49,3 +49,13 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "avoid_rebinding_intentionally_disconnected_wallpaper"
+ namespace: "systemui"
+ description: "Prevents rebinding with intentionally disconnected wallpaper services."
+ bug: "332871851"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index d474c6d..53ef49b 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -127,7 +127,7 @@
private Runnable mWaitForFinishTimedOut;
private static class JankInfo {
- final long frameVsyncId;
+ long frameVsyncId;
long totalDurationNanos;
boolean isFirstFrame;
boolean hwuiCallbackFired;
@@ -135,42 +135,29 @@
@JankType int jankType;
@RefreshRate int refreshRate;
- static JankInfo createFromHwuiCallback(
- long frameVsyncId, long totalDurationNanos, boolean isFirstFrame) {
- return new JankInfo(frameVsyncId).update(totalDurationNanos, isFirstFrame);
+ static JankInfo createFromHwuiCallback(long frameVsyncId, long totalDurationNanos,
+ boolean isFirstFrame) {
+ return new JankInfo(frameVsyncId, true, false, JANK_NONE, UNKNOWN_REFRESH_RATE,
+ totalDurationNanos, isFirstFrame);
}
- static JankInfo createFromSurfaceControlCallback(SurfaceControl.JankData jankStat) {
- return new JankInfo(jankStat.frameVsyncId).update(jankStat);
+ static JankInfo createFromSurfaceControlCallback(long frameVsyncId,
+ @JankType int jankType, @RefreshRate int refreshRate) {
+ return new JankInfo(
+ frameVsyncId, false, true, jankType, refreshRate, 0, false /* isFirstFrame */);
}
- private JankInfo(long frameVsyncId) {
+ private JankInfo(long frameVsyncId, boolean hwuiCallbackFired,
+ boolean surfaceControlCallbackFired, @JankType int jankType,
+ @RefreshRate int refreshRate,
+ long totalDurationNanos, boolean isFirstFrame) {
this.frameVsyncId = frameVsyncId;
- this.hwuiCallbackFired = false;
- this.surfaceControlCallbackFired = false;
- this.jankType = JANK_NONE;
- this.refreshRate = UNKNOWN_REFRESH_RATE;
- this.totalDurationNanos = 0;
- this.isFirstFrame = false;
- }
-
- private JankInfo update(SurfaceControl.JankData jankStat) {
- this.surfaceControlCallbackFired = true;
- this.jankType = jankStat.jankType;
- this.refreshRate = DisplayRefreshRate.getRefreshRate(jankStat.frameIntervalNs);
- if (Flags.useSfFrameDuration()) {
- this.totalDurationNanos = jankStat.actualAppFrameTimeNs;
- }
- return this;
- }
-
- private JankInfo update(long totalDurationNanos, boolean isFirstFrame) {
- this.hwuiCallbackFired = true;
- if (!Flags.useSfFrameDuration()) {
- this.totalDurationNanos = totalDurationNanos;
- }
+ this.hwuiCallbackFired = hwuiCallbackFired;
+ this.surfaceControlCallbackFired = surfaceControlCallbackFired;
+ this.jankType = jankType;
+ this.refreshRate = refreshRate;
+ this.totalDurationNanos = totalDurationNanos;
this.isFirstFrame = isFirstFrame;
- return this;
}
@Override
@@ -470,12 +457,16 @@
if (!isInRange(jankStat.frameVsyncId)) {
continue;
}
+ int refreshRate = DisplayRefreshRate.getRefreshRate(jankStat.frameIntervalNs);
JankInfo info = findJankInfo(jankStat.frameVsyncId);
if (info != null) {
- info.update(jankStat);
+ info.surfaceControlCallbackFired = true;
+ info.jankType = jankStat.jankType;
+ info.refreshRate = refreshRate;
} else {
mJankInfos.put((int) jankStat.frameVsyncId,
- JankInfo.createFromSurfaceControlCallback(jankStat));
+ JankInfo.createFromSurfaceControlCallback(
+ jankStat.frameVsyncId, jankStat.jankType, refreshRate));
}
}
processJankInfos();
@@ -522,7 +513,9 @@
}
JankInfo info = findJankInfo(frameVsyncId);
if (info != null) {
- info.update(totalDurationNanos, isFirstFrame);
+ info.hwuiCallbackFired = true;
+ info.totalDurationNanos = totalDurationNanos;
+ info.isFirstFrame = isFirstFrame;
} else {
mJankInfos.put((int) frameVsyncId, JankInfo.createFromHwuiCallback(
frameVsyncId, totalDurationNanos, isFirstFrame));
diff --git a/core/java/com/android/internal/jank/flags.aconfig b/core/java/com/android/internal/jank/flags.aconfig
deleted file mode 100644
index 676bb70..0000000
--- a/core/java/com/android/internal/jank/flags.aconfig
+++ /dev/null
@@ -1,9 +0,0 @@
-package: "com.android.internal.jank"
-container: "system"
-
-flag {
- name: "use_sf_frame_duration"
- namespace: "android_platform_window_surfaces"
- description: "Whether to get the frame duration from SurfaceFlinger, or HWUI"
- bug: "354763298"
-}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index b9cc457..2acda8a 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -631,21 +631,20 @@
*/
private static Runnable forkSystemServer(String abiList, String socketName,
ZygoteServer zygoteServer) {
- long capabilities = posixCapabilitiesAsBits(
- OsConstants.CAP_IPC_LOCK,
- OsConstants.CAP_KILL,
- OsConstants.CAP_NET_ADMIN,
- OsConstants.CAP_NET_BIND_SERVICE,
- OsConstants.CAP_NET_BROADCAST,
- OsConstants.CAP_NET_RAW,
- OsConstants.CAP_SYS_MODULE,
- OsConstants.CAP_SYS_NICE,
- OsConstants.CAP_SYS_PTRACE,
- OsConstants.CAP_SYS_TIME,
- OsConstants.CAP_SYS_TTY_CONFIG,
- OsConstants.CAP_WAKE_ALARM,
- OsConstants.CAP_BLOCK_SUSPEND
- );
+ long capabilities =
+ (1L << OsConstants.CAP_IPC_LOCK) |
+ (1L << OsConstants.CAP_KILL) |
+ (1L << OsConstants.CAP_NET_ADMIN) |
+ (1L << OsConstants.CAP_NET_BIND_SERVICE) |
+ (1L << OsConstants.CAP_NET_BROADCAST) |
+ (1L << OsConstants.CAP_NET_RAW) |
+ (1L << OsConstants.CAP_SYS_MODULE) |
+ (1L << OsConstants.CAP_SYS_NICE) |
+ (1L << OsConstants.CAP_SYS_PTRACE) |
+ (1L << OsConstants.CAP_SYS_TIME) |
+ (1L << OsConstants.CAP_SYS_TTY_CONFIG) |
+ (1L << OsConstants.CAP_WAKE_ALARM) |
+ (1L << OsConstants.CAP_BLOCK_SUSPEND);
/* Containers run without some capabilities, so drop any caps that are not available. */
StructCapUserHeader header = new StructCapUserHeader(
OsConstants._LINUX_CAPABILITY_VERSION_3, 0);
@@ -742,20 +741,6 @@
}
/**
- * Gets the bit array representation of the provided list of POSIX capabilities.
- */
- private static long posixCapabilitiesAsBits(int... capabilities) {
- long result = 0;
- for (int capability : capabilities) {
- if ((capability < 0) || (capability > OsConstants.CAP_LAST_CAP)) {
- throw new IllegalArgumentException(String.valueOf(capability));
- }
- result |= (1L << capability);
- }
- return result;
- }
-
- /**
* This is the entry point for a Zygote process. It creates the Zygote server, loads resources,
* and handles other tasks related to preparing the process for forking into applications.
*
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index e14249c..e0529b3 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -3333,6 +3333,7 @@
Bundle args = new Bundle();
args.putInt(Intent.EXTRA_ASSIST_INPUT_DEVICE_ID, event.getDeviceId());
args.putLong(Intent.EXTRA_TIME, event.getEventTime());
+ args.putBoolean(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD, true);
((SearchManager) getContext().getSystemService(Context.SEARCH_SERVICE))
.launchAssist(args);
return true;
diff --git a/core/java/com/android/internal/statusbar/StatusBarIcon.java b/core/java/com/android/internal/statusbar/StatusBarIcon.java
index f8a1436..1938cdb 100644
--- a/core/java/com/android/internal/statusbar/StatusBarIcon.java
+++ b/core/java/com/android/internal/statusbar/StatusBarIcon.java
@@ -51,6 +51,19 @@
ResourceIcon
}
+ public enum Shape {
+ /**
+ * Icon view should use WRAP_CONTENT -- so that the horizontal space occupied depends on the
+ * icon's shape (skinny/fat icons take less/more). Most icons will want to use this option
+ * for a nicer-looking overall spacing in the status bar, as long as the icon is "known"
+ * (i.e. not coming from a 3P package).
+ */
+ WRAP_CONTENT,
+
+ /** Icon should always be displayed in a space as wide as the status bar is tall. */
+ FIXED_SPACE,
+ }
+
public UserHandle user;
public String pkg;
public Icon icon;
@@ -59,6 +72,7 @@
public int number;
public CharSequence contentDescription;
public Type type;
+ public Shape shape;
/**
* Optional {@link Drawable} corresponding to {@link #icon}. This field is not parcelable, so
@@ -68,7 +82,7 @@
@Nullable public Drawable preloadedIcon;
public StatusBarIcon(UserHandle user, String resPackage, Icon icon, int iconLevel, int number,
- CharSequence contentDescription, Type type) {
+ CharSequence contentDescription, Type type, Shape shape) {
if (icon.getType() == Icon.TYPE_RESOURCE
&& TextUtils.isEmpty(icon.getResPackage())) {
// This is an odd situation where someone's managed to hand us an icon without a
@@ -83,6 +97,13 @@
this.number = number;
this.contentDescription = contentDescription;
this.type = type;
+ this.shape = shape;
+ }
+
+ public StatusBarIcon(UserHandle user, String resPackage, Icon icon, int iconLevel, int number,
+ CharSequence contentDescription, Type type) {
+ this(user, resPackage, icon, iconLevel, number, contentDescription, type,
+ Shape.WRAP_CONTENT);
}
public StatusBarIcon(String iconPackage, UserHandle user,
@@ -107,7 +128,7 @@
@Override
public StatusBarIcon clone() {
StatusBarIcon that = new StatusBarIcon(this.user, this.pkg, this.icon,
- this.iconLevel, this.number, this.contentDescription, this.type);
+ this.iconLevel, this.number, this.contentDescription, this.type, this.shape);
that.visible = this.visible;
that.preloadedIcon = this.preloadedIcon;
return that;
@@ -129,6 +150,7 @@
this.number = in.readInt();
this.contentDescription = in.readCharSequence();
this.type = Type.valueOf(in.readString());
+ this.shape = Shape.valueOf(in.readString());
}
public void writeToParcel(Parcel out, int flags) {
@@ -140,6 +162,7 @@
out.writeInt(this.number);
out.writeCharSequence(this.contentDescription);
out.writeString(this.type.name());
+ out.writeString(this.shape.name());
}
public int describeContents() {
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 2abdd57..90cb10a 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -108,6 +108,7 @@
"libtracing_perfetto",
"libharfbuzz_ng",
"liblog",
+ "libmediautils",
"libminikin",
"libz",
"server_configurable_flags",
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 638591f..46710b5 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -17,6 +17,7 @@
//#define LOG_NDEBUG 0
+#include <atomic>
#define LOG_TAG "AudioSystem-JNI"
#include <android/binder_ibinder_jni.h>
#include <android/binder_libbinder.h>
@@ -34,15 +35,16 @@
#include <media/AudioContainers.h>
#include <media/AudioPolicy.h>
#include <media/AudioSystem.h>
+#include <mediautils/jthread.h>
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedLocalRef.h>
#include <nativehelper/ScopedPrimitiveArray.h>
#include <nativehelper/jni_macros.h>
#include <system/audio.h>
#include <system/audio_policy.h>
+#include <sys/system_properties.h>
#include <utils/Log.h>
-#include <thread>
#include <optional>
#include <sstream>
#include <memory>
@@ -57,6 +59,7 @@
#include "android_media_AudioMixerAttributes.h"
#include "android_media_AudioProfile.h"
#include "android_media_MicrophoneInfo.h"
+#include "android_media_JNIUtils.h"
#include "android_util_Binder.h"
#include "core_jni_helpers.h"
@@ -3375,42 +3378,53 @@
class JavaSystemPropertyListener {
public:
JavaSystemPropertyListener(JNIEnv* env, jobject javaCallback, std::string sysPropName) :
- mCallback(env->NewGlobalRef(javaCallback)),
- mCachedProperty(android::base::CachedProperty{std::move(sysPropName)}) {
- mListenerThread = std::thread([this]() mutable {
- JNIEnv* threadEnv = GetOrAttachJNIEnvironment(gVm);
- while (!mCleanupSignal.load()) {
- using namespace std::chrono_literals;
- // 1s timeout so this thread can read the cleanup signal to (slowly) be able to
- // be destroyed.
- std::string newVal = mCachedProperty.WaitForChange(1000ms) ?: "";
- if (newVal != "" && mLastVal != newVal) {
- threadEnv->CallVoidMethod(mCallback, gRunnableClassInfo.run);
- mLastVal = std::move(newVal);
+ mCallback {javaCallback, env},
+ mPi {__system_property_find(sysPropName.c_str())},
+ mListenerThread([this](mediautils::stop_token stok) mutable {
+ static const struct timespec close_delay = { .tv_sec = 1 };
+ while (!stok.stop_requested()) {
+ uint32_t old_serial = mSerial.load();
+ uint32_t new_serial;
+ if (__system_property_wait(mPi, old_serial, &new_serial, &close_delay)) {
+ while (new_serial > old_serial) {
+ if (mSerial.compare_exchange_weak(old_serial, new_serial)) {
+ fireUpdate();
+ break;
+ }
+ }
+ }
}
- }
- });
- }
+ }) {}
- ~JavaSystemPropertyListener() {
- mCleanupSignal.store(true);
- mListenerThread.join();
- JNIEnv* env = GetOrAttachJNIEnvironment(gVm);
- env->DeleteGlobalRef(mCallback);
+ void triggerUpdateIfChanged() {
+ uint32_t old_serial = mSerial.load();
+ uint32_t new_serial = __system_property_serial(mPi);
+ while (new_serial > old_serial) {
+ if (mSerial.compare_exchange_weak(old_serial, new_serial)) {
+ fireUpdate();
+ break;
+ }
+ }
}
private:
- jobject mCallback;
- android::base::CachedProperty mCachedProperty;
- std::thread mListenerThread;
- std::atomic<bool> mCleanupSignal{false};
- std::string mLastVal = "";
+ void fireUpdate() {
+ const auto threadEnv = GetOrAttachJNIEnvironment(gVm);
+ threadEnv->CallVoidMethod(mCallback.get(), gRunnableClassInfo.run);
+ }
+
+ // Should outlive thread object
+ const GlobalRef mCallback;
+ const prop_info * const mPi;
+ std::atomic<uint32_t> mSerial = 0;
+ const mediautils::jthread mListenerThread;
};
+// A logical set keyed by address
std::vector<std::unique_ptr<JavaSystemPropertyListener>> gSystemPropertyListeners;
std::mutex gSysPropLock{};
-static void android_media_AudioSystem_listenForSystemPropertyChange(JNIEnv *env, jobject thiz,
+static jlong android_media_AudioSystem_listenForSystemPropertyChange(JNIEnv *env, jobject thiz,
jstring sysProp,
jobject javaCallback) {
ScopedUtfChars sysPropChars{env, sysProp};
@@ -3418,6 +3432,19 @@
std::string{sysPropChars.c_str()});
std::unique_lock _l{gSysPropLock};
gSystemPropertyListeners.push_back(std::move(listener));
+ return reinterpret_cast<jlong>(gSystemPropertyListeners.back().get());
+}
+
+static void android_media_AudioSystem_triggerSystemPropertyUpdate(JNIEnv *env, jobject thiz,
+ jlong nativeHandle) {
+ std::unique_lock _l{gSysPropLock};
+ const auto iter = std::find_if(gSystemPropertyListeners.begin(), gSystemPropertyListeners.end(),
+ [nativeHandle](const auto& x) { return reinterpret_cast<jlong>(x.get()) == nativeHandle; });
+ if (iter != gSystemPropertyListeners.end()) {
+ (*iter)->triggerUpdateIfChanged();
+ } else {
+ jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid handle");
+ }
}
@@ -3595,8 +3622,11 @@
MAKE_AUDIO_SYSTEM_METHOD(setBluetoothVariableLatencyEnabled),
MAKE_AUDIO_SYSTEM_METHOD(isBluetoothVariableLatencyEnabled),
MAKE_JNI_NATIVE_METHOD("listenForSystemPropertyChange",
- "(Ljava/lang/String;Ljava/lang/Runnable;)V",
+ "(Ljava/lang/String;Ljava/lang/Runnable;)J",
android_media_AudioSystem_listenForSystemPropertyChange),
+ MAKE_JNI_NATIVE_METHOD("triggerSystemPropertyUpdate",
+ "(J)V",
+ android_media_AudioSystem_triggerSystemPropertyUpdate),
};
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 17c89f8..0f53164 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -2089,11 +2089,9 @@
jobjectArray jJankDataArray = env->NewObjectArray(jankData.size(),
gJankDataClassInfo.clazz, nullptr);
for (size_t i = 0; i < jankData.size(); i++) {
- jobject jJankData =
- env->NewObject(gJankDataClassInfo.clazz, gJankDataClassInfo.ctor,
- jankData[i].frameVsyncId, jankData[i].jankType,
- jankData[i].frameIntervalNs, jankData[i].scheduledAppFrameTimeNs,
- jankData[i].actualAppFrameTimeNs);
+ jobject jJankData = env->NewObject(gJankDataClassInfo.clazz, gJankDataClassInfo.ctor,
+ jankData[i].frameVsyncId, jankData[i].jankType,
+ jankData[i].frameIntervalNs);
env->SetObjectArrayElement(jJankDataArray, i, jJankData);
env->DeleteLocalRef(jJankData);
}
@@ -2729,7 +2727,7 @@
jclass jankDataClazz =
FindClassOrDie(env, "android/view/SurfaceControl$JankData");
gJankDataClassInfo.clazz = MakeGlobalRefOrDie(env, jankDataClazz);
- gJankDataClassInfo.ctor = GetMethodIDOrDie(env, gJankDataClassInfo.clazz, "<init>", "(JIJJJ)V");
+ gJankDataClassInfo.ctor = GetMethodIDOrDie(env, gJankDataClassInfo.clazz, "<init>", "(JIJ)V");
jclass onJankDataListenerClazz =
FindClassOrDie(env, "android/view/SurfaceControl$OnJankDataListener");
gJankDataListenerClassInfo.clazz = MakeGlobalRefOrDie(env, onJankDataListenerClazz);
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index fba0d81..7ad18b8 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -17,6 +17,7 @@
#define LOG_TAG "NativeLibraryHelper"
//#define LOG_NDEBUG 0
+#include <android-base/properties.h>
#include <androidfw/ApkParsing.h>
#include <androidfw/ZipFileRO.h>
#include <androidfw/ZipUtils.h>
@@ -36,6 +37,7 @@
#include <zlib.h>
#include <memory>
+#include <string>
#include "com_android_internal_content_FileSystemUtils.h"
#include "core_jni_helpers.h"
@@ -125,72 +127,10 @@
return INSTALL_SUCCEEDED;
}
-/*
- * Copy the native library if needed.
- *
- * This function assumes the library and path names passed in are considered safe.
- */
-static install_status_t
-copyFileIfChanged(JNIEnv *env, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntry, const char* fileName)
-{
- static const size_t kPageSize = getpagesize();
- void** args = reinterpret_cast<void**>(arg);
- jstring* javaNativeLibPath = (jstring*) args[0];
- jboolean extractNativeLibs = *(jboolean*) args[1];
- jboolean debuggable = *(jboolean*) args[2];
-
- ScopedUtfChars nativeLibPath(env, *javaNativeLibPath);
-
- uint32_t uncompLen;
- uint32_t when;
- uint32_t crc;
-
- uint16_t method;
- off64_t offset;
- uint16_t extraFieldLength;
- if (!zipFile->getEntryInfo(zipEntry, &method, &uncompLen, nullptr, &offset, &when, &crc,
- &extraFieldLength)) {
- ALOGE("Couldn't read zip entry info\n");
- return INSTALL_FAILED_INVALID_APK;
- }
-
- // Always extract wrap.sh for debuggable, even if extractNativeLibs=false. This makes it
- // easier to use wrap.sh because it only works when it is extracted, see
- // frameworks/base/services/core/java/com/android/server/am/ProcessList.java.
- bool forceExtractCurrentFile = debuggable && strcmp(fileName, "wrap.sh") == 0;
-
- if (!extractNativeLibs && !forceExtractCurrentFile) {
- // check if library is uncompressed and page-aligned
- if (method != ZipFileRO::kCompressStored) {
- ALOGE("Library '%s' is compressed - will not be able to open it directly from apk.\n",
- fileName);
- return INSTALL_FAILED_INVALID_APK;
- }
-
- if (offset % kPageSize != 0) {
- ALOGE("Library '%s' is not PAGE(%zu)-aligned - will not be able to open it directly "
- "from apk.\n", fileName, kPageSize);
- return INSTALL_FAILED_INVALID_APK;
- }
-
-#ifdef ENABLE_PUNCH_HOLES
- // if library is uncompressed, punch hole in it in place
- if (!punchHolesInElf64(zipFile->getZipFileName(), offset)) {
- ALOGW("Failed to punch uncompressed elf file :%s inside apk : %s at offset: "
- "%" PRIu64 "",
- fileName, zipFile->getZipFileName(), offset);
- }
-
- // if extra field for this zip file is present with some length, possibility is that it is
- // padding added for zip alignment. Punch holes there too.
- if (!punchHolesInZip(zipFile->getZipFileName(), offset, extraFieldLength)) {
- ALOGW("Failed to punch apk : %s at extra field", zipFile->getZipFileName());
- }
-#endif // ENABLE_PUNCH_HOLES
-
- return INSTALL_SUCCEEDED;
- }
-
+static install_status_t extractNativeLibFromApk(ZipFileRO* zipFile, ZipEntryRO zipEntry,
+ const char* fileName,
+ const std::string nativeLibPath, uint32_t when,
+ uint32_t uncompLen, uint32_t crc) {
// Build local file path
const size_t fileNameLen = strlen(fileName);
char localFileName[nativeLibPath.size() + fileNameLen + 2];
@@ -313,6 +253,88 @@
}
/*
+ * Copy the native library if needed.
+ *
+ * This function assumes the library and path names passed in are considered safe.
+ */
+static install_status_t copyFileIfChanged(JNIEnv* env, void* arg, ZipFileRO* zipFile,
+ ZipEntryRO zipEntry, const char* fileName) {
+ static const size_t kPageSize = getpagesize();
+ void** args = reinterpret_cast<void**>(arg);
+ jstring* javaNativeLibPath = (jstring*)args[0];
+ jboolean extractNativeLibs = *(jboolean*)args[1];
+ jboolean debuggable = *(jboolean*)args[2];
+ jboolean app_compat_16kb = *(jboolean*)args[3];
+ install_status_t ret = INSTALL_SUCCEEDED;
+
+ ScopedUtfChars nativeLibPath(env, *javaNativeLibPath);
+
+ uint32_t uncompLen;
+ uint32_t when;
+ uint32_t crc;
+
+ uint16_t method;
+ off64_t offset;
+ uint16_t extraFieldLength;
+ if (!zipFile->getEntryInfo(zipEntry, &method, &uncompLen, nullptr, &offset, &when, &crc,
+ &extraFieldLength)) {
+ ALOGE("Couldn't read zip entry info\n");
+ return INSTALL_FAILED_INVALID_APK;
+ }
+
+ // Always extract wrap.sh for debuggable, even if extractNativeLibs=false. This makes it
+ // easier to use wrap.sh because it only works when it is extracted, see
+ // frameworks/base/services/core/java/com/android/server/am/ProcessList.java.
+ bool forceExtractCurrentFile = debuggable && strcmp(fileName, "wrap.sh") == 0;
+
+ if (!extractNativeLibs && !forceExtractCurrentFile) {
+ // check if library is uncompressed and page-aligned
+ if (method != ZipFileRO::kCompressStored) {
+ ALOGE("Library '%s' is compressed - will not be able to open it directly from apk.\n",
+ fileName);
+ return INSTALL_FAILED_INVALID_APK;
+ }
+
+ if (offset % kPageSize != 0) {
+ // If the library is zip-aligned correctly for 4kb devices and app compat is
+ // enabled, on 16kb devices fallback to extraction
+ if (offset % 0x1000 == 0 && app_compat_16kb) {
+ ALOGI("16kB AppCompat: Library '%s' is not PAGE(%zu)-aligned - falling back to "
+ "extraction from apk\n",
+ fileName, kPageSize);
+ return extractNativeLibFromApk(zipFile, zipEntry, fileName, nativeLibPath.c_str(),
+ when, uncompLen, crc);
+ }
+
+ ALOGE("Library '%s' is not PAGE(%zu)-aligned - will not be able to open it directly "
+ "from apk.\n",
+ fileName, kPageSize);
+ return INSTALL_FAILED_INVALID_APK;
+ }
+
+#ifdef ENABLE_PUNCH_HOLES
+ // if library is uncompressed, punch hole in it in place
+ if (!punchHolesInElf64(zipFile->getZipFileName(), offset)) {
+ ALOGW("Failed to punch uncompressed elf file :%s inside apk : %s at offset: "
+ "%" PRIu64 "",
+ fileName, zipFile->getZipFileName(), offset);
+ }
+
+ // if extra field for this zip file is present with some length, possibility is that it is
+ // padding added for zip alignment. Punch holes there too.
+ if (!punchHolesInZip(zipFile->getZipFileName(), offset, extraFieldLength)) {
+ ALOGW("Failed to punch apk : %s at extra field", zipFile->getZipFileName());
+ }
+#endif // ENABLE_PUNCH_HOLES
+
+ return INSTALL_SUCCEEDED;
+ }
+
+ return extractNativeLibFromApk(zipFile, zipEntry, fileName, nativeLibPath.c_str(), when,
+ uncompLen, crc);
+}
+
+/*
* An iterator over all shared libraries in a zip file. An entry is
* considered to be a shared library if all of the conditions below are
* satisfied :
@@ -498,12 +520,24 @@
return status;
}
+static inline bool app_compat_16kb_enabled() {
+ static const size_t kPageSize = getpagesize();
+
+ // App compat is only applicable on 16kb-page-size devices.
+ if (kPageSize != 0x4000) {
+ return false;
+ }
+
+ return android::base::GetBoolProperty("bionic.linker.16kb.app_compat.enabled", false);
+}
+
static jint
com_android_internal_content_NativeLibraryHelper_copyNativeBinaries(JNIEnv *env, jclass clazz,
jlong apkHandle, jstring javaNativeLibPath, jstring javaCpuAbi,
jboolean extractNativeLibs, jboolean debuggable)
{
- void* args[] = { &javaNativeLibPath, &extractNativeLibs, &debuggable };
+ jboolean app_compat_16kb = app_compat_16kb_enabled();
+ void* args[] = { &javaNativeLibPath, &extractNativeLibs, &debuggable, &app_compat_16kb };
return (jint) iterateOverNativeFiles(env, apkHandle, javaCpuAbi, debuggable,
copyFileIfChanged, reinterpret_cast<void*>(args));
}
diff --git a/core/res/Android.bp b/core/res/Android.bp
index 17d7bfa..bcc0a97 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -167,7 +167,6 @@
"android.os.flags-aconfig",
"android.os.vibrator.flags-aconfig",
"android.media.tv.flags-aconfig",
- "com.android.hardware.input.input-aconfig",
],
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 854deb0..17ff2eb 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3766,7 +3766,6 @@
privileged app such as the Assistant app.
<p>Protection level: internal|role
<p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
- @FlaggedApi("android.app.admin.flags.assist_content_user_restriction_enabled")
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_ASSIST_CONTENT"
android:protectionLevel="internal|role" />
@@ -4027,7 +4026,6 @@
APIs protected by this permission on users different to the calling user.
<p>Protection level: internal|role
<p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
- @FlaggedApi("android.app.admin.flags.esim_management_enabled")
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS"
android:protectionLevel="internal|role" />
@@ -8172,8 +8170,7 @@
<p>Not for use by third-party applications.
@hide -->
<permission android:name="android.permission.MANAGE_KEY_GESTURES"
- android:protectionLevel="signature"
- android:featureFlag="com.android.hardware.input.manage_key_gestures" />
+ android:protectionLevel="signature" />
<uses-permission android:name="android.permission.HANDLE_QUERY_PACKAGE_RESTART" />
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/AndroidManifest.xml b/core/tests/batterystatstests/BatteryStatsViewer/AndroidManifest.xml
index 94bde68..127dbfd 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/AndroidManifest.xml
+++ b/core/tests/batterystatstests/BatteryStatsViewer/AndroidManifest.xml
@@ -20,6 +20,7 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.BATTERY_STATS"/>
+ <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
@@ -31,7 +32,8 @@
<activity android:name=".BatteryConsumerPickerActivity"
android:label="Battery Stats"
android:launchMode="singleTop"
- android:exported="true">
+ android:exported="true"
+ android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
@@ -41,5 +43,25 @@
<activity android:name=".BatteryStatsViewerActivity"
android:label="Battery Stats"
android:parentActivityName=".BatteryConsumerPickerActivity"/>
+
+ <activity android:name=".TrampolineActivity"
+ android:exported="true"
+ android:theme="@android:style/Theme.NoDisplay">
+ <intent-filter>
+ <action android:name="com.android.settings.action.IA_SETTINGS"/>
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+
+ <meta-data android:name="com.android.settings.category"
+ android:value="com.android.settings.category.ia.development" />
+ <meta-data android:name="com.android.settings.title"
+ android:resource="@string/settings_title" />
+ <meta-data android:name="com.android.settings.summary"
+ android:resource="@string/settings_summary" />
+ <meta-data android:name="com.android.settings.group_key"
+ android:value="debug_debugging_category" />
+ <meta-data android:name="com.android.settings.order"
+ android:value="2" />
+ </activity>
</application>
</manifest>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/values/strings.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/values/strings.xml
new file mode 100644
index 0000000..c23c148
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/values/strings.xml
@@ -0,0 +1,20 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settings_title">Launch Battery Stats Viewer</string>
+ <string name="settings_summary">The Battery Stats Viewer will be visible in the Launcher after it is opened once.</string>
+</resources>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/TrampolineActivity.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/TrampolineActivity.java
new file mode 100644
index 0000000..b016488
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/TrampolineActivity.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.core.batterystatsviewer;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+
+public class TrampolineActivity extends Activity {
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ showLauncherIcon();
+ launchMainActivity();
+ }
+
+ private void showLauncherIcon() {
+ PackageManager pm = getPackageManager();
+ pm.setComponentEnabledSetting(new ComponentName(this, BatteryConsumerPickerActivity.class),
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ PackageManager.DONT_KILL_APP);
+ }
+
+ private void launchMainActivity() {
+ startActivity(new Intent(this, BatteryConsumerPickerActivity.class));
+ }
+}
diff --git a/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java b/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java
index 4d9b591c..00ffda8 100644
--- a/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java
+++ b/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java
@@ -254,11 +254,8 @@
float progress = 0.5f;
mBackAnimationController.onBackProgressed(new BackEvent(100f, 0f, progress, EDGE_LEFT));
// verify correct ime insets manipulation
- float interpolatedProgress = BACK_GESTURE.getInterpolation(progress);
- int expectedInset =
- (int) (IME_HEIGHT - interpolatedProgress * PEEK_FRACTION * IME_HEIGHT);
verify(mWindowInsetsAnimationController, times(1)).setInsetsAndAlpha(
- eq(Insets.of(0, 0, 0, expectedInset)), eq(1f), anyFloat());
+ eq(Insets.of(0, 0, 0, getImeHeight(progress))), eq(1f), anyFloat());
}
@Test
@@ -268,12 +265,13 @@
WindowInsetsAnimationControlListener animationControlListener = startBackGesture();
// progress back gesture
- mBackAnimationController.onBackProgressed(new BackEvent(100f, 0f, 0.5f, EDGE_LEFT));
+ float progress = 0.5f;
+ mBackAnimationController.onBackProgressed(new BackEvent(100f, 0f, progress, EDGE_LEFT));
// commit back gesture
mBackAnimationController.onBackInvoked();
- // verify setInsetsAndAlpha never called due onReady delayed
+ // verify setInsetsAndAlpha never called due to onReady delayed
verify(mWindowInsetsAnimationController, never()).setInsetsAndAlpha(any(), anyInt(),
anyFloat());
verify(mInsetsController, never()).setPredictiveBackImeHideAnimInProgress(eq(true));
@@ -283,7 +281,7 @@
// verify setInsetsAndAlpha immediately called
verify(mWindowInsetsAnimationController, times(1)).setInsetsAndAlpha(
- eq(Insets.of(0, 0, 0, IME_HEIGHT)), eq(1f), anyFloat());
+ eq(Insets.of(0, 0, 0, getImeHeight(progress))), eq(1f), anyFloat());
// verify post-commit hide anim has started
verify(mInsetsController, times(1)).setPredictiveBackImeHideAnimInProgress(eq(true));
});
@@ -319,4 +317,9 @@
return animationControlListener.getValue();
}
+
+ private int getImeHeight(float gestureProgress) {
+ float interpolatedProgress = BACK_GESTURE.getInterpolation(gestureProgress);
+ return (int) (IME_HEIGHT - interpolatedProgress * PEEK_FRACTION * IME_HEIGHT);
+ }
}
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index ce7e858..bec8b1f 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -1022,7 +1022,7 @@
}
@Test
- public void testImeRequestedVisibleDuringPredictiveBackAnim() {
+ public void testImeRequestedVisibleDuringPredictiveBackAnimWithoutCallback() {
prepareControls();
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
// show ime as initial state
@@ -1051,6 +1051,42 @@
}
@Test
+ public void testImeRequestedInvisibleDuringPredictiveBackAnimWithCallback() {
+ prepareControls();
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ // set WindowInsetsAnimationCallback on ViewRoot
+ mViewRoot.getView().setWindowInsetsAnimationCallback(
+ new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
+ @Override
+ public WindowInsets onProgress(
+ @NonNull WindowInsets insets,
+ @NonNull List<WindowInsetsAnimation> runningAnimations) {
+ return insets;
+ }
+ });
+
+ // show ime as initial state
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ mController.cancelExistingAnimations(); // fast forward show animation
+ assertTrue(mController.getState().peekSource(ID_IME).isVisible());
+
+ // start control request (for predictive back animation)
+ WindowInsetsAnimationControlListener listener =
+ mock(WindowInsetsAnimationControlListener.class);
+ mController.controlWindowInsetsAnimation(ime(), /*cancellationSignal*/ null,
+ listener, /*fromIme*/ false, /*duration*/ -1, /*interpolator*/ null,
+ ANIMATION_TYPE_USER, /*fromPredictiveBack*/ true);
+
+ // Verify that onReady is called (after next predraw)
+ mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
+ verify(listener).onReady(notNull(), eq(ime()));
+
+ // verify that insets are requested invisible during animation
+ assertFalse(isRequestedVisible(mController, ime()));
+ });
+ }
+
+ @Test
public void testImeShowRequestCancelsPredictiveBackPostCommitAnim() {
prepareControls();
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
@@ -1131,6 +1167,37 @@
});
}
+ @Test
+ public void testPredictiveBackControlRequestCancelledDuringImeHideAnim() {
+ prepareControls();
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ // show ime as initial state
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+ }
+ mController.cancelExistingAnimations(); // fast forward show animation
+ mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
+ assertTrue(mController.getState().peekSource(ID_IME).isVisible());
+
+ // start IME hide animation
+ mController.hide(ime(), true /* fromIme */, null /* statsToken */);
+ assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ime()));
+
+ // start control request (for predictive back animation)
+ WindowInsetsAnimationControlListener listener =
+ mock(WindowInsetsAnimationControlListener.class);
+ mController.controlWindowInsetsAnimation(ime(), /*cancellationSignal*/ null,
+ listener, /*fromIme*/ false, /*duration*/ -1, /*interpolator*/ null,
+ ANIMATION_TYPE_USER, /*fromPredictiveBack*/ true);
+
+ // verify that control request is cancelled and animation type remains HIDE
+ verify(listener).onCancelled(any());
+ assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ime()));
+ });
+ }
+
private void waitUntilNextFrame() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
Choreographer.getMainThreadInstance().postCallback(Choreographer.CALLBACK_COMMIT,
diff --git a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
index c3a5b19c94..499caf5 100644
--- a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
@@ -359,7 +359,7 @@
tracker.end(FrameTracker.REASON_END_NORMAL);
// Send incomplete callback for 102L
- sendSfFrame(tracker, 4, 102L, JANK_NONE);
+ sendSfFrame(tracker, 102L, JANK_NONE);
// Send janky but complete callbck fo 103L
sendFrame(tracker, 50, JANK_APP_DEADLINE_MISSED, 103L);
@@ -629,7 +629,7 @@
if (!tracker.mSurfaceOnly) {
sendHwuiFrame(tracker, durationMillis, vsyncId, firstWindowFrame);
}
- sendSfFrame(tracker, durationMillis, vsyncId, jankType);
+ sendSfFrame(tracker, vsyncId, jankType);
}
private void sendHwuiFrame(FrameTracker tracker, long durationMillis, long vsyncId,
@@ -645,13 +645,11 @@
captor.getValue().run();
}
- private void sendSfFrame(
- FrameTracker tracker, long durationMillis, long vsyncId, @JankType int jankType) {
+ private void sendSfFrame(FrameTracker tracker, long vsyncId, @JankType int jankType) {
final ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
doNothing().when(tracker).postCallback(captor.capture());
mListenerCapture.getValue().onJankDataAvailable(new JankData[] {
- new JankData(vsyncId, jankType, FRAME_TIME_60Hz, FRAME_TIME_60Hz,
- TimeUnit.MILLISECONDS.toNanos(durationMillis))
+ new JankData(vsyncId, jankType, FRAME_TIME_60Hz)
});
captor.getValue().run();
}
diff --git a/core/tests/coretests/src/com/android/internal/statusbar/StatusBarIconTest.java b/core/tests/coretests/src/com/android/internal/statusbar/StatusBarIconTest.java
index b183ecb..149e132 100644
--- a/core/tests/coretests/src/com/android/internal/statusbar/StatusBarIconTest.java
+++ b/core/tests/coretests/src/com/android/internal/statusbar/StatusBarIconTest.java
@@ -20,6 +20,7 @@
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Icon;
import android.os.Parcel;
import android.os.UserHandle;
@@ -69,22 +70,22 @@
assertThat(copy.preloadedIcon).isEqualTo(original.preloadedIcon);
}
-
private static StatusBarIcon newStatusBarIcon() {
final UserHandle dummyUserHandle = UserHandle.of(100);
final String dummyIconPackageName = "com.android.internal.statusbar.test";
- final int dummyIconId = 123;
+ final Icon dummyIcon = Icon.createWithResource(dummyIconPackageName, 123);
final int dummyIconLevel = 1;
final int dummyIconNumber = 2;
final CharSequence dummyIconContentDescription = "dummyIcon";
return new StatusBarIcon(
- dummyIconPackageName,
dummyUserHandle,
- dummyIconId,
+ dummyIconPackageName,
+ dummyIcon,
dummyIconLevel,
dummyIconNumber,
dummyIconContentDescription,
- StatusBarIcon.Type.SystemIcon);
+ StatusBarIcon.Type.SystemIcon,
+ StatusBarIcon.Shape.FIXED_SPACE);
}
private static void assertSerializableFieldsEqual(StatusBarIcon copy, StatusBarIcon original) {
@@ -96,6 +97,7 @@
assertThat(copy.number).isEqualTo(original.number);
assertThat(copy.contentDescription).isEqualTo(original.contentDescription);
assertThat(copy.type).isEqualTo(original.type);
+ assertThat(copy.shape).isEqualTo(original.shape);
}
private static StatusBarIcon parcelAndUnparcel(StatusBarIcon original) {
diff --git a/data/keyboards/Vendor_0957_Product_0033.idc b/data/keyboards/Vendor_0957_Product_0033.idc
new file mode 100644
index 0000000..7dfbe2c
--- /dev/null
+++ b/data/keyboards/Vendor_0957_Product_0033.idc
@@ -0,0 +1,23 @@
+# Copyright 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Input Device Configuration file for Google Reference RCU Remote.
+# PID 0033 is for new G20 with start button.
+
+# Basic Parameters
+keyboard.layout = Vendor_0957_Product_0031
+# The reason why we set is follow https://docs.partner.android.com/tv/build/gtv/boot-resume
+keyboard.doNotWakeByDefault = 1
+audio.mic = 1
diff --git a/graphics/java/android/graphics/Matrix44.java b/graphics/java/android/graphics/Matrix44.java
index a99e201..683f614 100644
--- a/graphics/java/android/graphics/Matrix44.java
+++ b/graphics/java/android/graphics/Matrix44.java
@@ -19,6 +19,7 @@
import android.annotation.FlaggedApi;
import android.annotation.IntRange;
import android.annotation.NonNull;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
import com.android.graphics.hwui.flags.Flags;
@@ -30,6 +31,7 @@
* in row-major order. The values and operations are treated as column vectors.
*/
@FlaggedApi(Flags.FLAG_MATRIX_44)
+@RavenwoodKeepWholeClass
public class Matrix44 {
final float[] mBackingArray;
/**
diff --git a/graphics/java/android/graphics/Outline.java b/graphics/java/android/graphics/Outline.java
index 618e6dc..c7b8941 100644
--- a/graphics/java/android/graphics/Outline.java
+++ b/graphics/java/android/graphics/Outline.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.drawable.Drawable;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -35,6 +36,7 @@
* @see android.view.View#setOutlineProvider(android.view.ViewOutlineProvider)
* @see Drawable#getOutline(Outline)
*/
+@RavenwoodKeepWholeClass
public final class Outline {
private static final float RADIUS_UNDEFINED = Float.NEGATIVE_INFINITY;
diff --git a/graphics/java/android/graphics/ParcelableColorSpace.java b/graphics/java/android/graphics/ParcelableColorSpace.java
index 748d66c..76c17154 100644
--- a/graphics/java/android/graphics/ParcelableColorSpace.java
+++ b/graphics/java/android/graphics/ParcelableColorSpace.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
/**
* A {@link Parcelable} wrapper for a {@link ColorSpace}. In order to enable parceling, the
@@ -27,6 +28,7 @@
* {@link ColorSpace.Rgb} instance that has an ICC parametric transfer function as returned by
* {@link ColorSpace.Rgb#getTransferParameters()}.
*/
+@RavenwoodKeepWholeClass
public final class ParcelableColorSpace implements Parcelable {
private final ColorSpace mColorSpace;
diff --git a/graphics/java/android/graphics/PixelFormat.java b/graphics/java/android/graphics/PixelFormat.java
index 3ec5b9c..a872e03 100644
--- a/graphics/java/android/graphics/PixelFormat.java
+++ b/graphics/java/android/graphics/PixelFormat.java
@@ -17,10 +17,12 @@
package android.graphics;
import android.annotation.IntDef;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+@RavenwoodKeepWholeClass
public class PixelFormat {
/** @hide */
@IntDef({UNKNOWN, TRANSLUCENT, TRANSPARENT, OPAQUE})
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index 1eb95c1..9ea2943 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -70,6 +70,10 @@
@NonNull
private final TaskFragmentCallback mCallback;
+ @VisibleForTesting
+ @Nullable
+ TaskFragmentAnimationController mAnimationController;
+
/**
* Callback that notifies the controller about changes to task fragments.
*/
@@ -87,6 +91,25 @@
mCallback = callback;
}
+ @Override
+ public void unregisterOrganizer() {
+ if (mAnimationController != null) {
+ mAnimationController.unregisterRemoteAnimations();
+ mAnimationController = null;
+ }
+ super.unregisterOrganizer();
+ }
+
+ /**
+ * Overrides the animation for transitions of embedded activities organized by this organizer.
+ */
+ void overrideSplitAnimation() {
+ if (mAnimationController == null) {
+ mAnimationController = new TaskFragmentAnimationController(this);
+ }
+ mAnimationController.registerRemoteAnimations();
+ }
+
/**
* Starts a new Activity and puts it into split with an existing Activity side-by-side.
* @param launchingFragmentToken token for the launching TaskFragment. If it exists, it will
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 24b56ae..766a758 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -116,6 +116,7 @@
public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmentCallback,
ActivityEmbeddingComponent, DividerPresenter.DragEventCallback {
static final String TAG = "SplitController";
+ static final boolean ENABLE_SHELL_TRANSITIONS = true;
// TODO(b/243518738): Move to WM Extensions if we have requirement of overlay without
// association. It's not set in WM Extensions nor Wm Jetpack library currently.
@@ -919,7 +920,8 @@
// Update all TaskFragments in the Task. Make a copy of the list since some may be
// removed on updating.
- final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers();
+ final List<TaskFragmentContainer> containers
+ = new ArrayList<>(taskContainer.getTaskFragmentContainers());
for (int i = containers.size() - 1; i >= 0; i--) {
final TaskFragmentContainer container = containers.get(i);
// Wait until onTaskFragmentAppeared to update new container.
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 5637320..abc7b29 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -175,6 +175,11 @@
registerOrganizer();
}
mBackupHelper = new BackupHelper(controller, outSavedState);
+ if (!SplitController.ENABLE_SHELL_TRANSITIONS) {
+ // TODO(b/207070762): cleanup with legacy app transition
+ // Animation will be handled by WM Shell when Shell transition is enabled.
+ overrideSplitAnimation();
+ }
}
void scheduleBackup() {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
new file mode 100644
index 0000000..33220c4
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
@@ -0,0 +1,224 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import static android.graphics.Matrix.MTRANS_X;
+import static android.graphics.Matrix.MTRANS_Y;
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.Choreographer;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.animation.Animation;
+import android.view.animation.Transformation;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Wrapper to handle the TaskFragment animation update in one {@link SurfaceControl.Transaction}.
+ *
+ * The base adapter can be used for {@link RemoteAnimationTarget} that is simple open/close.
+ */
+class TaskFragmentAnimationAdapter {
+
+ /**
+ * If {@link #mOverrideLayer} is set to this value, we don't want to override the surface layer.
+ */
+ private static final int LAYER_NO_OVERRIDE = -1;
+
+ @NonNull
+ final Animation mAnimation;
+ @NonNull
+ final RemoteAnimationTarget mTarget;
+ @NonNull
+ final SurfaceControl mLeash;
+ /** Area in absolute coordinate that the animation surface shouldn't go beyond. */
+ @NonNull
+ private final Rect mWholeAnimationBounds = new Rect();
+ /**
+ * Area in absolute coordinate that should represent all the content to show for this window.
+ * This should be the end bounds for opening window, and start bounds for closing window in case
+ * the window is resizing during the open/close transition.
+ */
+ @NonNull
+ private final Rect mContentBounds = new Rect();
+ /** Offset relative to the window parent surface for {@link #mContentBounds}. */
+ @NonNull
+ private final Point mContentRelOffset = new Point();
+
+ @NonNull
+ final Transformation mTransformation = new Transformation();
+ @NonNull
+ final float[] mMatrix = new float[9];
+ @NonNull
+ final float[] mVecs = new float[4];
+ @NonNull
+ final Rect mRect = new Rect();
+ private boolean mIsFirstFrame = true;
+ private int mOverrideLayer = LAYER_NO_OVERRIDE;
+
+ TaskFragmentAnimationAdapter(@NonNull Animation animation,
+ @NonNull RemoteAnimationTarget target) {
+ this(animation, target, target.leash, target.screenSpaceBounds);
+ }
+
+ /**
+ * @param leash the surface to animate.
+ * @param wholeAnimationBounds area in absolute coordinate that the animation surface shouldn't
+ * go beyond.
+ */
+ TaskFragmentAnimationAdapter(@NonNull Animation animation,
+ @NonNull RemoteAnimationTarget target, @NonNull SurfaceControl leash,
+ @NonNull Rect wholeAnimationBounds) {
+ mAnimation = animation;
+ mTarget = target;
+ mLeash = leash;
+ mWholeAnimationBounds.set(wholeAnimationBounds);
+ if (target.mode == MODE_CLOSING) {
+ // When it is closing, we want to show the content at the start position in case the
+ // window is resizing as well. For example, when the activities is changing from split
+ // to stack, the bottom TaskFragment will be resized to fullscreen when hiding.
+ final Rect startBounds = target.startBounds;
+ final Rect endBounds = target.screenSpaceBounds;
+ mContentBounds.set(startBounds);
+ mContentRelOffset.set(target.localBounds.left, target.localBounds.top);
+ mContentRelOffset.offset(
+ startBounds.left - endBounds.left,
+ startBounds.top - endBounds.top);
+ } else {
+ mContentBounds.set(target.screenSpaceBounds);
+ mContentRelOffset.set(target.localBounds.left, target.localBounds.top);
+ }
+ }
+
+ /**
+ * Surface layer to be set at the first frame of the animation. We will not set the layer if it
+ * is set to {@link #LAYER_NO_OVERRIDE}.
+ */
+ final void overrideLayer(int layer) {
+ mOverrideLayer = layer;
+ }
+
+ /** Called on frame update. */
+ final void onAnimationUpdate(@NonNull SurfaceControl.Transaction t, long currentPlayTime) {
+ if (mIsFirstFrame) {
+ t.show(mLeash);
+ if (mOverrideLayer != LAYER_NO_OVERRIDE) {
+ t.setLayer(mLeash, mOverrideLayer);
+ }
+ mIsFirstFrame = false;
+ }
+
+ // Extract the transformation to the current time.
+ mAnimation.getTransformation(Math.min(currentPlayTime, mAnimation.getDuration()),
+ mTransformation);
+ t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
+ onAnimationUpdateInner(t);
+ }
+
+ /** To be overridden by subclasses to adjust the animation surface change. */
+ void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
+ // Update the surface position and alpha.
+ mTransformation.getMatrix().postTranslate(mContentRelOffset.x, mContentRelOffset.y);
+ t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
+ t.setAlpha(mLeash, mTransformation.getAlpha());
+
+ // Get current surface bounds in absolute coordinate.
+ // positionX/Y are in local coordinate, so minus the local offset to get the slide amount.
+ final int positionX = Math.round(mMatrix[MTRANS_X]);
+ final int positionY = Math.round(mMatrix[MTRANS_Y]);
+ final Rect cropRect = new Rect(mContentBounds);
+ cropRect.offset(positionX - mContentRelOffset.x, positionY - mContentRelOffset.y);
+
+ // Store the current offset of the surface top left from (0,0) in absolute coordinate.
+ final int offsetX = cropRect.left;
+ final int offsetY = cropRect.top;
+
+ // Intersect to make sure the animation happens within the whole animation bounds.
+ if (!cropRect.intersect(mWholeAnimationBounds)) {
+ // Hide the surface when it is outside of the animation area.
+ t.setAlpha(mLeash, 0);
+ }
+
+ // cropRect is in absolute coordinate, so we need to translate it to surface top left.
+ cropRect.offset(-offsetX, -offsetY);
+ t.setCrop(mLeash, cropRect);
+ }
+
+ /** Called after animation finished. */
+ final void onAnimationEnd(@NonNull SurfaceControl.Transaction t) {
+ onAnimationUpdate(t, mAnimation.getDuration());
+ }
+
+ final long getDurationHint() {
+ return mAnimation.computeDurationHint();
+ }
+
+ /**
+ * Should be used for the animation of the snapshot of a {@link RemoteAnimationTarget} that has
+ * size change.
+ */
+ static class SnapshotAdapter extends TaskFragmentAnimationAdapter {
+
+ SnapshotAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target) {
+ // Start leash is the snapshot of the starting surface.
+ super(animation, target, target.startLeash, target.screenSpaceBounds);
+ }
+
+ @Override
+ void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
+ // Snapshot should always be placed at the top left of the animation leash.
+ mTransformation.getMatrix().postTranslate(0, 0);
+ t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
+ t.setAlpha(mLeash, mTransformation.getAlpha());
+ }
+ }
+
+ /**
+ * Should be used for the animation of the {@link RemoteAnimationTarget} that has size change.
+ */
+ static class BoundsChangeAdapter extends TaskFragmentAnimationAdapter {
+
+ BoundsChangeAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target) {
+ super(animation, target);
+ }
+
+ @Override
+ void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
+ mTransformation.getMatrix().postTranslate(
+ mTarget.localBounds.left, mTarget.localBounds.top);
+ t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
+ t.setAlpha(mLeash, mTransformation.getAlpha());
+
+ // The following applies an inverse scale to the clip-rect so that it crops "after" the
+ // scale instead of before.
+ mVecs[1] = mVecs[2] = 0;
+ mVecs[0] = mVecs[3] = 1;
+ mTransformation.getMatrix().mapVectors(mVecs);
+ mVecs[0] = 1.f / mVecs[0];
+ mVecs[3] = 1.f / mVecs[3];
+ final Rect clipRect = mTransformation.getClipRect();
+ mRect.left = (int) (clipRect.left * mVecs[0] + 0.5f);
+ mRect.right = (int) (clipRect.right * mVecs[0] + 0.5f);
+ mRect.top = (int) (clipRect.top * mVecs[3] + 0.5f);
+ mRect.bottom = (int) (clipRect.bottom * mVecs[3] + 0.5f);
+ t.setWindowCrop(mLeash, mRect);
+ }
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
new file mode 100644
index 0000000..d7eb9a0
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
@@ -0,0 +1,79 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
+
+import android.util.Log;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationDefinition;
+import android.window.TaskFragmentOrganizer;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/** Controls the TaskFragment remote animations. */
+class TaskFragmentAnimationController {
+
+ private static final String TAG = "TaskFragAnimationCtrl";
+ static final boolean DEBUG = false;
+
+ private final TaskFragmentOrganizer mOrganizer;
+ private final TaskFragmentAnimationRunner mRemoteRunner = new TaskFragmentAnimationRunner();
+ @VisibleForTesting
+ final RemoteAnimationDefinition mDefinition;
+ private boolean mIsRegistered;
+
+ TaskFragmentAnimationController(@NonNull TaskFragmentOrganizer organizer) {
+ mOrganizer = organizer;
+ mDefinition = new RemoteAnimationDefinition();
+ final RemoteAnimationAdapter animationAdapter =
+ new RemoteAnimationAdapter(mRemoteRunner, 0, 0, true /* changeNeedsSnapshot */);
+ mDefinition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, animationAdapter);
+ mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, animationAdapter);
+ mDefinition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_CLOSE, animationAdapter);
+ mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, animationAdapter);
+ mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, animationAdapter);
+ }
+
+ void registerRemoteAnimations() {
+ if (DEBUG) {
+ Log.v(TAG, "registerRemoteAnimations");
+ }
+ if (mIsRegistered) {
+ return;
+ }
+ mOrganizer.registerRemoteAnimations(mDefinition);
+ mIsRegistered = true;
+ }
+
+ void unregisterRemoteAnimations() {
+ if (DEBUG) {
+ Log.v(TAG, "unregisterRemoteAnimations");
+ }
+ if (!mIsRegistered) {
+ return;
+ }
+ mOrganizer.unregisterRemoteAnimations();
+ mIsRegistered = false;
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
new file mode 100644
index 0000000..d9b73a8
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
@@ -0,0 +1,309 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import static android.os.Process.THREAD_PRIORITY_DISPLAY;
+import static android.view.RemoteAnimationTarget.MODE_CHANGING;
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
+import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
+
+import android.animation.Animator;
+import android.animation.ValueAnimator;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.view.animation.Animation;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BiFunction;
+
+/** To run the TaskFragment animations. */
+class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
+
+ private static final String TAG = "TaskFragAnimationRunner";
+ private final Handler mHandler;
+ private final TaskFragmentAnimationSpec mAnimationSpec;
+
+ TaskFragmentAnimationRunner() {
+ HandlerThread animationThread = new HandlerThread(
+ "androidx.window.extensions.embedding", THREAD_PRIORITY_DISPLAY);
+ animationThread.start();
+ mHandler = animationThread.getThreadHandler();
+ mAnimationSpec = new TaskFragmentAnimationSpec(mHandler);
+ }
+
+ @Nullable
+ private Animator mAnimator;
+
+ @Override
+ public void onAnimationStart(@WindowManager.TransitionOldType int transit,
+ @NonNull RemoteAnimationTarget[] apps,
+ @NonNull RemoteAnimationTarget[] wallpapers,
+ @NonNull RemoteAnimationTarget[] nonApps,
+ @NonNull IRemoteAnimationFinishedCallback finishedCallback) {
+ if (wallpapers.length != 0 || nonApps.length != 0) {
+ throw new IllegalArgumentException("TaskFragment shouldn't handle animation with"
+ + "wallpaper or non-app windows.");
+ }
+ if (TaskFragmentAnimationController.DEBUG) {
+ Log.v(TAG, "onAnimationStart transit=" + transit);
+ }
+ mHandler.post(() -> startAnimation(transit, apps, finishedCallback));
+ }
+
+ @Override
+ public void onAnimationCancelled() {
+ mHandler.post(this::cancelAnimation);
+ }
+
+ /** Creates and starts animation. */
+ private void startAnimation(@WindowManager.TransitionOldType int transit,
+ @NonNull RemoteAnimationTarget[] targets,
+ @NonNull IRemoteAnimationFinishedCallback finishedCallback) {
+ if (mAnimator != null) {
+ Log.w(TAG, "start new animation when the previous one is not finished yet.");
+ mAnimator.cancel();
+ }
+ mAnimator = createAnimator(transit, targets, finishedCallback);
+ mAnimator.start();
+ }
+
+ /** Cancels animation. */
+ private void cancelAnimation() {
+ if (mAnimator == null) {
+ return;
+ }
+ mAnimator.cancel();
+ mAnimator = null;
+ }
+
+ /** Creates the animator given the transition type and windows. */
+ @NonNull
+ private Animator createAnimator(@WindowManager.TransitionOldType int transit,
+ @NonNull RemoteAnimationTarget[] targets,
+ @NonNull IRemoteAnimationFinishedCallback finishedCallback) {
+ final List<TaskFragmentAnimationAdapter> adapters =
+ createAnimationAdapters(transit, targets);
+ long duration = 0;
+ for (TaskFragmentAnimationAdapter adapter : adapters) {
+ duration = Math.max(duration, adapter.getDurationHint());
+ }
+ final ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
+ animator.setDuration(duration);
+ animator.addUpdateListener((anim) -> {
+ // Update all adapters in the same transaction.
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ for (TaskFragmentAnimationAdapter adapter : adapters) {
+ adapter.onAnimationUpdate(t, animator.getCurrentPlayTime());
+ }
+ t.apply();
+ });
+ animator.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {}
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ for (TaskFragmentAnimationAdapter adapter : adapters) {
+ adapter.onAnimationEnd(t);
+ }
+ t.apply();
+
+ try {
+ finishedCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ mAnimator = null;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {}
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {}
+ });
+ return animator;
+ }
+
+ /** List of {@link TaskFragmentAnimationAdapter} to handle animations on all window targets. */
+ @NonNull
+ private List<TaskFragmentAnimationAdapter> createAnimationAdapters(
+ @WindowManager.TransitionOldType int transit,
+ @NonNull RemoteAnimationTarget[] targets) {
+ switch (transit) {
+ case TRANSIT_OLD_ACTIVITY_OPEN:
+ case TRANSIT_OLD_TASK_FRAGMENT_OPEN:
+ return createOpenAnimationAdapters(targets);
+ case TRANSIT_OLD_ACTIVITY_CLOSE:
+ case TRANSIT_OLD_TASK_FRAGMENT_CLOSE:
+ return createCloseAnimationAdapters(targets);
+ case TRANSIT_OLD_TASK_FRAGMENT_CHANGE:
+ return createChangeAnimationAdapters(targets);
+ default:
+ throw new IllegalArgumentException("Unhandled transit type=" + transit);
+ }
+ }
+
+ @NonNull
+ private List<TaskFragmentAnimationAdapter> createOpenAnimationAdapters(
+ @NonNull RemoteAnimationTarget[] targets) {
+ return createOpenCloseAnimationAdapters(targets, true /* isOpening */,
+ mAnimationSpec::loadOpenAnimation);
+ }
+
+ @NonNull
+ private List<TaskFragmentAnimationAdapter> createCloseAnimationAdapters(
+ @NonNull RemoteAnimationTarget[] targets) {
+ return createOpenCloseAnimationAdapters(targets, false /* isOpening */,
+ mAnimationSpec::loadCloseAnimation);
+ }
+
+ /**
+ * Creates {@link TaskFragmentAnimationAdapter} for OPEN and CLOSE types of transition.
+ * @param isOpening {@code true} for OPEN type, {@code false} for CLOSE type.
+ */
+ @NonNull
+ private List<TaskFragmentAnimationAdapter> createOpenCloseAnimationAdapters(
+ @NonNull RemoteAnimationTarget[] targets, boolean isOpening,
+ @NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider) {
+ // We need to know if the target window is only a partial of the whole animation screen.
+ // If so, we will need to adjust it to make the whole animation screen looks like one.
+ final List<RemoteAnimationTarget> openingTargets = new ArrayList<>();
+ final List<RemoteAnimationTarget> closingTargets = new ArrayList<>();
+ final Rect openingWholeScreenBounds = new Rect();
+ final Rect closingWholeScreenBounds = new Rect();
+ for (RemoteAnimationTarget target : targets) {
+ if (target.mode != MODE_CLOSING) {
+ openingTargets.add(target);
+ openingWholeScreenBounds.union(target.screenSpaceBounds);
+ } else {
+ closingTargets.add(target);
+ closingWholeScreenBounds.union(target.screenSpaceBounds);
+ // Union the start bounds since this may be the ClosingChanging animation.
+ closingWholeScreenBounds.union(target.startBounds);
+ }
+ }
+
+ // For OPEN transition, open windows should be above close windows.
+ // For CLOSE transition, open windows should be below close windows.
+ int offsetLayer = TYPE_LAYER_OFFSET;
+ final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
+ for (RemoteAnimationTarget target : openingTargets) {
+ final TaskFragmentAnimationAdapter adapter = createOpenCloseAnimationAdapter(target,
+ animationProvider, openingWholeScreenBounds);
+ if (isOpening) {
+ adapter.overrideLayer(offsetLayer++);
+ }
+ adapters.add(adapter);
+ }
+ for (RemoteAnimationTarget target : closingTargets) {
+ final TaskFragmentAnimationAdapter adapter = createOpenCloseAnimationAdapter(target,
+ animationProvider, closingWholeScreenBounds);
+ if (!isOpening) {
+ adapter.overrideLayer(offsetLayer++);
+ }
+ adapters.add(adapter);
+ }
+ return adapters;
+ }
+
+ @NonNull
+ private TaskFragmentAnimationAdapter createOpenCloseAnimationAdapter(
+ @NonNull RemoteAnimationTarget target,
+ @NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider,
+ @NonNull Rect wholeAnimationBounds) {
+ final Animation animation = animationProvider.apply(target, wholeAnimationBounds);
+ return new TaskFragmentAnimationAdapter(animation, target, target.leash,
+ wholeAnimationBounds);
+ }
+
+ @NonNull
+ private List<TaskFragmentAnimationAdapter> createChangeAnimationAdapters(
+ @NonNull RemoteAnimationTarget[] targets) {
+ if (shouldUseJumpCutForChangeAnimation(targets)) {
+ return new ArrayList<>();
+ }
+
+ final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
+ for (RemoteAnimationTarget target : targets) {
+ if (target.mode == MODE_CHANGING) {
+ // This is the target with bounds change.
+ final Animation[] animations =
+ mAnimationSpec.createChangeBoundsChangeAnimations(target);
+ // Adapter for the starting snapshot leash.
+ adapters.add(new TaskFragmentAnimationAdapter.SnapshotAdapter(
+ animations[0], target));
+ // Adapter for the ending bounds changed leash.
+ adapters.add(new TaskFragmentAnimationAdapter.BoundsChangeAdapter(
+ animations[1], target));
+ continue;
+ }
+
+ // These are the other targets that don't have bounds change in the same transition.
+ final Animation animation;
+ if (target.hasAnimatingParent) {
+ // No-op if it will be covered by the changing parent window.
+ animation = TaskFragmentAnimationSpec.createNoopAnimation(target);
+ } else if (target.mode == MODE_CLOSING) {
+ animation = mAnimationSpec.createChangeBoundsCloseAnimation(target);
+ } else {
+ animation = mAnimationSpec.createChangeBoundsOpenAnimation(target);
+ }
+ adapters.add(new TaskFragmentAnimationAdapter(animation, target));
+ }
+ return adapters;
+ }
+
+ /**
+ * Whether we should use jump cut for the change transition.
+ * This normally happens when opening a new secondary with the existing primary using a
+ * different split layout. This can be complicated, like from horizontal to vertical split with
+ * new split pairs.
+ * Uses a jump cut animation to simplify.
+ */
+ private boolean shouldUseJumpCutForChangeAnimation(@NonNull RemoteAnimationTarget[] targets) {
+ boolean hasOpeningWindow = false;
+ boolean hasClosingWindow = false;
+ for (RemoteAnimationTarget target : targets) {
+ if (target.hasAnimatingParent) {
+ continue;
+ }
+ hasOpeningWindow |= target.mode == MODE_OPENING;
+ hasClosingWindow |= target.mode == MODE_CLOSING;
+ }
+ return hasOpeningWindow && hasClosingWindow;
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
new file mode 100644
index 0000000..1f866c3
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
@@ -0,0 +1,267 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+
+import android.app.ActivityThread;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.provider.Settings;
+import android.view.RemoteAnimationTarget;
+import android.view.WindowManager;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.AnimationUtils;
+import android.view.animation.ClipRectAnimation;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.ScaleAnimation;
+import android.view.animation.TranslateAnimation;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.R;
+import com.android.internal.policy.AttributeCache;
+import com.android.internal.policy.TransitionAnimation;
+
+/** Animation spec for TaskFragment transition. */
+// TODO(b/206557124): provide an easier way to customize animation
+class TaskFragmentAnimationSpec {
+
+ private static final String TAG = "TaskFragAnimationSpec";
+ private static final int CHANGE_ANIMATION_DURATION = 517;
+ private static final int CHANGE_ANIMATION_FADE_DURATION = 80;
+ private static final int CHANGE_ANIMATION_FADE_OFFSET = 30;
+
+ private final Context mContext;
+ private final TransitionAnimation mTransitionAnimation;
+ private final Interpolator mFastOutExtraSlowInInterpolator;
+ private final LinearInterpolator mLinearInterpolator;
+ private float mTransitionAnimationScaleSetting;
+
+ TaskFragmentAnimationSpec(@NonNull Handler handler) {
+ mContext = ActivityThread.currentActivityThread().getApplication();
+ mTransitionAnimation = new TransitionAnimation(mContext, false /* debug */, TAG);
+ // Initialize the AttributeCache for the TransitionAnimation.
+ AttributeCache.init(mContext);
+ mFastOutExtraSlowInInterpolator = AnimationUtils.loadInterpolator(
+ mContext, android.R.interpolator.fast_out_extra_slow_in);
+ mLinearInterpolator = new LinearInterpolator();
+
+ // The transition animation should be adjusted based on the developer option.
+ final ContentResolver resolver = mContext.getContentResolver();
+ mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
+ resolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE), false,
+ new SettingsObserver(handler));
+ }
+
+ /** For target that doesn't need to be animated. */
+ @NonNull
+ static Animation createNoopAnimation(@NonNull RemoteAnimationTarget target) {
+ // Noop but just keep the target showing/hiding.
+ final float alpha = target.mode == MODE_CLOSING ? 0f : 1f;
+ return new AlphaAnimation(alpha, alpha);
+ }
+
+ /** Animation for target that is opening in a change transition. */
+ @NonNull
+ Animation createChangeBoundsOpenAnimation(@NonNull RemoteAnimationTarget target) {
+ final Rect parentBounds = target.taskInfo.configuration.windowConfiguration.getBounds();
+ final Rect bounds = target.screenSpaceBounds;
+ final int startLeft;
+ final int startTop;
+ if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) {
+ // The window will be animated in from left or right depending on its position.
+ startTop = 0;
+ startLeft = parentBounds.left == bounds.left ? -bounds.width() : bounds.width();
+ } else {
+ // The window will be animated in from top or bottom depending on its position.
+ startTop = parentBounds.top == bounds.top ? -bounds.height() : bounds.height();
+ startLeft = 0;
+ }
+
+ // The position should be 0-based as we will post translate in
+ // TaskFragmentAnimationAdapter#onAnimationUpdate
+ final Animation animation = new TranslateAnimation(startLeft, 0, startTop, 0);
+ animation.setInterpolator(mFastOutExtraSlowInInterpolator);
+ animation.setDuration(CHANGE_ANIMATION_DURATION);
+ animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
+ animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+ return animation;
+ }
+
+ /** Animation for target that is closing in a change transition. */
+ @NonNull
+ Animation createChangeBoundsCloseAnimation(@NonNull RemoteAnimationTarget target) {
+ final Rect parentBounds = target.taskInfo.configuration.windowConfiguration.getBounds();
+ // Use startBounds if the window is closing in case it may also resize.
+ final Rect bounds = target.startBounds;
+ final int endTop;
+ final int endLeft;
+ if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) {
+ // The window will be animated out to left or right depending on its position.
+ endTop = 0;
+ endLeft = parentBounds.left == bounds.left ? -bounds.width() : bounds.width();
+ } else {
+ // The window will be animated out to top or bottom depending on its position.
+ endTop = parentBounds.top == bounds.top ? -bounds.height() : bounds.height();
+ endLeft = 0;
+ }
+
+ // The position should be 0-based as we will post translate in
+ // TaskFragmentAnimationAdapter#onAnimationUpdate
+ final Animation animation = new TranslateAnimation(0, endLeft, 0, endTop);
+ animation.setInterpolator(mFastOutExtraSlowInInterpolator);
+ animation.setDuration(CHANGE_ANIMATION_DURATION);
+ animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
+ animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+ return animation;
+ }
+
+ /**
+ * Animation for target that is changing (bounds change) in a change transition.
+ * @return the return array always has two elements. The first one is for the start leash, and
+ * the second one is for the end leash.
+ */
+ @NonNull
+ Animation[] createChangeBoundsChangeAnimations(@NonNull RemoteAnimationTarget target) {
+ // Both start bounds and end bounds are in screen coordinates. We will post translate
+ // to the local coordinates in TaskFragmentAnimationAdapter#onAnimationUpdate
+ final Rect startBounds = target.startBounds;
+ final Rect parentBounds = target.taskInfo.configuration.windowConfiguration.getBounds();
+ final Rect endBounds = target.screenSpaceBounds;
+ float scaleX = ((float) startBounds.width()) / endBounds.width();
+ float scaleY = ((float) startBounds.height()) / endBounds.height();
+ // Start leash is a child of the end leash. Reverse the scale so that the start leash won't
+ // be scaled up with its parent.
+ float startScaleX = 1.f / scaleX;
+ float startScaleY = 1.f / scaleY;
+
+ // The start leash will be fade out.
+ final AnimationSet startSet = new AnimationSet(false /* shareInterpolator */);
+ final Animation startAlpha = new AlphaAnimation(1f, 0f);
+ startAlpha.setInterpolator(mLinearInterpolator);
+ startAlpha.setDuration(CHANGE_ANIMATION_FADE_DURATION);
+ startAlpha.setStartOffset(CHANGE_ANIMATION_FADE_OFFSET);
+ startSet.addAnimation(startAlpha);
+ final Animation startScale = new ScaleAnimation(startScaleX, startScaleX, startScaleY,
+ startScaleY);
+ startScale.setInterpolator(mFastOutExtraSlowInInterpolator);
+ startScale.setDuration(CHANGE_ANIMATION_DURATION);
+ startSet.addAnimation(startScale);
+ startSet.initialize(startBounds.width(), startBounds.height(), endBounds.width(),
+ endBounds.height());
+ startSet.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+
+ // The end leash will be moved into the end position while scaling.
+ final AnimationSet endSet = new AnimationSet(true /* shareInterpolator */);
+ endSet.setInterpolator(mFastOutExtraSlowInInterpolator);
+ final Animation endScale = new ScaleAnimation(scaleX, 1, scaleY, 1);
+ endScale.setDuration(CHANGE_ANIMATION_DURATION);
+ endSet.addAnimation(endScale);
+ // The position should be 0-based as we will post translate in
+ // TaskFragmentAnimationAdapter#onAnimationUpdate
+ final Animation endTranslate = new TranslateAnimation(startBounds.left - endBounds.left, 0,
+ startBounds.top - endBounds.top, 0);
+ endTranslate.setDuration(CHANGE_ANIMATION_DURATION);
+ endSet.addAnimation(endTranslate);
+ // The end leash is resizing, we should update the window crop based on the clip rect.
+ final Rect startClip = new Rect(startBounds);
+ final Rect endClip = new Rect(endBounds);
+ startClip.offsetTo(0, 0);
+ endClip.offsetTo(0, 0);
+ final Animation clipAnim = new ClipRectAnimation(startClip, endClip);
+ clipAnim.setDuration(CHANGE_ANIMATION_DURATION);
+ endSet.addAnimation(clipAnim);
+ endSet.initialize(startBounds.width(), startBounds.height(), parentBounds.width(),
+ parentBounds.height());
+ endSet.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+
+ return new Animation[]{startSet, endSet};
+ }
+
+ @NonNull
+ Animation loadOpenAnimation(@NonNull RemoteAnimationTarget target,
+ @NonNull Rect wholeAnimationBounds) {
+ final boolean isEnter = target.mode != MODE_CLOSING;
+ final Animation animation;
+ // Background color on TaskDisplayArea has already been set earlier in
+ // WindowContainer#getAnimationAdapter.
+ if (target.showBackdrop) {
+ animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
+ ? com.android.internal.R.anim.task_fragment_clear_top_open_enter
+ : com.android.internal.R.anim.task_fragment_clear_top_open_exit);
+ } else {
+ animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
+ ? com.android.internal.R.anim.task_fragment_open_enter
+ : com.android.internal.R.anim.task_fragment_open_exit);
+ }
+ // Use the whole animation bounds instead of the change bounds, so that when multiple change
+ // targets are opening at the same time, the animation applied to each will be the same.
+ // Otherwise, we may see gap between the activities that are launching together.
+ animation.initialize(wholeAnimationBounds.width(), wholeAnimationBounds.height(),
+ wholeAnimationBounds.width(), wholeAnimationBounds.height());
+ animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+ return animation;
+ }
+
+ @NonNull
+ Animation loadCloseAnimation(@NonNull RemoteAnimationTarget target,
+ @NonNull Rect wholeAnimationBounds) {
+ final boolean isEnter = target.mode != MODE_CLOSING;
+ final Animation animation;
+ if (target.showBackdrop) {
+ animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
+ ? com.android.internal.R.anim.task_fragment_clear_top_close_enter
+ : com.android.internal.R.anim.task_fragment_clear_top_close_exit);
+ } else {
+ animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
+ ? com.android.internal.R.anim.task_fragment_close_enter
+ : com.android.internal.R.anim.task_fragment_close_exit);
+ }
+ // Use the whole animation bounds instead of the change bounds, so that when multiple change
+ // targets are closing at the same time, the animation applied to each will be the same.
+ // Otherwise, we may see gap between the activities that are finishing together.
+ animation.initialize(wholeAnimationBounds.width(), wholeAnimationBounds.height(),
+ wholeAnimationBounds.width(), wholeAnimationBounds.height());
+ animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+ return animation;
+ }
+
+ private float getTransitionAnimationScaleSetting() {
+ return WindowManager.fixScale(Settings.Global.getFloat(mContext.getContentResolver(),
+ Settings.Global.TRANSITION_ANIMATION_SCALE, mContext.getResources().getFloat(
+ R.dimen.config_appTransitionAnimationDurationScaleDefault)));
+ }
+
+ private class SettingsObserver extends ContentObserver {
+ SettingsObserver(@NonNull Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
+ }
+ }
+}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
index 8911d18..ac004c3 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
@@ -23,6 +23,8 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -84,6 +86,24 @@
}
@Test
+ public void testUnregisterOrganizer() {
+ mOrganizer.overrideSplitAnimation();
+ mOrganizer.unregisterOrganizer();
+
+ verify(mOrganizer).unregisterRemoteAnimations();
+ }
+
+ @Test
+ public void testOverrideSplitAnimation() {
+ assertNull(mOrganizer.mAnimationController);
+
+ mOrganizer.overrideSplitAnimation();
+
+ assertNotNull(mOrganizer.mAnimationController);
+ verify(mOrganizer).registerRemoteAnimations(mOrganizer.mAnimationController.mDefinition);
+ }
+
+ @Test
public void testExpandTaskFragment() {
final TaskContainer taskContainer = createTestTaskContainer();
doReturn(taskContainer).when(mSplitController).getTaskContainer(anyInt());
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java
new file mode 100644
index 0000000..a1e9f08
--- /dev/null
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.mockito.Mockito.never;
+
+import android.platform.test.annotations.Presubmit;
+import android.window.TaskFragmentOrganizer;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/**
+ * Test class for {@link TaskFragmentAnimationController}.
+ *
+ * Build/Install/Run:
+ * atest WMJetpackUnitTests:TaskFragmentAnimationControllerTest
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TaskFragmentAnimationControllerTest {
+ @Rule
+ public MockitoRule rule = MockitoJUnit.rule();
+
+ @Mock
+ private TaskFragmentOrganizer mOrganizer;
+ private TaskFragmentAnimationController mAnimationController;
+
+ @Before
+ public void setup() {
+ mAnimationController = new TaskFragmentAnimationController(mOrganizer);
+ }
+
+ @Test
+ public void testRegisterRemoteAnimations() {
+ mAnimationController.registerRemoteAnimations();
+
+ verify(mOrganizer).registerRemoteAnimations(mAnimationController.mDefinition);
+
+ mAnimationController.registerRemoteAnimations();
+
+ // No extra call if it has been registered.
+ verify(mOrganizer).registerRemoteAnimations(mAnimationController.mDefinition);
+ }
+
+ @Test
+ public void testUnregisterRemoteAnimations() {
+ mAnimationController.unregisterRemoteAnimations();
+
+ // No call if it is not registered.
+ verify(mOrganizer, never()).unregisterRemoteAnimations();
+
+ mAnimationController.registerRemoteAnimations();
+ mAnimationController.unregisterRemoteAnimations();
+
+ verify(mOrganizer).unregisterRemoteAnimations();
+
+ mAnimationController.unregisterRemoteAnimations();
+
+ // No extra call if it has been unregistered.
+ verify(mOrganizer).unregisterRemoteAnimations();
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/handles/RegionSamplingHelper.java
similarity index 98%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/handles/RegionSamplingHelper.java
index 9999f08..b92b8ef 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/handles/RegionSamplingHelper.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.shared.navigationbar;
+package com.android.wm.shell.shared.handles;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -194,7 +194,7 @@
ViewRootImpl viewRootImpl = mSampledView.getViewRootImpl();
SurfaceControl stopLayerControl = null;
if (viewRootImpl != null) {
- stopLayerControl = viewRootImpl.getSurfaceControl();
+ stopLayerControl = viewRootImpl.getSurfaceControl();
}
if (stopLayerControl == null || !stopLayerControl.isValid()) {
if (!mWaitingOnDraw) {
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 33949f5..ef679da 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
@@ -74,6 +74,7 @@
import android.window.IOnBackInvokedCallback;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
+import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
@@ -1272,19 +1273,24 @@
ComponentName openComponent = null;
int tmpSize;
int openTaskId = INVALID_TASK_ID;
+ WindowContainerToken openToken = null;
for (int j = init.getChanges().size() - 1; j >= 0; --j) {
final TransitionInfo.Change change = init.getChanges().get(j);
if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
openComponent = findComponentName(change);
openTaskId = findTaskId(change);
+ openToken = findToken(change);
if (change.hasFlags(FLAG_SHOW_WALLPAPER)) {
openShowWallpaper = true;
}
break;
}
}
- if (openComponent == null && openTaskId == INVALID_TASK_ID) {
- // shouldn't happen.
+ if (openComponent == null && openTaskId == INVALID_TASK_ID && openToken == null) {
+ // This shouldn't happen, but if that happen, consume the initial transition anyway.
+ Log.e(TAG, "Unable to merge following transition, cannot find the gesture "
+ + "animated target from the open transition=" + mOpenTransitionInfo);
+ mOpenTransitionInfo = null;
return;
}
// find first non-prepare open target
@@ -1315,7 +1321,7 @@
boolean moveToTop = false;
for (int j = info.getChanges().size() - 1; j >= 0; --j) {
final TransitionInfo.Change change = info.getChanges().get(j);
- if (isSameChangeTarget(openComponent, openTaskId, change)) {
+ if (isSameChangeTarget(openComponent, openTaskId, openToken, change)) {
moveToTop = change.hasFlags(FLAG_MOVED_TO_TOP);
info.getChanges().remove(j);
} else if ((openShowWallpaper && change.hasFlags(FLAG_IS_WALLPAPER))
@@ -1329,7 +1335,7 @@
for (int i = 0; i < tmpSize; ++i) {
final TransitionInfo.Change change = init.getChanges().get(i);
if (moveToTop) {
- if (isSameChangeTarget(openComponent, openTaskId, change)) {
+ if (isSameChangeTarget(openComponent, openTaskId, openToken, change)) {
change.setFlags(change.getFlags() | FLAG_MOVED_TO_TOP);
}
}
@@ -1358,7 +1364,7 @@
if (nonBackClose && nonBackOpen) {
for (int j = info.getChanges().size() - 1; j >= 0; --j) {
final TransitionInfo.Change change = info.getChanges().get(j);
- if (isSameChangeTarget(openComponent, openTaskId, change)) {
+ if (isSameChangeTarget(openComponent, openTaskId, openToken, change)) {
info.getChanges().remove(j);
} else if ((openShowWallpaper && change.hasFlags(FLAG_IS_WALLPAPER))) {
info.getChanges().remove(j);
@@ -1368,6 +1374,8 @@
}
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation transition, merge pending "
+ "transitions result=%s", info);
+ // Only handle one merge transition request.
+ mOpenTransitionInfo = null;
}
@Override
@@ -1378,7 +1386,9 @@
mClosePrepareTransition = null;
}
// try to handle unexpected transition
- mergePendingTransitions(info);
+ if (mOpenTransitionInfo != null) {
+ mergePendingTransitions(info);
+ }
if (isNotGestureBackTransition(info) || shouldCancelAnimation(info)
|| !mCloseTransitionRequested) {
@@ -1628,6 +1638,10 @@
return false;
}
+ private static WindowContainerToken findToken(TransitionInfo.Change change) {
+ return change.getContainer();
+ }
+
private static ComponentName findComponentName(TransitionInfo.Change change) {
final ComponentName componentName = change.getActivityComponent();
if (componentName != null) {
@@ -1649,11 +1663,13 @@
}
private static boolean isSameChangeTarget(ComponentName topActivity, int taskId,
- TransitionInfo.Change change) {
+ WindowContainerToken token, TransitionInfo.Change change) {
final ComponentName openChange = findComponentName(change);
final int firstTaskId = findTaskId(change);
+ final WindowContainerToken openToken = findToken(change);
return (openChange != null && openChange == topActivity)
- || (firstTaskId != INVALID_TASK_ID && firstTaskId == taskId);
+ || (firstTaskId != INVALID_TASK_ID && firstTaskId == taskId)
+ || (openToken != null && token == openToken);
}
private static boolean canBeTransitionTarget(TransitionInfo.Change change) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 3e758bb..4622dcf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -349,7 +349,8 @@
getPackageName(),
getTitle(),
getAppName(),
- isImportantConversation());
+ isImportantConversation(),
+ !isAppLaunchIntent());
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java
index 829af08..e873cbd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java
@@ -48,10 +48,11 @@
@Nullable
private String mAppName;
private boolean mIsImportantConversation;
+ private boolean mShowAppBadge;
public BubbleInfo(String key, int flags, @Nullable String shortcutId, @Nullable Icon icon,
int userId, String packageName, @Nullable String title, @Nullable String appName,
- boolean isImportantConversation) {
+ boolean isImportantConversation, boolean showAppBadge) {
mKey = key;
mFlags = flags;
mShortcutId = shortcutId;
@@ -61,6 +62,7 @@
mTitle = title;
mAppName = appName;
mIsImportantConversation = isImportantConversation;
+ mShowAppBadge = showAppBadge;
}
private BubbleInfo(Parcel source) {
@@ -73,6 +75,7 @@
mTitle = source.readString();
mAppName = source.readString();
mIsImportantConversation = source.readBoolean();
+ mShowAppBadge = source.readBoolean();
}
public String getKey() {
@@ -115,6 +118,10 @@
return mIsImportantConversation;
}
+ public boolean showAppBadge() {
+ return mShowAppBadge;
+ }
+
/**
* Whether this bubble is currently being hidden from the stack.
*/
@@ -172,6 +179,7 @@
parcel.writeString(mTitle);
parcel.writeString(mAppName);
parcel.writeBoolean(mIsImportantConversation);
+ parcel.writeBoolean(mShowAppBadge);
}
@NonNull
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 42937c1..4adea23 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
@@ -148,7 +148,11 @@
* dependencies that are device/form factor SystemUI implementation specific should go into their
* respective modules (ie. {@link WMShellModule} for handheld, {@link TvWMShellModule} for tv, etc.)
*/
-@Module(includes = WMShellConcurrencyModule.class)
+@Module(
+ includes = {
+ WMShellConcurrencyModule.class,
+ WMShellCoroutinesModule.class
+ })
public abstract class WMShellBaseModule {
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 46cb6ec..02ecfd9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -73,6 +73,7 @@
import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator;
import com.android.wm.shell.desktopmode.SpringDragToDesktopTransitionHandler;
import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler;
+import com.android.wm.shell.desktopmode.education.AppHandleEducationController;
import com.android.wm.shell.desktopmode.education.AppHandleEducationFilter;
import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository;
import com.android.wm.shell.draganddrop.DragAndDropController;
@@ -118,6 +119,8 @@
import dagger.Module;
import dagger.Provides;
+import kotlinx.coroutines.CoroutineScope;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@@ -743,6 +746,17 @@
return new AppHandleEducationFilter(context, appHandleEducationDatastoreRepository);
}
+ @WMSingleton
+ @Provides
+ static AppHandleEducationController provideAppHandleEducationController(
+ AppHandleEducationFilter appHandleEducationFilter,
+ ShellTaskOrganizer shellTaskOrganizer,
+ AppHandleEducationDatastoreRepository appHandleEducationDatastoreRepository,
+ @ShellMainThread CoroutineScope applicationScope) {
+ return new AppHandleEducationController(appHandleEducationFilter,
+ shellTaskOrganizer, appHandleEducationDatastoreRepository, applicationScope);
+ }
+
//
// Drag and drop
//
@@ -784,7 +798,8 @@
@Provides
static Object provideIndependentShellComponentsToCreate(
DragAndDropController dragAndDropController,
- Optional<DesktopTasksTransitionObserver> desktopTasksTransitionObserverOptional
+ Optional<DesktopTasksTransitionObserver> desktopTasksTransitionObserverOptional,
+ AppHandleEducationController appHandleEducationController
) {
return new Object();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 90f8276..4b30ed0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -734,17 +734,33 @@
* Quick-resize to the right or left half of the stable bounds.
*
* @param taskInfo current task that is being snap-resized via dragging or maximize menu button
+ * @param taskSurface the leash of the task being dragged
* @param currentDragBounds current position of the task leash being dragged (or current task
* bounds if being snapped resize via maximize menu button)
* @param position the portion of the screen (RIGHT or LEFT) we want to snap the task to.
*/
fun snapToHalfScreen(
taskInfo: RunningTaskInfo,
+ taskSurface: SurfaceControl,
currentDragBounds: Rect,
position: SnapPosition
) {
val destinationBounds = getSnapBounds(taskInfo, position)
- if (destinationBounds == taskInfo.configuration.windowConfiguration.bounds) return
+ if (destinationBounds == taskInfo.configuration.windowConfiguration.bounds) {
+ // Handle the case where we attempt to snap resize when already snap resized: the task
+ // position won't need to change but we want to animate the surface going back to the
+ // snapped position from the "dragged-to-the-edge" position.
+ if (destinationBounds != currentDragBounds) {
+ returnToDragStartAnimator.start(
+ taskInfo.taskId,
+ taskSurface,
+ startBounds = currentDragBounds,
+ endBounds = destinationBounds,
+ isResizable = taskInfo.isResizeable
+ )
+ }
+ return
+ }
taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(true)
val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
@@ -774,13 +790,14 @@
taskInfo.taskId,
taskSurface,
startBounds = currentDragBounds,
- endBounds = dragStartBounds
+ endBounds = dragStartBounds,
+ isResizable = taskInfo.isResizeable,
)
} else {
interactionJankMonitor.begin(
taskSurface, context, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_resizable"
)
- snapToHalfScreen(taskInfo, currentDragBounds, position)
+ snapToHalfScreen(taskInfo, taskSurface, currentDragBounds, position)
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt
index 4c5258f..f4df42c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt
@@ -48,7 +48,13 @@
}
/** Builds new animator and starts animation of task leash reposition. */
- fun start(taskId: Int, taskSurface: SurfaceControl, startBounds: Rect, endBounds: Rect) {
+ fun start(
+ taskId: Int,
+ taskSurface: SurfaceControl,
+ startBounds: Rect,
+ endBounds: Rect,
+ isResizable: Boolean
+ ) {
val tx = transactionSupplier.get()
boundsAnimator?.cancel()
@@ -81,11 +87,13 @@
.apply()
taskRepositionAnimationListener.onAnimationEnd(taskId)
boundsAnimator = null
- Toast.makeText(
- context,
- R.string.desktop_mode_non_resizable_snap_text,
- Toast.LENGTH_SHORT
- ).show()
+ if (!isResizable) {
+ Toast.makeText(
+ context,
+ R.string.desktop_mode_non_resizable_snap_text,
+ Toast.LENGTH_SHORT
+ ).show()
+ }
interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE)
}
)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt
new file mode 100644
index 0000000..6013e97
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode.education
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.os.SystemProperties
+import com.android.window.flags.Flags
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.debounce
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+/**
+ * Controls app handle education end to end.
+ *
+ * Listen to the user trigger for app handle education, calls an api to check if the education
+ * should be shown and calls an api to show education.
+ */
+@OptIn(kotlinx.coroutines.FlowPreview::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class AppHandleEducationController(
+ private val appHandleEducationFilter: AppHandleEducationFilter,
+ shellTaskOrganizer: ShellTaskOrganizer,
+ private val appHandleEducationDatastoreRepository: AppHandleEducationDatastoreRepository,
+ @ShellMainThread private val applicationCoroutineScope: CoroutineScope
+) {
+ init {
+ runIfEducationFeatureEnabled {
+ // TODO: b/361038716 - Use app handle state flow instead of focus task change flow
+ val focusTaskChangeFlow = focusTaskChangeFlow(shellTaskOrganizer)
+ applicationCoroutineScope.launch {
+ // Central block handling the app's educational flow end-to-end.
+ // This flow listens to the changes to the result of
+ // [WindowingEducationProto#hasEducationViewedTimestampMillis()] in datastore proto object
+ isEducationViewedFlow()
+ .flatMapLatest { isEducationViewed ->
+ if (isEducationViewed) {
+ // If the education is viewed then return emptyFlow() that completes immediately.
+ // This will help us to not listen to focus task changes after the education has
+ // been viewed already.
+ emptyFlow()
+ } else {
+ // This flow listens for focus task changes, which trigger the app handle education.
+ focusTaskChangeFlow
+ .filter { runningTaskInfo ->
+ runningTaskInfo.topActivityInfo?.packageName?.let {
+ appHandleEducationFilter.shouldShowAppHandleEducation(it)
+ } ?: false && runningTaskInfo.windowingMode != WINDOWING_MODE_FREEFORM
+ }
+ .distinctUntilChanged()
+ }
+ }
+ .debounce(
+ APP_HANDLE_EDUCATION_DELAY) // Wait for few seconds, if the focus task changes.
+ // During the delay then current emission will be cancelled.
+ .flowOn(Dispatchers.IO)
+ .collectLatest {
+ // Fire and forget show education suspend function, manage entire lifecycle of
+ // tooltip in UI class.
+ }
+ }
+ }
+ }
+
+ private inline fun runIfEducationFeatureEnabled(block: () -> Unit) {
+ if (Flags.enableDesktopWindowingAppHandleEducation()) block()
+ }
+
+ private fun isEducationViewedFlow(): Flow<Boolean> =
+ appHandleEducationDatastoreRepository.dataStoreFlow
+ .map { preferences -> preferences.hasEducationViewedTimestampMillis() }
+ .distinctUntilChanged()
+
+ private fun focusTaskChangeFlow(shellTaskOrganizer: ShellTaskOrganizer): Flow<RunningTaskInfo> =
+ callbackFlow {
+ val focusTaskChange = ShellTaskOrganizer.FocusListener { taskInfo -> trySend(taskInfo) }
+ shellTaskOrganizer.addFocusListener(focusTaskChange)
+ awaitClose { shellTaskOrganizer.removeFocusListener(focusTaskChange) }
+ }
+
+ private companion object {
+ val APP_HANDLE_EDUCATION_DELAY: Long
+ get() = SystemProperties.getLong("persist.windowing_app_handle_education_delay", 3000L)
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt
index a7fff8a..f420c5b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt
@@ -25,9 +25,12 @@
import androidx.datastore.dataStoreFile
import com.android.framework.protobuf.InvalidProtocolBufferException
import com.android.internal.annotations.VisibleForTesting
+import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.time.Duration
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.first
/**
@@ -46,17 +49,26 @@
serializer = WindowingEducationProtoSerializer,
produceFile = { context.dataStoreFile(APP_HANDLE_EDUCATION_DATASTORE_FILEPATH) }))
+ /** Provides dataStore.data flow and handles exceptions thrown during collection */
+ val dataStoreFlow: Flow<WindowingEducationProto> =
+ dataStore.data.catch { exception ->
+ // dataStore.data throws an IOException when an error is encountered when reading data
+ if (exception is IOException) {
+ Log.e(
+ TAG,
+ "Error in reading app handle education related data from datastore, data is " +
+ "stored in a file named $APP_HANDLE_EDUCATION_DATASTORE_FILEPATH",
+ exception)
+ } else {
+ throw exception
+ }
+ }
+
/**
* Reads and returns the [WindowingEducationProto] Proto object from the DataStore. If the
* DataStore is empty or there's an error reading, it returns the default value of Proto.
*/
- suspend fun windowingEducationProto(): WindowingEducationProto =
- try {
- dataStore.data.first()
- } catch (e: Exception) {
- Log.e(TAG, "Unable to read from datastore")
- WindowingEducationProto.getDefaultInstance()
- }
+ suspend fun windowingEducationProto(): WindowingEducationProto = dataStoreFlow.first()
/**
* Updates [AppHandleEducation.appUsageStats] and
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterExitAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterExitAnimator.java
index 8a9302b..8ebdc96 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterExitAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterExitAnimator.java
@@ -22,6 +22,7 @@
import android.annotation.IntDef;
import android.content.Context;
import android.graphics.Rect;
+import android.view.Surface;
import android.view.SurfaceControl;
import androidx.annotation.NonNull;
@@ -51,8 +52,10 @@
@NonNull private final SurfaceControl mLeash;
private final SurfaceControl.Transaction mStartTransaction;
- private final int mEnterAnimationDuration;
+ private final SurfaceControl.Transaction mFinishTransaction;
+ private final int mEnterExitAnimationDuration;
private final @BOUNDS int mDirection;
+ private final @Surface.Rotation int mRotation;
// optional callbacks for tracking animation start and end
@Nullable private Runnable mAnimationStartCallback;
@@ -62,37 +65,59 @@
private final Rect mStartBounds = new Rect();
private final Rect mEndBounds = new Rect();
+ @Nullable private final Rect mSourceRectHint;
+ private final Rect mSourceRectHintInsets = new Rect();
+ private final Rect mZeroInsets = new Rect(0, 0, 0, 0);
+
// Bounds updated by the evaluator as animator is running.
private final Rect mAnimatedRect = new Rect();
private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
mSurfaceControlTransactionFactory;
private final RectEvaluator mRectEvaluator;
+ private final RectEvaluator mInsetEvaluator;
private final PipSurfaceTransactionHelper mPipSurfaceTransactionHelper;
public PipEnterExitAnimator(Context context,
@NonNull SurfaceControl leash,
SurfaceControl.Transaction startTransaction,
+ SurfaceControl.Transaction finishTransaction,
@NonNull Rect baseBounds,
@NonNull Rect startBounds,
@NonNull Rect endBounds,
- @BOUNDS int direction) {
+ @Nullable Rect sourceRectHint,
+ @BOUNDS int direction,
+ @Surface.Rotation int rotation) {
mLeash = leash;
mStartTransaction = startTransaction;
+ mFinishTransaction = finishTransaction;
mBaseBounds.set(baseBounds);
mStartBounds.set(startBounds);
mAnimatedRect.set(startBounds);
mEndBounds.set(endBounds);
mRectEvaluator = new RectEvaluator(mAnimatedRect);
+ mInsetEvaluator = new RectEvaluator(new Rect());
mPipSurfaceTransactionHelper = new PipSurfaceTransactionHelper(context);
mDirection = direction;
+ mRotation = rotation;
+
+ mSourceRectHint = sourceRectHint != null ? new Rect(sourceRectHint) : null;
+ if (mSourceRectHint != null) {
+ mSourceRectHintInsets.set(
+ mSourceRectHint.left - mBaseBounds.left,
+ mSourceRectHint.top - mBaseBounds.top,
+ mBaseBounds.right - mSourceRectHint.right,
+ mBaseBounds.bottom - mSourceRectHint.bottom
+ );
+ }
mSurfaceControlTransactionFactory =
new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
- mEnterAnimationDuration = context.getResources()
+ mEnterExitAnimationDuration = context.getResources()
.getInteger(R.integer.config_pipEnterAnimationDuration);
- setDuration(mEnterAnimationDuration);
+ setObjectValues(startBounds, endBounds);
+ setDuration(mEnterExitAnimationDuration);
setEvaluator(mRectEvaluator);
addListener(this);
addUpdateListener(this);
@@ -118,6 +143,14 @@
@Override
public void onAnimationEnd(@NonNull Animator animation) {
+ if (mFinishTransaction != null) {
+ // finishTransaction might override some state (eg. corner radii) so we want to
+ // manually set the state to the end of the animation
+ mPipSurfaceTransactionHelper.scaleAndCrop(mFinishTransaction, mLeash, mSourceRectHint,
+ mBaseBounds, mAnimatedRect, getInsets(1f), isInPipDirection(), 1f)
+ .round(mFinishTransaction, mLeash, isInPipDirection())
+ .shadow(mFinishTransaction, mLeash, isInPipDirection());
+ }
if (mAnimationEndCallback != null) {
mAnimationEndCallback.run();
}
@@ -127,19 +160,32 @@
public void onAnimationUpdate(@NonNull ValueAnimator animation) {
final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
final float fraction = getAnimatedFraction();
+ Rect insets = getInsets(fraction);
+
// TODO (b/350801661): implement fixed rotation
- mPipSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, null,
- mBaseBounds, mAnimatedRect, null, isInPipDirection(), fraction)
+ mPipSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mSourceRectHint,
+ mBaseBounds, mAnimatedRect, insets, isInPipDirection(), fraction)
.round(tx, mLeash, isInPipDirection())
.shadow(tx, mLeash, isInPipDirection());
tx.apply();
}
+ private Rect getInsets(float fraction) {
+ Rect startInsets = isInPipDirection() ? mZeroInsets : mSourceRectHintInsets;
+ Rect endInsets = isInPipDirection() ? mSourceRectHintInsets : mZeroInsets;
+
+ return mInsetEvaluator.evaluate(fraction, startInsets, endInsets);
+ }
+
private boolean isInPipDirection() {
return mDirection == BOUNDS_ENTER;
}
+ private boolean isOutPipDirection() {
+ return mDirection == BOUNDS_EXIT;
+ }
+
// no-ops
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
index 7f16880..262c14d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
@@ -25,6 +25,7 @@
import android.os.Bundle;
import android.view.SurfaceControl;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.util.Preconditions;
@@ -88,6 +89,11 @@
: new PictureInPictureParams.Builder().build());
}
+ @NonNull
+ public PictureInPictureParams getPictureInPictureParams() {
+ return mPictureInPictureParams;
+ }
+
@Override
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
PictureInPictureParams params = taskInfo.pictureInPictureParams;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 44baabd..f93233e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -36,6 +36,7 @@
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
+import android.view.Surface;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
@@ -398,17 +399,22 @@
SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash;
Preconditions.checkNotNull(pipLeash, "Leash is null for bounds transition.");
+ Rect sourceRectHint = null;
+ if (pipChange.getTaskInfo() != null
+ && pipChange.getTaskInfo().pictureInPictureParams != null) {
+ sourceRectHint = pipChange.getTaskInfo().pictureInPictureParams.getSourceRectHint();
+ }
+
PipEnterExitAnimator animator = new PipEnterExitAnimator(mContext, pipLeash,
- startTransaction, startBounds, startBounds, endBounds,
- PipEnterExitAnimator.BOUNDS_ENTER);
+ startTransaction, finishTransaction, startBounds, startBounds, endBounds,
+ sourceRectHint, PipEnterExitAnimator.BOUNDS_ENTER, Surface.ROTATION_0);
tx.addTransactionCommittedListener(mPipScheduler.getMainExecutor(),
this::onClientDrawAtTransitionEnd);
finishWct.setBoundsChangeTransaction(pipTaskToken, tx);
- animator.setAnimationEndCallback(() -> {
- finishCallback.onTransitionFinished(finishWct.isEmpty() ? null : finishWct);
- });
+ animator.setAnimationEndCallback(() ->
+ finishCallback.onTransitionFinished(finishWct));
animator.start();
return true;
@@ -452,19 +458,53 @@
TransitionInfo.Change pipChange = getChangeByToken(info, pipToken);
if (pipChange == null) {
- return false;
+ // pipChange is null, check to see if we've reparented the PIP activity for
+ // the multi activity case. If so we should use the activity leash instead
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (change.getTaskInfo() == null
+ && change.getLastParent() != null
+ && change.getLastParent().equals(pipToken)) {
+ pipChange = change;
+ break;
+ }
+ }
+
+ // failsafe
+ if (pipChange == null) {
+ return false;
+ }
+ }
+
+ // for multi activity, we need to manually set the leash layer
+ if (pipChange.getTaskInfo() == null) {
+ TransitionInfo.Change parent = getChangeByToken(info, pipChange.getParent());
+ if (parent != null) {
+ startTransaction.setLayer(parent.getLeash(), Integer.MAX_VALUE - 1);
+ }
}
Rect startBounds = pipChange.getStartAbsBounds();
Rect endBounds = pipChange.getEndAbsBounds();
SurfaceControl pipLeash = pipChange.getLeash();
+ Preconditions.checkNotNull(pipLeash, "Leash is null for exit transition.");
+
+ Rect sourceRectHint = null;
+ if (pipChange.getTaskInfo() != null
+ && pipChange.getTaskInfo().pictureInPictureParams != null) {
+ // single activity
+ sourceRectHint = pipChange.getTaskInfo().pictureInPictureParams.getSourceRectHint();
+ } else if (mPipTaskListener.getPictureInPictureParams().hasSourceBoundsHint()) {
+ // multi activity
+ sourceRectHint = mPipTaskListener.getPictureInPictureParams().getSourceRectHint();
+ }
PipEnterExitAnimator animator = new PipEnterExitAnimator(mContext, pipLeash,
- startTransaction, startBounds, startBounds, endBounds,
- PipEnterExitAnimator.BOUNDS_EXIT);
+ startTransaction, finishTransaction, endBounds, startBounds, endBounds,
+ sourceRectHint, PipEnterExitAnimator.BOUNDS_EXIT, Surface.ROTATION_0);
+
animator.setAnimationEndCallback(() -> {
- finishCallback.onTransitionFinished(null);
mPipTransitionState.setState(PipTransitionState.EXITED_PIP);
+ finishCallback.onTransitionFinished(null);
});
animator.start();
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 b18feefe..81f444b 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
@@ -352,12 +352,17 @@
/** Extract the window background color from {@code attrs}. */
private static int peekWindowBGColor(Context context, SplashScreenWindowAttrs attrs) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "peekWindowBGColor");
- final Drawable themeBGDrawable;
+ Drawable themeBGDrawable = null;
if (attrs.mWindowBgColor != 0) {
themeBGDrawable = new ColorDrawable(attrs.mWindowBgColor);
} else if (attrs.mWindowBgResId != 0) {
- themeBGDrawable = context.getDrawable(attrs.mWindowBgResId);
- } else {
+ try {
+ themeBGDrawable = context.getDrawable(attrs.mWindowBgResId);
+ } catch (Resources.NotFoundException e) {
+ Slog.w(TAG, "Unable get drawable from resource", e);
+ }
+ }
+ if (themeBGDrawable == null) {
themeBGDrawable = createDefaultBackgroundDrawable();
Slog.w(TAG, "Window background does not exist, using " + themeBGDrawable);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index ac354593..c88c1e2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -491,7 +491,9 @@
} else {
mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext,
Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE, "maximize_menu_resizable");
- mDesktopTasksController.snapToHalfScreen(decoration.mTaskInfo,
+ mDesktopTasksController.snapToHalfScreen(
+ decoration.mTaskInfo,
+ decoration.mTaskSurface,
decoration.mTaskInfo.configuration.windowConfiguration.getBounds(),
left ? SnapPosition.LEFT : SnapPosition.RIGHT);
}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DesktopScenarioCustomAppTestBase.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DesktopScenarioCustomAppTestBase.kt
new file mode 100644
index 0000000..5a69b27
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DesktopScenarioCustomAppTestBase.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.scenarios
+
+import android.app.Instrumentation
+import android.tools.traces.parsers.WindowManagerStateHelper
+import android.tools.traces.parsers.toFlickerComponent
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.LetterboxAppHelper
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import org.junit.Ignore
+
+/** Base test class for desktop CUJ with customizable test app. */
+@Ignore("Base Test Class")
+abstract class DesktopScenarioCustomAppTestBase(
+ isResizeable: Boolean = true,
+ isLandscapeApp: Boolean = true
+) {
+ val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ val tapl = LauncherInstrumentation()
+ val wmHelper = WindowManagerStateHelper(instrumentation)
+ val device = UiDevice.getInstance(instrumentation)
+ // TODO(b/363181411): Consolidate in LetterboxAppHelper.
+ val testApp = when {
+ isResizeable && isLandscapeApp -> SimpleAppHelper(instrumentation)
+ isResizeable && !isLandscapeApp -> SimpleAppHelper(
+ instrumentation,
+ launcherName = ActivityOptions.PortraitOnlyActivity.LABEL,
+ component = ActivityOptions.PortraitOnlyActivity.COMPONENT.toFlickerComponent()
+ )
+ // NonResizeablAppHelper has no fixed orientation.
+ !isResizeable && isLandscapeApp -> NonResizeableAppHelper(instrumentation)
+ // Opens NonResizeablePortraitActivity.
+ else -> LetterboxAppHelper(instrumentation)
+ }.let { DesktopModeAppHelper(it) }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt
index 0f0d2df..5f759e8 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt
@@ -17,15 +17,8 @@
package com.android.wm.shell.scenarios
import android.platform.test.annotations.Postsubmit
-import android.app.Instrumentation
import android.tools.NavBar
import android.tools.Rotation
-import android.tools.traces.parsers.WindowManagerStateHelper
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.uiautomator.UiDevice
-import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
-import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.window.flags.Flags
import com.android.wm.shell.Utils
import org.junit.After
@@ -40,13 +33,11 @@
@Postsubmit
open class EnterDesktopWithDrag
@JvmOverloads
-constructor(val rotation: Rotation = Rotation.ROTATION_0) {
-
- private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
- private val tapl = LauncherInstrumentation()
- private val wmHelper = WindowManagerStateHelper(instrumentation)
- private val device = UiDevice.getInstance(instrumentation)
- private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+constructor(
+ val rotation: Rotation = Rotation.ROTATION_0,
+ isResizeable: Boolean = true,
+ isLandscapeApp: Boolean = true
+) : DesktopScenarioCustomAppTestBase(isResizeable, isLandscapeApp) {
@Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt
index 533be88..b616e53 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt
@@ -17,15 +17,8 @@
package com.android.wm.shell.scenarios
import android.platform.test.annotations.Postsubmit
-import android.app.Instrumentation
import android.tools.NavBar
import android.tools.Rotation
-import android.tools.traces.parsers.WindowManagerStateHelper
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.uiautomator.UiDevice
-import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
-import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.window.flags.Flags
import com.android.wm.shell.Utils
import org.junit.After
@@ -40,13 +33,11 @@
@Postsubmit
open class ExitDesktopWithDragToTopDragZone
@JvmOverloads
-constructor(val rotation: Rotation = Rotation.ROTATION_0) {
-
- private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
- private val tapl = LauncherInstrumentation()
- private val wmHelper = WindowManagerStateHelper(instrumentation)
- private val device = UiDevice.getInstance(instrumentation)
- private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+constructor(
+ val rotation: Rotation = Rotation.ROTATION_0,
+ isResizeable: Boolean = true,
+ isLandscapeApp: Boolean = true
+) : DesktopScenarioCustomAppTestBase(isResizeable, isLandscapeApp) {
@Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index b85d793..a9ed13a 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -16,10 +16,14 @@
package com.android.wm.shell.flicker.pip
+import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.subject.exceptions.ExceptionMessageBuilder
+import android.tools.flicker.subject.exceptions.IncorrectRegionException
+import android.tools.flicker.subject.layers.LayerSubject
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.pip.common.EnterPipTransition
@@ -29,6 +33,7 @@
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
+import kotlin.math.abs
/**
* Test entering pip from an app via auto-enter property when navigating to home.
@@ -67,9 +72,24 @@
override val defaultTeardown: FlickerBuilder.() -> Unit = { teardown { pipApp.exit(wmHelper) } }
- @FlakyTest(bugId = 293133362)
+ private val widthNotSmallerThan: LayerSubject.(LayerSubject) -> Unit = {
+ val width = visibleRegion.region.bounds.width()
+ val otherWidth = it.visibleRegion.region.bounds.width()
+ if (width < otherWidth && abs(width - otherWidth) > EPSILON) {
+ val errorMsgBuilder =
+ ExceptionMessageBuilder()
+ .forSubject(this)
+ .forIncorrectRegion("width. $width smaller than $otherWidth")
+ .setExpected(width)
+ .setActual(otherWidth)
+ throw IncorrectRegionException(errorMsgBuilder)
+ }
+ }
+
+ @Postsubmit
@Test
override fun pipLayerReduces() {
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
flicker.assertLayers {
val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
pipLayerList.zipWithNext { previous, current ->
@@ -78,6 +98,32 @@
}
}
+ /** Checks that [pipApp] window's width is first decreasing then increasing. */
+ @Postsubmit
+ @Test
+ fun pipLayerWidthDecreasesThenIncreases() {
+ Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
+ flicker.assertLayers {
+ val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
+ var previousLayer = pipLayerList[0]
+ var currentLayer = previousLayer
+ var i = 0
+ invoke("layer area is decreasing") {
+ if (i < pipLayerList.size - 1) {
+ previousLayer = currentLayer
+ currentLayer = pipLayerList[++i]
+ previousLayer.widthNotSmallerThan(currentLayer)
+ }
+ }.then().invoke("layer are is increasing", true /* isOptional */) {
+ if (i < pipLayerList.size - 1) {
+ previousLayer = currentLayer
+ currentLayer = pipLayerList[++i]
+ currentLayer.widthNotSmallerThan(previousLayer)
+ }
+ }
+ }
+ }
+
/** Checks that [pipApp] window is animated towards default position in right bottom corner */
@FlakyTest(bugId = 255578530)
@Test
@@ -108,4 +154,9 @@
override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
super.visibleLayersShownMoreThanOneConsecutiveEntry()
}
+
+ companion object {
+ // TODO(b/363080056): A margin of error allowed on certain layer size calculations.
+ const val EPSILON = 1
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt
index 70be58f..5b7521a 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt
@@ -22,6 +22,7 @@
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
import com.android.wm.shell.flicker.pip.common.PipTransition
import org.junit.FixMethodOrder
import org.junit.Test
@@ -45,6 +46,22 @@
flicker.assertLayersEnd { this.visibleRegion(pipApp).isSameAspectRatio(1, 2) }
}
+ @FlakyTest(bugId = 358278071)
+ override fun hasAtMostOnePipDismissOverlayWindow() =
+ super.hasAtMostOnePipDismissOverlayWindow()
+
+ @FlakyTest(bugId = 358278071)
+ override fun statusBarLayerPositionAtStartAndEnd() =
+ super.statusBarLayerPositionAtStartAndEnd()
+
+ @FlakyTest(bugId = 358278071)
+ override fun taskBarWindowIsAlwaysVisible() =
+ super.taskBarWindowIsAlwaysVisible()
+
+ @FlakyTest(bugId = 358278071)
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
companion object {
/**
* Creates the test configurations.
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
index 90b9798..cbd4a52 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
@@ -181,6 +181,13 @@
super.taskBarWindowIsAlwaysVisible()
}
+ // Overridden to remove @Postsubmit annotation
+ @Test
+ @FlakyTest(bugId = 294993100)
+ override fun pipLayerHasCorrectCornersAtEnd() {
+ // No rounded corners as we go back to fullscreen in new orientation.
+ }
+
companion object {
/**
* Creates the test configurations.
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
index ed2a0a7..578a9b5 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
@@ -147,6 +147,12 @@
@Test
override fun entireScreenCovered() = super.entireScreenCovered()
+ @Postsubmit
+ @Test
+ override fun pipLayerHasCorrectCornersAtEnd() {
+ flicker.assertLayersEnd { hasNoRoundedCorners(pipApp) }
+ }
+
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt
index 8cb81b4..f57335c 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt
@@ -70,6 +70,11 @@
}
}
+ @Test
+ override fun pipLayerHasCorrectCornersAtEnd() {
+ // PiP might have completely faded out by this point, so corner radii not applicable.
+ }
+
companion object {
/**
* Creates the test configurations.
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt
index 0742cf9..ce84eb6 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.flicker.pip.common
+import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.Rotation
import android.tools.flicker.legacy.LegacyFlickerTest
@@ -123,6 +124,12 @@
}
}
+ @Postsubmit
+ @Test
+ override fun pipLayerHasCorrectCornersAtEnd() {
+ flicker.assertLayersEnd { hasNoRoundedCorners(pipApp) }
+ }
+
/** {@inheritDoc} */
@Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
index 99c1ad2..bc2bfdb 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
@@ -18,6 +18,7 @@
import android.app.Instrumentation
import android.content.Intent
+import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.Rotation
import android.tools.flicker.legacy.FlickerBuilder
@@ -105,4 +106,10 @@
.doesNotContain(false)
}
}
+
+ @Postsubmit
+ @Test
+ open fun pipLayerHasCorrectCornersAtEnd() {
+ flicker.assertLayersEnd { hasRoundedCorners(pipApp) }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
index 4998702..f31722d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
@@ -19,6 +19,7 @@
import com.android.wm.shell.common.ShellExecutor;
import java.util.ArrayList;
+import java.util.List;
/**
* Really basic test executor. It just gathers all events in a blob. The only option is to
@@ -52,4 +53,9 @@
mRunnables.remove(0).run();
}
}
+
+ /** Returns the list of callbacks for this executor. */
+ public List<Runnable> getCallbacks() {
+ return mRunnables;
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleInfoTest.kt
index 5b22edd..6695a1e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleInfoTest.kt
@@ -41,6 +41,7 @@
"com.some.package",
"title",
"Some app",
+ true,
true
)
val parcel = Parcel.obtain()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 5474e53..10557dd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -123,12 +123,12 @@
import org.mockito.ArgumentMatchers.isNull
import org.mockito.Mock
import org.mockito.Mockito
-import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.mock
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.atLeastOnce
import org.mockito.kotlin.eq
@@ -2859,7 +2859,7 @@
}
@Test
- fun getSnapBounds_calculatesBoundsForResizable() {
+ fun snapToHalfScreen_getSnapBounds_calculatesBoundsForResizable() {
val bounds = Rect(100, 100, 300, 300)
val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds).apply {
topActivityInfo = ActivityInfo().apply {
@@ -2874,13 +2874,45 @@
STABLE_BOUNDS.left, STABLE_BOUNDS.top, STABLE_BOUNDS.right / 2, STABLE_BOUNDS.bottom
)
- controller.snapToHalfScreen(task, currentDragBounds, SnapPosition.LEFT)
+ controller.snapToHalfScreen(task, mockSurface, currentDragBounds, SnapPosition.LEFT)
// Assert bounds set to stable bounds
val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds)
assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds)
}
@Test
+ fun snapToHalfScreen_snapBoundsWhenAlreadySnapped_animatesSurfaceWithoutWCT() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+ // Set up task to already be in snapped-left bounds
+ val bounds = Rect(
+ STABLE_BOUNDS.left, STABLE_BOUNDS.top, STABLE_BOUNDS.right / 2, STABLE_BOUNDS.bottom
+ )
+ val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds).apply {
+ topActivityInfo = ActivityInfo().apply {
+ screenOrientation = SCREEN_ORIENTATION_LANDSCAPE
+ configuration.windowConfiguration.appBounds = bounds
+ }
+ isResizeable = true
+ }
+
+ // Attempt to snap left again
+ val currentDragBounds = Rect(bounds).apply { offset(-100, 0) }
+ controller.snapToHalfScreen(task, mockSurface, currentDragBounds, SnapPosition.LEFT)
+
+ // Assert that task is NOT updated via WCT
+ verify(toggleResizeDesktopTaskTransitionHandler, never()).startTransition(any(), any())
+
+ // Assert that task leash is updated via Surface Animations
+ verify(mReturnToDragStartAnimator).start(
+ eq(task.taskId),
+ eq(mockSurface),
+ eq(currentDragBounds),
+ eq(bounds),
+ eq(true)
+ )
+ }
+
+ @Test
@DisableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING)
fun handleSnapResizingTask_nonResizable_snapsToHalfScreen() {
val task = setUpFreeformTask(DEFAULT_DISPLAY, Rect(0, 0, 200, 100)).apply {
@@ -2911,7 +2943,8 @@
eq(task.taskId),
eq(mockSurface),
eq(currentDragBounds),
- eq(preDragBounds)
+ eq(preDragBounds),
+ eq(false)
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/handles/RegionSamplingHelperTest.kt
similarity index 75%
rename from packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/handles/RegionSamplingHelperTest.kt
index 2a6754c..d3e291f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/handles/RegionSamplingHelperTest.kt
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-package com.android.systemui.shared.navigationbar
+package com.android.wm.shell.shared.handles
+
import android.graphics.Rect
import android.testing.TestableLooper.RunWithLooper
@@ -24,16 +25,15 @@
import androidx.concurrent.futures.DirectExecutor
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.time.FakeSystemClock
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestShellExecutor
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
@@ -42,11 +42,12 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.argumentCaptor
@RunWith(AndroidJUnit4::class)
@SmallTest
@RunWithLooper
-class RegionSamplingHelperTest : SysuiTestCase() {
+class RegionSamplingHelperTest : ShellTestCase() {
@Mock
lateinit var sampledView: View
@@ -72,12 +73,16 @@
whenever(surfaceControl.isValid).thenReturn(true)
whenever(wrappedSurfaceControl.isValid).thenReturn(true)
whenever(samplingCallback.isSamplingEnabled).thenReturn(true)
- regionSamplingHelper = object : RegionSamplingHelper(sampledView, samplingCallback,
- DirectExecutor.INSTANCE, DirectExecutor.INSTANCE, compositionListener) {
- override fun wrap(stopLayerControl: SurfaceControl?): SurfaceControl {
- return wrappedSurfaceControl
+ getInstrumentation().runOnMainSync(Runnable {
+ regionSamplingHelper = object : RegionSamplingHelper(
+ sampledView, samplingCallback,
+ DirectExecutor.INSTANCE, DirectExecutor.INSTANCE, compositionListener
+ ) {
+ override fun wrap(stopLayerControl: SurfaceControl?): SurfaceControl {
+ return wrappedSurfaceControl
+ }
}
- }
+ })
regionSamplingHelper.setWindowVisible(true)
}
@@ -99,7 +104,7 @@
regionSamplingHelper.setWindowHasBlurs(true)
regionSamplingHelper.start(Rect(0, 0, 100, 100))
verify(compositionListener, never())
- .register(any(), anyInt(), eq(wrappedSurfaceControl), any())
+ .register(any(), anyInt(), eq(wrappedSurfaceControl), any())
}
@Test
@@ -112,35 +117,38 @@
@Test
fun testCompositionSamplingListener_has_nonEmptyRect() {
// simulate race condition
- val fakeExecutor = FakeExecutor(FakeSystemClock()) // pass in as backgroundExecutor
+ val fakeExecutor = TestShellExecutor() // pass in as backgroundExecutor
val fakeSamplingCallback = mock(RegionSamplingHelper.SamplingCallback::class.java)
whenever(fakeSamplingCallback.isSamplingEnabled).thenReturn(true)
whenever(wrappedSurfaceControl.isValid).thenReturn(true)
-
- regionSamplingHelper = object : RegionSamplingHelper(sampledView, fakeSamplingCallback,
- DirectExecutor.INSTANCE, fakeExecutor, compositionListener) {
- override fun wrap(stopLayerControl: SurfaceControl?): SurfaceControl {
- return wrappedSurfaceControl
+ getInstrumentation().runOnMainSync(Runnable {
+ regionSamplingHelper = object : RegionSamplingHelper(
+ sampledView, fakeSamplingCallback,
+ DirectExecutor.INSTANCE, fakeExecutor, compositionListener
+ ) {
+ override fun wrap(stopLayerControl: SurfaceControl?): SurfaceControl {
+ return wrappedSurfaceControl
+ }
}
- }
+ })
regionSamplingHelper.setWindowVisible(true)
regionSamplingHelper.start(Rect(0, 0, 100, 100))
// make sure background task is enqueued
- assertThat(fakeExecutor.numPending()).isEqualTo(1)
+ assertThat(fakeExecutor.getCallbacks().size).isEqualTo(1)
// make sure regionSamplingHelper will have empty Rect
whenever(fakeSamplingCallback.getSampledRegion(any())).thenReturn(Rect(0, 0, 0, 0))
regionSamplingHelper.onLayoutChange(sampledView, 0, 0, 0, 0, 0, 0, 0, 0)
// resume running of background thread
- fakeExecutor.runAllReady()
+ fakeExecutor.flushAll()
// grab Rect passed into compositionSamplingListener and make sure it's not empty
val argumentGrabber = argumentCaptor<Rect>()
verify(compositionListener).register(any(), anyInt(), eq(wrappedSurfaceControl),
- argumentGrabber.capture())
- assertThat(argumentGrabber.value.isEmpty).isFalse()
+ argumentGrabber.capture())
+ assertThat(argumentGrabber.firstValue.isEmpty).isFalse()
}
-}
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 0b5c678..be0549b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -113,7 +113,7 @@
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
+import org.mockito.kotlin.verify
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.argThat
@@ -600,6 +600,7 @@
@Test
fun testOnDecorSnappedLeft_snapResizes() {
+ val taskSurfaceCaptor = argumentCaptor<SurfaceControl>()
val onLeftSnapClickListenerCaptor = forClass(Function0::class.java)
as ArgumentCaptor<Function0<Unit>>
val decor = createOpenTaskDecoration(
@@ -610,8 +611,13 @@
val currentBounds = decor.mTaskInfo.configuration.windowConfiguration.bounds
onLeftSnapClickListenerCaptor.value.invoke()
- verify(mockDesktopTasksController)
- .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.LEFT)
+ verify(mockDesktopTasksController).snapToHalfScreen(
+ eq(decor.mTaskInfo),
+ taskSurfaceCaptor.capture(),
+ eq(currentBounds),
+ eq(SnapPosition.LEFT)
+ )
+ assertEquals(taskSurfaceCaptor.firstValue, decor.mTaskSurface)
}
@Test
@@ -632,6 +638,7 @@
@Test
@DisableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING)
fun testOnSnapResizeLeft_nonResizable_decorSnappedLeft() {
+ val taskSurfaceCaptor = argumentCaptor<SurfaceControl>()
val onLeftSnapClickListenerCaptor = forClass(Function0::class.java)
as ArgumentCaptor<Function0<Unit>>
val decor = createOpenTaskDecoration(
@@ -642,8 +649,13 @@
val currentBounds = decor.mTaskInfo.configuration.windowConfiguration.bounds
onLeftSnapClickListenerCaptor.value.invoke()
- verify(mockDesktopTasksController)
- .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.LEFT)
+ verify(mockDesktopTasksController).snapToHalfScreen(
+ eq(decor.mTaskInfo),
+ taskSurfaceCaptor.capture(),
+ eq(currentBounds),
+ eq(SnapPosition.LEFT)
+ )
+ assertEquals(decor.mTaskSurface, taskSurfaceCaptor.firstValue)
}
@Test
@@ -660,12 +672,13 @@
onLeftSnapClickListenerCaptor.value.invoke()
verify(mockDesktopTasksController, never())
- .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.LEFT)
+ .snapToHalfScreen(eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.LEFT))
verify(mockToast).show()
}
@Test
fun testOnDecorSnappedRight_snapResizes() {
+ val taskSurfaceCaptor = argumentCaptor<SurfaceControl>()
val onRightSnapClickListenerCaptor = forClass(Function0::class.java)
as ArgumentCaptor<Function0<Unit>>
val decor = createOpenTaskDecoration(
@@ -676,8 +689,13 @@
val currentBounds = decor.mTaskInfo.configuration.windowConfiguration.bounds
onRightSnapClickListenerCaptor.value.invoke()
- verify(mockDesktopTasksController)
- .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.RIGHT)
+ verify(mockDesktopTasksController).snapToHalfScreen(
+ eq(decor.mTaskInfo),
+ taskSurfaceCaptor.capture(),
+ eq(currentBounds),
+ eq(SnapPosition.RIGHT)
+ )
+ assertEquals(decor.mTaskSurface, taskSurfaceCaptor.firstValue)
}
@Test
@@ -698,6 +716,7 @@
@Test
@DisableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING)
fun testOnSnapResizeRight_nonResizable_decorSnappedRight() {
+ val taskSurfaceCaptor = argumentCaptor<SurfaceControl>()
val onRightSnapClickListenerCaptor = forClass(Function0::class.java)
as ArgumentCaptor<Function0<Unit>>
val decor = createOpenTaskDecoration(
@@ -708,8 +727,13 @@
val currentBounds = decor.mTaskInfo.configuration.windowConfiguration.bounds
onRightSnapClickListenerCaptor.value.invoke()
- verify(mockDesktopTasksController)
- .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.RIGHT)
+ verify(mockDesktopTasksController).snapToHalfScreen(
+ eq(decor.mTaskInfo),
+ taskSurfaceCaptor.capture(),
+ eq(currentBounds),
+ eq(SnapPosition.RIGHT)
+ )
+ assertEquals(decor.mTaskSurface, taskSurfaceCaptor.firstValue)
}
@Test
@@ -726,7 +750,7 @@
onRightSnapClickListenerCaptor.value.invoke()
verify(mockDesktopTasksController, never())
- .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.RIGHT)
+ .snapToHalfScreen(eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.RIGHT))
verify(mockToast).show()
}
@@ -1033,6 +1057,7 @@
private fun createOpenTaskDecoration(
@WindowingMode windowingMode: Int,
+ taskSurface: SurfaceControl = SurfaceControl(),
onMaxOrRestoreListenerCaptor: ArgumentCaptor<Function0<Unit>> =
forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>>,
onLeftSnapClickListenerCaptor: ArgumentCaptor<Function0<Unit>> =
@@ -1051,7 +1076,7 @@
forClass(View.OnClickListener::class.java) as ArgumentCaptor<View.OnClickListener>
): DesktopModeWindowDecoration {
val decor = setUpMockDecorationForTask(createTask(windowingMode = windowingMode))
- onTaskOpening(decor.mTaskInfo)
+ onTaskOpening(decor.mTaskInfo, taskSurface)
verify(decor).setOnMaximizeOrRestoreClickListener(onMaxOrRestoreListenerCaptor.capture())
verify(decor).setOnLeftSnapClickListener(onLeftSnapClickListenerCaptor.capture())
verify(decor).setOnRightSnapClickListener(onRightSnapClickListenerCaptor.capture())
diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp
index eecc741..1afef75 100644
--- a/libs/input/MouseCursorController.cpp
+++ b/libs/input/MouseCursorController.cpp
@@ -25,6 +25,9 @@
#include <input/Input.h>
#include <log/log.h>
+#define INDENT " "
+#define INDENT2 " "
+
namespace {
// Time to spend fading out the pointer completely.
const nsecs_t POINTER_FADE_DURATION = 500 * 1000000LL; // 500 ms
@@ -449,6 +452,24 @@
return mLocked.resourcesLoaded;
}
+std::string MouseCursorController::dump() const {
+ std::string dump = INDENT "MouseCursorController:\n";
+ std::scoped_lock lock(mLock);
+ dump += StringPrintf(INDENT2 "viewport: %s\n", mLocked.viewport.toString().c_str());
+ dump += StringPrintf(INDENT2 "stylusHoverMode: %s\n",
+ mLocked.stylusHoverMode ? "true" : "false");
+ dump += StringPrintf(INDENT2 "pointerFadeDirection: %d\n", mLocked.pointerFadeDirection);
+ dump += StringPrintf(INDENT2 "updatePointerIcon: %s\n",
+ mLocked.updatePointerIcon ? "true" : "false");
+ dump += StringPrintf(INDENT2 "resourcesLoaded: %s\n",
+ mLocked.resourcesLoaded ? "true" : "false");
+ dump += StringPrintf(INDENT2 "requestedPointerType: %d\n", mLocked.requestedPointerType);
+ dump += StringPrintf(INDENT2 "resolvedPointerType: %d\n", mLocked.resolvedPointerType);
+ dump += StringPrintf(INDENT2 "skipScreenshot: %s\n", mLocked.skipScreenshot ? "true" : "false");
+ dump += StringPrintf(INDENT2 "animating: %s\n", mLocked.animating ? "true" : "false");
+ return dump;
+}
+
bool MouseCursorController::doAnimations(nsecs_t timestamp) {
std::scoped_lock lock(mLock);
bool keepFading = doFadingAnimationLocked(timestamp);
diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h
index 78f6413..8600341 100644
--- a/libs/input/MouseCursorController.h
+++ b/libs/input/MouseCursorController.h
@@ -67,6 +67,8 @@
bool resourcesLoaded();
+ std::string dump() const;
+
private:
mutable std::mutex mLock;
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 11b27a2..5ae967b 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -25,6 +25,7 @@
#include <android-base/stringprintf.h>
#include <android-base/thread_annotations.h>
#include <ftl/enum.h>
+#include <input/PrintTools.h>
#include <mutex>
@@ -353,6 +354,8 @@
for (const auto& [_, spotController] : mLocked.spotControllers) {
spotController.dump(dump, INDENT3);
}
+ dump += INDENT2 "Cursor Controller:\n";
+ dump += addLinePrefix(mCursorController.dump(), INDENT3);
return dump;
}
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index a255f73..ebdfd3e 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -2655,7 +2655,16 @@
/**
* Register a native listener for system property sysprop
* @param callback the listener which fires when the property changes
+ * @return a native handle for use in subsequent methods
* @hide
*/
- public static native void listenForSystemPropertyChange(String sysprop, Runnable callback);
+ public static native long listenForSystemPropertyChange(String sysprop, Runnable callback);
+
+ /**
+ * Trigger a sysprop listener update, if the property has been updated: synchronously validating
+ * there are no pending sysprop changes.
+ * @param handle the handle returned by {@link listenForSystemPropertyChange}
+ * @hide
+ */
+ public static native void triggerSystemPropertyUpdate(long handle);
}
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index 717e01e..0f97b2c 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -73,6 +73,7 @@
method public void onApplyRouting(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public void onBootFinished(int);
method public void onBootStarted();
+ method public void onCardEmulationActivated(boolean);
method public void onDisable(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public void onDisableFinished(int);
method public void onDisableStarted();
@@ -81,6 +82,8 @@
method public void onEnableStarted();
method public void onHceEventReceived(int);
method public void onNdefRead(@NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method public void onRfDiscoveryStarted(boolean);
+ method public void onRfFieldActivated(boolean);
method public void onRoutingChanged();
method public void onStateUpdated(int);
method public void onTagConnected(boolean, @NonNull android.nfc.Tag);
diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl
index 6c0f933..e2ec952 100644
--- a/nfc/java/android/nfc/INfcAdapter.aidl
+++ b/nfc/java/android/nfc/INfcAdapter.aidl
@@ -62,7 +62,7 @@
void dispatch(in Tag tag);
- void setReaderMode (IBinder b, IAppCallback callback, int flags, in Bundle extras);
+ void setReaderMode (IBinder b, IAppCallback callback, int flags, in Bundle extras, String pkg);
void addNfcUnlockHandler(INfcUnlockHandler unlockHandler, in int[] techList);
void removeNfcUnlockHandler(INfcUnlockHandler unlockHandler);
@@ -100,7 +100,7 @@
void unregisterWlcStateListener(in INfcWlcStateListener listener);
WlcListenerDeviceInfo getWlcListenerDeviceInfo();
- void updateDiscoveryTechnology(IBinder b, int pollFlags, int listenFlags);
+ void updateDiscoveryTechnology(IBinder b, int pollFlags, int listenFlags, String pkg);
void notifyPollingLoop(in PollingFrame frame);
void notifyHceDeactivated();
diff --git a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
index c19a44b..b65c837 100644
--- a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
+++ b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
@@ -37,4 +37,7 @@
void onTagDispatch(in ResultReceiver isSkipped);
void onRoutingChanged();
void onHceEventReceived(int action);
+ void onCardEmulationActivated(boolean isActivated);
+ void onRfFieldActivated(boolean isActivated);
+ void onRfDiscoveryStarted(boolean isDiscoveryStarted);
}
diff --git a/nfc/java/android/nfc/NfcActivityManager.java b/nfc/java/android/nfc/NfcActivityManager.java
index 0eb846d..909eca7 100644
--- a/nfc/java/android/nfc/NfcActivityManager.java
+++ b/nfc/java/android/nfc/NfcActivityManager.java
@@ -236,7 +236,8 @@
public void setReaderMode(Binder token, int flags, Bundle extras) {
if (DBG) Log.d(TAG, "Setting reader mode");
- NfcAdapter.callService(() -> NfcAdapter.sService.setReaderMode(token, this, flags, extras));
+ NfcAdapter.callService(() -> NfcAdapter.sService.setReaderMode(
+ token, this, flags, extras, mAdapter.getContext().getPackageName()));
}
/**
@@ -395,7 +396,8 @@
private void changeDiscoveryTech(Binder token, int pollTech, int listenTech) {
NfcAdapter.callService(
- () -> NfcAdapter.sService.updateDiscoveryTechnology(token, pollTech, listenTech));
+ () -> NfcAdapter.sService.updateDiscoveryTechnology(
+ token, pollTech, listenTech, mAdapter.getContext().getPackageName()));
}
}
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index 525e2c5..22ae612 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -1731,7 +1731,8 @@
}
Binder token = new Binder();
int flags = enable ? ENABLE_POLLING_FLAGS : DISABLE_POLLING_FLAGS;
- callService(() -> sService.setReaderMode(token, null, flags, null));
+ callService(() -> sService.setReaderMode(
+ token, null, flags, null, mContext.getPackageName()));
}
/**
@@ -1804,7 +1805,8 @@
|| (listenTechnology & FLAG_SET_DEFAULT_TECH) == FLAG_SET_DEFAULT_TECH)) {
Binder token = new Binder();
callService( () ->
- sService.updateDiscoveryTechnology(token, pollTechnology, listenTechnology));
+ sService.updateDiscoveryTechnology(
+ token, pollTechnology, listenTechnology, mContext.getPackageName()));
} else {
mNfcActivityManager.setDiscoveryTech(activity, pollTechnology, listenTechnology);
}
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index 6c02edd..632f693 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -32,7 +32,9 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
@@ -40,6 +42,7 @@
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -58,10 +61,13 @@
private static final int OEM_EXTENSION_RESPONSE_THRESHOLD_MS = 2000;
private final NfcAdapter mAdapter;
private final NfcOemExtensionCallback mOemNfcExtensionCallback;
+ private boolean mIsRegistered = false;
+ private final Map<Callback, Executor> mCallbackMap = new HashMap<>();
private final Context mContext;
- private Executor mExecutor = null;
- private Callback mCallback = null;
private final Object mLock = new Object();
+ private boolean mCardEmulationActivated = false;
+ private boolean mRfFieldActivated = false;
+ private boolean mRfDiscoveryStarted = false;
/**
* Event that Host Card Emulation is activated.
@@ -215,6 +221,32 @@
* @param action Flag indicating actions to activate, start and stop cpu boost.
*/
void onHceEventReceived(@HostCardEmulationAction int action);
+
+ /**
+ * Notifies NFC is activated in listen mode.
+ * NFC Forum NCI-2.3 ch.5.2.6 specification
+ *
+ * <p>NFCC is ready to communicate with a Card reader
+ *
+ * @param isActivated true, if card emulation activated, else de-activated.
+ */
+ void onCardEmulationActivated(boolean isActivated);
+
+ /**
+ * Notifies the Remote NFC Endpoint RF Field is activated.
+ * NFC Forum NCI-2.3 ch.5.3 specification
+ *
+ * @param isActivated true, if RF Field is ON, else RF Field is OFF.
+ */
+ void onRfFieldActivated(boolean isActivated);
+
+ /**
+ * Notifies the NFC RF discovery is started or in the IDLE state.
+ * NFC Forum NCI-2.3 ch.5.2 specification
+ *
+ * @param isDiscoveryStarted true, if RF discovery started, else RF state is Idle.
+ */
+ void onRfDiscoveryStarted(boolean isDiscoveryStarted);
}
@@ -229,7 +261,12 @@
/**
* Register an {@link Callback} to listen for NFC oem extension callbacks
+ * Multiple clients can register and callbacks will be invoked asynchronously.
+ *
* <p>The provided callback will be invoked by the given {@link Executor}.
+ * As part of {@link #registerCallback(Executor, Callback)} the
+ * {@link Callback} will be invoked with current NFC state
+ * before the {@link #registerCallback(Executor, Callback)} function completes.
*
* @param executor an {@link Executor} to execute given callback
* @param callback oem implementation of {@link Callback}
@@ -239,15 +276,35 @@
public void registerCallback(@NonNull @CallbackExecutor Executor executor,
@NonNull Callback callback) {
synchronized (mLock) {
- if (mCallback != null) {
+ if (executor == null || callback == null) {
+ Log.e(TAG, "Executor and Callback must not be null!");
+ throw new IllegalArgumentException();
+ }
+
+ if (mCallbackMap.containsKey(callback)) {
Log.e(TAG, "Callback already registered. Unregister existing callback before"
+ "registering");
throw new IllegalArgumentException();
}
- NfcAdapter.callService(() -> {
- NfcAdapter.sService.registerOemExtensionCallback(mOemNfcExtensionCallback);
- mCallback = callback;
- mExecutor = executor;
+ mCallbackMap.put(callback, executor);
+ if (!mIsRegistered) {
+ NfcAdapter.callService(() -> {
+ NfcAdapter.sService.registerOemExtensionCallback(mOemNfcExtensionCallback);
+ mIsRegistered = true;
+ });
+ } else {
+ updateNfCState(callback, executor);
+ }
+ }
+ }
+
+ private void updateNfCState(Callback callback, Executor executor) {
+ if (callback != null) {
+ Log.i(TAG, "updateNfCState");
+ executor.execute(() -> {
+ callback.onCardEmulationActivated(mCardEmulationActivated);
+ callback.onRfFieldActivated(mRfFieldActivated);
+ callback.onRfDiscoveryStarted(mRfDiscoveryStarted);
});
}
}
@@ -266,15 +323,19 @@
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
public void unregisterCallback(@NonNull Callback callback) {
synchronized (mLock) {
- if (mCallback == null || mCallback != callback) {
+ if (!mCallbackMap.containsKey(callback) || !mIsRegistered) {
Log.e(TAG, "Callback not registered");
throw new IllegalArgumentException();
}
- NfcAdapter.callService(() -> {
- NfcAdapter.sService.unregisterOemExtensionCallback(mOemNfcExtensionCallback);
- mCallback = null;
- mExecutor = null;
- });
+ if (mCallbackMap.size() == 1) {
+ NfcAdapter.callService(() -> {
+ NfcAdapter.sService.unregisterOemExtensionCallback(mOemNfcExtensionCallback);
+ mIsRegistered = false;
+ mCallbackMap.remove(callback);
+ });
+ } else {
+ mCallbackMap.remove(callback);
+ }
}
}
@@ -322,90 +383,133 @@
}
private final class NfcOemExtensionCallback extends INfcOemExtensionCallback.Stub {
+
@Override
public void onTagConnected(boolean connected, Tag tag) throws RemoteException {
+ mCallbackMap.forEach((cb, ex) ->
+ handleVoid2ArgCallback(connected, tag, cb::onTagConnected, ex));
+ }
+
+ @Override
+ public void onCardEmulationActivated(boolean isActivated) throws RemoteException {
+ mCardEmulationActivated = isActivated;
+ mCallbackMap.forEach((cb, ex) ->
+ handleVoidCallback(isActivated, cb::onCardEmulationActivated, ex));
+ }
+
+ @Override
+ public void onRfFieldActivated(boolean isActivated) throws RemoteException {
+ mRfFieldActivated = isActivated;
+ mCallbackMap.forEach((cb, ex) ->
+ handleVoidCallback(isActivated, cb::onRfFieldActivated, ex));
+ }
+
+ @Override
+ public void onRfDiscoveryStarted(boolean isDiscoveryStarted) throws RemoteException {
+ mRfDiscoveryStarted = isDiscoveryStarted;
+ mCallbackMap.forEach((cb, ex) ->
+ handleVoidCallback(isDiscoveryStarted, cb::onRfDiscoveryStarted, ex));
+ }
+
+ @Override
+ public void onStateUpdated(int state) throws RemoteException {
+ mCallbackMap.forEach((cb, ex) ->
+ handleVoidCallback(state, cb::onStateUpdated, ex));
+ }
+
+ @Override
+ public void onApplyRouting(ResultReceiver isSkipped) throws RemoteException {
+ mCallbackMap.forEach((cb, ex) ->
+ handleVoidCallback(
+ new ReceiverWrapper(isSkipped), cb::onApplyRouting, ex));
+ }
+ @Override
+ public void onNdefRead(ResultReceiver isSkipped) throws RemoteException {
+ mCallbackMap.forEach((cb, ex) ->
+ handleVoidCallback(
+ new ReceiverWrapper(isSkipped), cb::onNdefRead, ex));
+ }
+ @Override
+ public void onEnable(ResultReceiver isAllowed) throws RemoteException {
+ mCallbackMap.forEach((cb, ex) ->
+ handleVoidCallback(
+ new ReceiverWrapper(isAllowed), cb::onEnable, ex));
+ }
+ @Override
+ public void onDisable(ResultReceiver isAllowed) throws RemoteException {
+ mCallbackMap.forEach((cb, ex) ->
+ handleVoidCallback(
+ new ReceiverWrapper(isAllowed), cb::onDisable, ex));
+ }
+ @Override
+ public void onBootStarted() throws RemoteException {
+ mCallbackMap.forEach((cb, ex) ->
+ handleVoidCallback(null, (Object input) -> cb.onBootStarted(), ex));
+ }
+ @Override
+ public void onEnableStarted() throws RemoteException {
+ mCallbackMap.forEach((cb, ex) ->
+ handleVoidCallback(null, (Object input) -> cb.onEnableStarted(), ex));
+ }
+ @Override
+ public void onDisableStarted() throws RemoteException {
+ mCallbackMap.forEach((cb, ex) ->
+ handleVoidCallback(null, (Object input) -> cb.onDisableStarted(), ex));
+ }
+ @Override
+ public void onBootFinished(int status) throws RemoteException {
+ mCallbackMap.forEach((cb, ex) ->
+ handleVoidCallback(status, cb::onBootFinished, ex));
+ }
+ @Override
+ public void onEnableFinished(int status) throws RemoteException {
+ mCallbackMap.forEach((cb, ex) ->
+ handleVoidCallback(status, cb::onEnableFinished, ex));
+ }
+ @Override
+ public void onDisableFinished(int status) throws RemoteException {
+ mCallbackMap.forEach((cb, ex) ->
+ handleVoidCallback(status, cb::onDisableFinished, ex));
+ }
+ @Override
+ public void onTagDispatch(ResultReceiver isSkipped) throws RemoteException {
+ mCallbackMap.forEach((cb, ex) ->
+ handleVoidCallback(
+ new ReceiverWrapper(isSkipped), cb::onTagDispatch, ex));
+ }
+ @Override
+ public void onRoutingChanged() throws RemoteException {
+ mCallbackMap.forEach((cb, ex) ->
+ handleVoidCallback(null, (Object input) -> cb.onRoutingChanged(), ex));
+ }
+ @Override
+ public void onHceEventReceived(int action) throws RemoteException {
+ mCallbackMap.forEach((cb, ex) ->
+ handleVoidCallback(action, cb::onHceEventReceived, ex));
+ }
+
+ private <T> void handleVoidCallback(
+ T input, Consumer<T> callbackMethod, Executor executor) {
synchronized (mLock) {
- if (mCallback == null || mExecutor == null) {
- return;
- }
final long identity = Binder.clearCallingIdentity();
try {
- mExecutor.execute(() -> mCallback.onTagConnected(connected, tag));
+ executor.execute(() -> callbackMethod.accept(input));
+ } catch (RuntimeException ex) {
+ throw ex;
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
- @Override
- public void onStateUpdated(int state) throws RemoteException {
- handleVoidCallback(state, mCallback::onStateUpdated);
- }
- @Override
- public void onApplyRouting(ResultReceiver isSkipped) throws RemoteException {
- handleVoidCallback(
- new ReceiverWrapper(isSkipped), mCallback::onApplyRouting);
- }
- @Override
- public void onNdefRead(ResultReceiver isSkipped) throws RemoteException {
- handleVoidCallback(
- new ReceiverWrapper(isSkipped), mCallback::onNdefRead);
- }
- @Override
- public void onEnable(ResultReceiver isAllowed) throws RemoteException {
- handleVoidCallback(
- new ReceiverWrapper(isAllowed), mCallback::onEnable);
- }
- @Override
- public void onDisable(ResultReceiver isAllowed) throws RemoteException {
- handleVoidCallback(
- new ReceiverWrapper(isAllowed), mCallback::onDisable);
- }
- @Override
- public void onBootStarted() throws RemoteException {
- handleVoidCallback(null, (Object input) -> mCallback.onBootStarted());
- }
- @Override
- public void onEnableStarted() throws RemoteException {
- handleVoidCallback(null, (Object input) -> mCallback.onEnableStarted());
- }
- @Override
- public void onDisableStarted() throws RemoteException {
- handleVoidCallback(null, (Object input) -> mCallback.onDisableStarted());
- }
- @Override
- public void onBootFinished(int status) throws RemoteException {
- handleVoidCallback(status, mCallback::onBootFinished);
- }
- @Override
- public void onEnableFinished(int status) throws RemoteException {
- handleVoidCallback(status, mCallback::onEnableFinished);
- }
- @Override
- public void onDisableFinished(int status) throws RemoteException {
- handleVoidCallback(status, mCallback::onDisableFinished);
- }
- @Override
- public void onTagDispatch(ResultReceiver isSkipped) throws RemoteException {
- handleVoidCallback(
- new ReceiverWrapper(isSkipped), mCallback::onTagDispatch);
- }
- @Override
- public void onRoutingChanged() throws RemoteException {
- handleVoidCallback(null, (Object input) -> mCallback.onRoutingChanged());
- }
- @Override
- public void onHceEventReceived(int action) throws RemoteException {
- handleVoidCallback(action, mCallback::onHceEventReceived);
- }
- private <T> void handleVoidCallback(T input, Consumer<T> callbackMethod) {
+ private <T1, T2> void handleVoid2ArgCallback(
+ T1 input1, T2 input2, BiConsumer<T1, T2> callbackMethod, Executor executor) {
synchronized (mLock) {
- if (mCallback == null || mExecutor == null) {
- return;
- }
final long identity = Binder.clearCallingIdentity();
try {
- mExecutor.execute(() -> callbackMethod.accept(input));
+ executor.execute(() -> callbackMethod.accept(input1, input2));
+ } catch (RuntimeException ex) {
+ throw ex;
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -415,17 +519,12 @@
private <S, T> S handleNonVoidCallbackWithInput(
S defaultValue, T input, Function<T, S> callbackMethod) throws RemoteException {
synchronized (mLock) {
- if (mCallback == null) {
- return defaultValue;
- }
final long identity = Binder.clearCallingIdentity();
S result = defaultValue;
try {
ExecutorService executor = Executors.newSingleThreadExecutor();
- FutureTask<S> futureTask = new FutureTask<>(
- () -> callbackMethod.apply(input)
- );
- executor.submit(futureTask);
+ FutureTask<S> futureTask = new FutureTask<>(() -> callbackMethod.apply(input));
+ var unused = executor.submit(futureTask);
try {
result = futureTask.get(
OEM_EXTENSION_RESPONSE_THRESHOLD_MS, TimeUnit.MILLISECONDS);
@@ -447,17 +546,12 @@
private <T> T handleNonVoidCallbackWithoutInput(T defaultValue, Supplier<T> callbackMethod)
throws RemoteException {
synchronized (mLock) {
- if (mCallback == null) {
- return defaultValue;
- }
final long identity = Binder.clearCallingIdentity();
T result = defaultValue;
try {
ExecutorService executor = Executors.newSingleThreadExecutor();
- FutureTask<T> futureTask = new FutureTask<>(
- callbackMethod::get
- );
- executor.submit(futureTask);
+ FutureTask<T> futureTask = new FutureTask<>(callbackMethod::get);
+ var unused = executor.submit(futureTask);
try {
result = futureTask.get(
OEM_EXTENSION_RESPONSE_THRESHOLD_MS, TimeUnit.MILLISECONDS);
diff --git a/packages/SettingsLib/ActionButtonsPreference/res/drawable/half_rounded_left_bk.xml b/packages/SettingsLib/ActionButtonsPreference/res/drawable/half_rounded_left_bk.xml
index 8a25726..0d4ef3c 100644
--- a/packages/SettingsLib/ActionButtonsPreference/res/drawable/half_rounded_left_bk.xml
+++ b/packages/SettingsLib/ActionButtonsPreference/res/drawable/half_rounded_left_bk.xml
@@ -20,7 +20,7 @@
xmlns:tools="http://schemas.android.com/tools"
tools:targetApi="28"
android:shape="rectangle">
- <solid android:color="?androidprv:attr/colorSurface" />
+ <solid android:color="?androidprv:attr/materialColorSurfaceContainerHigh" />
<corners
android:topLeftRadius="?android:attr/dialogCornerRadius"
android:topRightRadius="0dp"
diff --git a/packages/SettingsLib/ActionButtonsPreference/res/drawable/half_rounded_right_bk.xml b/packages/SettingsLib/ActionButtonsPreference/res/drawable/half_rounded_right_bk.xml
index 7e626e5..3072772 100644
--- a/packages/SettingsLib/ActionButtonsPreference/res/drawable/half_rounded_right_bk.xml
+++ b/packages/SettingsLib/ActionButtonsPreference/res/drawable/half_rounded_right_bk.xml
@@ -20,7 +20,7 @@
xmlns:tools="http://schemas.android.com/tools"
tools:targetApi="28"
android:shape="rectangle">
- <solid android:color="?androidprv:attr/colorSurface" />
+ <solid android:color="?androidprv:attr/materialColorSurfaceContainerHigh" />
<corners
android:topLeftRadius="0dp"
android:topRightRadius="?android:attr/dialogCornerRadius"
diff --git a/packages/SettingsLib/ActionButtonsPreference/res/drawable/rounded_bk.xml b/packages/SettingsLib/ActionButtonsPreference/res/drawable/rounded_bk.xml
index 9f4980b..f1790f9 100644
--- a/packages/SettingsLib/ActionButtonsPreference/res/drawable/rounded_bk.xml
+++ b/packages/SettingsLib/ActionButtonsPreference/res/drawable/rounded_bk.xml
@@ -20,7 +20,7 @@
xmlns:tools="http://schemas.android.com/tools"
tools:targetApi="28"
android:shape="rectangle">
- <solid android:color="?androidprv:attr/colorSurface" />
+ <solid android:color="?androidprv:attr/materialColorSurfaceContainerHigh" />
<corners
android:radius="?android:attr/dialogCornerRadius"
/>
diff --git a/packages/SettingsLib/ActionButtonsPreference/res/drawable/square_bk.xml b/packages/SettingsLib/ActionButtonsPreference/res/drawable/square_bk.xml
index 67b5107..f0da7b4 100644
--- a/packages/SettingsLib/ActionButtonsPreference/res/drawable/square_bk.xml
+++ b/packages/SettingsLib/ActionButtonsPreference/res/drawable/square_bk.xml
@@ -18,7 +18,7 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:shape="rectangle">
- <solid android:color="?androidprv:attr/colorSurface" />
+ <solid android:color="?androidprv:attr/materialColorSurfaceContainerHigh" />
<corners
android:radius="0dp"
/>
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
index aceb545..62af08e 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
@@ -117,7 +117,7 @@
val indication = LocalIndication.current
val onChangeWithLog = wrapOnSwitchWithLog(onCheckedChange)
val interactionSource = remember { MutableInteractionSource() }
- val modifier = remember(checked, changeable) {
+ val modifier =
if (checked != null && onChangeWithLog != null) {
Modifier.toggleable(
value = checked,
@@ -128,7 +128,6 @@
onValueChange = onChangeWithLog,
)
} else Modifier
- }
BasePreference(
title = title,
summary = summary,
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconKeys.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconKeys.java
index 0a0b65b..79dabf0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconKeys.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconKeys.java
@@ -37,7 +37,7 @@
* chosen one via Settings).
*/
static final ZenIcon.Key IMPLICIT_MODE_DEFAULT = ZenIcon.Key.forSystemResource(
- R.drawable.ic_zen_mode_type_unknown);
+ R.drawable.ic_zen_mode_type_special_dnd);
private static final ImmutableMap<Integer, ZenIcon.Key> TYPE_DEFAULTS = ImmutableMap.of(
AutomaticZenRule.TYPE_UNKNOWN,
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconLoader.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconLoader.java
index fe0f98a..43c6c50 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconLoader.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconLoader.java
@@ -31,7 +31,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
import com.google.common.util.concurrent.FluentFuture;
import com.google.common.util.concurrent.ListenableFuture;
@@ -53,25 +52,22 @@
private final LruCache<ZenIcon.Key, Drawable> mCache;
private final ListeningExecutorService mBackgroundExecutor;
+ /** Obtains the singleton {@link ZenIconLoader}. */
public static ZenIconLoader getInstance() {
if (sInstance == null) {
- sInstance = new ZenIconLoader();
+ sInstance = new ZenIconLoader(Executors.newFixedThreadPool(4));
}
return sInstance;
}
- /** Replaces the singleton instance of {@link ZenIconLoader} by the provided one. */
- @VisibleForTesting(otherwise = VisibleForTesting.NONE)
- public static void setInstance(@Nullable ZenIconLoader instance) {
- sInstance = instance;
- }
-
- private ZenIconLoader() {
- this(Executors.newFixedThreadPool(4));
- }
-
- @VisibleForTesting
- public ZenIconLoader(ExecutorService backgroundExecutor) {
+ /**
+ * Constructs a ZenIconLoader with the specified {@code backgroundExecutor}.
+ *
+ * <p>ZenIconLoader <em>should be a singleton</em>, so this should only be used to instantiate
+ * and provide the singleton instance in a module. If the app doesn't support dependency
+ * injection, use {@link #getInstance} instead.
+ */
+ public ZenIconLoader(@NonNull ExecutorService backgroundExecutor) {
mCache = new LruCache<>(50);
mBackgroundExecutor =
MoreExecutors.listeningDecorator(backgroundExecutor);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
index e64b0c6..14b0c25 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
@@ -359,7 +359,7 @@
}
@Test
- public void getIconKey_implicitModeWithoutCustomIcon_isSpecialIcon() {
+ public void getIconKey_implicitModeWithoutCustomIcon_isDndIcon() {
ZenMode mode = new TestModeBuilder()
.setId(ZenModeConfig.implicitRuleId("some.package"))
.setPackage("some_package")
@@ -370,7 +370,7 @@
assertThat(iconKey.resPackage()).isNull();
assertThat(iconKey.resId()).isEqualTo(
- com.android.internal.R.drawable.ic_zen_mode_type_unknown);
+ com.android.internal.R.drawable.ic_zen_mode_type_special_dnd);
}
private static void assertUnparceledIsEqualToOriginal(String type, ZenMode original) {
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index c1bb55c..d26a906 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -949,36 +949,37 @@
strict_mode: false,
}
-// Disable for now. TODO(b/356666754) Re-enable it
-// android_ravenwood_test {
-// name: "SystemUiRavenTests",
-// srcs: [
-// ":SystemUI-tests-utils",
-// ":SystemUI-tests-multivalent",
-// // TODO(b/294256649): pivot to using {.aapt.jar} and re-enable
-// // use_resource_processor: true when better supported by soong
-// ":SystemUIRobo-stub{.aapt.srcjar}",
-// ],
-// static_libs: [
-// "SystemUI-core",
-// "SystemUI-res",
-// "SystemUI-tests-base",
-// "androidx.test.uiautomator_uiautomator",
-// "androidx.core_core-animation-testing",
-// "androidx.test.ext.junit",
-// "kosmos",
-// "mockito-kotlin-nodeps",
-// ],
-// libs: [
-// "android.test.runner",
-// "android.test.base",
-// "android.test.mock",
-// ],
-// auto_gen_config: true,
-// plugins: [
-// "dagger2-compiler",
-// ],
-// }
+android_ravenwood_test {
+ name: "SystemUiRavenTests",
+ srcs: [
+ ":SystemUI-tests-utils",
+ ":SystemUI-tests-multivalent",
+ // TODO(b/294256649): pivot to using {.aapt.jar} and re-enable
+ // use_resource_processor: true when better supported by soong
+ ":SystemUIRobo-stub{.aapt.srcjar}",
+ ],
+ static_libs: [
+ "SystemUI-core",
+ "SystemUI-res",
+ "SystemUI-tests-base",
+ "androidx.test.uiautomator_uiautomator",
+ "androidx.core_core-animation-testing",
+ "androidx.test.ext.junit",
+ "kosmos",
+ "kotlin-test",
+ "mockito-kotlin-nodeps",
+ "androidx.compose.runtime_runtime",
+ ],
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ "android.test.mock",
+ ],
+ auto_gen_config: true,
+ plugins: [
+ "dagger2-compiler",
+ ],
+}
// Opt-out config for optimizing the SystemUI target using R8.
// Disabled via `export SYSTEMUI_OPTIMIZE_JAVA=false`, or explicitly in Make via
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index d394976..157af7d 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -92,6 +92,7 @@
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.MASTER_CLEAR" />
<uses-permission android:name="android.permission.VIBRATE" />
+ <uses-permission android:name="android.permission.VIBRATE_SYSTEM_CONSTANTS" />
<uses-permission android:name="android.permission.MANAGE_SENSOR_PRIVACY" />
<uses-permission android:name="android.permission.OBSERVE_SENSOR_PRIVACY" />
<uses-permission android:name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT" />
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 8a1d81b..1ce1716 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -139,13 +139,6 @@
}
flag {
- name: "notifications_heads_up_refactor"
- namespace: "systemui"
- description: "Use HeadsUpInteractor to feed HUN updates to the NSSL."
- bug: "325936094"
-}
-
-flag {
name: "notification_transparent_header_fix"
namespace: "systemui"
description: "fix the transparent group header issue for async header inflation."
@@ -370,6 +363,13 @@
}
flag {
+ name: "status_bar_signal_policy_refactor"
+ namespace: "systemui"
+ description: "Use a settings observer for airplane mode and make StatusBarSignalPolicy startable"
+ bug: "264539100"
+}
+
+flag {
name: "status_bar_swipe_over_chip"
namespace: "systemui"
description: "Allow users to swipe over the status bar chip to open the shade"
@@ -380,6 +380,17 @@
}
flag {
+ name: "status_bar_always_check_underlying_networks"
+ namespace: "systemui"
+ description: "For status bar connectivity UI, always check underlying networks for wifi and "
+ "carrier merged information, regardless of the sepcified transport type"
+ bug: "352162710"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "status_bar_stop_updating_window_height"
namespace: "systemui"
description: "Don't have PhoneStatusBarView manually trigger an update of the height in "
@@ -391,6 +402,13 @@
}
flag {
+ name: "status_bar_ron_chips"
+ namespace: "systemui"
+ description: "Show rich ongoing notifications as chips in the status bar"
+ bug: "361346412"
+}
+
+flag {
name: "compose_bouncer"
namespace: "systemui"
description: "Use the new compose bouncer in SystemUI"
@@ -588,16 +606,6 @@
}
flag {
- name: "screenshot_private_profile_behavior_fix"
- namespace: "systemui"
- description: "Private profile support for screenshots"
- bug: "327613051"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "screenshot_save_image_exporter"
namespace: "systemui"
description: "Save all screenshots using ImageExporter"
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/NotificationsShadeOverlayModule.kt
similarity index 60%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt
copy to packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/NotificationsShadeOverlayModule.kt
index 60d97d1..e55520a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/NotificationsShadeOverlayModule.kt
@@ -14,9 +14,16 @@
* limitations under the License.
*/
-package com.android.systemui.bouncer.shared.flag
+package com.android.systemui.scene
-import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.notifications.ui.composable.NotificationsShadeOverlay
+import com.android.systemui.scene.ui.composable.Overlay
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
-var Kosmos.fakeComposeBouncerFlags by Kosmos.Fixture { FakeComposeBouncerFlags() }
-val Kosmos.composeBouncerFlags by Kosmos.Fixture<ComposeBouncerFlags> { fakeComposeBouncerFlags }
+@Module
+interface NotificationsShadeOverlayModule {
+
+ @Binds @IntoSet fun notificationsShade(overlay: NotificationsShadeOverlay): Overlay
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt
index 04bcc36..c60e11e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt
@@ -73,8 +73,7 @@
initialValue = null
)
- // TODO (b/353955910): back handling doesn't work
- BackHandler { alternateBouncerDependencies.viewModel.onBackRequested() }
+ BackHandler(enabled = isVisible) { alternateBouncerDependencies.viewModel.onBackRequested() }
AnimatedVisibility(
visible = isVisible,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
new file mode 100644
index 0000000..37888f2
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.notifications.ui.composable
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.ContentScope
+import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.lifecycle.rememberViewModel
+import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeOverlayActionsViewModel
+import com.android.systemui.scene.session.ui.composable.SaveableSession
+import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.scene.ui.composable.Overlay
+import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
+import com.android.systemui.shade.ui.composable.OverlayShade
+import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel
+import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
+import com.android.systemui.statusbar.phone.ui.StatusBarIconController
+import com.android.systemui.statusbar.phone.ui.TintedIconManager
+import dagger.Lazy
+import java.util.Optional
+import javax.inject.Inject
+
+@SysUISingleton
+class NotificationsShadeOverlay
+@Inject
+constructor(
+ private val actionsViewModelFactory: NotificationsShadeOverlayActionsViewModel.Factory,
+ private val overlayShadeViewModelFactory: OverlayShadeViewModel.Factory,
+ private val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
+ private val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
+ private val tintedIconManagerFactory: TintedIconManager.Factory,
+ private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
+ private val statusBarIconController: StatusBarIconController,
+ private val shadeSession: SaveableSession,
+ private val stackScrollView: Lazy<NotificationScrollView>,
+) : Overlay {
+
+ override val key = Overlays.NotificationsShade
+
+ private val actionsViewModel: NotificationsShadeOverlayActionsViewModel by lazy {
+ actionsViewModelFactory.create()
+ }
+
+ override suspend fun activate(): Nothing {
+ actionsViewModel.activate()
+ }
+
+ @Composable
+ override fun ContentScope.Content(
+ modifier: Modifier,
+ ) {
+ OverlayShade(
+ modifier = modifier,
+ viewModelFactory = overlayShadeViewModelFactory,
+ lockscreenContent = { Optional.empty() },
+ ) {
+ Column {
+ val placeholderViewModel =
+ rememberViewModel("NotificationsShadeOverlay") {
+ notificationsPlaceholderViewModelFactory.create()
+ }
+
+ ExpandedShadeHeader(
+ viewModelFactory = shadeHeaderViewModelFactory,
+ createTintedIconManager = tintedIconManagerFactory::create,
+ createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
+ statusBarIconController = statusBarIconController,
+ modifier = Modifier.padding(horizontal = 16.dp),
+ )
+
+ NotificationScrollingStack(
+ shadeSession = shadeSession,
+ stackScrollView = stackScrollView.get(),
+ viewModel = placeholderViewModel,
+ maxScrimTop = { 0f },
+ shouldPunchHoleBehindScrim = false,
+ shouldFillMaxSize = false,
+ shouldReserveSpaceForNavBar = false,
+ shadeMode = ShadeMode.Dual,
+ modifier = Modifier.fillMaxWidth(),
+ )
+
+ // Communicates the bottom position of the drawable area within the shade to NSSL.
+ NotificationStackCutoffGuideline(
+ stackScrollView = stackScrollView.get(),
+ viewModel = placeholderViewModel,
+ )
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index b7c6edc..d8ab0a1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -30,7 +30,7 @@
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
-import androidx.compose.foundation.layout.displayCutoutPadding
+import androidx.compose.foundation.layout.displayCutout
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -47,8 +47,9 @@
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.CompositingStrategy
@@ -99,7 +100,6 @@
import com.android.systemui.notifications.ui.composable.NotificationStackCutoffGuideline
import com.android.systemui.qs.footer.ui.compose.FooterActionsWithAnimatedVisibility
import com.android.systemui.qs.ui.composable.BrightnessMirror
-import com.android.systemui.qs.ui.composable.QSMediaMeasurePolicy
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.qs.ui.composable.QuickSettings.SharedValues.MediaLandscapeTopOffset
import com.android.systemui.qs.ui.composable.QuickSettings.SharedValues.MediaOffset.InQQS
@@ -269,13 +269,14 @@
shadeSession: SaveableSession,
) {
val cutoutLocation = LocalDisplayCutout.current.location
+ val cutoutInsets = WindowInsets.Companion.displayCutout
val isLandscape = LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Compact
val usingCollapsedLandscapeMedia =
Utils.useCollapsedMediaInLandscape(LocalContext.current.resources)
val isExpanded = !usingCollapsedLandscapeMedia || !isLandscape
mediaHost.expansion = if (isExpanded) EXPANDED else COLLAPSED
- val maxNotifScrimTop = remember { mutableStateOf(0f) }
+ var maxNotifScrimTop by remember { mutableIntStateOf(0) }
val tileSquishiness by
animateSceneFloatAsState(
value = 1f,
@@ -301,6 +302,24 @@
viewModel.qsSceneAdapter,
)
}
+ val shadeMeasurePolicy =
+ remember(mediaInRow) {
+ SingleShadeMeasurePolicy(
+ isMediaInRow = mediaInRow,
+ mediaOffset = { mediaOffset.roundToPx() },
+ onNotificationsTopChanged = { maxNotifScrimTop = it },
+ mediaZIndex = {
+ if (MediaContentPicker.shouldElevateMedia(layoutState)) 1f else 0f
+ },
+ cutoutInsetsProvider = {
+ if (cutoutLocation == CutoutLocation.CENTER) {
+ null
+ } else {
+ cutoutInsets
+ }
+ }
+ )
+ }
Box(
modifier =
@@ -318,101 +337,54 @@
.background(colorResource(R.color.shade_scrim_background_dark)),
)
Layout(
- contents =
- listOf(
- {
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- modifier =
- Modifier.fillMaxWidth()
- .thenIf(isEmptySpaceClickable) {
- Modifier.clickable(
- onClick = { viewModel.onEmptySpaceClicked() }
- )
- }
- .thenIf(cutoutLocation != CutoutLocation.CENTER) {
- Modifier.displayCutoutPadding()
- },
- ) {
- CollapsedShadeHeader(
- viewModelFactory = viewModel.shadeHeaderViewModelFactory,
- createTintedIconManager = createTintedIconManager,
- createBatteryMeterViewController = createBatteryMeterViewController,
- statusBarIconController = statusBarIconController,
- )
-
- val content: @Composable () -> Unit = {
- Box(
- Modifier.element(QuickSettings.Elements.QuickQuickSettings)
- .layoutId(QSMediaMeasurePolicy.LayoutId.QS)
- ) {
- QuickSettings(
- viewModel.qsSceneAdapter,
- { viewModel.qsSceneAdapter.qqsHeight },
- isSplitShade = false,
- squishiness = { tileSquishiness },
- )
- }
-
- ShadeMediaCarousel(
- isVisible = isMediaVisible,
- mediaHost = mediaHost,
- mediaOffsetProvider = mediaOffsetProvider,
- modifier =
- Modifier.layoutId(QSMediaMeasurePolicy.LayoutId.Media),
- carouselController = mediaCarouselController,
- )
- }
- val landscapeQsMediaMeasurePolicy = remember {
- QSMediaMeasurePolicy(
- { viewModel.qsSceneAdapter.qqsHeight },
- { mediaOffset.roundToPx() },
- )
- }
- if (mediaInRow) {
- Layout(
- content = content,
- measurePolicy = landscapeQsMediaMeasurePolicy,
- )
- } else {
- content()
- }
- }
- },
- {
- NotificationScrollingStack(
- shadeSession = shadeSession,
- stackScrollView = notificationStackScrollView,
- viewModel = notificationsPlaceholderViewModel,
- maxScrimTop = { maxNotifScrimTop.value },
- shadeMode = ShadeMode.Single,
- shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim,
- onEmptySpaceClick =
- viewModel::onEmptySpaceClicked.takeIf { isEmptySpaceClickable },
- )
- },
+ modifier =
+ Modifier.thenIf(isEmptySpaceClickable) {
+ Modifier.clickable { viewModel.onEmptySpaceClicked() }
+ },
+ content = {
+ CollapsedShadeHeader(
+ viewModelFactory = viewModel.shadeHeaderViewModelFactory,
+ createTintedIconManager = createTintedIconManager,
+ createBatteryMeterViewController = createBatteryMeterViewController,
+ statusBarIconController = statusBarIconController,
+ modifier = Modifier.layoutId(SingleShadeMeasurePolicy.LayoutId.ShadeHeader),
)
- ) { measurables, constraints ->
- check(measurables.size == 2)
- check(measurables[0].size == 1)
- check(measurables[1].size == 1)
- val quickSettingsPlaceable = measurables[0][0].measure(constraints)
- val notificationsPlaceable = measurables[1][0].measure(constraints)
+ Box(
+ Modifier.element(QuickSettings.Elements.QuickQuickSettings)
+ .layoutId(SingleShadeMeasurePolicy.LayoutId.QuickSettings)
+ ) {
+ QuickSettings(
+ viewModel.qsSceneAdapter,
+ { viewModel.qsSceneAdapter.qqsHeight },
+ isSplitShade = false,
+ squishiness = { tileSquishiness },
+ )
+ }
- maxNotifScrimTop.value = quickSettingsPlaceable.height.toFloat()
+ ShadeMediaCarousel(
+ isVisible = isMediaVisible,
+ isInRow = mediaInRow,
+ mediaHost = mediaHost,
+ mediaOffsetProvider = mediaOffsetProvider,
+ carouselController = mediaCarouselController,
+ modifier = Modifier.layoutId(SingleShadeMeasurePolicy.LayoutId.Media),
+ )
- layout(constraints.maxWidth, constraints.maxHeight) {
- val qsZIndex =
- if (MediaContentPicker.shouldElevateMedia(layoutState)) {
- 1f
- } else {
- 0f
- }
- quickSettingsPlaceable.placeRelative(x = 0, y = 0, zIndex = qsZIndex)
- notificationsPlaceable.placeRelative(x = 0, y = maxNotifScrimTop.value.roundToInt())
- }
- }
+ NotificationScrollingStack(
+ shadeSession = shadeSession,
+ stackScrollView = notificationStackScrollView,
+ viewModel = notificationsPlaceholderViewModel,
+ maxScrimTop = { maxNotifScrimTop.toFloat() },
+ shadeMode = ShadeMode.Single,
+ shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim,
+ onEmptySpaceClick =
+ viewModel::onEmptySpaceClicked.takeIf { isEmptySpaceClickable },
+ modifier = Modifier.layoutId(SingleShadeMeasurePolicy.LayoutId.Notifications),
+ )
+ },
+ measurePolicy = shadeMeasurePolicy,
+ )
Box(
modifier =
Modifier.align(Alignment.BottomCenter)
@@ -600,6 +572,7 @@
ShadeMediaCarousel(
isVisible = isMediaVisible,
+ isInRow = false,
mediaHost = mediaHost,
mediaOffsetProvider = mediaOffsetProvider,
modifier =
@@ -657,6 +630,7 @@
@Composable
private fun SceneScope.ShadeMediaCarousel(
isVisible: Boolean,
+ isInRow: Boolean,
mediaHost: MediaHost,
carouselController: MediaCarouselController,
mediaOffsetProvider: ShadeMediaOffsetProvider,
@@ -668,7 +642,7 @@
mediaHost = mediaHost,
carouselController = carouselController,
offsetProvider =
- if (MediaContentPicker.shouldElevateMedia(layoutState)) {
+ if (isInRow || MediaContentPicker.shouldElevateMedia(layoutState)) {
null
} else {
{ mediaOffsetProvider.offset }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/SingleShadeMeasurePolicy.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/SingleShadeMeasurePolicy.kt
new file mode 100644
index 0000000..6275ac3
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/SingleShadeMeasurePolicy.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.ui.composable
+
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasurePolicy
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.layout.layoutId
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.offset
+import androidx.compose.ui.util.fastFirst
+import androidx.compose.ui.util.fastFirstOrNull
+import com.android.systemui.shade.ui.composable.SingleShadeMeasurePolicy.LayoutId
+import kotlin.math.max
+
+/**
+ * Lays out elements from the [LayoutId] in the shade. This policy supports the case when the QS and
+ * UMO share the same row and when they should be one below another.
+ */
+class SingleShadeMeasurePolicy(
+ private val isMediaInRow: Boolean,
+ private val mediaOffset: MeasureScope.() -> Int,
+ private val onNotificationsTopChanged: (Int) -> Unit,
+ private val mediaZIndex: () -> Float,
+ private val cutoutInsetsProvider: () -> WindowInsets?,
+) : MeasurePolicy {
+
+ enum class LayoutId {
+ QuickSettings,
+ Media,
+ Notifications,
+ ShadeHeader,
+ }
+
+ override fun MeasureScope.measure(
+ measurables: List<Measurable>,
+ constraints: Constraints,
+ ): MeasureResult {
+ val cutoutInsets: WindowInsets? = cutoutInsetsProvider()
+ val constraintsWithCutout = applyCutout(constraints, cutoutInsets)
+ val insetsLeft = cutoutInsets?.getLeft(this, layoutDirection) ?: 0
+ val insetsTop = cutoutInsets?.getTop(this) ?: 0
+
+ val shadeHeaderPlaceable =
+ measurables
+ .fastFirst { it.layoutId == LayoutId.ShadeHeader }
+ .measure(constraintsWithCutout)
+ val mediaPlaceable =
+ measurables
+ .fastFirstOrNull { it.layoutId == LayoutId.Media }
+ ?.measure(applyMediaConstraints(constraintsWithCutout, isMediaInRow))
+ val quickSettingsPlaceable =
+ measurables
+ .fastFirst { it.layoutId == LayoutId.QuickSettings }
+ .measure(constraintsWithCutout)
+ val notificationsPlaceable =
+ measurables.fastFirst { it.layoutId == LayoutId.Notifications }.measure(constraints)
+
+ val notificationsTop =
+ calculateNotificationsTop(
+ statusBarHeaderPlaceable = shadeHeaderPlaceable,
+ quickSettingsPlaceable = quickSettingsPlaceable,
+ mediaPlaceable = mediaPlaceable,
+ insetsTop = insetsTop,
+ isMediaInRow = isMediaInRow,
+ )
+ onNotificationsTopChanged(notificationsTop)
+
+ return layout(constraints.maxWidth, constraints.maxHeight) {
+ shadeHeaderPlaceable.placeRelative(x = insetsLeft, y = insetsTop)
+ quickSettingsPlaceable.placeRelative(
+ x = insetsLeft,
+ y = insetsTop + shadeHeaderPlaceable.height,
+ )
+
+ if (isMediaInRow) {
+ mediaPlaceable?.placeRelative(
+ x = insetsLeft + constraintsWithCutout.maxWidth / 2,
+ y = mediaOffset() + insetsTop + shadeHeaderPlaceable.height,
+ zIndex = mediaZIndex(),
+ )
+ } else {
+ mediaPlaceable?.placeRelative(
+ x = insetsLeft,
+ y = insetsTop + shadeHeaderPlaceable.height + quickSettingsPlaceable.height,
+ zIndex = mediaZIndex(),
+ )
+ }
+
+ // Notifications don't need to accommodate for horizontal insets
+ notificationsPlaceable.placeRelative(x = 0, y = notificationsTop)
+ }
+ }
+
+ private fun calculateNotificationsTop(
+ statusBarHeaderPlaceable: Placeable,
+ quickSettingsPlaceable: Placeable,
+ mediaPlaceable: Placeable?,
+ insetsTop: Int,
+ isMediaInRow: Boolean,
+ ): Int {
+ val mediaHeight = mediaPlaceable?.height ?: 0
+ return insetsTop +
+ statusBarHeaderPlaceable.height +
+ if (isMediaInRow) {
+ max(quickSettingsPlaceable.height, mediaHeight)
+ } else {
+ quickSettingsPlaceable.height + mediaHeight
+ }
+ }
+
+ private fun applyMediaConstraints(
+ constraints: Constraints,
+ isMediaInRow: Boolean,
+ ): Constraints {
+ return if (isMediaInRow) {
+ constraints.copy(maxWidth = constraints.maxWidth / 2)
+ } else {
+ constraints
+ }
+ }
+
+ private fun MeasureScope.applyCutout(
+ constraints: Constraints,
+ cutoutInsets: WindowInsets?,
+ ): Constraints {
+ return if (cutoutInsets == null) {
+ constraints
+ } else {
+ val left = cutoutInsets.getLeft(this, layoutDirection)
+ val top = cutoutInsets.getTop(this)
+ val right = cutoutInsets.getRight(this, layoutDirection)
+ val bottom = cutoutInsets.getBottom(this)
+
+ constraints.offset(horizontal = -(left + right), vertical = -(top + bottom))
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
index 1db96cf..c9b8013 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
@@ -92,7 +92,12 @@
true
}
},
- color = MaterialTheme.colorScheme.surface,
+ color =
+ if (enabled) {
+ MaterialTheme.colorScheme.surface
+ } else {
+ MaterialTheme.colorScheme.surfaceContainerHighest
+ },
shape = RoundedCornerShape(28.dp),
onClick =
if (enabled) {
@@ -119,7 +124,7 @@
modifier = Modifier.basicMarquee(),
text = connectedDeviceViewModel.label.toString(),
style = MaterialTheme.typography.labelMedium,
- color = MaterialTheme.colorScheme.onSurfaceVariant,
+ color = connectedDeviceViewModel.labelColor.toColor(),
maxLines = 1,
)
connectedDeviceViewModel.deviceName?.let {
@@ -127,7 +132,7 @@
modifier = Modifier.basicMarquee(),
text = it.toString(),
style = MaterialTheme.typography.titleMedium,
- color = MaterialTheme.colorScheme.onSurface,
+ color = connectedDeviceViewModel.deviceNameColor.toColor(),
maxLines = 1,
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
index 072e91a..d4f3b5b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
@@ -23,7 +23,6 @@
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.tween
-import androidx.compose.animation.core.updateTransition
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
@@ -78,7 +77,6 @@
modifier: Modifier = Modifier,
) {
require(viewModels.isNotEmpty())
- val transition = updateTransition(isExpanded, label = "CollapsableSliders")
Column(modifier = modifier) {
Box(
modifier = Modifier.fillMaxWidth(),
@@ -106,8 +104,9 @@
sliderColors = sliderColors,
)
}
- transition.AnimatedVisibility(
- visible = { it || !isExpandable },
+ AnimatedVisibility(
+ visible = isExpanded || !isExpandable,
+ label = "CollapsableSliders",
enter =
expandVertically(animationSpec = tween(durationMillis = EXPAND_DURATION_MILLIS)),
exit =
@@ -120,23 +119,31 @@
for (index in 1..viewModels.lastIndex) {
val sliderViewModel: SliderViewModel = viewModels[index]
val sliderState by sliderViewModel.slider.collectAsStateWithLifecycle()
- transition.AnimatedVisibility(
- modifier = Modifier.padding(top = 16.dp),
- visible = { it || !isExpandable },
- enter = enterTransition(index = index, totalCount = viewModels.size),
- exit = exitTransition(index = index, totalCount = viewModels.size)
- ) {
- VolumeSlider(
- modifier = Modifier.fillMaxWidth(),
- state = sliderState,
- onValueChange = { newValue: Float ->
- sliderViewModel.onValueChanged(sliderState, newValue)
- },
- onValueChangeFinished = { sliderViewModel.onValueChangeFinished() },
- onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
- sliderColors = sliderColors,
- )
- }
+
+ VolumeSlider(
+ modifier =
+ Modifier.padding(top = 16.dp)
+ .fillMaxWidth()
+ .animateEnterExit(
+ enter =
+ enterTransition(
+ index = index,
+ totalCount = viewModels.size,
+ ),
+ exit =
+ exitTransition(
+ index = index,
+ totalCount = viewModels.size,
+ ),
+ ),
+ state = sliderState,
+ onValueChange = { newValue: Float ->
+ sliderViewModel.onValueChanged(sliderState, newValue)
+ },
+ onValueChangeFinished = { sliderViewModel.onValueChangeFinished() },
+ onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
+ sliderColors = sliderColors,
+ )
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
index 5ffb6f8..1cc0fb2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
@@ -25,13 +25,13 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
-import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.paneTitle
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.compose.theme.PlatformTheme
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.res.R
import com.android.systemui.volume.panel.ui.layout.ComponentsLayout
@@ -43,7 +43,6 @@
private val padding = 24.dp
@Composable
-@OptIn(ExperimentalComposeUiApi::class)
fun VolumePanelRoot(
viewModel: VolumePanelViewModel,
modifier: Modifier = Modifier,
@@ -54,18 +53,20 @@
with(VolumePanelComposeScope(state)) {
components?.let { componentsState ->
- Components(
- componentsState,
- modifier
- .sysuiResTag(VolumePanelTestTag)
- .semantics { paneTitle = accessibilityTitle }
- .padding(
- start = padding,
- top = padding,
- end = padding,
- bottom = 20.dp,
- )
- )
+ PlatformTheme {
+ Components(
+ componentsState,
+ modifier
+ .sysuiResTag(VolumePanelTestTag)
+ .semantics { paneTitle = accessibilityTitle }
+ .padding(
+ start = padding,
+ top = padding,
+ end = padding,
+ bottom = 20.dp,
+ )
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
index b166737..d876606 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
@@ -21,9 +21,7 @@
import androidx.compose.animation.core.SpringSpec
import com.android.compose.animation.scene.content.state.TransitionState
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
internal fun CoroutineScope.animateContent(
layoutState: MutableSceneTransitionLayoutStateImpl,
@@ -31,37 +29,24 @@
oneOffAnimation: OneOffAnimation,
targetProgress: Float,
chain: Boolean = true,
-) {
- // Start the transition. This will compute the TransformationSpec associated to [transition],
- // which we need to initialize the Animatable that will actually animate it.
- layoutState.startTransition(transition, chain)
-
- // The transition now contains the transformation spec that we should use to instantiate the
- // Animatable.
- val animationSpec = transition.transformationSpec.progressSpec
- val visibilityThreshold =
- (animationSpec as? SpringSpec)?.visibilityThreshold ?: ProgressVisibilityThreshold
- val replacedTransition = transition.replacedTransition
- val initialProgress = replacedTransition?.progress ?: 0f
- val initialVelocity = replacedTransition?.progressVelocity ?: 0f
- val animatable =
- Animatable(initialProgress, visibilityThreshold = visibilityThreshold).also {
- oneOffAnimation.animatable = it
- }
-
- // Animate the progress to its target value.
- //
- // Important: We start atomically to make sure that we start the coroutine even if it is
- // cancelled right after it is launched, so that finishTransition() is correctly called.
- // Otherwise, this transition will never be stopped and we will never settle to Idle.
- oneOffAnimation.job =
- launch(start = CoroutineStart.ATOMIC) {
- try {
- animatable.animateTo(targetProgress, animationSpec, initialVelocity)
- } finally {
- layoutState.finishTransition(transition)
+): Job {
+ oneOffAnimation.onRun = {
+ // Animate the progress to its target value.
+ val animationSpec = transition.transformationSpec.progressSpec
+ val visibilityThreshold =
+ (animationSpec as? SpringSpec)?.visibilityThreshold ?: ProgressVisibilityThreshold
+ val replacedTransition = transition.replacedTransition
+ val initialProgress = replacedTransition?.progress ?: 0f
+ val initialVelocity = replacedTransition?.progressVelocity ?: 0f
+ val animatable =
+ Animatable(initialProgress, visibilityThreshold = visibilityThreshold).also {
+ oneOffAnimation.animatable = it
}
- }
+
+ animatable.animateTo(targetProgress, animationSpec, initialVelocity)
+ }
+
+ return layoutState.startTransitionImmediately(animationScope = this, transition, chain)
}
internal class OneOffAnimation {
@@ -74,8 +59,8 @@
*/
lateinit var animatable: Animatable<Float, AnimationVector1D>
- /** The job that is animating [animatable]. */
- lateinit var job: Job
+ /** The runnable to run for this animation. */
+ lateinit var onRun: suspend () -> Unit
val progress: Float
get() = animatable.value
@@ -83,7 +68,13 @@
val progressVelocity: Float
get() = animatable.velocity
- fun finish(): Job = job
+ suspend fun run() {
+ onRun()
+ }
+
+ fun freezeAndAnimateToCurrentState() {
+ // Do nothing, the state of one-off animations never change and we directly animate to it.
+ }
}
// TODO(b/290184746): Compute a good default visibility threshold that depends on the layout size
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateOverlay.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateOverlay.kt
index e020f14..28116cb 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateOverlay.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateOverlay.kt
@@ -18,7 +18,6 @@
import com.android.compose.animation.scene.content.state.TransitionState
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Job
/** Trigger a one-off transition to show or hide an overlay. */
internal fun CoroutineScope.showOrHideOverlay(
@@ -120,7 +119,13 @@
override val isInitiatedByUserInput: Boolean = false
override val isUserInputOngoing: Boolean = false
- override fun finish(): Job = oneOffAnimation.finish()
+ override suspend fun run() {
+ oneOffAnimation.run()
+ }
+
+ override fun freezeAndAnimateToCurrentState() {
+ oneOffAnimation.freezeAndAnimateToCurrentState()
+ }
}
private class OneOffOverlayReplacingTransition(
@@ -140,5 +145,11 @@
override val isInitiatedByUserInput: Boolean = false
override val isUserInputOngoing: Boolean = false
- override fun finish(): Job = oneOffAnimation.finish()
+ override suspend fun run() {
+ oneOffAnimation.run()
+ }
+
+ override fun freezeAndAnimateToCurrentState() {
+ oneOffAnimation.freezeAndAnimateToCurrentState()
+ }
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
index e15bc12..86be4a4 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
@@ -28,7 +28,7 @@
layoutState: MutableSceneTransitionLayoutStateImpl,
target: SceneKey,
transitionKey: TransitionKey?,
-): TransitionState.Transition.ChangeScene? {
+): Pair<TransitionState.Transition.ChangeScene, Job>? {
val transitionState = layoutState.transitionState
if (transitionState.currentScene == target) {
// This can happen in 3 different situations, for which there isn't anything else to do:
@@ -139,7 +139,7 @@
reversed: Boolean = false,
fromScene: SceneKey = layoutState.transitionState.currentScene,
chain: Boolean = true,
-): TransitionState.Transition.ChangeScene {
+): Pair<TransitionState.Transition.ChangeScene, Job> {
val oneOffAnimation = OneOffAnimation()
val targetProgress = if (reversed) 0f else 1f
val transition =
@@ -165,15 +165,16 @@
)
}
- animateContent(
- layoutState = layoutState,
- transition = transition,
- oneOffAnimation = oneOffAnimation,
- targetProgress = targetProgress,
- chain = chain,
- )
+ val job =
+ animateContent(
+ layoutState = layoutState,
+ transition = transition,
+ oneOffAnimation = oneOffAnimation,
+ targetProgress = targetProgress,
+ chain = chain,
+ )
- return transition
+ return transition to job
}
private class OneOffSceneTransition(
@@ -193,5 +194,11 @@
override val isUserInputOngoing: Boolean = false
- override fun finish(): Job = oneOffAnimation.finish()
+ override suspend fun run() {
+ oneOffAnimation.run()
+ }
+
+ override fun freezeAndAnimateToCurrentState() {
+ oneOffAnimation.freezeAndAnimateToCurrentState()
+ }
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 37e4daa..24fef71 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -28,7 +28,6 @@
import com.android.compose.animation.scene.content.state.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
import com.android.compose.nestedscroll.PriorityNestedScrollConnection
import kotlin.math.absoluteValue
-import kotlinx.coroutines.CoroutineScope
internal interface DraggableHandler {
/**
@@ -63,7 +62,6 @@
internal class DraggableHandlerImpl(
internal val layoutImpl: SceneTransitionLayoutImpl,
internal val orientation: Orientation,
- internal val coroutineScope: CoroutineScope,
) : DraggableHandler {
internal val nestedScrollKey = Any()
/** The [DraggableHandler] can only have one active [DragController] at a time. */
@@ -101,11 +99,6 @@
val swipeAnimation = dragController.swipeAnimation
- // Don't intercept a transition that is finishing.
- if (swipeAnimation.isFinishing) {
- return false
- }
-
// Only intercept the current transition if one of the 2 swipes results is also a transition
// between the same pair of contents.
val swipes = computeSwipes(startedPosition, pointersDown = 1)
@@ -140,7 +133,6 @@
// This [transition] was already driving the animation: simply take over it.
// Stop animating and start from the current offset.
val oldSwipeAnimation = oldDragController.swipeAnimation
- oldSwipeAnimation.cancelOffsetAnimation()
// We need to recompute the swipe results since this is a new gesture, and the
// fromScene.userActions may have changed.
@@ -192,13 +184,7 @@
else -> error("Unknown result $result ($upOrLeftResult $downOrRightResult)")
}
- return createSwipeAnimation(
- layoutImpl,
- layoutImpl.coroutineScope,
- result,
- isUpOrLeft,
- orientation
- )
+ return createSwipeAnimation(layoutImpl, result, isUpOrLeft, orientation)
}
private fun computeSwipes(startedPosition: Offset?, pointersDown: Int): Swipes {
@@ -279,16 +265,14 @@
fun updateTransition(newTransition: SwipeAnimation<*>, force: Boolean = false) {
if (force || isDrivingTransition) {
- layoutState.startTransition(newTransition.contentTransition)
+ layoutState.startTransitionImmediately(
+ animationScope = draggableHandler.layoutImpl.animationScope,
+ newTransition.contentTransition,
+ true
+ )
}
- val previous = swipeAnimation
swipeAnimation = newTransition
-
- // Finish the previous transition.
- if (previous != newTransition) {
- layoutState.finishTransition(previous.contentTransition)
- }
}
/**
@@ -302,7 +286,7 @@
}
private fun <T : ContentKey> onDrag(delta: Float, swipeAnimation: SwipeAnimation<T>): Float {
- if (delta == 0f || !isDrivingTransition || swipeAnimation.isFinishing) {
+ if (delta == 0f || !isDrivingTransition || swipeAnimation.isAnimatingOffset()) {
return 0f
}
@@ -409,7 +393,7 @@
swipeAnimation: SwipeAnimation<T>,
): Float {
// The state was changed since the drag started; don't do anything.
- if (!isDrivingTransition || swipeAnimation.isFinishing) {
+ if (!isDrivingTransition || swipeAnimation.isAnimatingOffset()) {
return 0f
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
index 3f8f5e7..ced177c 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
@@ -153,4 +153,15 @@
override fun toString(): String {
return "TransitionKey(debugName=$debugName)"
}
+
+ companion object {
+ /**
+ * A special transition key indicating that the associated transition should be used for
+ * Predictive Back gestures.
+ *
+ * Use this key when defining a transition that you want to be specifically triggered when
+ * the user performs a Predictive Back gesture.
+ */
+ val PredictiveBack = TransitionKey("PredictiveBack")
+ }
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index fd4c310..5780c08 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -56,7 +56,7 @@
import com.android.compose.ui.util.SpaceVectorConverter
import kotlin.coroutines.cancellation.CancellationException
import kotlin.math.sign
-import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
@@ -143,8 +143,8 @@
CompositionLocalConsumerModifierNode,
ObserverModifierNode,
SpaceVectorConverter {
- private val pointerInputHandler: suspend PointerInputScope.() -> Unit = { pointerInput() }
- private val delegate = delegate(SuspendingPointerInputModifierNode(pointerInputHandler))
+ private val pointerTracker = delegate(SuspendingPointerInputModifierNode { pointerTracker() })
+ private val pointerInput = delegate(SuspendingPointerInputModifierNode { pointerInput() })
private val velocityTracker = VelocityTracker()
private var previousEnabled: Boolean = false
@@ -153,7 +153,7 @@
// Reset the pointer input whenever enabled changed.
if (value != field) {
field = value
- delegate.resetPointerInputHandler()
+ pointerInput.resetPointerInputHandler()
}
}
@@ -173,7 +173,7 @@
if (value != field) {
field = value
converter = SpaceVectorConverter(value)
- delegate.resetPointerInputHandler()
+ pointerInput.resetPointerInputHandler()
}
}
@@ -186,19 +186,26 @@
observeReads {
val newEnabled = enabled()
if (newEnabled != previousEnabled) {
- delegate.resetPointerInputHandler()
+ pointerInput.resetPointerInputHandler()
}
previousEnabled = newEnabled
}
}
- override fun onCancelPointerInput() = delegate.onCancelPointerInput()
+ override fun onCancelPointerInput() {
+ pointerTracker.onCancelPointerInput()
+ pointerInput.onCancelPointerInput()
+ }
override fun onPointerEvent(
pointerEvent: PointerEvent,
pass: PointerEventPass,
bounds: IntSize
- ) = delegate.onPointerEvent(pointerEvent, pass, bounds)
+ ) {
+ // The order is important here: the tracker is always called first.
+ pointerTracker.onPointerEvent(pointerEvent, pass, bounds)
+ pointerInput.onPointerEvent(pointerEvent, pass, bounds)
+ }
private var startedPosition: Offset? = null
private var pointersDown: Int = 0
@@ -211,81 +218,77 @@
)
}
+ private suspend fun PointerInputScope.pointerTracker() {
+ val currentContext = currentCoroutineContext()
+ awaitPointerEventScope {
+ // Intercepts pointer inputs and exposes [PointersInfo], via
+ // [requireAncestorPointersInfoOwner], to our descendants.
+ while (currentContext.isActive) {
+ // During the Initial pass, we receive the event after our ancestors.
+ val pointers = awaitPointerEvent(PointerEventPass.Initial).changes
+ pointersDown = pointers.countDown()
+ if (pointersDown == 0) {
+ // There are no more pointers down
+ startedPosition = null
+ } else if (startedPosition == null) {
+ startedPosition = pointers.first().position
+ if (enabled()) {
+ onFirstPointerDown()
+ }
+ }
+ }
+ }
+ }
+
private suspend fun PointerInputScope.pointerInput() {
if (!enabled()) {
return
}
- coroutineScope {
- launch {
- // Intercepts pointer inputs and exposes [PointersInfo], via
- // [requireAncestorPointersInfoOwner], to our descendants.
- awaitPointerEventScope {
- while (isActive) {
- // During the Initial pass, we receive the event after our ancestors.
- val pointers = awaitPointerEvent(PointerEventPass.Initial).changes
-
- pointersDown = pointers.countDown()
- if (pointersDown == 0) {
- // There are no more pointers down
- startedPosition = null
- } else if (startedPosition == null) {
- startedPosition = pointers.first().position
- onFirstPointerDown()
- }
- }
- }
- }
-
- // The order is important here: we want to make sure that the previous PointerEventScope
- // is initialized first. This ensures that the following PointerEventScope doesn't
- // receive more events than the first one.
- launch {
- awaitPointerEventScope {
- while (isActive) {
- try {
- detectDragGestures(
- orientation = orientation,
- startDragImmediately = startDragImmediately,
- onDragStart = { startedPosition, overSlop, pointersDown ->
- velocityTracker.resetTracking()
- onDragStarted(startedPosition, overSlop, pointersDown)
- },
- onDrag = { controller, change, amount ->
- velocityTracker.addPointerInputChange(change)
- dispatchScrollEvents(
- availableOnPreScroll = amount,
- onScroll = { controller.onDrag(it) },
- source = NestedScrollSource.UserInput,
- )
- },
- onDragEnd = { controller ->
- startFlingGesture(
- initialVelocity =
- currentValueOf(LocalViewConfiguration)
- .maximumFlingVelocity
- .let {
- val maxVelocity = Velocity(it, it)
- velocityTracker.calculateVelocity(maxVelocity)
- }
- .toFloat(),
- onFling = { controller.onStop(it, canChangeContent = true) }
- )
- },
- onDragCancel = { controller ->
- startFlingGesture(
- initialVelocity = 0f,
- onFling = { controller.onStop(it, canChangeContent = true) }
- )
- },
- swipeDetector = swipeDetector,
+ val currentContext = currentCoroutineContext()
+ awaitPointerEventScope {
+ while (currentContext.isActive) {
+ try {
+ detectDragGestures(
+ orientation = orientation,
+ startDragImmediately = startDragImmediately,
+ onDragStart = { startedPosition, overSlop, pointersDown ->
+ velocityTracker.resetTracking()
+ onDragStarted(startedPosition, overSlop, pointersDown)
+ },
+ onDrag = { controller, change, amount ->
+ velocityTracker.addPointerInputChange(change)
+ dispatchScrollEvents(
+ availableOnPreScroll = amount,
+ onScroll = { controller.onDrag(it) },
+ source = NestedScrollSource.UserInput,
)
- } catch (exception: CancellationException) {
- // If the coroutine scope is active, we can just restart the drag cycle.
- if (!isActive) {
- throw exception
- }
- }
+ },
+ onDragEnd = { controller ->
+ startFlingGesture(
+ initialVelocity =
+ currentValueOf(LocalViewConfiguration)
+ .maximumFlingVelocity
+ .let {
+ val maxVelocity = Velocity(it, it)
+ velocityTracker.calculateVelocity(maxVelocity)
+ }
+ .toFloat(),
+ onFling = { controller.onStop(it, canChangeContent = true) }
+ )
+ },
+ onDragCancel = { controller ->
+ startFlingGesture(
+ initialVelocity = 0f,
+ onFling = { controller.onStop(it, canChangeContent = true) }
+ )
+ },
+ swipeDetector = swipeDetector,
+ )
+ } catch (exception: CancellationException) {
+ // If the coroutine scope is active, we can just restart the drag cycle.
+ if (!currentContext.isActive) {
+ throw exception
}
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
index be4fea1..3bf19fc 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
@@ -18,12 +18,14 @@
import androidx.activity.BackEventCompat
import androidx.activity.compose.PredictiveBackHandler
-import androidx.compose.animation.core.spring
+import androidx.compose.animation.core.AnimationSpec
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.runtime.Composable
import kotlin.coroutines.cancellation.CancellationException
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
@Composable
internal fun PredictiveBackHandler(
@@ -42,8 +44,9 @@
val animation =
createSwipeAnimation(
layoutImpl,
- layoutImpl.coroutineScope,
- result,
+ result.userActionCopy(
+ transitionKey = result.transitionKey ?: TransitionKey.PredictiveBack
+ ),
isUpOrLeft = false,
// Note that the orientation does not matter here given that it's only used to
// compute the distance. In our case the distance is always 1f.
@@ -60,9 +63,10 @@
animation: SwipeAnimation<T>,
progress: Flow<BackEventCompat>,
) {
- fun animateOffset(targetContent: T) {
+ fun animateOffset(targetContent: T, spec: AnimationSpec<Float>? = null) {
if (
- layoutImpl.state.transitionState != animation.contentTransition || animation.isFinishing
+ layoutImpl.state.transitionState != animation.contentTransition ||
+ animation.isAnimatingOffset()
) {
return
}
@@ -70,23 +74,27 @@
animation.animateOffset(
initialVelocity = 0f,
targetContent = targetContent,
-
- // TODO(b/350705972): Allow to customize or reuse the same customization endpoints as
- // the normal swipe transitions. We can't just reuse them here because other swipe
- // transitions animate pixels while this transition animates progress, so the visibility
- // thresholds will be completely different.
- spec = spring(),
+ spec = spec,
)
}
- layoutImpl.state.startTransition(animation.contentTransition)
- try {
- progress.collect { backEvent -> animation.dragOffset = backEvent.progress }
+ coroutineScope {
+ launch {
+ try {
+ progress.collect { backEvent -> animation.dragOffset = backEvent.progress }
- // Back gesture successful.
- animateOffset(animation.toContent)
- } catch (e: CancellationException) {
- // Back gesture cancelled.
- animateOffset(animation.fromContent)
+ // Back gesture successful.
+ animateOffset(
+ animation.toContent,
+ animation.contentTransition.transformationSpec.progressSpec,
+ )
+ } catch (e: CancellationException) {
+ // Back gesture cancelled.
+ animateOffset(animation.fromContent)
+ }
+ }
+
+ // Start the transition.
+ layoutImpl.state.startTransition(animation.contentTransition)
}
}
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 b3f74f7..a0d512c 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
@@ -492,6 +492,17 @@
) {
internal abstract fun toContent(currentScene: SceneKey): ContentKey
+ internal fun userActionCopy(
+ transitionKey: TransitionKey? = this.transitionKey
+ ): UserActionResult {
+ return when (this) {
+ is ChangeScene -> copy(transitionKey = transitionKey)
+ is ShowOverlay -> copy(transitionKey = transitionKey)
+ is HideOverlay -> copy(transitionKey = transitionKey)
+ is ReplaceByOverlay -> copy(transitionKey = transitionKey)
+ }
+ }
+
data class ChangeScene
internal constructor(
/** The scene we should be transitioning to during the [UserAction]. */
@@ -503,19 +514,19 @@
}
/** A [UserActionResult] that shows [overlay]. */
- class ShowOverlay(
+ data class ShowOverlay(
val overlay: OverlayKey,
- transitionKey: TransitionKey? = null,
- requiresFullDistanceSwipe: Boolean = false,
+ override val transitionKey: TransitionKey? = null,
+ override val requiresFullDistanceSwipe: Boolean = false,
) : UserActionResult(transitionKey, requiresFullDistanceSwipe) {
override fun toContent(currentScene: SceneKey): ContentKey = overlay
}
/** A [UserActionResult] that hides [overlay]. */
- class HideOverlay(
+ data class HideOverlay(
val overlay: OverlayKey,
- transitionKey: TransitionKey? = null,
- requiresFullDistanceSwipe: Boolean = false,
+ override val transitionKey: TransitionKey? = null,
+ override val requiresFullDistanceSwipe: Boolean = false,
) : UserActionResult(transitionKey, requiresFullDistanceSwipe) {
override fun toContent(currentScene: SceneKey): ContentKey = currentScene
}
@@ -526,10 +537,10 @@
* Note: This result can only be used for user actions of overlays and an exception will be
* thrown if it is used for a scene.
*/
- class ReplaceByOverlay(
+ data class ReplaceByOverlay(
val overlay: OverlayKey,
- transitionKey: TransitionKey? = null,
- requiresFullDistanceSwipe: Boolean = false,
+ override val transitionKey: TransitionKey? = null,
+ override val requiresFullDistanceSwipe: Boolean = false,
) : UserActionResult(transitionKey, requiresFullDistanceSwipe) {
override fun toContent(currentScene: SceneKey): ContentKey = overlay
}
@@ -606,7 +617,7 @@
swipeSourceDetector = swipeSourceDetector,
transitionInterceptionThreshold = transitionInterceptionThreshold,
builder = builder,
- coroutineScope = coroutineScope,
+ animationScope = coroutineScope,
)
.also { onLayoutImpl?.invoke(it) }
}
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 b33b4f6..f36c0fa 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
@@ -59,7 +59,13 @@
internal var swipeSourceDetector: SwipeSourceDetector,
internal var transitionInterceptionThreshold: Float,
builder: SceneTransitionLayoutScope.() -> Unit,
- internal val coroutineScope: CoroutineScope,
+
+ /**
+ * The scope that should be used by *animations started by this layout only*, i.e. animations
+ * triggered by gestures set up on this layout in [swipeToScene] or interruption decay
+ * animations.
+ */
+ internal val animationScope: CoroutineScope,
) {
/**
* The map of [Scene]s.
@@ -142,18 +148,10 @@
// DraggableHandlerImpl must wait for the scenes to be initialized, in order to access the
// current scene (required for SwipeTransition).
horizontalDraggableHandler =
- DraggableHandlerImpl(
- layoutImpl = this,
- orientation = Orientation.Horizontal,
- coroutineScope = coroutineScope,
- )
+ DraggableHandlerImpl(layoutImpl = this, orientation = Orientation.Horizontal)
verticalDraggableHandler =
- DraggableHandlerImpl(
- layoutImpl = this,
- orientation = Orientation.Vertical,
- coroutineScope = coroutineScope,
- )
+ DraggableHandlerImpl(layoutImpl = this, orientation = Orientation.Vertical)
// Make sure that the state is created on the same thread (most probably the main thread)
// than this STLImpl.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index f3128f1..cc7d146 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -30,6 +30,10 @@
import com.android.compose.animation.scene.transition.link.StateLink
import kotlin.math.absoluteValue
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
/**
* The state of a [SceneTransitionLayout].
@@ -108,24 +112,25 @@
* If [targetScene] is different than the [currentScene][TransitionState.currentScene] of
* [transitionState], then this will animate to [targetScene]. The associated
* [TransitionState.Transition] will be returned and will be set as the current
- * [transitionState] of this [MutableSceneTransitionLayoutState].
+ * [transitionState] of this [MutableSceneTransitionLayoutState]. The [Job] in which the
+ * transition runs will be returned, allowing you to easily [join][Job.join] or
+ * [cancel][Job.cancel] the animation.
*
* Note that because a non-null [TransitionState.Transition] is returned does not mean that the
* transition will finish and that we will settle to [targetScene]. The returned transition
* might still be interrupted, for instance by another call to [setTargetScene] or by a user
* gesture.
*
- * If [this] [CoroutineScope] is cancelled during the transition and that the transition was
- * still active, then the [transitionState] of this [MutableSceneTransitionLayoutState] will be
- * set to `TransitionState.Idle(targetScene)`.
- *
- * TODO(b/318794193): Add APIs to await() and cancel() any [TransitionState.Transition].
+ * If [coroutineScope] is cancelled during the transition and that the transition was still
+ * active, then the [transitionState] of this [MutableSceneTransitionLayoutState] will be set to
+ * `TransitionState.Idle(targetScene)`.
*/
fun setTargetScene(
targetScene: SceneKey,
+ // TODO(b/362727477): Rename to animationScope.
coroutineScope: CoroutineScope,
transitionKey: TransitionKey? = null,
- ): TransitionState.Transition?
+ ): Pair<TransitionState.Transition, Job>?
/** Immediately snap to the given [scene]. */
fun snapToScene(
@@ -299,7 +304,7 @@
targetScene: SceneKey,
coroutineScope: CoroutineScope,
transitionKey: TransitionKey?,
- ): TransitionState.Transition.ChangeScene? {
+ ): Pair<TransitionState.Transition.ChangeScene, Job>? {
checkThread()
return coroutineScope.animateToScene(
@@ -310,17 +315,67 @@
}
/**
+ * Instantly start a [transition], running it in [animationScope].
+ *
+ * This call returns immediately and [transition] will be the [currentTransition] of this
+ * [MutableSceneTransitionLayoutState].
+ *
+ * @see startTransition
+ */
+ internal fun startTransitionImmediately(
+ animationScope: CoroutineScope,
+ transition: TransitionState.Transition,
+ chain: Boolean = true,
+ ): Job {
+ // Note that we start with UNDISPATCHED so that startTransition() is called directly and
+ // transition becomes the current [transitionState] right after this call.
+ return animationScope.launch(
+ start = CoroutineStart.UNDISPATCHED,
+ ) {
+ startTransition(transition, chain)
+ }
+ }
+
+ /**
* Start a new [transition].
*
* If [chain] is `true`, then the transitions will simply be added to [currentTransitions] and
* will run in parallel to the current transitions. If [chain] is `false`, then the list of
* [currentTransitions] will be cleared and [transition] will be the only running transition.
*
- * Important: you *must* call [finishTransition] once the transition is finished.
+ * If any transition is currently ongoing, it will be interrupted and forced to animate to its
+ * current state.
+ *
+ * This method returns when [transition] is done running, i.e. when the call to
+ * [run][TransitionState.Transition.run] returns.
*/
- internal fun startTransition(transition: TransitionState.Transition, chain: Boolean = true) {
+ internal suspend fun startTransition(
+ transition: TransitionState.Transition,
+ chain: Boolean = true,
+ ) {
checkThread()
+ try {
+ // Keep a reference to the previous transition (if any).
+ val previousTransition = currentTransition
+
+ // Start the transition.
+ startTransitionInternal(transition, chain)
+
+ // Handle transition links.
+ previousTransition?.let { cancelActiveTransitionLinks(it) }
+ if (stateLinks.isNotEmpty()) {
+ coroutineScope { setupTransitionLinks(transition) }
+ }
+
+ // Run the transition until it is finished.
+ transition.run()
+ } finally {
+ finishTransition(transition)
+ }
+ }
+
+ private fun startTransitionInternal(transition: TransitionState.Transition, chain: Boolean) {
// Set the current scene and overlays on the transition.
val currentState = transitionState
transition.currentSceneWhenTransitionStarted = currentState.currentScene
@@ -349,10 +404,6 @@
transition.updateOverscrollSpecs(fromSpec = null, toSpec = null)
}
- // Handle transition links.
- currentTransition?.let { cancelActiveTransitionLinks(it) }
- setupTransitionLinks(transition)
-
if (!enableInterruptions) {
// Set the current transition.
check(transitionStates.size == 1)
@@ -367,9 +418,8 @@
transitionStates = listOf(transition)
}
is TransitionState.Transition -> {
- // Force the current transition to finish to currentScene. The transition will call
- // [finishTransition] once it's finished.
- currentState.finish()
+ // Force the current transition to finish to currentScene.
+ currentState.freezeAndAnimateToCurrentState()
val tooManyTransitions = transitionStates.size >= MAX_CONCURRENT_TRANSITIONS
val clearCurrentTransitions = !chain || tooManyTransitions
@@ -423,7 +473,7 @@
transition.activeTransitionLinks.clear()
}
- private fun setupTransitionLinks(transition: TransitionState.Transition) {
+ private fun CoroutineScope.setupTransitionLinks(transition: TransitionState.Transition) {
stateLinks.fastForEach { stateLink ->
val matchingLinks =
stateLink.transitionLinks.fastFilter { it.isMatchingLink(transition) }
@@ -443,7 +493,11 @@
key = matchingLink.targetTransitionKey,
)
- stateLink.target.startTransition(linkedTransition)
+ // Start with UNDISPATCHED so that startTransition is called directly and the new linked
+ // transition is observable directly.
+ launch(start = CoroutineStart.UNDISPATCHED) {
+ stateLink.target.startTransition(linkedTransition)
+ }
transition.activeTransitionLinks[stateLink] = linkedTransition
}
}
@@ -453,7 +507,7 @@
* [currentScene][TransitionState.currentScene]. This will do nothing if [transition] was
* interrupted since it was started.
*/
- internal fun finishTransition(transition: TransitionState.Transition) {
+ private fun finishTransition(transition: TransitionState.Transition) {
checkThread()
if (finishedTransitions.contains(transition)) {
@@ -461,6 +515,10 @@
return
}
+ // Make sure that this transition settles in case it was force finished, for instance by
+ // calling snapToScene().
+ transition.freezeAndAnimateToCurrentState()
+
val transitionStates = this.transitionStates
if (!transitionStates.contains(transition)) {
// This transition was already removed from transitionStates.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index cefcff7..e65ed9b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -90,10 +90,19 @@
return relaxedSpec
}
- return transition(from, to, key) {
+ val relaxedReversed =
+ transition(from, to, key) {
(it.from == to && it.to == null) || (it.to == from && it.from == null)
}
- ?.reversed() ?: defaultTransition(from, to)
+ if (relaxedReversed != null) {
+ return relaxedReversed.reversed()
+ }
+
+ return if (key != null) {
+ findSpec(from, to, null)
+ } else {
+ defaultTransition(from, to)
+ }
}
private fun transition(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
index 57ff597..99a9777 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
@@ -17,8 +17,8 @@
package com.android.compose.animation.scene
import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.AnimationVector1D
-import androidx.compose.animation.core.SpringSpec
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
@@ -28,14 +28,10 @@
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.content.state.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
import kotlin.math.absoluteValue
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.CoroutineStart
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.CompletableDeferred
internal fun createSwipeAnimation(
layoutState: MutableSceneTransitionLayoutStateImpl,
- animationScope: CoroutineScope,
result: UserActionResult,
isUpOrLeft: Boolean,
orientation: Orientation,
@@ -43,7 +39,6 @@
): SwipeAnimation<*> {
return createSwipeAnimation(
layoutState,
- animationScope,
result,
isUpOrLeft,
orientation,
@@ -56,7 +51,6 @@
internal fun createSwipeAnimation(
layoutImpl: SceneTransitionLayoutImpl,
- animationScope: CoroutineScope,
result: UserActionResult,
isUpOrLeft: Boolean,
orientation: Orientation,
@@ -88,7 +82,6 @@
return createSwipeAnimation(
layoutImpl.state,
- animationScope,
result,
isUpOrLeft,
orientation,
@@ -99,7 +92,6 @@
private fun createSwipeAnimation(
layoutState: MutableSceneTransitionLayoutStateImpl,
- animationScope: CoroutineScope,
result: UserActionResult,
isUpOrLeft: Boolean,
orientation: Orientation,
@@ -109,7 +101,6 @@
fun <T : ContentKey> swipeAnimation(fromContent: T, toContent: T): SwipeAnimation<T> {
return SwipeAnimation(
layoutState = layoutState,
- animationScope = animationScope,
fromContent = fromContent,
toContent = toContent,
orientation = orientation,
@@ -197,7 +188,6 @@
/** A helper class that contains the main logic for swipe transitions. */
internal class SwipeAnimation<T : ContentKey>(
val layoutState: MutableSceneTransitionLayoutStateImpl,
- val animationScope: CoroutineScope,
val fromContent: T,
val toContent: T,
override val orientation: Orientation,
@@ -210,14 +200,22 @@
/** The [TransitionState.Transition] whose implementation delegates to this [SwipeAnimation]. */
lateinit var contentTransition: TransitionState.Transition
- var currentContent by mutableStateOf(currentContent)
+ private var _currentContent by mutableStateOf(currentContent)
+ var currentContent: T
+ get() = _currentContent
+ set(value) {
+ check(!isAnimatingOffset()) {
+ "currentContent can not be changed once we are animating the offset"
+ }
+ _currentContent = value
+ }
val progress: Float
get() {
// Important: If we are going to return early because distance is equal to 0, we should
// still make sure we read the offset before returning so that the calling code still
// subscribes to the offset value.
- val animatable = offsetAnimation?.animatable
+ val animatable = offsetAnimation
val offset =
when {
animatable != null -> animatable.value
@@ -238,7 +236,7 @@
val progressVelocity: Float
get() {
- val animatable = offsetAnimation?.animatable ?: return 0f
+ val animatable = offsetAnimation ?: return 0f
val distance = distance()
if (distance == DistanceUnspecified) {
return 0f
@@ -263,7 +261,8 @@
var dragOffset by mutableFloatStateOf(dragOffset)
/** The offset animation that animates the offset once the user lifts their finger. */
- private var offsetAnimation: OffsetAnimation? by mutableStateOf(null)
+ private var offsetAnimation: Animatable<Float, AnimationVector1D>? by mutableStateOf(null)
+ private val offsetAnimationRunnable = CompletableDeferred<(suspend () -> Unit)?>()
val isUserInputOngoing: Boolean
get() = offsetAnimation == null
@@ -271,15 +270,10 @@
override val absoluteDistance: Float
get() = distance().absoluteValue
- /** Whether [finish] was called on this animation. */
- var isFinishing = false
- private set
-
constructor(
other: SwipeAnimation<T>
) : this(
layoutState = other.layoutState,
- animationScope = other.animationScope,
fromContent = other.fromContent,
toContent = other.toContent,
orientation = other.orientation,
@@ -287,9 +281,16 @@
requiresFullDistanceSwipe = other.requiresFullDistanceSwipe,
distance = other.distance,
currentContent = other.currentContent,
- dragOffset = other.dragOffset,
+ dragOffset = other.offsetAnimation?.value ?: other.dragOffset,
)
+ suspend fun run() {
+ // When this animation is started, wait for the offset animation runnable to be set and
+ // run it.
+ val runAnimation = offsetAnimationRunnable.await() ?: return
+ runAnimation()
+ }
+
/**
* The signed distance between [fromContent] and [toContent]. It is negative if [fromContent] is
* above or to the left of [toContent].
@@ -300,28 +301,15 @@
*/
fun distance(): Float = distance(this)
- /** Ends any previous [offsetAnimation] and runs the new [animation]. */
- private fun startOffsetAnimation(animation: () -> OffsetAnimation): OffsetAnimation {
- cancelOffsetAnimation()
- return animation().also { offsetAnimation = it }
- }
-
- /** Cancel any ongoing offset animation. */
- // TODO(b/317063114) This should be a suspended function to avoid multiple jobs running at
- // the same time.
- fun cancelOffsetAnimation() {
- val animation = offsetAnimation ?: return
- offsetAnimation = null
-
- dragOffset = animation.animatable.value
- animation.job.cancel()
- }
+ fun isAnimatingOffset(): Boolean = offsetAnimation != null
fun animateOffset(
initialVelocity: Float,
targetContent: T,
- spec: SpringSpec<Float>? = null,
- ): OffsetAnimation {
+ spec: AnimationSpec<Float>? = null,
+ ) {
+ check(!isAnimatingOffset()) { "SwipeAnimation.animateOffset() can only be called once" }
+
val initialProgress = progress
// Skip the animation if we have already reached the target content and the overscroll does
// not animate anything.
@@ -358,74 +346,76 @@
currentContent = targetContent
}
- return startOffsetAnimation {
- val startProgress =
- if (contentTransition.previewTransformationSpec != null) 0f else dragOffset
- val animatable = Animatable(startProgress, OffsetVisibilityThreshold)
- val isTargetGreater = targetOffset > animatable.value
- val startedWhenOvercrollingTargetContent =
- if (targetContent == fromContent) initialProgress < 0f else initialProgress > 1f
- val job =
- animationScope
- // Important: We start atomically to make sure that we start the coroutine even
- // if it is cancelled right after it is launched, so that snapToContent() is
- // correctly called. Otherwise, this transition will never be stopped and we
- // will never settle to Idle.
- .launch(start = CoroutineStart.ATOMIC) {
- // TODO(b/327249191): Refactor the code so that we don't even launch a
- // coroutine if we don't need to animate.
- if (skipAnimation) {
- snapToContent(targetContent)
- dragOffset = targetOffset
- return@launch
- }
+ val startProgress =
+ if (contentTransition.previewTransformationSpec != null) 0f else dragOffset
- try {
- val swipeSpec =
- spec
- ?: contentTransition.transformationSpec.swipeSpec
- ?: layoutState.transitions.defaultSwipeSpec
- animatable.animateTo(
- targetValue = targetOffset,
- animationSpec = swipeSpec,
- initialVelocity = initialVelocity,
- ) {
- if (bouncingContent == null) {
- val isBouncing =
- if (isTargetGreater) {
- if (startedWhenOvercrollingTargetContent) {
- value >= targetOffset
- } else {
- value > targetOffset
- }
- } else {
- if (startedWhenOvercrollingTargetContent) {
- value <= targetOffset
- } else {
- value < targetOffset
- }
- }
+ val animatable =
+ Animatable(startProgress, OffsetVisibilityThreshold).also { offsetAnimation = it }
- if (isBouncing) {
- bouncingContent = targetContent
+ check(isAnimatingOffset())
- // Immediately stop this transition if we are bouncing on a
- // content that does not bounce.
- if (!contentTransition.isWithinProgressRange(progress)) {
- snapToContent(targetContent)
- }
- }
+ // Note: we still create the animatable and set it on offsetAnimation even when
+ // skipAnimation is true, just so that isUserInputOngoing and isAnimatingOffset() are
+ // unchanged even despite this small skip-optimization (which is just an implementation
+ // detail).
+ if (skipAnimation) {
+ // Unblock the job.
+ offsetAnimationRunnable.complete(null)
+ return
+ }
+
+ val isTargetGreater = targetOffset > animatable.value
+ val startedWhenOvercrollingTargetContent =
+ if (targetContent == fromContent) initialProgress < 0f else initialProgress > 1f
+
+ val swipeSpec =
+ spec
+ ?: contentTransition.transformationSpec.swipeSpec
+ ?: layoutState.transitions.defaultSwipeSpec
+
+ offsetAnimationRunnable.complete {
+ try {
+ animatable.animateTo(
+ targetValue = targetOffset,
+ animationSpec = swipeSpec,
+ initialVelocity = initialVelocity,
+ ) {
+ if (bouncingContent == null) {
+ val isBouncing =
+ if (isTargetGreater) {
+ if (startedWhenOvercrollingTargetContent) {
+ value >= targetOffset
+ } else {
+ value > targetOffset
+ }
+ } else {
+ if (startedWhenOvercrollingTargetContent) {
+ value <= targetOffset
+ } else {
+ value < targetOffset
}
}
- } finally {
- snapToContent(targetContent)
+
+ if (isBouncing) {
+ bouncingContent = targetContent
+
+ // Immediately stop this transition if we are bouncing on a content that
+ // does not bounce.
+ if (!contentTransition.isWithinProgressRange(progress)) {
+ throw SnapException()
+ }
}
}
-
- OffsetAnimation(animatable, job)
+ }
+ } catch (_: SnapException) {
+ /* Ignore. */
+ }
}
}
+ /** An exception thrown during the animation to stop it immediately. */
+ private class SnapException : Exception()
+
private fun canChangeContent(targetContent: ContentKey): Boolean {
return when (val transition = contentTransition) {
is TransitionState.Transition.ChangeScene ->
@@ -446,34 +436,11 @@
}
}
- private fun snapToContent(content: T) {
- cancelOffsetAnimation()
- check(currentContent == content)
- layoutState.finishTransition(contentTransition)
+ fun freezeAndAnimateToCurrentState() {
+ if (isAnimatingOffset()) return
+
+ animateOffset(initialVelocity = 0f, targetContent = currentContent)
}
-
- fun finish(): Job {
- if (isFinishing) return requireNotNull(offsetAnimation).job
- isFinishing = true
-
- // If we were already animating the offset, simply return the job.
- offsetAnimation?.let {
- return it.job
- }
-
- // Animate to the current content.
- val animation = animateOffset(initialVelocity = 0f, targetContent = currentContent)
- check(offsetAnimation == animation)
- return animation.job
- }
-
- internal class OffsetAnimation(
- /** The animatable used to animate the offset. */
- val animatable: Animatable<Float, AnimationVector1D>,
-
- /** The job in which [animatable] is animated. */
- val job: Job,
- )
}
private object DefaultSwipeDistance : UserActionDistance {
@@ -537,7 +504,13 @@
override val isUserInputOngoing: Boolean
get() = swipeAnimation.isUserInputOngoing
- override fun finish(): Job = swipeAnimation.finish()
+ override suspend fun run() {
+ swipeAnimation.run()
+ }
+
+ override fun freezeAndAnimateToCurrentState() {
+ swipeAnimation.freezeAndAnimateToCurrentState()
+ }
}
private class ShowOrHideOverlaySwipeTransition(
@@ -594,7 +567,13 @@
override val isUserInputOngoing: Boolean
get() = swipeAnimation.isUserInputOngoing
- override fun finish(): Job = swipeAnimation.finish()
+ override suspend fun run() {
+ swipeAnimation.run()
+ }
+
+ override fun freezeAndAnimateToCurrentState() {
+ swipeAnimation.freezeAndAnimateToCurrentState()
+ }
}
private class ReplaceOverlaySwipeTransition(
@@ -645,5 +624,11 @@
override val isUserInputOngoing: Boolean
get() = swipeAnimation.isUserInputOngoing
- override fun finish(): Job = swipeAnimation.finish()
+ override suspend fun run() {
+ swipeAnimation.run()
+ }
+
+ override fun freezeAndAnimateToCurrentState() {
+ swipeAnimation.freezeAndAnimateToCurrentState()
+ }
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
index 0cd8c1a..a47caaa 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
@@ -35,7 +35,6 @@
import com.android.compose.animation.scene.TransitionKey
import com.android.compose.animation.scene.transition.link.LinkedTransition
import com.android.compose.animation.scene.transition.link.StateLink
-import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
/** The state associated to a [SceneTransitionLayout] at some specific point in time. */
@@ -300,19 +299,19 @@
return fromContent == content || toContent == content
}
+ /** Run this transition and return once it is finished. */
+ internal abstract suspend fun run()
+
/**
- * Force this transition to finish and animate to an [Idle] state.
+ * Freeze this transition state so that neither [currentScene] nor [currentOverlays] will
+ * change in the future, and animate the progress towards that state. For instance, a
+ * [Transition.ChangeScene] should animate the progress to 0f if its [currentScene] is equal
+ * to its [fromScene][Transition.ChangeScene.fromScene] or animate it to 1f if its equal to
+ * its [toScene][Transition.ChangeScene.toScene].
*
- * Important: Once this is called, the effective state of the transition should remain
- * unchanged. For instance, in the case of a [TransitionState.Transition], its
- * [currentScene][TransitionState.Transition.currentScene] should never change once [finish]
- * is called.
- *
- * @return the [Job] that animates to the idle state. It can be used to wait until the
- * animation is complete or cancel it to snap the animation. Calling [finish] multiple
- * times will return the same [Job].
+ * This is called when this transition is interrupted (replaced) by another transition.
*/
- internal abstract fun finish(): Job
+ internal abstract fun freezeAndAnimateToCurrentState()
internal fun updateOverscrollSpecs(
fromSpec: OverscrollSpecImpl?,
@@ -350,7 +349,7 @@
fun create(): Animatable<Float, AnimationVector1D> {
val animatable = Animatable(1f, visibilityThreshold = ProgressVisibilityThreshold)
- layoutImpl.coroutineScope.launch {
+ layoutImpl.animationScope.launch {
val swipeSpec = layoutImpl.state.transitions.defaultSwipeSpec
val progressSpec =
spring(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
index 564d4b3..42ba9ba 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
@@ -19,7 +19,6 @@
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.TransitionKey
import com.android.compose.animation.scene.content.state.TransitionState
-import kotlinx.coroutines.Job
/** A linked transition which is driven by a [originalTransition]. */
internal class LinkedTransition(
@@ -50,5 +49,11 @@
override val progressVelocity: Float
get() = originalTransition.progressVelocity
- override fun finish(): Job = originalTransition.finish()
+ override suspend fun run() {
+ originalTransition.run()
+ }
+
+ override fun freezeAndAnimateToCurrentState() {
+ originalTransition.freezeAndAnimateToCurrentState()
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
index 8ebb42a..a491349 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
@@ -39,7 +39,10 @@
import com.android.compose.animation.scene.TestScenes.SceneB
import com.android.compose.animation.scene.TestScenes.SceneC
import com.android.compose.animation.scene.TestScenes.SceneD
+import com.android.compose.test.setContentAndCreateMainScope
+import com.android.compose.test.transition
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertThrows
import org.junit.Rule
@@ -406,30 +409,33 @@
}
}
- rule.setContent {
- SceneTransitionLayout(state) {
- // foo goes from 0f to 100f in A => B.
- scene(SceneA) { animateFloat(0f, foo) }
- scene(SceneB) { animateFloat(100f, foo) }
+ val scope =
+ rule.setContentAndCreateMainScope {
+ SceneTransitionLayout(state) {
+ // foo goes from 0f to 100f in A => B.
+ scene(SceneA) { animateFloat(0f, foo) }
+ scene(SceneB) { animateFloat(100f, foo) }
- // bar goes from 0f to 10f in C => D.
- scene(SceneC) { animateFloat(0f, bar) }
- scene(SceneD) { animateFloat(10f, bar) }
+ // bar goes from 0f to 10f in C => D.
+ scene(SceneC) { animateFloat(0f, bar) }
+ scene(SceneD) { animateFloat(10f, bar) }
+ }
}
- }
- rule.runOnUiThread {
- // A => B is at 30%.
+ // A => B is at 30%.
+ scope.launch {
state.startTransition(
transition(
from = SceneA,
to = SceneB,
progress = { 0.3f },
- onFinish = neverFinish(),
+ onFreezeAndAnimate = { /* never finish */ },
)
)
+ }
- // C => D is at 70%.
+ // C => D is at 70%.
+ scope.launch {
state.startTransition(transition(from = SceneC, to = SceneD, progress = { 0.7f }))
}
rule.waitForIdle()
@@ -466,17 +472,18 @@
}
}
- rule.setContent {
- SceneTransitionLayout(state) {
- scene(SceneA) { animateFloat(0f, key) }
- scene(SceneB) { animateFloat(100f, key) }
+ val scope =
+ rule.setContentAndCreateMainScope {
+ SceneTransitionLayout(state) {
+ scene(SceneA) { animateFloat(0f, key) }
+ scene(SceneB) { animateFloat(100f, key) }
+ }
}
- }
// Overscroll on A at -100%: value should be interpolated given that there is no overscroll
// defined for scene A.
var progress by mutableStateOf(-1f)
- rule.runOnIdle {
+ scope.launch {
state.startTransition(transition(from = SceneA, to = SceneB, progress = { progress }))
}
rule.waitForIdle()
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index 9fa4722..26743fc 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -41,9 +41,9 @@
import com.android.compose.animation.scene.subjects.assertThat
import com.android.compose.test.MonotonicClockTestScope
import com.android.compose.test.runMonotonicClockTest
+import com.android.compose.test.transition
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.launch
import org.junit.Test
import org.junit.runner.RunWith
@@ -132,7 +132,10 @@
swipeSourceDetector = DefaultEdgeDetector,
transitionInterceptionThreshold = transitionInterceptionThreshold,
builder = scenesBuilder,
- coroutineScope = testScope,
+
+ // Use testScope and not backgroundScope here because backgroundScope does not
+ // work well with advanceUntilIdle(), which is used by some tests.
+ animationScope = testScope,
)
.apply { setContentsAndLayoutTargetSizeForTest(LAYOUT_SIZE) }
@@ -301,8 +304,20 @@
runMonotonicClockTest {
val testGestureScope = TestGestureScope(testScope = this)
- // run the test
- testGestureScope.block()
+ try {
+ // Run the test.
+ testGestureScope.block()
+ } finally {
+ // Make sure we stop the last transition if it was not explicitly stopped, otherwise
+ // tests will time out after 10s given that the transitions are now started on the
+ // test scope. We don't use backgroundScope when starting the test transitions
+ // because coroutines started on the background scope don't work well with
+ // advanceUntilIdle(), which is used in a few tests.
+ if (testGestureScope.draggableHandler.isDrivingTransition) {
+ (testGestureScope.layoutState.transitionState as Transition)
+ .freezeAndAnimateToCurrentState()
+ }
+ }
}
}
@@ -940,7 +955,7 @@
}
@Test
- fun finish() = runGestureTest {
+ fun freezeAndAnimateToCurrentState() = runGestureTest {
// Start at scene C.
navigateToSceneC()
@@ -952,35 +967,25 @@
// The current transition can be intercepted.
assertThat(draggableHandler.shouldImmediatelyIntercept(middle)).isTrue()
- // Finish the transition.
+ // Freeze the transition.
val transition = transitionState as Transition
- val job = transition.finish()
+ transition.freezeAndAnimateToCurrentState()
assertTransition(isUserInputOngoing = false)
-
- // The current transition can not be intercepted anymore.
- assertThat(draggableHandler.shouldImmediatelyIntercept(middle)).isFalse()
-
- // Calling finish() multiple times returns the same Job.
- assertThat(transition.finish()).isSameInstanceAs(job)
- assertThat(transition.finish()).isSameInstanceAs(job)
- assertThat(transition.finish()).isSameInstanceAs(job)
-
- // We can join the job to wait for the animation to end.
- assertTransition()
- job.join()
+ advanceUntilIdle()
assertIdle(SceneC)
}
@Test
- fun finish_cancelled() = runGestureTest {
- // Swipe up from the middle to transition to scene B.
- val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
- onDragStarted(startedPosition = middle, overSlop = up(0.1f))
- assertTransition(fromScene = SceneA, toScene = SceneB)
+ fun interruptedTransitionCanNotBeImmediatelyIntercepted() = runGestureTest {
+ assertThat(draggableHandler.shouldImmediatelyIntercept(startedPosition = null)).isFalse()
+ onDragStarted(overSlop = up(0.1f))
+ assertThat(draggableHandler.shouldImmediatelyIntercept(startedPosition = null)).isTrue()
- // Finish the transition and cancel the returned job.
- (transitionState as Transition).finish().cancelAndJoin()
- assertIdle(SceneA)
+ layoutState.startTransitionImmediately(
+ animationScope = testScope.backgroundScope,
+ transition(SceneA, SceneB)
+ )
+ assertThat(draggableHandler.shouldImmediatelyIntercept(startedPosition = null)).isFalse()
}
@Test
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 770c0f8..60596de 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
@@ -71,10 +71,11 @@
import com.android.compose.animation.scene.TestScenes.SceneC
import com.android.compose.animation.scene.subjects.assertThat
import com.android.compose.test.assertSizeIsEqualTo
+import com.android.compose.test.setContentAndCreateMainScope
+import com.android.compose.test.transition
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertThrows
import org.junit.Ignore
import org.junit.Rule
@@ -504,7 +505,7 @@
}
@Test
- fun elementModifierNodeIsRecycledInLazyLayouts() = runTest {
+ fun elementModifierNodeIsRecycledInLazyLayouts() {
val nPages = 2
val pagerState = PagerState(currentPage = 0) { nPages }
var nullableLayoutImpl: SceneTransitionLayoutImpl? = null
@@ -630,18 +631,19 @@
)
}
- rule.setContent {
- SceneTransitionLayout(state) {
- scene(SceneA) { Box(Modifier.element(TestElements.Foo).size(20.dp)) }
- scene(SceneB) {}
+ val scope =
+ rule.setContentAndCreateMainScope {
+ SceneTransitionLayout(state) {
+ scene(SceneA) { Box(Modifier.element(TestElements.Foo).size(20.dp)) }
+ scene(SceneB) {}
+ }
}
- }
// Pause the clock to block recompositions.
rule.mainClock.autoAdvance = false
// Change the current transition.
- rule.runOnUiThread {
+ scope.launch {
state.startTransition(transition(from = SceneA, to = SceneB, progress = { 0.5f }))
}
@@ -1296,7 +1298,7 @@
}
@Test
- fun interruption() = runTest {
+ fun interruption() {
// 4 frames of animation.
val duration = 4 * 16
@@ -1336,37 +1338,41 @@
val valueInC = 200f
lateinit var layoutImpl: SceneTransitionLayoutImpl
- rule.setContent {
- SceneTransitionLayoutForTesting(
- state,
- Modifier.size(layoutSize),
- onLayoutImpl = { layoutImpl = it },
- ) {
- // In scene A, Foo is aligned at the TopStart.
- scene(SceneA) {
- Box(Modifier.fillMaxSize()) {
- Foo(sizeInA, valueInA, Modifier.align(Alignment.TopStart))
+ val scope =
+ rule.setContentAndCreateMainScope {
+ SceneTransitionLayoutForTesting(
+ state,
+ Modifier.size(layoutSize),
+ onLayoutImpl = { layoutImpl = it },
+ ) {
+ // In scene A, Foo is aligned at the TopStart.
+ scene(SceneA) {
+ Box(Modifier.fillMaxSize()) {
+ Foo(sizeInA, valueInA, Modifier.align(Alignment.TopStart))
+ }
}
- }
- // In scene C, Foo is aligned at the BottomEnd, so it moves vertically when coming
- // from B. We put it before (below) scene B so that we can check that interruptions
- // values and deltas are properly cleared once all transitions are done.
- scene(SceneC) {
- Box(Modifier.fillMaxSize()) {
- Foo(sizeInC, valueInC, Modifier.align(Alignment.BottomEnd))
+ // In scene C, Foo is aligned at the BottomEnd, so it moves vertically when
+ // coming
+ // from B. We put it before (below) scene B so that we can check that
+ // interruptions
+ // values and deltas are properly cleared once all transitions are done.
+ scene(SceneC) {
+ Box(Modifier.fillMaxSize()) {
+ Foo(sizeInC, valueInC, Modifier.align(Alignment.BottomEnd))
+ }
}
- }
- // In scene B, Foo is aligned at the TopEnd, so it moves horizontally when coming
- // from A.
- scene(SceneB) {
- Box(Modifier.fillMaxSize()) {
- Foo(sizeInB, valueInB, Modifier.align(Alignment.TopEnd))
+ // In scene B, Foo is aligned at the TopEnd, so it moves horizontally when
+ // coming
+ // from A.
+ scene(SceneB) {
+ Box(Modifier.fillMaxSize()) {
+ Foo(sizeInB, valueInB, Modifier.align(Alignment.TopEnd))
+ }
}
}
}
- }
// The offset of Foo when idle in A, B or C.
val offsetInA = DpOffset.Zero
@@ -1390,12 +1396,12 @@
from = SceneA,
to = SceneB,
progress = { aToBProgress },
- onFinish = neverFinish(),
+ onFreezeAndAnimate = { /* never finish */ },
)
val offsetInAToB = lerp(offsetInA, offsetInB, aToBProgress)
val sizeInAToB = lerp(sizeInA, sizeInB, aToBProgress)
val valueInAToB = lerp(valueInA, valueInB, aToBProgress)
- rule.runOnUiThread { state.startTransition(aToB) }
+ scope.launch { state.startTransition(aToB) }
rule
.onNode(isElement(TestElements.Foo, SceneB))
.assertSizeIsEqualTo(sizeInAToB)
@@ -1415,7 +1421,7 @@
progress = { bToCProgress },
interruptionProgress = { interruptionProgress },
)
- rule.runOnUiThread { state.startTransition(bToC) }
+ scope.launch { state.startTransition(bToC) }
// The interruption deltas, which will be multiplied by the interruption progress then added
// to the current transition offset and size.
@@ -1476,10 +1482,8 @@
.assertSizeIsEqualTo(sizeInC)
// Manually finish the transition.
- rule.runOnUiThread {
- state.finishTransition(aToB)
- state.finishTransition(bToC)
- }
+ aToB.finish()
+ bToC.finish()
rule.waitForIdle()
assertThat(state.transitionState).isIdle()
@@ -1498,7 +1502,7 @@
}
@Test
- fun interruption_sharedTransitionDisabled() = runTest {
+ fun interruption_sharedTransitionDisabled() {
// 4 frames of animation.
val duration = 4 * 16
val layoutSize = DpSize(200.dp, 100.dp)
@@ -1524,21 +1528,22 @@
Box(modifier.element(TestElements.Foo).size(fooSize))
}
- rule.setContent {
- SceneTransitionLayout(state, Modifier.size(layoutSize)) {
- scene(SceneA) {
- Box(Modifier.fillMaxSize()) { Foo(Modifier.align(Alignment.TopStart)) }
- }
+ val scope =
+ rule.setContentAndCreateMainScope {
+ SceneTransitionLayout(state, Modifier.size(layoutSize)) {
+ scene(SceneA) {
+ Box(Modifier.fillMaxSize()) { Foo(Modifier.align(Alignment.TopStart)) }
+ }
- scene(SceneB) {
- Box(Modifier.fillMaxSize()) { Foo(Modifier.align(Alignment.TopEnd)) }
- }
+ scene(SceneB) {
+ Box(Modifier.fillMaxSize()) { Foo(Modifier.align(Alignment.TopEnd)) }
+ }
- scene(SceneC) {
- Box(Modifier.fillMaxSize()) { Foo(Modifier.align(Alignment.BottomEnd)) }
+ scene(SceneC) {
+ Box(Modifier.fillMaxSize()) { Foo(Modifier.align(Alignment.BottomEnd)) }
+ }
}
}
- }
// The offset of Foo when idle in A, B or C.
val offsetInA = DpOffset.Zero
@@ -1547,7 +1552,12 @@
// State is a transition A => B at 50% interrupted by B => C at 30%.
val aToB =
- transition(from = SceneA, to = SceneB, progress = { 0.5f }, onFinish = neverFinish())
+ transition(
+ from = SceneA,
+ to = SceneB,
+ progress = { 0.5f },
+ onFreezeAndAnimate = { /* never finish */ },
+ )
var bToCInterruptionProgress by mutableStateOf(1f)
val bToC =
transition(
@@ -1555,11 +1565,11 @@
to = SceneC,
progress = { 0.3f },
interruptionProgress = { bToCInterruptionProgress },
- onFinish = neverFinish(),
+ onFreezeAndAnimate = { /* never finish */ },
)
- rule.runOnUiThread { state.startTransition(aToB) }
+ scope.launch { state.startTransition(aToB) }
rule.waitForIdle()
- rule.runOnUiThread { state.startTransition(bToC) }
+ scope.launch { state.startTransition(bToC) }
// Foo is placed in both B and C given that the shared transition is disabled. In B, its
// offset is impacted by the interruption but in C it is not.
@@ -1579,7 +1589,8 @@
// Manually finish A => B so only B => C is remaining.
bToCInterruptionProgress = 0f
- rule.runOnUiThread { state.finishTransition(aToB) }
+ aToB.finish()
+
rule
.onNode(isElement(TestElements.Foo, SceneB))
.assertPositionInRootIsEqualTo(offsetInB.x, offsetInB.y)
@@ -1595,7 +1606,7 @@
progress = { 0.7f },
interruptionProgress = { 1f },
)
- rule.runOnUiThread { state.startTransition(bToA) }
+ scope.launch { state.startTransition(bToA) }
// Foo should have the position it had in B right before the interruption.
rule
@@ -1609,32 +1620,35 @@
val state =
rule.runOnUiThread {
MutableSceneTransitionLayoutStateImpl(
- SceneA,
- transitions { overscrollDisabled(SceneA, Orientation.Horizontal) }
- )
- .apply {
- startTransition(
- transition(
- from = SceneA,
- to = SceneB,
- progress = { -1f },
- orientation = Orientation.Horizontal
- )
- )
- }
+ SceneA,
+ transitions { overscrollDisabled(SceneA, Orientation.Horizontal) }
+ )
}
lateinit var layoutImpl: SceneTransitionLayoutImpl
- rule.setContent {
- SceneTransitionLayoutForTesting(
- state,
- Modifier.size(100.dp),
- onLayoutImpl = { layoutImpl = it },
- ) {
- scene(SceneA) {}
- scene(SceneB) { Box(Modifier.element(TestElements.Foo)) }
+ val scope =
+ rule.setContentAndCreateMainScope {
+ SceneTransitionLayoutForTesting(
+ state,
+ Modifier.size(100.dp),
+ onLayoutImpl = { layoutImpl = it },
+ ) {
+ scene(SceneA) {}
+ scene(SceneB) { Box(Modifier.element(TestElements.Foo)) }
+ }
}
+
+ scope.launch {
+ state.startTransition(
+ transition(
+ from = SceneA,
+ to = SceneB,
+ progress = { -1f },
+ orientation = Orientation.Horizontal
+ )
+ )
}
+ rule.waitForIdle()
assertThat(layoutImpl.elements).containsKey(TestElements.Foo)
val foo = layoutImpl.elements.getValue(TestElements.Foo)
@@ -1647,7 +1661,7 @@
}
@Test
- fun lastAlphaIsNotSetByOutdatedLayer() = runTest {
+ fun lastAlphaIsNotSetByOutdatedLayer() {
val state =
rule.runOnUiThread {
MutableSceneTransitionLayoutStateImpl(
@@ -1657,23 +1671,24 @@
}
lateinit var layoutImpl: SceneTransitionLayoutImpl
- rule.setContent {
- SceneTransitionLayoutForTesting(state, onLayoutImpl = { layoutImpl = it }) {
- scene(SceneA) {}
- scene(SceneB) { Box(Modifier.element(TestElements.Foo)) }
- scene(SceneC) { Box(Modifier.element(TestElements.Foo)) }
+ val scope =
+ rule.setContentAndCreateMainScope {
+ SceneTransitionLayoutForTesting(state, onLayoutImpl = { layoutImpl = it }) {
+ scene(SceneA) {}
+ scene(SceneB) { Box(Modifier.element(TestElements.Foo)) }
+ scene(SceneC) { Box(Modifier.element(TestElements.Foo)) }
+ }
}
- }
// Start A => B at 0.5f.
var aToBProgress by mutableStateOf(0.5f)
- rule.runOnUiThread {
+ scope.launch {
state.startTransition(
transition(
from = SceneA,
to = SceneB,
progress = { aToBProgress },
- onFinish = neverFinish(),
+ onFreezeAndAnimate = { /* never finish */ },
)
)
}
@@ -1692,7 +1707,7 @@
assertThat(fooInB.lastAlpha).isEqualTo(0.7f)
// Start B => C at 0.3f.
- rule.runOnUiThread {
+ scope.launch {
state.startTransition(transition(from = SceneB, to = SceneC, progress = { 0.3f }))
}
rule.waitForIdle()
@@ -1720,16 +1735,17 @@
}
lateinit var layoutImpl: SceneTransitionLayoutImpl
- rule.setContent {
- SceneTransitionLayoutForTesting(state, onLayoutImpl = { layoutImpl = it }) {
- scene(SceneA) {}
- scene(SceneB) { Box(Modifier.element(TestElements.Foo)) }
+ val scope =
+ rule.setContentAndCreateMainScope {
+ SceneTransitionLayoutForTesting(state, onLayoutImpl = { layoutImpl = it }) {
+ scene(SceneA) {}
+ scene(SceneB) { Box(Modifier.element(TestElements.Foo)) }
+ }
}
- }
// Start A => B at 60%.
var interruptionProgress by mutableStateOf(1f)
- rule.runOnUiThread {
+ scope.launch {
state.startTransition(
transition(
from = SceneA,
@@ -1774,19 +1790,20 @@
Box(Modifier.element(TestElements.Foo).size(10.dp))
}
- rule.setContent {
- SceneTransitionLayout(state) {
- scene(SceneA) { Foo() }
- scene(SceneB) { Foo() }
+ val scope =
+ rule.setContentAndCreateMainScope {
+ SceneTransitionLayout(state) {
+ scene(SceneA) { Foo() }
+ scene(SceneB) { Foo() }
+ }
}
- }
rule.onNode(isElement(TestElements.Foo, SceneA)).assertIsDisplayed()
rule.onNode(isElement(TestElements.Foo, SceneB)).assertDoesNotExist()
// A => B while overscrolling at scene B.
var progress by mutableStateOf(2f)
- rule.runOnUiThread {
+ scope.launch {
state.startTransition(transition(from = SceneA, to = SceneB, progress = { progress }))
}
rule.waitForIdle()
@@ -1827,19 +1844,20 @@
MovableElement(key, modifier) { content { Text(text) } }
}
- rule.setContent {
- SceneTransitionLayout(state) {
- scene(SceneA) { MovableFoo(text = fooInA) }
- scene(SceneB) { MovableFoo(text = fooInB) }
+ val scope =
+ rule.setContentAndCreateMainScope {
+ SceneTransitionLayout(state) {
+ scene(SceneA) { MovableFoo(text = fooInA) }
+ scene(SceneB) { MovableFoo(text = fooInB) }
+ }
}
- }
rule.onNode(hasText(fooInA)).assertIsDisplayed()
rule.onNode(hasText(fooInB)).assertDoesNotExist()
// A => B while overscrolling at scene B.
var progress by mutableStateOf(2f)
- rule.runOnUiThread {
+ scope.launch {
state.startTransition(transition(from = SceneA, to = SceneB, progress = { progress }))
}
rule.waitForIdle()
@@ -1858,7 +1876,7 @@
}
@Test
- fun interruptionThenOverscroll() = runTest {
+ fun interruptionThenOverscroll() {
val state =
rule.runOnUiThread {
MutableSceneTransitionLayoutStateImpl(
@@ -1879,22 +1897,23 @@
}
}
- rule.setContent {
- SceneTransitionLayout(state, Modifier.size(200.dp)) {
- scene(SceneA) { SceneWithFoo(offset = DpOffset.Zero) }
- scene(SceneB) { SceneWithFoo(offset = DpOffset(x = 40.dp, y = 0.dp)) }
- scene(SceneC) { SceneWithFoo(offset = DpOffset(x = 40.dp, y = 40.dp)) }
+ val scope =
+ rule.setContentAndCreateMainScope {
+ SceneTransitionLayout(state, Modifier.size(200.dp)) {
+ scene(SceneA) { SceneWithFoo(offset = DpOffset.Zero) }
+ scene(SceneB) { SceneWithFoo(offset = DpOffset(x = 40.dp, y = 0.dp)) }
+ scene(SceneC) { SceneWithFoo(offset = DpOffset(x = 40.dp, y = 40.dp)) }
+ }
}
- }
// Start A => B at 75%.
- rule.runOnUiThread {
+ scope.launch {
state.startTransition(
transition(
from = SceneA,
to = SceneB,
progress = { 0.75f },
- onFinish = neverFinish(),
+ onFreezeAndAnimate = { /* never finish */ },
)
)
}
@@ -1907,7 +1926,7 @@
// Interrupt A => B with B => C at 0%.
var progress by mutableStateOf(0f)
var interruptionProgress by mutableStateOf(1f)
- rule.runOnUiThread {
+ scope.launch {
state.startTransition(
transition(
from = SceneB,
@@ -1915,7 +1934,7 @@
progress = { progress },
interruptionProgress = { interruptionProgress },
orientation = Orientation.Vertical,
- onFinish = neverFinish(),
+ onFreezeAndAnimate = { /* never finish */ },
)
)
}
@@ -1963,12 +1982,13 @@
}
lateinit var layoutImpl: SceneTransitionLayoutImpl
- rule.setContent {
- SceneTransitionLayoutForTesting(state, onLayoutImpl = { layoutImpl = it }) {
- scene(SceneA) { NestedFooBar() }
- scene(SceneB) { NestedFooBar() }
+ val scope =
+ rule.setContentAndCreateMainScope {
+ SceneTransitionLayoutForTesting(state, onLayoutImpl = { layoutImpl = it }) {
+ scene(SceneA) { NestedFooBar() }
+ scene(SceneB) { NestedFooBar() }
+ }
}
- }
// Idle on A: composed and placed only in B.
rule.onNode(isElement(TestElements.Foo, SceneA)).assertIsDisplayed()
@@ -1997,7 +2017,7 @@
assertThat(barInA.lastScale).isNotEqualTo(Scale.Unspecified)
// A => B: composed in both and placed only in B.
- rule.runOnUiThread { state.startTransition(transition(from = SceneA, to = SceneB)) }
+ scope.launch { state.startTransition(transition(from = SceneA, to = SceneB)) }
rule.onNode(isElement(TestElements.Foo, SceneA)).assertExists().assertIsNotDisplayed()
rule.onNode(isElement(TestElements.Bar, SceneA)).assertExists().assertIsNotDisplayed()
rule.onNode(isElement(TestElements.Foo, SceneB)).assertIsDisplayed()
@@ -2024,7 +2044,7 @@
}
@Test
- fun currentTransitionSceneIsUsedToComputeElementValues() = runTest {
+ fun currentTransitionSceneIsUsedToComputeElementValues() {
val state =
rule.runOnIdle {
MutableSceneTransitionLayoutStateImpl(
@@ -2044,23 +2064,31 @@
}
}
- rule.setContent {
- SceneTransitionLayout(state, Modifier.size(200.dp)) {
- scene(SceneA) { Foo() }
- scene(SceneB) {}
- scene(SceneC) { Foo() }
+ val scope =
+ rule.setContentAndCreateMainScope {
+ SceneTransitionLayout(state, Modifier.size(200.dp)) {
+ scene(SceneA) { Foo() }
+ scene(SceneB) {}
+ scene(SceneC) { Foo() }
+ }
}
- }
// We have 2 transitions:
// - A => B at 100%
// - B => C at 0%
// So Foo should have a size of (40dp, 60dp) in both A and C given that it is scaling its
// size in B => C.
- rule.runOnUiThread {
+ scope.launch {
state.startTransition(
- transition(from = SceneA, to = SceneB, progress = { 1f }, onFinish = neverFinish())
+ transition(
+ from = SceneA,
+ to = SceneB,
+ progress = { 1f },
+ onFreezeAndAnimate = { /* never finish */ },
+ )
)
+ }
+ scope.launch {
state.startTransition(transition(from = SceneB, to = SceneC, progress = { 0f }))
}
@@ -2069,7 +2097,7 @@
}
@Test
- fun interruptionDeltasAreProperlyCleaned() = runTest {
+ fun interruptionDeltasAreProperlyCleaned() {
val state = rule.runOnIdle { MutableSceneTransitionLayoutStateImpl(SceneA) }
@Composable
@@ -2079,18 +2107,24 @@
}
}
- rule.setContent {
- SceneTransitionLayout(state, Modifier.size(200.dp)) {
- scene(SceneA) { Foo(offset = 0.dp) }
- scene(SceneB) { Foo(offset = 20.dp) }
- scene(SceneC) { Foo(offset = 40.dp) }
+ val scope =
+ rule.setContentAndCreateMainScope {
+ SceneTransitionLayout(state, Modifier.size(200.dp)) {
+ scene(SceneA) { Foo(offset = 0.dp) }
+ scene(SceneB) { Foo(offset = 20.dp) }
+ scene(SceneC) { Foo(offset = 40.dp) }
+ }
}
- }
// Start A => B at 50%.
val aToB =
- transition(from = SceneA, to = SceneB, progress = { 0.5f }, onFinish = neverFinish())
- rule.runOnUiThread { state.startTransition(aToB) }
+ transition(
+ from = SceneA,
+ to = SceneB,
+ progress = { 0.5f },
+ onFreezeAndAnimate = { /* never finish */ },
+ )
+ scope.launch { state.startTransition(aToB) }
rule.onNode(isElement(TestElements.Foo, SceneB)).assertPositionInRootIsEqualTo(10.dp, 10.dp)
// Start B => C at 0%. This will compute an interruption delta of (-10dp, -10dp) so that the
@@ -2103,9 +2137,9 @@
current = { SceneB },
progress = { 0f },
interruptionProgress = { interruptionProgress },
- onFinish = neverFinish(),
+ onFreezeAndAnimate = { /* never finish */ },
)
- rule.runOnUiThread { state.startTransition(bToC) }
+ scope.launch { state.startTransition(bToC) }
rule.onNode(isElement(TestElements.Foo, SceneC)).assertPositionInRootIsEqualTo(10.dp, 10.dp)
// Finish the interruption and leave the transition progress at 0f. We should be at the same
@@ -2116,9 +2150,9 @@
// Finish both transitions but directly start a new one B => A with interruption progress
// 100%. We should be at (20dp, 20dp), unless the interruption deltas have not been
// correctly cleaned.
- rule.runOnUiThread {
- state.finishTransition(aToB)
- state.finishTransition(bToC)
+ aToB.finish()
+ bToC.finish()
+ scope.launch {
state.startTransition(
transition(
from = SceneB,
@@ -2132,7 +2166,7 @@
}
@Test
- fun lastSizeIsUnspecifiedWhenOverscrollingOtherScene() = runTest {
+ fun lastSizeIsUnspecifiedWhenOverscrollingOtherScene() {
val state =
rule.runOnIdle {
MutableSceneTransitionLayoutStateImpl(
@@ -2147,17 +2181,23 @@
}
lateinit var layoutImpl: SceneTransitionLayoutImpl
- rule.setContent {
- SceneTransitionLayoutForTesting(state, onLayoutImpl = { layoutImpl = it }) {
- scene(SceneA) { Foo() }
- scene(SceneB) { Foo() }
+ val scope =
+ rule.setContentAndCreateMainScope {
+ SceneTransitionLayoutForTesting(state, onLayoutImpl = { layoutImpl = it }) {
+ scene(SceneA) { Foo() }
+ scene(SceneB) { Foo() }
+ }
}
- }
// Overscroll A => B on A.
- rule.runOnUiThread {
+ scope.launch {
state.startTransition(
- transition(from = SceneA, to = SceneB, progress = { -1f }, onFinish = neverFinish())
+ transition(
+ from = SceneA,
+ to = SceneB,
+ progress = { -1f },
+ onFreezeAndAnimate = { /* never finish */ },
+ )
)
}
rule.waitForIdle()
@@ -2173,7 +2213,7 @@
}
@Test
- fun transparentElementIsNotImpactingInterruption() = runTest {
+ fun transparentElementIsNotImpactingInterruption() {
val state =
rule.runOnIdle {
MutableSceneTransitionLayoutStateImpl(
@@ -2200,23 +2240,24 @@
Box(modifier.element(TestElements.Foo).size(10.dp))
}
- rule.setContent {
- SceneTransitionLayout(state) {
- scene(SceneB) { Foo(Modifier.offset(40.dp, 60.dp)) }
+ val scope =
+ rule.setContentAndCreateMainScope {
+ SceneTransitionLayout(state) {
+ scene(SceneB) { Foo(Modifier.offset(40.dp, 60.dp)) }
- // Define A after B so that Foo is placed in A during A <=> B.
- scene(SceneA) { Foo() }
+ // Define A after B so that Foo is placed in A during A <=> B.
+ scene(SceneA) { Foo() }
+ }
}
- }
// Start A => B at 70%.
- rule.runOnUiThread {
+ scope.launch {
state.startTransition(
transition(
from = SceneA,
to = SceneB,
progress = { 0.7f },
- onFinish = neverFinish(),
+ onFreezeAndAnimate = { /* never finish */ },
)
)
}
@@ -2227,14 +2268,14 @@
// Start B => A at 50% with interruptionProgress = 100%. Foo is placed in A and should still
// be at (40dp, 60dp) given that it was fully transparent in A before the interruption.
var interruptionProgress by mutableStateOf(1f)
- rule.runOnUiThread {
+ scope.launch {
state.startTransition(
transition(
from = SceneB,
to = SceneA,
progress = { 0.5f },
interruptionProgress = { interruptionProgress },
- onFinish = neverFinish(),
+ onFreezeAndAnimate = { /* never finish */ },
)
)
}
@@ -2250,7 +2291,7 @@
}
@Test
- fun replacedTransitionDoesNotTriggerInterruption() = runTest {
+ fun replacedTransitionDoesNotTriggerInterruption() {
val state = rule.runOnIdle { MutableSceneTransitionLayoutStateImpl(SceneA) }
@Composable
@@ -2258,17 +2299,23 @@
Box(modifier.element(TestElements.Foo).size(10.dp))
}
- rule.setContent {
- SceneTransitionLayout(state) {
- scene(SceneA) { Foo() }
- scene(SceneB) { Foo(Modifier.offset(40.dp, 60.dp)) }
+ val scope =
+ rule.setContentAndCreateMainScope {
+ SceneTransitionLayout(state) {
+ scene(SceneA) { Foo() }
+ scene(SceneB) { Foo(Modifier.offset(40.dp, 60.dp)) }
+ }
}
- }
// Start A => B at 50%.
val aToB1 =
- transition(from = SceneA, to = SceneB, progress = { 0.5f }, onFinish = neverFinish())
- rule.runOnUiThread { state.startTransition(aToB1) }
+ transition(
+ from = SceneA,
+ to = SceneB,
+ progress = { 0.5f },
+ onFreezeAndAnimate = { /* never finish */ },
+ )
+ scope.launch { state.startTransition(aToB1) }
rule.onNode(isElement(TestElements.Foo, SceneA)).assertIsNotDisplayed()
rule.onNode(isElement(TestElements.Foo, SceneB)).assertPositionInRootIsEqualTo(20.dp, 30.dp)
@@ -2282,7 +2329,7 @@
interruptionProgress = { 1f },
replacedTransition = aToB1,
)
- rule.runOnUiThread { state.startTransition(aToB2) }
+ scope.launch { state.startTransition(aToB2) }
rule.onNode(isElement(TestElements.Foo, SceneA)).assertIsNotDisplayed()
rule.onNode(isElement(TestElements.Foo, SceneB)).assertPositionInRootIsEqualTo(40.dp, 60.dp)
}
@@ -2428,12 +2475,13 @@
}
lateinit var layoutImpl: SceneTransitionLayoutImpl
- rule.setContent {
- SceneTransitionLayoutForTesting(state, onLayoutImpl = { layoutImpl = it }) {
- scene(from) { Box { exitingElements.forEach { Foo(it) } } }
- scene(to) { Box { enteringElements.forEach { Foo(it) } } }
+ val scope =
+ rule.setContentAndCreateMainScope {
+ SceneTransitionLayoutForTesting(state, onLayoutImpl = { layoutImpl = it }) {
+ scene(from) { Box { exitingElements.forEach { Foo(it) } } }
+ scene(to) { Box { enteringElements.forEach { Foo(it) } } }
+ }
}
- }
val bToA =
transition(
@@ -2443,7 +2491,7 @@
previewProgress = { previewProgress },
isInPreviewStage = { isInPreviewStage }
)
- rule.runOnUiThread { state.startTransition(bToA) }
+ scope.launch { state.startTransition(bToA) }
rule.waitForIdle()
return layoutImpl
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt
index 3f6bd2c..7498df1 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt
@@ -25,9 +25,9 @@
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.subjects.assertThat
import com.android.compose.test.runMonotonicClockTest
+import com.android.compose.test.transition
import com.google.common.truth.Correspondence
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.launch
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -155,13 +155,21 @@
// Progress must be > visibility threshold otherwise we will directly snap to A.
progress = { 0.5f },
progressVelocity = { progressVelocity },
- onFinish = { launch {} },
)
- state.startTransition(aToB)
+ state.startTransitionImmediately(animationScope = backgroundScope, aToB)
// Animate back to A. The previous transition is reversed, i.e. it has the same (from, to)
// pair, and its velocity is used when animating the progress back to 0.
- val bToA = checkNotNull(state.setTargetScene(SceneA, coroutineScope = this))
+ val bToA =
+ checkNotNull(
+ state.setTargetScene(
+ SceneA,
+ // We use testScope here and not backgroundScope because setTargetScene
+ // needs the monotonic clock that is only available in the test scope.
+ coroutineScope = this,
+ )
+ )
+ .first
testScheduler.runCurrent()
assertThat(bToA).hasFromScene(SceneA)
assertThat(bToA).hasToScene(SceneB)
@@ -181,13 +189,21 @@
to = SceneB,
current = { SceneA },
progressVelocity = { progressVelocity },
- onFinish = { launch {} },
)
- state.startTransition(aToB)
+ state.startTransitionImmediately(animationScope = backgroundScope, aToB)
// Animate to B. The previous transition is reversed, i.e. it has the same (from, to) pair,
// and its velocity is used when animating the progress to 1.
- val bToA = checkNotNull(state.setTargetScene(SceneB, coroutineScope = this))
+ val bToA =
+ checkNotNull(
+ state.setTargetScene(
+ SceneB,
+ // We use testScope here and not backgroundScope because setTargetScene
+ // needs the monotonic clock that is only available in the test scope.
+ coroutineScope = this,
+ )
+ )
+ .first
testScheduler.runCurrent()
assertThat(bToA).hasFromScene(SceneA)
assertThat(bToA).hasToScene(SceneB)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementContentPickerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementContentPickerTest.kt
index e1d0945..c8e7e65 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementContentPickerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementContentPickerTest.kt
@@ -17,6 +17,7 @@
package com.android.compose.animation.scene
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.test.transition
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertThrows
import org.junit.Test
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
index 0543e7f..2c723ec 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
@@ -29,6 +29,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.compose.animation.scene.TestScenes.SceneA
import com.android.compose.animation.scene.TestScenes.SceneB
+import com.android.compose.test.transition
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.launch
@@ -139,7 +140,7 @@
var transitionCurrentScene by mutableStateOf(SceneA)
val transition =
transition(from = SceneA, to = SceneB, current = { transitionCurrentScene })
- state.startTransition(transition)
+ state.startTransitionImmediately(animationScope = backgroundScope, transition)
assertThat(currentScene.value).isEqualTo(SceneA)
// Change the transition current scene.
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
index c5b6cdf..9284ffd 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
@@ -18,6 +18,8 @@
import androidx.activity.BackEventCompat
import androidx.activity.ComponentActivity
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
@@ -65,7 +67,23 @@
@Test
fun testPredictiveBack() {
- val layoutState = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val transitionFrames = 2
+ val layoutState =
+ rule.runOnUiThread {
+ MutableSceneTransitionLayoutState(
+ SceneA,
+ transitions =
+ transitions {
+ from(SceneA, to = SceneB) {
+ spec =
+ tween(
+ durationMillis = transitionFrames * 16,
+ easing = LinearEasing
+ )
+ }
+ }
+ )
+ }
rule.setContent {
SceneTransitionLayout(layoutState) {
scene(SceneA, mapOf(Back to SceneB)) { Box(Modifier.fillMaxSize()) }
@@ -94,12 +112,27 @@
assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
assertThat(layoutState.transitionState).isIdle()
+ rule.mainClock.autoAdvance = false
+
// Start again and commit it.
rule.runOnUiThread {
dispatcher.dispatchOnBackStarted(backEvent())
dispatcher.dispatchOnBackProgressed(backEvent(progress = 0.4f))
dispatcher.onBackPressed()
}
+ rule.mainClock.advanceTimeByFrame()
+ rule.waitForIdle()
+ val transition2 = assertThat(layoutState.transitionState).isSceneTransition()
+ // verify that transition picks up progress from preview
+ assertThat(transition2).hasProgress(0.4f, tolerance = 0.0001f)
+
+ rule.mainClock.advanceTimeByFrame()
+ rule.waitForIdle()
+ // verify that transition is half way between preview-end-state (0.4f) and target-state (1f)
+ // after one frame
+ assertThat(transition2).hasProgress(0.7f, tolerance = 0.0001f)
+
+ rule.mainClock.autoAdvance = true
rule.waitForIdle()
assertThat(layoutState.transitionState).hasCurrentScene(SceneB)
assertThat(layoutState.transitionState).isIdle()
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index 69f2cba..29eedf6 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -30,14 +30,14 @@
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.subjects.assertThat
import com.android.compose.animation.scene.transition.link.StateLink
+import com.android.compose.test.MonotonicClockTestScope
+import com.android.compose.test.TestTransition
import com.android.compose.test.runMonotonicClockTest
+import com.android.compose.test.transition
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineStart
-import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.launch
-import kotlinx.coroutines.sync.Mutex
-import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
@@ -58,9 +58,12 @@
}
@Test
- fun isTransitioningTo_transition() {
+ fun isTransitioningTo_transition() = runTest {
val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
- state.startTransition(transition(from = SceneA, to = SceneB))
+ state.startTransitionImmediately(
+ animationScope = backgroundScope,
+ transition(from = SceneA, to = SceneB)
+ )
assertThat(state.isTransitioning()).isTrue()
assertThat(state.isTransitioning(from = SceneA)).isTrue()
@@ -79,11 +82,10 @@
@Test
fun setTargetScene_idleToDifferentScene() = runMonotonicClockTest {
val state = MutableSceneTransitionLayoutState(SceneA)
- val transition = state.setTargetScene(SceneB, coroutineScope = this)
- assertThat(transition).isNotNull()
+ val (transition, job) = checkNotNull(state.setTargetScene(SceneB, coroutineScope = this))
assertThat(state.transitionState).isEqualTo(transition)
- transition!!.finish().join()
+ job.join()
assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
}
@@ -91,11 +93,10 @@
fun setTargetScene_transitionToSameScene() = runMonotonicClockTest {
val state = MutableSceneTransitionLayoutState(SceneA)
- val transition = state.setTargetScene(SceneB, coroutineScope = this)
- assertThat(transition).isNotNull()
+ val (_, job) = checkNotNull(state.setTargetScene(SceneB, coroutineScope = this))
assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNull()
- transition!!.finish().join()
+ job.join()
assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
}
@@ -104,10 +105,9 @@
val state = MutableSceneTransitionLayoutState(SceneA)
assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull()
- val transition = state.setTargetScene(SceneC, coroutineScope = this)
- assertThat(transition).isNotNull()
+ val (_, job) = checkNotNull(state.setTargetScene(SceneC, coroutineScope = this))
- transition!!.finish().join()
+ job.join()
assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneC))
}
@@ -118,7 +118,7 @@
lateinit var transition: TransitionState.Transition
val job =
launch(start = CoroutineStart.UNDISPATCHED) {
- transition = state.setTargetScene(SceneB, coroutineScope = this)!!
+ transition = checkNotNull(state.setTargetScene(SceneB, coroutineScope = this)).first
}
assertThat(state.transitionState).isEqualTo(transition)
@@ -127,18 +127,6 @@
assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
}
- @Test
- fun transition_finishReturnsTheSameJobWhenCalledMultipleTimes() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
- val transition = state.setTargetScene(SceneB, coroutineScope = this)
- assertThat(transition).isNotNull()
-
- val job = transition!!.finish()
- assertThat(transition.finish()).isSameInstanceAs(job)
- assertThat(transition.finish()).isSameInstanceAs(job)
- assertThat(transition.finish()).isSameInstanceAs(job)
- }
-
private fun setupLinkedStates(
parentInitialScene: SceneKey = SceneC,
childInitialScene: SceneKey = SceneA,
@@ -163,22 +151,24 @@
}
@Test
- fun linkedTransition_startsLinkAndFinishesLinkInToState() {
+ fun linkedTransition_startsLinkAndFinishesLinkInToState() = runTest {
val (parentState, childState) = setupLinkedStates()
val childTransition = transition(SceneA, SceneB)
- childState.startTransition(childTransition)
+ val job =
+ childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
- childState.finishTransition(childTransition)
+ childTransition.finish()
+ job.join()
assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
}
@Test
- fun linkedTransition_transitiveLink() {
+ fun linkedTransition_transitiveLink() = runTest {
val parentParentState =
MutableSceneTransitionLayoutState(SceneB) as MutableSceneTransitionLayoutStateImpl
val parentLink =
@@ -204,25 +194,27 @@
val childTransition = transition(SceneA, SceneB)
- childState.startTransition(childTransition)
+ val job =
+ childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
assertThat(parentParentState.isTransitioning(SceneB, SceneC)).isTrue()
- childState.finishTransition(childTransition)
+ childTransition.finish()
+ job.join()
assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
assertThat(parentParentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
}
@Test
- fun linkedTransition_linkProgressIsEqual() {
+ fun linkedTransition_linkProgressIsEqual() = runTest {
val (parentState, childState) = setupLinkedStates()
var progress = 0f
val childTransition = transition(SceneA, SceneB, progress = { progress })
- childState.startTransition(childTransition)
+ childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
assertThat(parentState.currentTransition?.progress).isEqualTo(0f)
progress = .5f
@@ -230,28 +222,32 @@
}
@Test
- fun linkedTransition_reverseTransitionIsNotLinked() {
+ fun linkedTransition_reverseTransitionIsNotLinked() = runTest {
val (parentState, childState) = setupLinkedStates()
val childTransition = transition(SceneB, SceneA, current = { SceneB })
- childState.startTransition(childTransition)
+ val job =
+ childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
assertThat(childState.isTransitioning(SceneB, SceneA)).isTrue()
assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
- childState.finishTransition(childTransition)
+ childTransition.finish()
+ job.join()
assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
}
@Test
- fun linkedTransition_startsLinkAndFinishesLinkInFromState() {
+ fun linkedTransition_startsLinkAndFinishesLinkInFromState() = runTest {
val (parentState, childState) = setupLinkedStates()
val childTransition = transition(SceneA, SceneB, current = { SceneA })
- childState.startTransition(childTransition)
+ val job =
+ childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
- childState.finishTransition(childTransition)
+ childTransition.finish()
+ job.join()
assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneA))
assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
}
@@ -260,22 +256,14 @@
fun linkedTransition_startsLinkButLinkedStateIsTakenOver() = runTest {
val (parentState, childState) = setupLinkedStates()
- val childTransition =
- transition(
- SceneA,
- SceneB,
- onFinish = { launch { /* Do nothing. */ } },
- )
- val parentTransition =
- transition(
- SceneC,
- SceneA,
- onFinish = { launch { /* Do nothing. */ } },
- )
- childState.startTransition(childTransition)
- parentState.startTransition(parentTransition)
+ val childTransition = transition(SceneA, SceneB)
+ val parentTransition = transition(SceneC, SceneA)
+ val job =
+ childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
+ parentState.startTransitionImmediately(animationScope = backgroundScope, parentTransition)
- childState.finishTransition(childTransition)
+ childTransition.finish()
+ job.join()
assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
assertThat(parentState.transitionState).isEqualTo(parentTransition)
}
@@ -321,7 +309,8 @@
@Test
fun snapToIdleIfClose_snapToStart() = runMonotonicClockTest {
val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
- state.startTransition(
+ state.startTransitionImmediately(
+ animationScope = backgroundScope,
transition(from = SceneA, to = SceneB, current = { SceneA }, progress = { 0.2f })
)
assertThat(state.isTransitioning()).isTrue()
@@ -339,7 +328,10 @@
@Test
fun snapToIdleIfClose_snapToEnd() = runMonotonicClockTest {
val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
- state.startTransition(transition(from = SceneA, to = SceneB, progress = { 0.8f }))
+ state.startTransitionImmediately(
+ animationScope = backgroundScope,
+ transition(from = SceneA, to = SceneB, progress = { 0.8f })
+ )
assertThat(state.isTransitioning()).isTrue()
// Ignore the request if the progress is not close to 0 or 1, using the threshold.
@@ -356,18 +348,12 @@
fun snapToIdleIfClose_multipleTransitions() = runMonotonicClockTest {
val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
- val aToB =
- transition(
- from = SceneA,
- to = SceneB,
- progress = { 0.5f },
- onFinish = { launch { /* do nothing */ } },
- )
- state.startTransition(aToB)
+ val aToB = transition(from = SceneA, to = SceneB, progress = { 0.5f })
+ state.startTransitionImmediately(animationScope = backgroundScope, aToB)
assertThat(state.currentTransitions).containsExactly(aToB).inOrder()
val bToC = transition(from = SceneB, to = SceneC, progress = { 0.8f })
- state.startTransition(bToC)
+ state.startTransitionImmediately(animationScope = backgroundScope, bToC)
assertThat(state.currentTransitions).containsExactly(aToB, bToC).inOrder()
// Ignore the request if the progress is not close to 0 or 1, using the threshold.
@@ -385,7 +371,8 @@
val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
var progress by mutableStateOf(0f)
var currentScene by mutableStateOf(SceneB)
- state.startTransition(
+ state.startTransitionImmediately(
+ animationScope = backgroundScope,
transition(
from = SceneA,
to = SceneB,
@@ -406,47 +393,51 @@
}
@Test
- fun linkedTransition_fuzzyLinksAreMatchedAndStarted() {
+ fun linkedTransition_fuzzyLinksAreMatchedAndStarted() = runTest {
val (parentState, childState) = setupLinkedStates(SceneC, SceneA, null, null, null, SceneD)
val childTransition = transition(SceneA, SceneB)
- childState.startTransition(childTransition)
+ val job =
+ childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
- childState.finishTransition(childTransition)
+ childTransition.finish()
+ job.join()
assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
}
@Test
- fun linkedTransition_fuzzyLinksAreMatchedAndResetToProperPreviousScene() {
+ fun linkedTransition_fuzzyLinksAreMatchedAndResetToProperPreviousScene() = runTest {
val (parentState, childState) =
setupLinkedStates(SceneC, SceneA, SceneA, null, null, SceneD)
val childTransition = transition(SceneA, SceneB, current = { SceneA })
- childState.startTransition(childTransition)
+ val job =
+ childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
- childState.finishTransition(childTransition)
+ childTransition.finish()
+ job.join()
assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneA))
assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
}
@Test
- fun linkedTransition_fuzzyLinksAreNotMatched() {
+ fun linkedTransition_fuzzyLinksAreNotMatched() = runTest {
val (parentState, childState) =
setupLinkedStates(SceneC, SceneA, SceneB, null, SceneC, SceneD)
val childTransition = transition(SceneA, SceneB)
- childState.startTransition(childTransition)
+ childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
assertThat(parentState.isTransitioning(SceneC, SceneD)).isFalse()
}
- private fun startOverscrollableTransistionFromAtoB(
+ private fun MonotonicClockTestScope.startOverscrollableTransistionFromAtoB(
progress: () -> Float,
sceneTransitions: SceneTransitions,
): MutableSceneTransitionLayoutStateImpl {
@@ -455,7 +446,8 @@
SceneA,
sceneTransitions,
)
- state.startTransition(
+ state.startTransitionImmediately(
+ animationScope = backgroundScope,
transition(
from = SceneA,
to = SceneB,
@@ -560,54 +552,54 @@
@Test
fun multipleTransitions() = runTest {
- val finishingTransitions = mutableSetOf<TransitionState.Transition>()
- fun onFinish(transition: TransitionState.Transition): Job {
- // Instead of letting the transition finish, we put the transition in the
- // finishingTransitions set so that we can verify that finish() is called when expected
- // and then we call state STLState.finishTransition() ourselves.
- finishingTransitions.add(transition)
+ val frozenTransitions = mutableSetOf<TestTransition>()
+ fun onFreezeAndAnimate(transition: TestTransition): () -> Unit {
+ // Instead of letting the transition finish when it is frozen, we put the transition in
+ // the frozenTransitions set so that we can verify that freezeAndAnimateToCurrentState()
+ // is called when expected and then we call finish() ourselves to finish the
+ // transitions.
+ frozenTransitions.add(transition)
- return backgroundScope.launch {
- // Try to acquire a locked mutex so that this code never completes.
- Mutex(locked = true).withLock {}
- }
+ return { /* do nothing */ }
}
val state = MutableSceneTransitionLayoutStateImpl(SceneA, EmptyTestTransitions)
- val aToB = transition(SceneA, SceneB, onFinish = ::onFinish)
- val bToC = transition(SceneB, SceneC, onFinish = ::onFinish)
- val cToA = transition(SceneC, SceneA, onFinish = ::onFinish)
+ val aToB = transition(SceneA, SceneB, onFreezeAndAnimate = ::onFreezeAndAnimate)
+ val bToC = transition(SceneB, SceneC, onFreezeAndAnimate = ::onFreezeAndAnimate)
+ val cToA = transition(SceneC, SceneA, onFreezeAndAnimate = ::onFreezeAndAnimate)
// Starting state.
- assertThat(finishingTransitions).isEmpty()
+ assertThat(frozenTransitions).isEmpty()
assertThat(state.currentTransitions).isEmpty()
// A => B.
- state.startTransition(aToB)
- assertThat(finishingTransitions).isEmpty()
+ val aToBJob = state.startTransitionImmediately(animationScope = backgroundScope, aToB)
+ assertThat(frozenTransitions).isEmpty()
assertThat(state.finishedTransitions).isEmpty()
assertThat(state.currentTransitions).containsExactly(aToB).inOrder()
- // B => C. This should automatically call finish() on aToB.
- state.startTransition(bToC)
- assertThat(finishingTransitions).containsExactly(aToB)
+ // B => C. This should automatically call freezeAndAnimateToCurrentState() on aToB.
+ val bToCJob = state.startTransitionImmediately(animationScope = backgroundScope, bToC)
+ assertThat(frozenTransitions).containsExactly(aToB)
assertThat(state.finishedTransitions).isEmpty()
assertThat(state.currentTransitions).containsExactly(aToB, bToC).inOrder()
- // C => A. This should automatically call finish() on bToC.
- state.startTransition(cToA)
- assertThat(finishingTransitions).containsExactly(aToB, bToC)
+ // C => A. This should automatically call freezeAndAnimateToCurrentState() on bToC.
+ state.startTransitionImmediately(animationScope = backgroundScope, cToA)
+ assertThat(frozenTransitions).containsExactly(aToB, bToC)
assertThat(state.finishedTransitions).isEmpty()
assertThat(state.currentTransitions).containsExactly(aToB, bToC, cToA).inOrder()
// Mark bToC as finished. The list of current transitions does not change because aToB is
// still not marked as finished.
- state.finishTransition(bToC)
+ bToC.finish()
+ bToCJob.join()
assertThat(state.finishedTransitions).containsExactly(bToC)
assertThat(state.currentTransitions).containsExactly(aToB, bToC, cToA).inOrder()
// Mark aToB as finished. This will remove both aToB and bToC from the list of transitions.
- state.finishTransition(aToB)
+ aToB.finish()
+ aToBJob.join()
assertThat(state.finishedTransitions).isEmpty()
assertThat(state.currentTransitions).containsExactly(cToA).inOrder()
}
@@ -617,8 +609,9 @@
val state = MutableSceneTransitionLayoutStateImpl(SceneA, EmptyTestTransitions)
fun startTransition() {
- val transition = transition(SceneA, SceneB, onFinish = { launch { /* do nothing */ } })
- state.startTransition(transition)
+ val transition =
+ transition(SceneA, SceneB, onFreezeAndAnimate = { launch { /* do nothing */ } })
+ state.startTransitionImmediately(animationScope = backgroundScope, transition)
}
var hasLoggedWtf = false
@@ -650,4 +643,21 @@
assertThat(state.transitionState).isIdle()
assertThat(state.transitionState).hasCurrentScene(SceneC)
}
+
+ @Test
+ fun snapToScene_freezesCurrentTransition() = runMonotonicClockTest {
+ val state = MutableSceneTransitionLayoutStateImpl(SceneA)
+
+ // Start a transition that is never finished. We don't use backgroundScope on purpose so
+ // that this test would fail if the transition was not frozen when snapping.
+ state.startTransitionImmediately(animationScope = this, transition(SceneA, SceneB))
+ val transition = assertThat(state.transitionState).isSceneTransition()
+ assertThat(transition).hasFromScene(SceneA)
+ assertThat(transition).hasToScene(SceneB)
+
+ // Snap to C.
+ state.snapToScene(SceneC)
+ assertThat(state.transitionState).isIdle()
+ assertThat(state.transitionState).hasCurrentScene(SceneC)
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index b8e13da..63ab04f 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -55,10 +55,13 @@
import com.android.compose.animation.scene.TestScenes.SceneC
import com.android.compose.animation.scene.subjects.assertThat
import com.android.compose.test.assertSizeIsEqualTo
+import com.android.compose.test.setContentAndCreateMainScope
import com.android.compose.test.subjects.DpOffsetSubject
import com.android.compose.test.subjects.assertThat
+import com.android.compose.test.transition
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
import org.junit.Assert.assertThrows
import org.junit.Rule
import org.junit.Test
@@ -327,17 +330,18 @@
}
val layoutTag = "layout"
- rule.setContent {
- SceneTransitionLayout(state, Modifier.testTag(layoutTag)) {
- scene(SceneA) { Box(Modifier.size(50.dp)) }
- scene(SceneB) { Box(Modifier.size(70.dp)) }
+ val scope =
+ rule.setContentAndCreateMainScope {
+ SceneTransitionLayout(state, Modifier.testTag(layoutTag)) {
+ scene(SceneA) { Box(Modifier.size(50.dp)) }
+ scene(SceneB) { Box(Modifier.size(70.dp)) }
+ }
}
- }
// Overscroll on A at -100%: size should be interpolated given that there is no overscroll
// defined for scene A.
var progress by mutableStateOf(-1f)
- rule.runOnIdle {
+ scope.launch {
state.startTransition(transition(from = SceneA, to = SceneB, progress = { progress }))
}
rule.onNodeWithTag(layoutTag).assertSizeIsEqualTo(30.dp)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
index bed6cef..f8068e6 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
@@ -232,6 +232,47 @@
}
@Test
+ fun defaultPredictiveBack() {
+ val transitions = transitions {
+ from(
+ TestScenes.SceneA,
+ to = TestScenes.SceneB,
+ preview = { fractionRange(start = 0.1f, end = 0.8f) { fade(TestElements.Foo) } }
+ ) {
+ spec = tween(500)
+ fractionRange(start = 0.1f, end = 0.8f) { fade(TestElements.Foo) }
+ timestampRange(startMillis = 100, endMillis = 300) { fade(TestElements.Foo) }
+ }
+ }
+
+ // Verify that fetching the transitionSpec with the PredictiveBack key defaults to the above
+ // transition despite it not having the PredictiveBack key set.
+ val transitionSpec =
+ transitions.transitionSpec(
+ from = TestScenes.SceneA,
+ to = TestScenes.SceneB,
+ key = TransitionKey.PredictiveBack
+ )
+
+ val transformations = transitionSpec.transformationSpec().transformations
+
+ assertThat(transformations)
+ .comparingElementsUsing(TRANSFORMATION_RANGE)
+ .containsExactly(
+ TransformationRange(start = 0.1f, end = 0.8f),
+ TransformationRange(start = 100 / 500f, end = 300 / 500f),
+ )
+
+ val previewTransformations = transitionSpec.previewTransformationSpec()?.transformations
+
+ assertThat(previewTransformations)
+ .comparingElementsUsing(TRANSFORMATION_RANGE)
+ .containsExactly(
+ TransformationRange(start = 0.1f, end = 0.8f),
+ )
+ }
+
+ @Test
fun springSpec() {
val defaultSpec = spring<Float>(stiffness = 1f)
val specFromAToC = spring<Float>(stiffness = 2f)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt
index 46075c3..5bfc947 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt
@@ -27,7 +27,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.compose.animation.scene.TestElements
import com.android.compose.animation.scene.testTransition
-import com.android.compose.animation.scene.transition
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/SetContentAndCreateScope.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/SetContentAndCreateScope.kt
new file mode 100644
index 0000000..28a864f
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/SetContentAndCreateScope.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.test
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+
+/**
+ * Set [content] as this rule's content and return a [CoroutineScope] bound to [Dispatchers.Main]
+ * and scoped to this rule.
+ */
+fun ComposeContentTestRule.setContentAndCreateMainScope(
+ content: @Composable () -> Unit,
+): CoroutineScope {
+ lateinit var coroutineScope: CoroutineScope
+ setContent {
+ coroutineScope = rememberCoroutineScope(getContext = { Dispatchers.Main })
+ content()
+ }
+ return coroutineScope
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/TestTransition.kt
similarity index 64%
rename from packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/test/TestTransition.kt
index 467031a..a6a83ee 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/TestTransition.kt
@@ -14,17 +14,35 @@
* limitations under the License.
*/
-package com.android.compose.animation.scene
+package com.android.compose.test
import androidx.compose.foundation.gestures.Orientation
+import com.android.compose.animation.scene.ContentKey
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneTransitionLayoutImpl
import com.android.compose.animation.scene.content.state.TransitionState
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.sync.Mutex
-import kotlinx.coroutines.sync.withLock
-import kotlinx.coroutines.test.TestScope
+import com.android.compose.animation.scene.content.state.TransitionState.Transition
+import kotlinx.coroutines.CompletableDeferred
-/** A utility to easily create a [TransitionState.Transition] in tests. */
+/** A transition for tests that will be finished once [finish] is called. */
+abstract class TestTransition(
+ fromScene: SceneKey,
+ toScene: SceneKey,
+ replacedTransition: Transition?,
+) : Transition.ChangeScene(fromScene, toScene, replacedTransition) {
+ private val finishCompletable = CompletableDeferred<Unit>()
+
+ override suspend fun run() {
+ finishCompletable.await()
+ }
+
+ /** Finish this transition. */
+ fun finish() {
+ finishCompletable.complete(Unit)
+ }
+}
+
+/** A utility to easily create a [TestTransition] in tests. */
fun transition(
from: SceneKey,
to: SceneKey,
@@ -40,12 +58,11 @@
isUpOrLeft: Boolean = false,
bouncingContent: ContentKey? = null,
orientation: Orientation = Orientation.Horizontal,
- onFinish: ((TransitionState.Transition) -> Job)? = null,
- replacedTransition: TransitionState.Transition? = null,
-): TransitionState.Transition.ChangeScene {
+ onFreezeAndAnimate: ((TestTransition) -> Unit)? = null,
+ replacedTransition: Transition? = null,
+): TestTransition {
return object :
- TransitionState.Transition.ChangeScene(from, to, replacedTransition),
- TransitionState.HasOverscrollProperties {
+ TestTransition(from, to, replacedTransition), TransitionState.HasOverscrollProperties {
override val currentScene: SceneKey
get() = current()
@@ -71,14 +88,12 @@
override val orientation: Orientation = orientation
override val absoluteDistance = 0f
- override fun finish(): Job {
- val onFinish =
- onFinish
- ?: error(
- "onFinish() must be provided if finish() is called on test transitions"
- )
-
- return onFinish(this)
+ override fun freezeAndAnimateToCurrentState() {
+ if (onFreezeAndAnimate != null) {
+ onFreezeAndAnimate(this)
+ } else {
+ finish()
+ }
}
override fun interruptionProgress(layoutImpl: SceneTransitionLayoutImpl): Float {
@@ -86,16 +101,3 @@
}
}
}
-
-/**
- * Return a onFinish lambda that can be used with [transition] so that the transition never
- * finishes. This allows to keep the transition in the current transitions list.
- */
-fun TestScope.neverFinish(): (TransitionState.Transition) -> Job {
- return {
- backgroundScope.launch {
- // Try to acquire a locked mutex so that this code never completes.
- Mutex(locked = true).withLock {}
- }
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index 7707a60..5f6ea1c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -29,6 +29,7 @@
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockscreenCredential
import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor
+import com.android.systemui.Flags as AconfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.flags.FakeFeatureFlags
@@ -56,7 +57,6 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
-import com.android.systemui.Flags as AconfigFlags
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -66,8 +66,7 @@
class KeyguardPasswordViewControllerTest : SysuiTestCase() {
@Mock private lateinit var keyguardPasswordView: KeyguardPasswordView
@Mock private lateinit var passwordEntry: EditText
- private var passwordEntryLayoutParams =
- ViewGroup.LayoutParams(/* width = */ 0, /* height = */ 0)
+ private var passwordEntryLayoutParams = ViewGroup.LayoutParams(/* width= */ 0, /* height= */ 0)
@Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock lateinit var securityMode: KeyguardSecurityModel.SecurityMode
@Mock lateinit var lockPatternUtils: LockPatternUtils
@@ -106,6 +105,8 @@
whenever(keyguardPasswordView.findViewById<ImageView>(R.id.switch_ime_button))
.thenReturn(mock(ImageView::class.java))
`when`(keyguardPasswordView.resources).thenReturn(context.resources)
+ // TODO(b/362362385): No need to mock keyguardPasswordView.context once this bug is fixed.
+ `when`(keyguardPasswordView.context).thenReturn(context)
whenever(passwordEntry.layoutParams).thenReturn(passwordEntryLayoutParams)
val keyguardKeyboardInteractor = KeyguardKeyboardInteractor(FakeKeyboardRepository())
val fakeFeatureFlags = FakeFeatureFlags()
@@ -130,6 +131,7 @@
fakeFeatureFlags,
mSelectedUserInteractor,
keyguardKeyboardInteractor,
+ null,
)
}
@@ -187,9 +189,11 @@
verify(passwordEntry).setOnKeyListener(keyListenerArgumentCaptor.capture())
val eventHandled =
- keyListenerArgumentCaptor.value.onKey(keyguardPasswordView,
+ keyListenerArgumentCaptor.value.onKey(
+ keyguardPasswordView,
KeyEvent.KEYCODE_SPACE,
- KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SPACE))
+ KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SPACE)
+ )
assertFalse("Unlock attempted.", eventHandled)
}
@@ -204,9 +208,11 @@
verify(passwordEntry).setOnKeyListener(keyListenerArgumentCaptor.capture())
val eventHandled =
- keyListenerArgumentCaptor.value.onKey(keyguardPasswordView,
+ keyListenerArgumentCaptor.value.onKey(
+ keyguardPasswordView,
KeyEvent.KEYCODE_ENTER,
- KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER))
+ KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER)
+ )
assertTrue("Unlock not attempted.", eventHandled)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index 0054d13..2af3b00 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -117,7 +117,7 @@
mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener,
mEmergencyButtonController, mFalsingCollector, featureFlags,
- mSelectedUserInteractor, keyguardKeyboardInteractor) {
+ mSelectedUserInteractor, keyguardKeyboardInteractor, null) {
@Override
public void onResume(int reason) {
super.onResume(reason);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index f7f69d3..fabc357 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -56,6 +56,7 @@
import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardDismissTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.Kosmos
@@ -238,6 +239,7 @@
featureFlags,
mSelectedUserInteractor,
keyguardKeyboardInteractor,
+ null,
)
kosmos = testKosmos()
@@ -279,7 +281,7 @@
deviceProvisionedController,
faceAuthAccessibilityDelegate,
devicePolicyManager,
- keyguardTransitionInteractor,
+ kosmos.keyguardDismissTransitionInteractor,
{ primaryBouncerInteractor },
) {
deviceEntryInteractor
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java
index d244482..58c3fec 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java
@@ -18,6 +18,8 @@
import static com.google.common.truth.Truth.assertThat;
+import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
+
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -35,15 +37,14 @@
import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
-import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.ambient.touch.scrim.ScrimController;
import com.android.systemui.ambient.touch.scrim.ScrimManager;
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.settings.FakeUserTracker;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -57,7 +58,6 @@
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
-import java.util.Collections;
import java.util.Optional;
@SmallTest
@@ -106,15 +106,13 @@
UiEventLogger mUiEventLogger;
@Mock
- LockPatternUtils mLockPatternUtils;
-
- @Mock
ActivityStarter mActivityStarter;
@Mock
CommunalViewModel mCommunalViewModel;
- FakeUserTracker mUserTracker;
+ @Mock
+ KeyguardInteractor mKeyguardInteractor;
private static final float TOUCH_REGION = .3f;
private static final float MIN_BOUNCER_HEIGHT = .05f;
@@ -130,7 +128,6 @@
public void setup() {
mKosmos = new KosmosJavaAdapter(this);
MockitoAnnotations.initMocks(this);
- mUserTracker = new FakeUserTracker();
mTouchHandler = new BouncerSwipeTouchHandler(
mKosmos.getTestScope(),
mScrimManager,
@@ -138,24 +135,21 @@
mNotificationShadeWindowController,
mValueAnimatorCreator,
mVelocityTrackerFactory,
- mLockPatternUtils,
- mUserTracker,
mCommunalViewModel,
mFlingAnimationUtils,
mFlingAnimationUtilsClosing,
TOUCH_REGION,
MIN_BOUNCER_HEIGHT,
mUiEventLogger,
- mActivityStarter);
+ mActivityStarter,
+ mKeyguardInteractor);
when(mScrimManager.getCurrentController()).thenReturn(mScrimController);
when(mValueAnimatorCreator.create(anyFloat(), anyFloat())).thenReturn(mValueAnimator);
when(mVelocityTrackerFactory.obtain()).thenReturn(mVelocityTracker);
when(mFlingAnimationUtils.getMinVelocityPxPerSecond()).thenReturn(Float.MAX_VALUE);
when(mTouchSession.getBounds()).thenReturn(SCREEN_BOUNDS);
- when(mLockPatternUtils.isSecure(CURRENT_USER_INFO.id)).thenReturn(true);
-
- mUserTracker.set(Collections.singletonList(CURRENT_USER_INFO), 0);
+ when(mKeyguardInteractor.isKeyguardDismissible()).thenReturn(MutableStateFlow(false));
}
/**
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
index b85e32b..9568167 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
@@ -18,6 +18,8 @@
import static com.google.common.truth.Truth.assertThat;
+import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.eq;
@@ -44,16 +46,15 @@
import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
-import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.ambient.touch.scrim.ScrimController;
import com.android.systemui.ambient.touch.scrim.ScrimManager;
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.settings.FakeUserTracker;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -69,7 +70,6 @@
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
-import java.util.Collections;
import java.util.Optional;
@SmallTest
@@ -116,9 +116,6 @@
UiEventLogger mUiEventLogger;
@Mock
- LockPatternUtils mLockPatternUtils;
-
- @Mock
ActivityStarter mActivityStarter;
@Mock
@@ -127,11 +124,12 @@
@Mock
CommunalViewModel mCommunalViewModel;
+ @Mock
+ KeyguardInteractor mKeyguardInteractor;
+
@Captor
ArgumentCaptor<Rect> mRectCaptor;
- FakeUserTracker mUserTracker;
-
private static final float TOUCH_REGION = .3f;
private static final int SCREEN_WIDTH_PX = 1024;
private static final int SCREEN_HEIGHT_PX = 100;
@@ -148,7 +146,6 @@
public void setup() {
mKosmos = new KosmosJavaAdapter(this);
MockitoAnnotations.initMocks(this);
- mUserTracker = new FakeUserTracker();
mTouchHandler = new BouncerSwipeTouchHandler(
mKosmos.getTestScope(),
mScrimManager,
@@ -156,24 +153,21 @@
mNotificationShadeWindowController,
mValueAnimatorCreator,
mVelocityTrackerFactory,
- mLockPatternUtils,
- mUserTracker,
mCommunalViewModel,
mFlingAnimationUtils,
mFlingAnimationUtilsClosing,
TOUCH_REGION,
MIN_BOUNCER_HEIGHT,
mUiEventLogger,
- mActivityStarter);
+ mActivityStarter,
+ mKeyguardInteractor);
when(mScrimManager.getCurrentController()).thenReturn(mScrimController);
when(mValueAnimatorCreator.create(anyFloat(), anyFloat())).thenReturn(mValueAnimator);
when(mVelocityTrackerFactory.obtain()).thenReturn(mVelocityTracker);
when(mFlingAnimationUtils.getMinVelocityPxPerSecond()).thenReturn(Float.MAX_VALUE);
when(mTouchSession.getBounds()).thenReturn(SCREEN_BOUNDS);
- when(mLockPatternUtils.isSecure(CURRENT_USER_INFO.id)).thenReturn(true);
-
- mUserTracker.set(Collections.singletonList(CURRENT_USER_INFO), 0);
+ when(mKeyguardInteractor.isKeyguardDismissible()).thenReturn(MutableStateFlow(false));
}
/**
@@ -391,7 +385,7 @@
*/
@Test
public void testSwipeUp_keyguardNotSecure_doesNotExpand() {
- when(mLockPatternUtils.isSecure(CURRENT_USER_INFO.id)).thenReturn(false);
+ when(mKeyguardInteractor.isKeyguardDismissible()).thenReturn(MutableStateFlow(true));
mTouchHandler.onSessionStart(mTouchSession);
ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
@@ -426,7 +420,7 @@
*/
@Test
public void testSwipeDown_keyguardNotSecure_doesNotExpand() {
- when(mLockPatternUtils.isSecure(CURRENT_USER_INFO.id)).thenReturn(false);
+ when(mKeyguardInteractor.isKeyguardDismissible()).thenReturn(MutableStateFlow(true));
mTouchHandler.onSessionStart(mTouchSession);
ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
index c161525..8c8faee 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
@@ -19,9 +19,11 @@
import android.content.pm.UserInfo
import android.hardware.biometrics.BiometricFaceConstants
import android.hardware.fingerprint.FingerprintManager
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
@@ -35,7 +37,6 @@
import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
-import com.android.systemui.bouncer.shared.flag.fakeComposeBouncerFlags
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
@@ -71,6 +72,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableFlags(Flags.FLAG_COMPOSE_BOUNCER)
class BouncerMessageViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
@@ -82,7 +84,6 @@
@Before
fun setUp() {
kosmos.fakeUserRepository.setUserInfos(listOf(PRIMARY_USER))
- kosmos.fakeComposeBouncerFlags.composeBouncerEnabled = true
overrideResource(
R.array.config_face_acquire_device_entry_ignorelist,
intArrayOf(ignoreHelpMessageId)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 99fcbb8..777ddab 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -68,15 +68,16 @@
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.settings.fakeUserTracker
+import com.android.systemui.statusbar.phone.fakeManagedProfileController
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.utils.leaks.FakeManagedProfileController
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -122,6 +123,7 @@
private lateinit var userTracker: FakeUserTracker
private lateinit var activityStarter: ActivityStarter
private lateinit var userManager: UserManager
+ private lateinit var managedProfileController: FakeManagedProfileController
private lateinit var underTest: CommunalInteractor
@@ -143,6 +145,7 @@
userTracker = kosmos.fakeUserTracker
activityStarter = kosmos.activityStarter
userManager = kosmos.userManager
+ managedProfileController = kosmos.fakeManagedProfileController
whenever(mainUser.isMain).thenReturn(true)
whenever(secondaryUser.isMain).thenReturn(false)
@@ -1070,6 +1073,14 @@
}
}
+ @Test
+ fun unpauseWorkProfileEnablesWorkMode() =
+ testScope.runTest {
+ underTest.unpauseWorkProfile()
+
+ assertThat(managedProfileController.isWorkModeEnabled()).isTrue()
+ }
+
private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) {
whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id)))
.thenReturn(disabledFlags)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 3e75ceb..d4a7691 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -96,6 +96,7 @@
import java.io.StringWriter
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
@@ -561,14 +562,29 @@
fun withSceneContainerEnabled_authenticateDoesNotRunWhenKeyguardIsGoingAway() =
testScope.runTest {
testGatingCheckForFaceAuth(sceneContainerEnabled = true) {
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- KeyguardState.LOCKSCREEN,
- KeyguardState.UNDEFINED,
- value = 0.5f,
- transitionState = TransitionState.RUNNING
- ),
- validateStep = false
+ kosmos.sceneInteractor.setTransitionState(
+ MutableStateFlow(
+ ObservableTransitionState.Transition(
+ fromScene = Scenes.Bouncer,
+ toScene = Scenes.Gone,
+ currentScene = flowOf(Scenes.Bouncer),
+ progress = MutableStateFlow(0.2f),
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+ )
+ runCurrent()
+ }
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun withSceneContainerEnabled_authenticateDoesNotRunWhenLockscreenIsGone() =
+ testScope.runTest {
+ testGatingCheckForFaceAuth(sceneContainerEnabled = true) {
+ kosmos.sceneInteractor.setTransitionState(
+ MutableStateFlow(ObservableTransitionState.Idle(Scenes.Gone))
)
runCurrent()
}
@@ -898,15 +914,32 @@
fun withSceneContainer_faceDetectDoesNotRunWhenKeyguardGoingAway() =
testScope.runTest {
testGatingCheckForDetect(sceneContainerEnabled = true) {
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- KeyguardState.LOCKSCREEN,
- KeyguardState.UNDEFINED,
- value = 0.5f,
- transitionState = TransitionState.RUNNING
- ),
- validateStep = false
+ kosmos.sceneInteractor.setTransitionState(
+ MutableStateFlow(
+ ObservableTransitionState.Transition(
+ fromScene = Scenes.Bouncer,
+ toScene = Scenes.Gone,
+ currentScene = flowOf(Scenes.Bouncer),
+ progress = MutableStateFlow(0.2f),
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
)
+
+ runCurrent()
+ }
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun withSceneContainer_faceDetectDoesNotRunWhenLockscreenIsGone() =
+ testScope.runTest {
+ testGatingCheckForDetect(sceneContainerEnabled = true) {
+ kosmos.sceneInteractor.setTransitionState(
+ MutableStateFlow(ObservableTransitionState.Idle(Scenes.Gone))
+ )
+
runCurrent()
}
}
@@ -1231,6 +1264,9 @@
TransitionStep(KeyguardState.OFF, KeyguardState.LOCKSCREEN, value = 1.0f),
validateStep = false
)
+ kosmos.sceneInteractor.setTransitionState(
+ MutableStateFlow(ObservableTransitionState.Idle(Scenes.Lockscreen))
+ )
} else {
keyguardRepository.setKeyguardGoingAway(false)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index a068118..5e6ff73 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -92,8 +92,11 @@
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
+import org.mockito.kotlin.firstValue
import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verifyZeroInteractions
import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@@ -423,6 +426,47 @@
}
@Test
+ fun testDeferredResetRespondsToAnimationEnd() {
+ val client = client
+
+ // Inform the overlay service of dream starting.
+ client.startDream(
+ mWindowParams,
+ mDreamOverlayCallback,
+ DREAM_COMPONENT,
+ false /*isPreview*/,
+ true /*shouldShowComplication*/
+ )
+ mMainExecutor.runAllReady()
+
+ whenever(mStateController.areExitAnimationsRunning()).thenReturn(true)
+ clearInvocations(mStateController, mTouchMonitor)
+
+ // Starting a dream will cause it to end first.
+ client.startDream(
+ mWindowParams,
+ mDreamOverlayCallback,
+ DREAM_COMPONENT,
+ false /*isPreview*/,
+ true /*shouldShowComplication*/
+ )
+
+ mMainExecutor.runAllReady()
+
+ verifyZeroInteractions(mTouchMonitor)
+
+ val captor = ArgumentCaptor.forClass(DreamOverlayStateController.Callback::class.java)
+ verify(mStateController).addCallback(captor.capture())
+
+ whenever(mStateController.areExitAnimationsRunning()).thenReturn(false)
+
+ captor.firstValue.onStateChanged()
+
+ // Should only be called once since it should be null during the second reset.
+ verify(mTouchMonitor).destroy()
+ }
+
+ @Test
fun testLowLightSetByStartDream() {
val client = client
@@ -1095,6 +1139,39 @@
}
@Test
+ fun testDreamActivityGesturesNotBlockedWhenNotificationShadeShowing() {
+ val client = client
+
+ // Inform the overlay service of dream starting.
+ client.startDream(
+ mWindowParams,
+ mDreamOverlayCallback,
+ DREAM_COMPONENT,
+ false /*isPreview*/,
+ false /*shouldShowComplication*/
+ )
+ mMainExecutor.runAllReady()
+
+ val matcherCaptor = argumentCaptor<TaskMatcher>()
+ verify(gestureInteractor)
+ .addGestureBlockedMatcher(matcherCaptor.capture(), eq(GestureInteractor.Scope.Global))
+ val matcher = matcherCaptor.firstValue
+
+ val dreamTaskInfo = TaskInfo(mock<ComponentName>(), WindowConfiguration.ACTIVITY_TYPE_DREAM)
+ assertThat(matcher.matches(dreamTaskInfo)).isTrue()
+
+ val callbackCaptor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
+ verify(mKeyguardUpdateMonitor).registerCallback(callbackCaptor.capture())
+
+ // Notification shade opens.
+ callbackCaptor.value.onShadeExpandedChanged(true)
+ mMainExecutor.runAllReady()
+
+ verify(gestureInteractor)
+ .removeGestureBlockedMatcher(eq(matcher), eq(GestureInteractor.Scope.Global))
+ }
+
+ @Test
fun testComponentsRecreatedBetweenDreams() {
clearInvocations(
mDreamComplicationComponentFactory,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
index 3b8ffcd..17e3006 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
@@ -116,7 +116,7 @@
reset(transitionRepository)
// Authentication results in calling startDismissKeyguardTransition.
- kosmos.keyguardTransitionInteractor.startDismissKeyguardTransition()
+ kosmos.keyguardDismissTransitionInteractor.startDismissKeyguardTransition()
runCurrent()
assertThat(transitionRepository)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
index 9273dce..33f3cd4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
@@ -327,7 +327,7 @@
testScope.runTest {
kosmos.fakeKeyguardRepository.setKeyguardShowing(false)
kosmos.fakeKeyguardRepository.setKeyguardDismissible(true)
- kosmos.keyguardTransitionInteractor.startDismissKeyguardTransition()
+ kosmos.keyguardDismissTransitionInteractor.startDismissKeyguardTransition()
powerInteractor.setAwakeForTest()
advanceTimeBy(100) // account for debouncing
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 6708ffa..12039c1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -1394,9 +1394,11 @@
kosmos.setSceneTransition(Transition(Scenes.Lockscreen, Scenes.Gone))
val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED)
+ sendSteps(sendStep1)
+ kosmos.setSceneTransition(Idle(Scenes.Gone))
val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 1f, FINISHED)
val sendStep3 = TransitionStep(LOCKSCREEN, AOD, 0f, STARTED)
- sendSteps(sendStep1, sendStep2, sendStep3)
+ sendSteps(sendStep2, sendStep3)
assertEquals(listOf(sendStep1, sendStep2), currentStates)
assertEquals(listOf(sendStep1, sendStep2), currentStatesConverted)
@@ -1410,6 +1412,7 @@
kosmos.setSceneTransition(Transition(Scenes.Gone, Scenes.Lockscreen))
val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED)
+ kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 1f, FINISHED)
val sendStep3 = TransitionStep(LOCKSCREEN, AOD, 0f, STARTED)
sendSteps(sendStep1, sendStep2, sendStep3)
@@ -1426,6 +1429,7 @@
kosmos.setSceneTransition(Transition(Scenes.Gone, Scenes.Lockscreen))
val sendStep1 = TransitionStep(LOCKSCREEN, DOZING, 0f, STARTED)
+ kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
val sendStep2 = TransitionStep(LOCKSCREEN, DOZING, 1f, FINISHED)
val sendStep3 = TransitionStep(LOCKSCREEN, AOD, 0f, STARTED)
sendSteps(sendStep1, sendStep2, sendStep3)
@@ -1443,6 +1447,7 @@
kosmos.setSceneTransition(Transition(Scenes.Gone, Scenes.Lockscreen))
val sendStep1 = TransitionStep(LOCKSCREEN, DOZING, 0f, STARTED)
+ kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
val sendStep2 = TransitionStep(LOCKSCREEN, DOZING, 1f, FINISHED)
val sendStep3 = TransitionStep(LOCKSCREEN, AOD, 0f, STARTED)
val sendStep4 = TransitionStep(AOD, LOCKSCREEN, 0f, STARTED)
@@ -1461,10 +1466,12 @@
kosmos.setSceneTransition(Transition(Scenes.Lockscreen, Scenes.Gone))
val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED)
+ sendSteps(sendStep1)
+ kosmos.setSceneTransition(Idle(Scenes.Gone))
val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 1f, FINISHED)
val sendStep3 = TransitionStep(UNDEFINED, AOD, 0f, STARTED)
val sendStep4 = TransitionStep(AOD, LOCKSCREEN, 0f, STARTED)
- sendSteps(sendStep1, sendStep2, sendStep3, sendStep4)
+ sendSteps(sendStep2, sendStep3, sendStep4)
assertEquals(listOf(sendStep1, sendStep2), currentStates)
assertEquals(listOf(sendStep1, sendStep2), currentStatesMapped)
@@ -1478,10 +1485,12 @@
kosmos.setSceneTransition(Transition(Scenes.Gone, Scenes.Lockscreen))
val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED)
+ sendSteps(sendStep1)
+ kosmos.setSceneTransition(Idle(Scenes.Gone))
val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 1f, FINISHED)
val sendStep3 = TransitionStep(UNDEFINED, AOD, 0f, STARTED)
val sendStep4 = TransitionStep(AOD, LOCKSCREEN, 0f, STARTED)
- sendSteps(sendStep1, sendStep2, sendStep3, sendStep4)
+ sendSteps(sendStep2, sendStep3, sendStep4)
assertEquals(listOf<TransitionStep>(), currentStatesMapped)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 3e1f4f6..3b2b12c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -360,6 +360,7 @@
}
@Test
+ @DisableSceneContainer
fun alpha_transitionBetweenHubAndDream_isZero() =
testScope.runTest {
val alpha by collectLastValue(underTest.alpha(viewState))
@@ -388,8 +389,8 @@
ObservableTransitionState.Transition(
fromScene = Scenes.Lockscreen,
toScene = Scenes.Communal,
- emptyFlow(),
- emptyFlow(),
+ flowOf(Scenes.Communal),
+ flowOf(0.5f),
false,
emptyFlow()
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt
new file mode 100644
index 0000000..cdba4db
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.notifications.ui.viewmodel
+
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.Back
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.shade.ui.viewmodel.notificationsShadeOverlayActionsViewModel
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+@EnableSceneContainer
+class NotificationsShadeOverlayActionsViewModelTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val fakeShadeRepository by lazy { kosmos.fakeShadeRepository }
+
+ private val underTest by lazy { kosmos.notificationsShadeOverlayActionsViewModel }
+
+ @Test
+ fun upTransitionSceneKey_topAligned_hidesShade() =
+ testScope.runTest {
+ val actions by collectLastValue(underTest.actions)
+ fakeShadeRepository.setDualShadeAlignedToBottom(false)
+ underTest.activateIn(this)
+
+ assertThat((actions?.get(Swipe.Up) as? UserActionResult.HideOverlay)?.overlay)
+ .isEqualTo(Overlays.NotificationsShade)
+ assertThat(actions?.get(Swipe.Down)).isNull()
+ }
+
+ @Test
+ fun upTransitionSceneKey_bottomAligned_doesNothing() =
+ testScope.runTest {
+ val actions by collectLastValue(underTest.actions)
+ fakeShadeRepository.setDualShadeAlignedToBottom(true)
+ underTest.activateIn(this)
+
+ assertThat(actions?.get(Swipe.Up)).isNull()
+ assertThat((actions?.get(Swipe.Down) as? UserActionResult.HideOverlay)?.overlay)
+ .isEqualTo(Overlays.NotificationsShade)
+ }
+
+ @Test
+ fun back_hidesShade() =
+ testScope.runTest {
+ val actions by collectLastValue(underTest.actions)
+ underTest.activateIn(this)
+
+ assertThat((actions?.get(Back) as? UserActionResult.HideOverlay)?.overlay)
+ .isEqualTo(Overlays.NotificationsShade)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
index 5925819..a18f450 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
@@ -24,9 +24,10 @@
import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.settingslib.notification.modes.ZenIconLoader
+import com.android.internal.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.asIcon
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
@@ -36,7 +37,6 @@
import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
-import com.google.common.util.concurrent.MoreExecutors
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.toCollection
@@ -60,10 +60,10 @@
@Before
fun setUp() {
context.orCreateTestableResources.apply {
- addOverride(com.android.internal.R.drawable.ic_zen_mode_type_bedtime, BEDTIME_DRAWABLE)
- addOverride(com.android.internal.R.drawable.ic_zen_mode_type_driving, DRIVING_DRAWABLE)
+ addOverride(MODES_DRAWABLE_ID, MODES_DRAWABLE)
+ addOverride(R.drawable.ic_zen_mode_type_bedtime, BEDTIME_DRAWABLE)
+ addOverride(R.drawable.ic_zen_mode_type_driving, DRIVING_DRAWABLE)
}
- ZenIconLoader.setInstance(ZenIconLoader(MoreExecutors.newDirectExecutorService()))
}
@EnableFlags(Flags.FLAG_MODES_UI)
@@ -128,28 +128,34 @@
@Test
@EnableFlags(Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS)
- fun changesIconWhenActiveModesChange() =
+ fun tileData_iconsFlagEnabled_changesIconWhenActiveModesChange() =
testScope.runTest {
- val dataList: List<ModesTileModel> by
- collectValues(
+ val tileData by
+ collectLastValue(
underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest))
)
- runCurrent()
- assertThat(dataList.map { it.icon }).containsExactly(null).inOrder()
- // Add an inactive mode: state hasn't changed, so this shouldn't cause another emission
+ // Tile starts with the generic Modes icon.
+ runCurrent()
+ assertThat(tileData?.icon).isEqualTo(MODES_ICON)
+ assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID)
+
+ // Add an inactive mode -> Still modes icon
zenModeRepository.addMode(id = "Mode", active = false)
runCurrent()
- assertThat(dataList.map { it.icon }).containsExactly(null).inOrder()
+ assertThat(tileData?.icon).isEqualTo(MODES_ICON)
+ assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID)
- // Add an active mode: icon should be the mode icon
+ // Add an active mode: icon should be the mode icon. No iconResId, because we don't
+ // really know that it's a system icon.
zenModeRepository.addMode(
id = "Bedtime",
type = AutomaticZenRule.TYPE_BEDTIME,
active = true
)
runCurrent()
- assertThat(dataList.map { it.icon }).containsExactly(null, BEDTIME_ICON).inOrder()
+ assertThat(tileData?.icon).isEqualTo(BEDTIME_ICON)
+ assertThat(tileData?.iconResId).isNull()
// Add another, less-prioritized mode: icon should remain the first mode icon
zenModeRepository.addMode(
@@ -158,29 +164,58 @@
active = true
)
runCurrent()
- assertThat(dataList.map { it.icon })
- .containsExactly(null, BEDTIME_ICON, BEDTIME_ICON)
- .inOrder()
+ assertThat(tileData?.icon).isEqualTo(BEDTIME_ICON)
+ assertThat(tileData?.iconResId).isNull()
- // Deactivate more important mode: icon should be the less important, still active mode.
+ // Deactivate more important mode: icon should be the less important, still active mode
zenModeRepository.deactivateMode("Bedtime")
runCurrent()
- assertThat(dataList.map { it.icon })
- .containsExactly(null, BEDTIME_ICON, BEDTIME_ICON, DRIVING_ICON)
- .inOrder()
+ assertThat(tileData?.icon).isEqualTo(DRIVING_ICON)
+ assertThat(tileData?.iconResId).isNull()
- // Deactivate remaining mode: no icon
+ // Deactivate remaining mode: back to the default modes icon
zenModeRepository.deactivateMode("Driving")
runCurrent()
- assertThat(dataList.map { it.icon })
- .containsExactly(null, BEDTIME_ICON, BEDTIME_ICON, DRIVING_ICON, null)
- .inOrder()
+ assertThat(tileData?.icon).isEqualTo(MODES_ICON)
+ assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_UI)
+ @DisableFlags(Flags.FLAG_MODES_UI_ICONS)
+ fun tileData_iconsFlagDisabled_hasPriorityModesIcon() =
+ testScope.runTest {
+ val tileData by
+ collectLastValue(
+ underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ runCurrent()
+ assertThat(tileData?.icon).isEqualTo(MODES_ICON)
+ assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID)
+
+ // Activate a Mode -> Icon doesn't change.
+ zenModeRepository.addMode(id = "Mode", active = true)
+ runCurrent()
+ assertThat(tileData?.icon).isEqualTo(MODES_ICON)
+ assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID)
+
+ zenModeRepository.deactivateMode(id = "Mode")
+ runCurrent()
+ assertThat(tileData?.icon).isEqualTo(MODES_ICON)
+ assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID)
}
private companion object {
val TEST_USER = UserHandle.of(1)!!
+
+ val MODES_DRAWABLE_ID = R.drawable.ic_zen_priority_modes
+
+ val MODES_DRAWABLE = TestStubDrawable("modes_icon")
val BEDTIME_DRAWABLE = TestStubDrawable("bedtime")
val DRIVING_DRAWABLE = TestStubDrawable("driving")
+
+ val MODES_ICON = MODES_DRAWABLE.asIcon()
val BEDTIME_ICON = BEDTIME_DRAWABLE.asIcon()
val DRIVING_ICON = DRIVING_DRAWABLE.asIcon()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
index 4b75649..cd58127 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
@@ -16,12 +16,14 @@
package com.android.systemui.qs.tiles.impl.modes.domain.interactor
+import android.graphics.drawable.TestStubDrawable
import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.asIcon
import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler
import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
@@ -54,10 +56,7 @@
fun handleClick_active() = runTest {
val expandable = mock<Expandable>()
underTest.handleInput(
- QSTileInputTestKtx.click(
- data = ModesTileModel(true, listOf("DND")),
- expandable = expandable
- )
+ QSTileInputTestKtx.click(data = modelOf(true, listOf("DND")), expandable = expandable)
)
verify(mockDialogDelegate).showDialog(eq(expandable))
@@ -67,10 +66,7 @@
fun handleClick_inactive() = runTest {
val expandable = mock<Expandable>()
underTest.handleInput(
- QSTileInputTestKtx.click(
- data = ModesTileModel(false, emptyList()),
- expandable = expandable
- )
+ QSTileInputTestKtx.click(data = modelOf(false, emptyList()), expandable = expandable)
)
verify(mockDialogDelegate).showDialog(eq(expandable))
@@ -78,7 +74,7 @@
@Test
fun handleLongClick_active() = runTest {
- underTest.handleInput(QSTileInputTestKtx.longClick(ModesTileModel(true, listOf("DND"))))
+ underTest.handleInput(QSTileInputTestKtx.longClick(modelOf(true, listOf("DND"))))
QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
assertThat(it.intent.action).isEqualTo(Settings.ACTION_ZEN_MODE_SETTINGS)
@@ -87,10 +83,14 @@
@Test
fun handleLongClick_inactive() = runTest {
- underTest.handleInput(QSTileInputTestKtx.longClick(ModesTileModel(false, emptyList())))
+ underTest.handleInput(QSTileInputTestKtx.longClick(modelOf(false, emptyList())))
QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
assertThat(it.intent.action).isEqualTo(Settings.ACTION_ZEN_MODE_SETTINGS)
}
}
+
+ private fun modelOf(isActivated: Boolean, activeModeNames: List<String>): ModesTileModel {
+ return ModesTileModel(isActivated, activeModeNames, TestStubDrawable("icon").asIcon(), 123)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
index a41f15d..f7bdcb8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
@@ -18,11 +18,12 @@
import android.app.Flags
import android.graphics.drawable.TestStubDrawable
+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.systemui.SysuiTestCase
-import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.asIcon
import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileConfigTestBuilder
import com.android.systemui.qs.tiles.viewmodel.QSTileState
@@ -58,47 +59,88 @@
@Test
fun inactiveState() {
- val model = ModesTileModel(isActivated = false, activeModes = emptyList())
+ val icon = TestStubDrawable("res123").asIcon()
+ val model =
+ ModesTileModel(
+ isActivated = false,
+ activeModes = emptyList(),
+ icon = icon,
+ )
val state = underTest.map(config, model)
assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.INACTIVE)
- assertThat(state.iconRes).isEqualTo(R.drawable.qs_dnd_icon_off)
+ assertThat(state.icon()).isEqualTo(icon)
assertThat(state.secondaryLabel).isEqualTo("No active modes")
}
@Test
fun activeState_oneMode() {
- val model = ModesTileModel(isActivated = true, activeModes = listOf("DND"))
+ val icon = TestStubDrawable("res123").asIcon()
+ val model =
+ ModesTileModel(
+ isActivated = true,
+ activeModes = listOf("DND"),
+ icon = icon,
+ )
val state = underTest.map(config, model)
assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.ACTIVE)
- assertThat(state.iconRes).isEqualTo(R.drawable.qs_dnd_icon_on)
+ assertThat(state.icon()).isEqualTo(icon)
assertThat(state.secondaryLabel).isEqualTo("DND is active")
}
@Test
fun activeState_multipleModes() {
+ val icon = TestStubDrawable("res123").asIcon()
val model =
- ModesTileModel(isActivated = true, activeModes = listOf("Mode 1", "Mode 2", "Mode 3"))
+ ModesTileModel(
+ isActivated = true,
+ activeModes = listOf("Mode 1", "Mode 2", "Mode 3"),
+ icon = icon,
+ )
val state = underTest.map(config, model)
assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.ACTIVE)
- assertThat(state.iconRes).isEqualTo(R.drawable.qs_dnd_icon_on)
+ assertThat(state.icon()).isEqualTo(icon)
assertThat(state.secondaryLabel).isEqualTo("3 modes are active")
}
@Test
@EnableFlags(Flags.FLAG_MODES_UI_ICONS)
- fun activeState_withIcon() {
- val icon = Icon.Resource(1234, contentDescription = null)
- val model = ModesTileModel(isActivated = true, activeModes = listOf("DND"), icon = icon)
+ fun state_withEnabledFlag_noIconResId() {
+ val icon = TestStubDrawable("res123").asIcon()
+ val model =
+ ModesTileModel(
+ isActivated = false,
+ activeModes = emptyList(),
+ icon = icon,
+ iconResId = 123 // Should not be populated, but is ignored even if present
+ )
val state = underTest.map(config, model)
- assertThat(state.iconRes).isNull()
assertThat(state.icon()).isEqualTo(icon)
+ assertThat(state.iconRes).isNull()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MODES_UI_ICONS)
+ fun state_withDisabledFlag_includesIconResId() {
+ val icon = TestStubDrawable("res123").asIcon()
+ val model =
+ ModesTileModel(
+ isActivated = false,
+ activeModes = emptyList(),
+ icon = icon,
+ iconResId = 123
+ )
+
+ val state = underTest.map(config, model)
+
+ assertThat(state.icon()).isEqualTo(icon)
+ assertThat(state.iconRes).isEqualTo(123)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt
index e2149d9..424afe1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.ui.viewmodel
+import android.platform.test.annotations.DisableFlags
import android.testing.TestableLooper.RunWithLooper
import androidx.lifecycle.LifecycleOwner
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -26,20 +27,26 @@
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.domain.startable.sceneContainerStartable
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModelFactory
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.ui.viewmodel.shadeHeaderViewModelFactory
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -51,6 +58,7 @@
@RunWith(AndroidJUnit4::class)
@RunWithLooper
@EnableSceneContainer
+@DisableFlags(com.android.systemui.Flags.FLAG_DUAL_SHADE)
class QuickSettingsSceneContentViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
@@ -64,6 +72,8 @@
private val footerActionsController = mock<FooterActionsController>()
private val sceneContainerStartable = kosmos.sceneContainerStartable
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
+ private val shadeInteractor by lazy { kosmos.shadeInteractor }
private lateinit var underTest: QuickSettingsSceneContentViewModel
@@ -80,7 +90,10 @@
footerActionsViewModelFactory = footerActionsViewModelFactory,
footerActionsController = footerActionsController,
mediaCarouselInteractor = kosmos.mediaCarouselInteractor,
+ shadeInteractor = shadeInteractor,
+ sceneInteractor = sceneInteractor,
)
+ underTest.activateIn(testScope)
}
@Test
@@ -122,4 +135,16 @@
assertThat(isMediaVisible).isTrue()
}
+
+ @Test
+ fun shadeModeChange_switchToShadeScene() =
+ testScope.runTest {
+ val scene by collectLastValue(sceneInteractor.currentScene)
+
+ // switch to split shade
+ kosmos.shadeRepository.setShadeLayoutWide(true)
+ runCurrent()
+
+ assertThat(scene).isEqualTo(Scenes.Shade)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelTest.kt
index 45a8b3e..db58c85 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelTest.kt
@@ -46,7 +46,6 @@
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
@@ -64,14 +63,10 @@
private val underTest by lazy { kosmos.quickSettingsShadeSceneActionsViewModel }
- @Before
- fun setUp() {
- underTest.activateIn(testScope)
- }
-
@Test
fun upTransitionSceneKey_deviceLocked_lockscreen() =
testScope.runTest {
+ underTest.activateIn(this)
val actions by collectLastValue(underTest.actions)
val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
lockDevice()
@@ -85,6 +80,7 @@
@Test
fun upTransitionSceneKey_deviceLocked_keyguardDisabled_gone() =
testScope.runTest {
+ underTest.activateIn(this)
val actions by collectLastValue(underTest.actions)
val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
lockDevice()
@@ -98,6 +94,7 @@
@Test
fun upTransitionSceneKey_deviceUnlocked_gone() =
testScope.runTest {
+ underTest.activateIn(this)
val actions by collectLastValue(underTest.actions)
val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
lockDevice()
@@ -113,6 +110,7 @@
fun downTransitionSceneKey_deviceLocked_bottomAligned_lockscreen() =
testScope.runTest {
kosmos.fakeShadeRepository.setDualShadeAlignedToBottom(true)
+ underTest.activateIn(this)
val actions by collectLastValue(underTest.actions)
val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
lockDevice()
@@ -127,6 +125,7 @@
fun downTransitionSceneKey_deviceUnlocked_bottomAligned_gone() =
testScope.runTest {
kosmos.fakeShadeRepository.setDualShadeAlignedToBottom(true)
+ underTest.activateIn(this)
val actions by collectLastValue(underTest.actions)
val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
lockDevice()
@@ -141,6 +140,7 @@
@Test
fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
testScope.runTest {
+ underTest.activateIn(this)
val actions by collectLastValue(underTest.actions)
val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
@@ -157,6 +157,7 @@
@Test
fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() =
testScope.runTest {
+ underTest.activateIn(this)
val actions by collectLastValue(underTest.actions)
val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
@@ -174,6 +175,7 @@
@Test
fun backTransitionSceneKey_notEditing_Home() =
testScope.runTest {
+ underTest.activateIn(this)
val actions by collectLastValue(underTest.actions)
assertThat((actions?.get(Back) as? UserActionResult.ChangeScene)?.toScene)
@@ -183,6 +185,7 @@
@Test
fun backTransition_editing_noDestination() =
testScope.runTest {
+ underTest.activateIn(this)
val actions by collectLastValue(underTest.actions)
kosmos.editModeViewModel.startEditing()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModelTest.kt
index dd4432d..900f2a4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModelTest.kt
@@ -35,7 +35,6 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -123,7 +122,7 @@
override suspend fun hydrateActions(
setActions: (Map<UserAction, UserActionResult>) -> Unit,
) {
- upstream.collectLatest { setActions(it) }
+ upstream.collect { setActions(it) }
}
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
index 14134cc..cea8857 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
@@ -18,11 +18,11 @@
package com.android.systemui.statusbar.notification.domain.interactor
-import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -31,7 +31,6 @@
import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
import com.android.systemui.statusbar.notification.data.repository.notificationsKeyguardViewStateRepository
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.testKosmos
@@ -44,7 +43,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
-@EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+@EnableSceneContainer
class HeadsUpNotificationInteractorTest : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index f96cf10..840aa92 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -26,6 +26,7 @@
import com.android.settingslib.notification.data.repository.updateNotificationPolicy
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.andSceneContainer
import com.android.systemui.flags.fakeFeatureFlagsClassic
@@ -41,7 +42,6 @@
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
import com.android.systemui.statusbar.policy.data.repository.fakeUserSetupRepository
import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
@@ -535,7 +535,7 @@
}
@Test
- @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+ @EnableSceneContainer
fun pinnedHeadsUpRows_filtersForPinnedItems() =
testScope.runTest {
val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows)
@@ -576,7 +576,7 @@
}
@Test
- @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+ @EnableSceneContainer
fun hasPinnedHeadsUpRows_true() =
testScope.runTest {
val hasPinnedHeadsUpRow by collectLastValue(underTest.hasPinnedHeadsUpRow)
@@ -591,7 +591,7 @@
}
@Test
- @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+ @EnableSceneContainer
fun hasPinnedHeadsUpRows_false() =
testScope.runTest {
val hasPinnedHeadsUpRow by collectLastValue(underTest.hasPinnedHeadsUpRow)
@@ -606,7 +606,7 @@
}
@Test
- @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+ @EnableSceneContainer
fun topHeadsUpRow_emptyList_null() =
testScope.runTest {
val topHeadsUpRow by collectLastValue(underTest.topHeadsUpRow)
@@ -618,7 +618,7 @@
}
@Test
- @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+ @EnableSceneContainer
fun headsUpAnimationsEnabled_true() =
testScope.runTest {
val animationsEnabled by collectLastValue(underTest.headsUpAnimationsEnabled)
@@ -631,7 +631,7 @@
}
@Test
- @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+ @EnableSceneContainer
fun headsUpAnimationsEnabled_keyguardShowing_true() =
testScope.runTest {
val animationsEnabled by collectLastValue(underTest.headsUpAnimationsEnabled)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
index ca106fa..469a7bc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
@@ -23,6 +23,7 @@
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -36,7 +37,6 @@
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
import com.android.systemui.statusbar.phone.KeyguardBypassController
@@ -469,9 +469,9 @@
@get:Parameters(name = "{0}")
val flags: List<FlagsParameterization>
get() = buildList {
- addAll(FlagsParameterization.allCombinationsOf(NotificationThrottleHun.FLAG_NAME))
addAll(
- FlagsParameterization.allCombinationsOf(NotificationsHeadsUpRefactor.FLAG_NAME)
+ FlagsParameterization.allCombinationsOf(NotificationThrottleHun.FLAG_NAME)
+ .andSceneContainer()
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
index 20d3a7b..639d34d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
@@ -22,8 +22,10 @@
import android.provider.Settings.Secure.ZEN_DURATION
import android.provider.Settings.Secure.ZEN_DURATION_FOREVER
import android.provider.Settings.Secure.ZEN_DURATION_PROMPT
+import android.service.notification.SystemZenRules
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.internal.R
import com.android.settingslib.notification.data.repository.updateNotificationPolicy
import com.android.settingslib.notification.modes.TestModeBuilder
import com.android.systemui.SysuiTestCase
@@ -220,30 +222,86 @@
}
@Test
- fun mainActiveMode_returnsMainActiveMode() =
+ fun activeModes_computesMainActiveMode() =
testScope.runTest {
- val mainActiveMode by collectLastValue(underTest.mainActiveMode)
+ val activeModes by collectLastValue(underTest.activeModes)
zenModeRepository.addMode(id = "Bedtime", type = AutomaticZenRule.TYPE_BEDTIME)
zenModeRepository.addMode(id = "Other", type = AutomaticZenRule.TYPE_OTHER)
runCurrent()
+ assertThat(activeModes?.modeNames).hasSize(0)
+ assertThat(activeModes?.mainMode).isNull()
+
+ zenModeRepository.activateMode("Other")
+ runCurrent()
+ assertThat(activeModes?.modeNames).containsExactly("Mode Other")
+ assertThat(activeModes?.mainMode?.name).isEqualTo("Mode Other")
+
+ zenModeRepository.activateMode("Bedtime")
+ runCurrent()
+ assertThat(activeModes?.modeNames)
+ .containsExactly("Mode Bedtime", "Mode Other")
+ .inOrder()
+ assertThat(activeModes?.mainMode?.name).isEqualTo("Mode Bedtime")
+
+ zenModeRepository.deactivateMode("Other")
+ runCurrent()
+ assertThat(activeModes?.modeNames).containsExactly("Mode Bedtime")
+ assertThat(activeModes?.mainMode?.name).isEqualTo("Mode Bedtime")
+
+ zenModeRepository.deactivateMode("Bedtime")
+ runCurrent()
+ assertThat(activeModes?.modeNames).hasSize(0)
+ assertThat(activeModes?.mainMode).isNull()
+ }
+
+ @Test
+ fun mainActiveMode_flows() =
+ testScope.runTest {
+ val mainActiveMode by collectLastValue(underTest.mainActiveMode)
+
+ zenModeRepository.addModes(
+ listOf(
+ TestModeBuilder()
+ .setId("Bedtime")
+ .setName("Mode Bedtime")
+ .setType(AutomaticZenRule.TYPE_BEDTIME)
+ .setActive(false)
+ .setPackage(mContext.packageName)
+ .setIconResId(R.drawable.ic_zen_mode_type_bedtime)
+ .build(),
+ TestModeBuilder()
+ .setId("Other")
+ .setName("Mode Other")
+ .setType(AutomaticZenRule.TYPE_OTHER)
+ .setActive(false)
+ .setPackage(SystemZenRules.PACKAGE_ANDROID)
+ .setIconResId(R.drawable.ic_zen_mode_type_other)
+ .build(),
+ )
+ )
+
+ runCurrent()
assertThat(mainActiveMode).isNull()
zenModeRepository.activateMode("Other")
runCurrent()
- assertThat(mainActiveMode).isNotNull()
- assertThat(mainActiveMode!!.id).isEqualTo("Other")
+ assertThat(mainActiveMode?.name).isEqualTo("Mode Other")
+ assertThat(mainActiveMode?.icon?.key?.resId)
+ .isEqualTo(R.drawable.ic_zen_mode_type_other)
zenModeRepository.activateMode("Bedtime")
runCurrent()
- assertThat(mainActiveMode).isNotNull()
- assertThat(mainActiveMode!!.id).isEqualTo("Bedtime")
+ assertThat(mainActiveMode?.name).isEqualTo("Mode Bedtime")
+ assertThat(mainActiveMode?.icon?.key?.resId)
+ .isEqualTo(R.drawable.ic_zen_mode_type_bedtime)
zenModeRepository.deactivateMode("Other")
runCurrent()
- assertThat(mainActiveMode).isNotNull()
- assertThat(mainActiveMode!!.id).isEqualTo("Bedtime")
+ assertThat(mainActiveMode?.name).isEqualTo("Mode Bedtime")
+ assertThat(mainActiveMode?.icon?.key?.resId)
+ .isEqualTo(R.drawable.ic_zen_mode_type_bedtime)
zenModeRepository.deactivateMode("Bedtime")
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
index 33f379d..a0f6431 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
@@ -23,7 +23,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.notification.modes.TestModeBuilder
-import com.android.settingslib.notification.modes.ZenIconLoader
import com.android.settingslib.notification.modes.ZenMode
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
@@ -35,12 +34,10 @@
import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogEventLogger
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
-import com.google.common.util.concurrent.MoreExecutors
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.clearInvocations
@@ -67,11 +64,6 @@
mockDialogEventLogger
)
- @Before
- fun setUp() {
- ZenIconLoader.setInstance(ZenIconLoader(MoreExecutors.newDirectExecutorService()))
- }
-
@Test
fun tiles_filtersOutUserDisabledModes() =
testScope.runTest {
@@ -338,6 +330,83 @@
}
@Test
+ fun tiles_populatesFieldsForAccessibility() =
+ testScope.runTest {
+ val tiles by collectLastValue(underTest.tiles)
+
+ repository.addModes(
+ listOf(
+ TestModeBuilder()
+ .setName("With description, inactive")
+ .setManualInvocationAllowed(true)
+ .setTriggerDescription("When the going gets tough")
+ .setActive(false)
+ .build(),
+ TestModeBuilder()
+ .setName("With description, active")
+ .setManualInvocationAllowed(true)
+ .setTriggerDescription("When in Rome")
+ .setActive(true)
+ .build(),
+ TestModeBuilder()
+ .setName("With description, needs setup")
+ .setManualInvocationAllowed(true)
+ .setTriggerDescription("When you find yourself in a hole")
+ .setEnabled(false, /* byUser= */ false)
+ .build(),
+ TestModeBuilder()
+ .setName("Without description, inactive")
+ .setManualInvocationAllowed(true)
+ .setTriggerDescription(null)
+ .setActive(false)
+ .build(),
+ TestModeBuilder()
+ .setName("Without description, active")
+ .setManualInvocationAllowed(true)
+ .setTriggerDescription(null)
+ .setActive(true)
+ .build(),
+ TestModeBuilder()
+ .setName("Without description, needs setup")
+ .setManualInvocationAllowed(true)
+ .setTriggerDescription(null)
+ .setEnabled(false, /* byUser= */ false)
+ .build(),
+ )
+ )
+ runCurrent()
+
+ assertThat(tiles!!).hasSize(6)
+ with(tiles?.elementAt(0)!!) {
+ assertThat(this.stateDescription).isEqualTo("Off")
+ assertThat(this.subtextDescription).isEqualTo("When the going gets tough")
+ }
+ with(tiles?.elementAt(1)!!) {
+ assertThat(this.stateDescription).isEqualTo("On")
+ assertThat(this.subtextDescription).isEqualTo("When in Rome")
+ }
+ with(tiles?.elementAt(2)!!) {
+ assertThat(this.stateDescription).isEqualTo("Off")
+ assertThat(this.subtextDescription).isEqualTo("Set up")
+ }
+ with(tiles?.elementAt(3)!!) {
+ assertThat(this.stateDescription).isEqualTo("Off")
+ assertThat(this.subtextDescription).isEmpty()
+ }
+ with(tiles?.elementAt(4)!!) {
+ assertThat(this.stateDescription).isEqualTo("On")
+ assertThat(this.subtextDescription).isEmpty()
+ }
+ with(tiles?.elementAt(5)!!) {
+ assertThat(this.stateDescription).isEqualTo("Off")
+ assertThat(this.subtextDescription).isEqualTo("Set up")
+ }
+
+ // All tiles have the same long click info
+ tiles!!.forEach { assertThat(it.onLongClickLabel).isEqualTo("Open settings") }
+ }
+
+ @Test
fun onClick_togglesTileState() =
testScope.runTest {
val tiles by collectLastValue(underTest.tiles)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorTest.kt
index 0f56d0b..fa7f37c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorTest.kt
@@ -90,8 +90,9 @@
assertThat(model)
.isEqualTo(
MediaOutputComponentModel.Calling(
- AudioOutputDevice.BuiltIn(builtInDeviceName, testIcon),
- false,
+ device = AudioOutputDevice.BuiltIn(builtInDeviceName, testIcon),
+ isInAudioSharing = false,
+ canOpenAudioSwitcher = false,
)
)
}
@@ -101,6 +102,9 @@
fun hasSession_stateIs_MediaSession() =
with(kosmos) {
testScope.runTest {
+ localMediaRepository.updateCurrentConnectedDevice(
+ TestMediaDevicesFactory.builtInMediaDevice()
+ )
mediaControllerRepository.setActiveSessions(listOf(localMediaController))
val model by collectLastValue(underTest.mediaOutputModel.filterData())
@@ -113,6 +117,7 @@
assertThat(device)
.isEqualTo(AudioOutputDevice.BuiltIn("built_in_media", testIcon))
assertThat(isInAudioSharing).isFalse()
+ assertThat(canOpenAudioSwitcher).isTrue()
}
}
}
@@ -129,8 +134,9 @@
assertThat(model)
.isEqualTo(
MediaOutputComponentModel.Idle(
- AudioOutputDevice.BuiltIn("built_in_media", testIcon),
- true,
+ device = AudioOutputDevice.BuiltIn("built_in_media", testIcon),
+ isInAudioSharing = true,
+ canOpenAudioSwitcher = false,
)
)
}
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index e6cc6cf..d3d757b 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -270,6 +270,7 @@
<!-- Add to note button used in App Clips flow to return the saved screenshot image to notes app. [CHAR LIMIT=NONE] -->
<string name="app_clips_save_add_to_note">Add to note</string>
<string name="backlinks_include_link">Include link</string>
+ <string name="backlinks_duplicate_label_format"><xliff:g id="appName" example="Google Chrome">%1$s</xliff:g> <xliff:g id="frequencyCount" example="(1)">(%2$d)</xliff:g></string>
<!-- Notification title displayed for screen recording [CHAR LIMIT=50]-->
<string name="screenrecord_title">Screen Recorder</string>
@@ -720,8 +721,8 @@
<!-- QuickSettings: Do not disturb - Priority only [CHAR LIMIT=NONE] -->
<!-- QuickSettings: Do not disturb - Alarms only [CHAR LIMIT=NONE] -->
<!-- QuickSettings: Do not disturb - Total silence [CHAR LIMIT=NONE] -->
- <!-- QuickSettings: Priority modes [CHAR LIMIT=NONE] -->
- <string name="quick_settings_modes_label">Priority modes</string>
+ <!-- QuickSettings: Modes [CHAR LIMIT=NONE] -->
+ <string name="quick_settings_modes_label">Modes</string>
<!-- QuickSettings: Bluetooth [CHAR LIMIT=NONE] -->
<string name="quick_settings_bluetooth_label">Bluetooth</string>
<!-- QuickSettings: Bluetooth (Multiple) [CHAR LIMIT=NONE] -->
@@ -1096,28 +1097,28 @@
<!-- QuickStep: Accessibility to toggle overview [CHAR LIMIT=40] -->
<string name="quick_step_accessibility_toggle_overview">Toggle Overview</string>
- <!-- Priority modes dialog title [CHAR LIMIT=35] -->
- <string name="zen_modes_dialog_title">Priority modes</string>
+ <!-- Modes dialog title [CHAR LIMIT=35] -->
+ <string name="zen_modes_dialog_title">Modes</string>
- <!-- Priority modes dialog confirmation button [CHAR LIMIT=15] -->
+ <!-- Modes dialog confirmation button [CHAR LIMIT=15] -->
<string name="zen_modes_dialog_done">Done</string>
- <!-- Priority modes dialog settings shortcut button [CHAR LIMIT=15] -->
+ <!-- Modes dialog settings shortcut button [CHAR LIMIT=15] -->
<string name="zen_modes_dialog_settings">Settings</string>
- <!-- Priority modes: label for an active mode [CHAR LIMIT=35] -->
+ <!-- Modes: label for an active mode [CHAR LIMIT=35] -->
<string name="zen_mode_on">On</string>
- <!-- Priority modes: label for an active mode, with details [CHAR LIMIT=10] -->
+ <!-- Modes: label for an active mode, with details [CHAR LIMIT=10] -->
<string name="zen_mode_on_with_details">On • <xliff:g id="trigger_description" example="Mon-Fri, 23:00-7:00">%1$s</xliff:g></string>
- <!-- Priority modes: label for an inactive mode [CHAR LIMIT=35] -->
+ <!-- Modes: label for an inactive mode [CHAR LIMIT=35] -->
<string name="zen_mode_off">Off</string>
- <!-- Priority modes: label for a mode that needs to be set up [CHAR LIMIT=35] -->
+ <!-- Modes: label for a mode that needs to be set up [CHAR LIMIT=35] -->
<string name="zen_mode_set_up">Set up</string>
- <!-- Priority modes: label for a mode that cannot be manually turned on [CHAR LIMIT=35] -->
+ <!-- Modes: label for a mode that cannot be manually turned on [CHAR LIMIT=35] -->
<string name="zen_mode_no_manual_invocation">Manage in settings</string>
<string name="zen_mode_active_modes">
@@ -1379,9 +1380,13 @@
<string name="media_projection_entry_app_permission_dialog_title">Share your screen with <xliff:g id="app_seeking_permission" example="Meet">%s</xliff:g>?</string>
<!-- 1P/3P app media projection permission option for capturing just a single app [CHAR LIMIT=50] -->
- <string name="media_projection_entry_app_permission_dialog_option_text_single_app">Share one app</string>
+ <string name="screen_share_permission_dialog_option_single_app">Share one app</string>
+ <!-- CTS tests rely on the `screen_share_permission_dialog_option_single_app` resource name, so just point the updated resource name to the old resource name. -->
+ <string name="media_projection_entry_app_permission_dialog_option_text_single_app">@string/screen_share_permission_dialog_option_single_app</string>
<!-- 1P/3P app media projection permission option for capturing the whole screen [CHAR LIMIT=50] -->
- <string name="media_projection_entry_app_permission_dialog_option_text_entire_screen">Share entire screen</string>
+ <string name="screen_share_permission_dialog_option_entire_screen">Share entire screen</string>
+ <!-- CTS tests rely on the `screen_share_permission_dialog_option_entire_screen` resource name, so just point the updated resource name to the old resource name. -->
+ <string name="media_projection_entry_app_permission_dialog_option_text_entire_screen">@string/screen_share_permission_dialog_option_entire_screen</string>
<!-- 1P/3P app media projection permission warning for capturing the whole screen. [CHAR LIMIT=350] -->
<string name="media_projection_entry_app_permission_dialog_warning_entire_screen">When you’re sharing your entire screen, anything on your screen is visible to <xliff:g id="app_seeking_permission" example="Meet">%s</xliff:g>. So be careful with things like passwords, payment details, messages, photos, and audio and video.</string>
<!-- 1P/3P app media projection permission warning for capturing an app. [CHAR LIMIT=350] -->
diff --git a/packages/SystemUI/src/com/android/keyguard/AuthInteractionProperties.kt b/packages/SystemUI/src/com/android/keyguard/AuthInteractionProperties.kt
new file mode 100644
index 0000000..efa13c6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/AuthInteractionProperties.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard
+
+import android.os.VibrationAttributes
+import com.google.android.msdl.domain.InteractionProperties
+
+/**
+ * This class represents the set of [InteractionProperties] that only hold [VibrationAttributes] for
+ * the case of user authentication.
+ */
+data class AuthInteractionProperties(
+ override val vibrationAttributes: VibrationAttributes =
+ VibrationAttributes.createForUsage(VibrationAttributes.USAGE_COMMUNICATION_REQUEST)
+) : InteractionProperties
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index dad4400..64ccbe1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -19,7 +19,9 @@
import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL;
import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED;
import static com.android.keyguard.KeyguardAbsKeyInputView.MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT;
+import static com.android.systemui.Flags.msdlFeedback;
+import android.annotation.Nullable;
import android.content.res.ColorStateList;
import android.os.AsyncTask;
import android.os.CountDownTimer;
@@ -40,6 +42,9 @@
import com.android.systemui.res.R;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.google.android.msdl.data.model.MSDLToken;
+import com.google.android.msdl.domain.MSDLPlayer;
+
import java.util.HashMap;
import java.util.Map;
@@ -55,6 +60,8 @@
protected AsyncTask<?, ?, ?> mPendingLockCheck;
protected boolean mResumed;
protected boolean mLockedOut;
+ @Nullable
+ protected MSDLPlayer mMSDLPlayer;
private final KeyDownListener mKeyDownListener = (keyCode, keyEvent) -> {
// Fingerprint sensor sends a KeyEvent.KEYCODE_UNKNOWN.
@@ -81,7 +88,8 @@
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
LatencyTracker latencyTracker, FalsingCollector falsingCollector,
EmergencyButtonController emergencyButtonController,
- FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor) {
+ FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor,
+ @Nullable MSDLPlayer msdlPlayer) {
super(view, securityMode, keyguardSecurityCallback, emergencyButtonController,
messageAreaControllerFactory, featureFlags, selectedUserInteractor);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -89,6 +97,7 @@
mLatencyTracker = latencyTracker;
mFalsingCollector = falsingCollector;
mEmergencyButtonController = emergencyButtonController;
+ mMSDLPlayer = msdlPlayer;
}
abstract void resetState();
@@ -178,6 +187,7 @@
void onPasswordChecked(int userId, boolean matched, int timeoutMs, boolean isValidPassword) {
boolean dismissKeyguard = mSelectedUserInteractor.getSelectedUserId() == userId;
if (matched) {
+ playAuthenticationHaptics(/* unlock= */true);
getKeyguardSecurityCallback().reportUnlockAttempt(userId, true, 0);
if (dismissKeyguard) {
mDismissing = true;
@@ -185,6 +195,7 @@
getKeyguardSecurityCallback().dismiss(true, userId, getSecurityMode());
}
} else {
+ playAuthenticationHaptics(/* unlock= */false);
mView.resetPasswordText(true /* animate */, false /* announce deletion if no match */);
if (isValidPassword) {
getKeyguardSecurityCallback().reportUnlockAttempt(userId, false, timeoutMs);
@@ -201,6 +212,18 @@
}
}
+ private void playAuthenticationHaptics(boolean unlock) {
+ if (!msdlFeedback() || mMSDLPlayer == null) return;
+
+ MSDLToken token;
+ if (unlock) {
+ token = MSDLToken.UNLOCK;
+ } else {
+ token = MSDLToken.FAILURE;
+ }
+ mMSDLPlayer.playToken(token, mAuthInteractionProperties);
+ }
+
protected void startErrorAnimation() { /* no-op */ }
protected void verifyPasswordAndUnlock() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index db14a0f..45fdbc6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -45,6 +45,9 @@
import com.android.systemui.util.ViewController;
import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.google.android.msdl.domain.InteractionProperties;
+import com.google.android.msdl.domain.MSDLPlayer;
+
import javax.inject.Inject;
/** Controller for a {@link KeyguardSecurityView}. */
@@ -63,6 +66,8 @@
private KeyguardSecurityCallback mNullCallback = new KeyguardSecurityCallback() {};
private final FeatureFlags mFeatureFlags;
protected final SelectedUserInteractor mSelectedUserInteractor;
+ protected final InteractionProperties mAuthInteractionProperties =
+ new AuthInteractionProperties();
protected KeyguardInputViewController(T view, SecurityMode securityMode,
KeyguardSecurityCallback keyguardSecurityCallback,
@@ -214,6 +219,7 @@
private final SelectedUserInteractor mSelectedUserInteractor;
private final UiEventLogger mUiEventLogger;
private final KeyguardKeyboardInteractor mKeyguardKeyboardInteractor;
+ private final MSDLPlayer mMSDLPlayer;
@Inject
public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -228,7 +234,8 @@
KeyguardViewController keyguardViewController,
FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor,
UiEventLogger uiEventLogger,
- KeyguardKeyboardInteractor keyguardKeyboardInteractor) {
+ KeyguardKeyboardInteractor keyguardKeyboardInteractor,
+ MSDLPlayer msdlPlayer) {
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
mLatencyTracker = latencyTracker;
@@ -246,6 +253,7 @@
mSelectedUserInteractor = selectedUserInteractor;
mUiEventLogger = uiEventLogger;
mKeyguardKeyboardInteractor = keyguardKeyboardInteractor;
+ mMSDLPlayer = msdlPlayer;
}
/** Create a new {@link KeyguardInputViewController}. */
@@ -268,14 +276,14 @@
mInputMethodManager, emergencyButtonController, mMainExecutor, mResources,
mFalsingCollector, mKeyguardViewController,
mDevicePostureController, mFeatureFlags, mSelectedUserInteractor,
- mKeyguardKeyboardInteractor);
+ mKeyguardKeyboardInteractor, mMSDLPlayer);
} else if (keyguardInputView instanceof KeyguardPINView) {
return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mLiftToActivateListener, emergencyButtonController, mFalsingCollector,
mDevicePostureController, mFeatureFlags, mSelectedUserInteractor,
- mUiEventLogger, mKeyguardKeyboardInteractor
+ mUiEventLogger, mKeyguardKeyboardInteractor, mMSDLPlayer
);
} else if (keyguardInputView instanceof KeyguardSimPinView) {
return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView,
@@ -283,14 +291,14 @@
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
emergencyButtonController, mFeatureFlags, mSelectedUserInteractor,
- mKeyguardKeyboardInteractor);
+ mKeyguardKeyboardInteractor, mMSDLPlayer);
} else if (keyguardInputView instanceof KeyguardSimPukView) {
return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
emergencyButtonController, mFeatureFlags, mSelectedUserInteractor,
- mKeyguardKeyboardInteractor
+ mKeyguardKeyboardInteractor, mMSDLPlayer
);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 490ad5c..6983a06 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -19,9 +19,13 @@
import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
+import android.os.UserManager;
import android.text.Editable;
import android.text.InputType;
import android.text.TextUtils;
@@ -52,6 +56,8 @@
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.google.android.msdl.domain.MSDLPlayer;
+
import java.util.List;
public class KeyguardPasswordViewController
@@ -131,10 +137,11 @@
DevicePostureController postureController,
FeatureFlags featureFlags,
SelectedUserInteractor selectedUserInteractor,
- KeyguardKeyboardInteractor keyguardKeyboardInteractor) {
+ KeyguardKeyboardInteractor keyguardKeyboardInteractor,
+ @Nullable MSDLPlayer msdlPlayer) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, falsingCollector,
- emergencyButtonController, featureFlags, selectedUserInteractor);
+ emergencyButtonController, featureFlags, selectedUserInteractor, msdlPlayer);
mKeyguardSecurityCallback = keyguardSecurityCallback;
mInputMethodManager = inputMethodManager;
mPostureController = postureController;
@@ -170,8 +177,33 @@
mPasswordEntry.setOnEditorActionListener(mOnEditorActionListener);
mPasswordEntry.setOnKeyListener(mKeyListener);
mPasswordEntry.addTextChangedListener(mTextWatcher);
+
// Poke the wakelock any time the text is selected or modified
- mPasswordEntry.setOnClickListener(v -> mKeyguardSecurityCallback.userActivity());
+ // TODO(b/362362385): Revert to the previous onClickListener implementation once this bug is
+ // fixed.
+ mPasswordEntry.setOnClickListener(new View.OnClickListener() {
+
+ private final boolean mAutomotiveAndVisibleBackgroundUsers =
+ isAutomotiveAndVisibleBackgroundUsers();
+
+ @Override
+ public void onClick(View v) {
+ if (mAutomotiveAndVisibleBackgroundUsers) {
+ mInputMethodManager.restartInput(v);
+ }
+ mKeyguardSecurityCallback.userActivity();
+ }
+
+ private boolean isAutomotiveAndVisibleBackgroundUsers() {
+ final Context context = getContext();
+ return context.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_AUTOMOTIVE)
+ && UserManager.isVisibleBackgroundUsersEnabled()
+ && context.getResources().getBoolean(
+ android.R.bool.config_perDisplayFocusEnabled);
+ }
+ });
+
mSwitchImeButton.setOnClickListener(v -> {
mKeyguardSecurityCallback.userActivity(); // Leave the screen on a bit longer
// Do not show auxiliary subtypes in password lock screen.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index 0f61233..dd7c3e4 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -16,9 +16,11 @@
package com.android.keyguard;
+import static com.android.systemui.Flags.msdlFeedback;
import static com.android.systemui.Flags.pinInputFieldStyledFocusState;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
+import android.annotation.Nullable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.StateListDrawable;
@@ -40,6 +42,9 @@
import com.android.systemui.res.R;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.google.android.msdl.data.model.MSDLToken;
+import com.google.android.msdl.domain.MSDLPlayer;
+
public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinBasedInputView>
extends KeyguardAbsKeyInputViewController<T> {
@@ -77,10 +82,11 @@
FalsingCollector falsingCollector,
FeatureFlags featureFlags,
SelectedUserInteractor selectedUserInteractor,
- KeyguardKeyboardInteractor keyguardKeyboardInteractor) {
+ KeyguardKeyboardInteractor keyguardKeyboardInteractor,
+ @Nullable MSDLPlayer msdlPlayer) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, falsingCollector,
- emergencyButtonController, featureFlags, selectedUserInteractor);
+ emergencyButtonController, featureFlags, selectedUserInteractor, msdlPlayer);
mLiftToActivateListener = liftToActivateListener;
mFalsingCollector = falsingCollector;
mKeyguardKeyboardInteractor = keyguardKeyboardInteractor;
@@ -102,12 +108,22 @@
return false;
});
button.setAnimationEnabled(showAnimations);
+ button.setMSDLPlayer(mMSDLPlayer);
}
mPasswordEntry.setOnKeyListener(mOnKeyListener);
mPasswordEntry.setUserActivityListener(this::onUserInput);
View deleteButton = mView.findViewById(R.id.delete_button);
- deleteButton.setOnTouchListener(mActionButtonTouchListener);
+ if (msdlFeedback()) {
+ deleteButton.setOnTouchListener((View view, MotionEvent event) -> {
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN && mMSDLPlayer != null) {
+ mMSDLPlayer.playToken(MSDLToken.KEYPRESS_DELETE, null);
+ }
+ return false;
+ });
+ } else {
+ deleteButton.setOnTouchListener(mActionButtonTouchListener);
+ }
deleteButton.setOnClickListener(v -> {
// check for time-based lockouts
if (mPasswordEntry.isEnabled()) {
@@ -119,13 +135,19 @@
if (mPasswordEntry.isEnabled()) {
mView.resetPasswordText(true /* animate */, true /* announce */);
}
- mView.doHapticKeyClick();
+ if (msdlFeedback() && mMSDLPlayer != null) {
+ mMSDLPlayer.playToken(MSDLToken.LONG_PRESS, null);
+ } else {
+ mView.doHapticKeyClick();
+ }
return true;
});
View okButton = mView.findViewById(R.id.key_enter);
if (okButton != null) {
- okButton.setOnTouchListener(mActionButtonTouchListener);
+ if (!msdlFeedback()) {
+ okButton.setOnTouchListener(mActionButtonTouchListener);
+ }
okButton.setOnClickListener(v -> {
if (mPasswordEntry.isEnabled()) {
verifyPasswordAndUnlock();
@@ -177,6 +199,7 @@
for (NumPadKey button : mView.getButtons()) {
button.setOnTouchListener(null);
+ button.setMSDLPlayer(null);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index f4cda02..7fc038f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -18,6 +18,7 @@
import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
+import android.annotation.Nullable;
import android.view.View;
import com.android.internal.logging.UiEvent;
@@ -33,6 +34,8 @@
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.google.android.msdl.domain.MSDLPlayer;
+
public class KeyguardPinViewController
extends KeyguardPinBasedInputViewController<KeyguardPINView> {
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -61,11 +64,12 @@
FalsingCollector falsingCollector,
DevicePostureController postureController, FeatureFlags featureFlags,
SelectedUserInteractor selectedUserInteractor, UiEventLogger uiEventLogger,
- KeyguardKeyboardInteractor keyguardKeyboardInteractor) {
+ KeyguardKeyboardInteractor keyguardKeyboardInteractor,
+ @Nullable MSDLPlayer msdlPlayer) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
- keyguardKeyboardInteractor);
+ keyguardKeyboardInteractor, msdlPlayer);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mPostureController = postureController;
mLockPatternUtils = lockPatternUtils;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index afd42cb..61f9800 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -81,7 +81,7 @@
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardWmStateRefactor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardDismissTransitionInteractor;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
@@ -134,7 +134,7 @@
private final DeviceEntryFaceAuthInteractor mDeviceEntryFaceAuthInteractor;
private final BouncerMessageInteractor mBouncerMessageInteractor;
private int mTranslationY;
- private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+ private final KeyguardDismissTransitionInteractor mKeyguardDismissTransitionInteractor;
private final DevicePolicyManager mDevicePolicyManager;
// Whether the volume keys should be handled by keyguard. If true, then
// they will be handled here for specific media types such as music, otherwise
@@ -321,7 +321,7 @@
}
if (KeyguardWmStateRefactor.isEnabled()) {
- mKeyguardTransitionInteractor.startDismissKeyguardTransition(
+ mKeyguardDismissTransitionInteractor.startDismissKeyguardTransition(
"KeyguardSecurityContainerController#finish");
}
}
@@ -458,7 +458,7 @@
DeviceProvisionedController deviceProvisionedController,
FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate,
DevicePolicyManager devicePolicyManager,
- KeyguardTransitionInteractor keyguardTransitionInteractor,
+ KeyguardDismissTransitionInteractor keyguardDismissTransitionInteractor,
Lazy<PrimaryBouncerInteractor> primaryBouncerInteractor,
Provider<DeviceEntryInteractor> deviceEntryInteractor
) {
@@ -490,7 +490,7 @@
mSelectedUserInteractor = selectedUserInteractor;
mDeviceEntryInteractor = deviceEntryInteractor;
mJavaAdapter = javaAdapter;
- mKeyguardTransitionInteractor = keyguardTransitionInteractor;
+ mKeyguardDismissTransitionInteractor = keyguardDismissTransitionInteractor;
mDeviceProvisionedController = deviceProvisionedController;
mPrimaryBouncerInteractor = primaryBouncerInteractor;
mDevicePolicyManager = devicePolicyManager;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index 3ef3418..ce5b5d7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -21,6 +21,7 @@
import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.app.Dialog;
@@ -48,6 +49,8 @@
import com.android.systemui.res.R;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.google.android.msdl.domain.MSDLPlayer;
+
public class KeyguardSimPinViewController
extends KeyguardPinBasedInputViewController<KeyguardSimPinView> {
public static final String TAG = "KeyguardSimPinView";
@@ -95,11 +98,12 @@
TelephonyManager telephonyManager, FalsingCollector falsingCollector,
EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags,
SelectedUserInteractor selectedUserInteractor,
- KeyguardKeyboardInteractor keyguardKeyboardInteractor) {
+ KeyguardKeyboardInteractor keyguardKeyboardInteractor,
+ @Nullable MSDLPlayer msdlPlayer) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
- keyguardKeyboardInteractor);
+ keyguardKeyboardInteractor, msdlPlayer);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mTelephonyManager = telephonyManager;
mSimImageView = mView.findViewById(R.id.keyguard_sim);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index 46225c7..86b29b2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -17,6 +17,7 @@
package com.android.keyguard;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
@@ -43,6 +44,8 @@
import com.android.systemui.res.R;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.google.android.msdl.domain.MSDLPlayer;
+
public class KeyguardSimPukViewController
extends KeyguardPinBasedInputViewController<KeyguardSimPukView> {
private static final boolean DEBUG = KeyguardConstants.DEBUG;
@@ -92,11 +95,12 @@
TelephonyManager telephonyManager, FalsingCollector falsingCollector,
EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags,
SelectedUserInteractor selectedUserInteractor,
- KeyguardKeyboardInteractor keyguardKeyboardInteractor) {
+ KeyguardKeyboardInteractor keyguardKeyboardInteractor,
+ @Nullable MSDLPlayer msdlPlayer) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
- keyguardKeyboardInteractor);
+ keyguardKeyboardInteractor, msdlPlayer);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mTelephonyManager = telephonyManager;
mSimImageView = mView.findViewById(R.id.keyguard_sim);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 9b45fa4..f731186 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -3811,7 +3811,8 @@
if (!mSimDatas.containsKey(subId)) {
refreshSimState(subId, SubscriptionManager.getSlotIndex(subId));
}
- return mSimDatas.get(subId).slotId;
+ SimData simData = mSimDatas.get(subId);
+ return simData != null ? simData.slotId : SubscriptionManager.INVALID_SUBSCRIPTION_ID;
}
private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index dcfa775..4fb80de 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -15,6 +15,7 @@
*/
package com.android.keyguard;
+import static com.android.systemui.Flags.msdlFeedback;
import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_KEY;
import android.content.Context;
@@ -38,6 +39,9 @@
import com.android.settingslib.Utils;
import com.android.systemui.res.R;
+import com.google.android.msdl.data.model.MSDLToken;
+import com.google.android.msdl.domain.MSDLPlayer;
+
/**
* Viewgroup for the bouncer numpad button, specifically for digits.
*/
@@ -57,6 +61,8 @@
@Nullable
private NumPadAnimator mAnimator;
private int mOrientation;
+ @Nullable
+ private MSDLPlayer mMSDLPlayer;
private View.OnClickListener mListener = new View.OnClickListener() {
@Override
@@ -221,8 +227,12 @@
// Cause a VIRTUAL_KEY vibration
public void doHapticKeyClick() {
- performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
- HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+ if (msdlFeedback() && mMSDLPlayer != null) {
+ mMSDLPlayer.playToken(MSDLToken.KEYPRESS_STANDARD, null);
+ } else {
+ performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
+ HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+ }
}
@Override
@@ -244,4 +254,8 @@
super.onInitializeAccessibilityNodeInfo(info);
info.setTextEntryKey(true);
}
+
+ public void setMSDLPlayer(@Nullable MSDLPlayer player) {
+ mMSDLPlayer = player;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
index 394f8dd..04afd86 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
@@ -408,6 +408,10 @@
if (!isActivated()) {
return;
}
+ if (!(mFullscreenBorder.getBackground() instanceof GradientDrawable)) {
+ // Wear doesn't use the same magnification border background. So early return here.
+ return;
+ }
float cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext);
GradientDrawable backgroundDrawable = (GradientDrawable) mFullscreenBorder.getBackground();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index 9b6501e..2f0ca6e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -482,6 +482,10 @@
} else { // mode = ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW
mEditButton.setVisibility(View.VISIBLE);
mAllowDiagonalScrollingView.setVisibility(View.VISIBLE);
+ if (Flags.saveAndRestoreMagnificationSettingsButtons()) {
+ selectedButtonIndex =
+ windowMagnificationFrameSizePrefs.getIndexForCurrentDensity();
+ }
}
break;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
index e4b7b7e..275147e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
@@ -21,11 +21,13 @@
import android.content.Context;
import android.hardware.display.DisplayManager;
+import android.os.Handler;
import android.os.UserHandle;
import android.text.TextUtils;
import android.view.Display;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.IUserInitializationCompleteCallback;
import androidx.annotation.MainThread;
@@ -68,6 +70,9 @@
private int mBtnMode;
private String mBtnTargets;
private boolean mIsKeyguardVisible;
+ private boolean mIsUserInInitialization;
+ @VisibleForTesting
+ Handler mHandler;
@VisibleForTesting
final KeyguardUpdateMonitorCallback mKeyguardCallback = new KeyguardUpdateMonitorCallback() {
@@ -86,18 +91,14 @@
@Override
public void onUserSwitching(int userId) {
destroyFloatingMenu();
- }
-
- @Override
- public void onUserSwitchComplete(int userId) {
- mContext = mContext.createContextAsUser(UserHandle.of(userId), /* flags= */ 0);
- mBtnMode = mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode();
- mBtnTargets =
- mAccessibilityButtonTargetsObserver.getCurrentAccessibilityButtonTargets();
- handleFloatingMenuVisibility(mIsKeyguardVisible, mBtnMode, mBtnTargets);
+ mIsUserInInitialization = true;
}
};
+ @VisibleForTesting
+ final UserInitializationCompleteCallback mUserInitializationCompleteCallback =
+ new UserInitializationCompleteCallback();
+
@Inject
public AccessibilityFloatingMenuController(Context context,
WindowManager windowManager,
@@ -109,7 +110,8 @@
KeyguardUpdateMonitor keyguardUpdateMonitor,
SecureSettings secureSettings,
DisplayTracker displayTracker,
- NavigationModeController navigationModeController) {
+ NavigationModeController navigationModeController,
+ Handler handler) {
mContext = context;
mWindowManager = windowManager;
mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager;
@@ -121,6 +123,7 @@
mSecureSettings = secureSettings;
mDisplayTracker = displayTracker;
mNavigationModeController = navigationModeController;
+ mHandler = handler;
mIsKeyguardVisible = false;
}
@@ -159,6 +162,8 @@
mAccessibilityButtonModeObserver.addListener(this);
mAccessibilityButtonTargetsObserver.addListener(this);
mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
+ mAccessibilityManager.registerUserInitializationCompleteCallback(
+ mUserInitializationCompleteCallback);
}
/**
@@ -172,7 +177,7 @@
*/
private void handleFloatingMenuVisibility(boolean keyguardVisible,
@AccessibilityButtonMode int mode, String targets) {
- if (keyguardVisible) {
+ if (keyguardVisible || mIsUserInInitialization) {
destroyFloatingMenu();
return;
}
@@ -210,4 +215,18 @@
mFloatingMenu.hide();
mFloatingMenu = null;
}
+
+ class UserInitializationCompleteCallback
+ extends IUserInitializationCompleteCallback.Stub {
+ @Override
+ public void onUserInitializationComplete(int userId) {
+ mIsUserInInitialization = false;
+ mContext = mContext.createContextAsUser(UserHandle.of(userId), /* flags= */ 0);
+ mBtnMode = mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode();
+ mBtnTargets =
+ mAccessibilityButtonTargetsObserver.getCurrentAccessibilityButtonTargets();
+ mHandler.post(
+ () -> handleFloatingMenuVisibility(mIsKeyguardVisible, mBtnMode, mBtnTargets));
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
index 03f282e..bb80396 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
@@ -120,42 +120,42 @@
@IntoMap
@StringKey(COLOR_CORRECTION_TILE_SPEC)
fun provideColorCorrectionAvailabilityInteractor(
- impl: ColorCorrectionTileDataInteractor
+ impl: ColorCorrectionTileDataInteractor
): QSTileAvailabilityInteractor
@Binds
@IntoMap
@StringKey(COLOR_INVERSION_TILE_SPEC)
fun provideColorInversionAvailabilityInteractor(
- impl: ColorCorrectionTileDataInteractor
+ impl: ColorCorrectionTileDataInteractor
): QSTileAvailabilityInteractor
@Binds
@IntoMap
@StringKey(FONT_SCALING_TILE_SPEC)
fun provideFontScalingAvailabilityInteractor(
- impl: FontScalingTileDataInteractor
+ impl: FontScalingTileDataInteractor
): QSTileAvailabilityInteractor
@Binds
@IntoMap
@StringKey(REDUCE_BRIGHTNESS_TILE_SPEC)
fun provideReduceBrightnessAvailabilityInteractor(
- impl: ReduceBrightColorsTileDataInteractor
+ impl: ReduceBrightColorsTileDataInteractor
): QSTileAvailabilityInteractor
@Binds
@IntoMap
@StringKey(ONE_HANDED_TILE_SPEC)
fun provideOneHandedAvailabilityInteractor(
- impl: OneHandedModeTileDataInteractor
+ impl: OneHandedModeTileDataInteractor
): QSTileAvailabilityInteractor
@Binds
@IntoMap
@StringKey(NIGHT_DISPLAY_TILE_SPEC)
fun provideNightDisplayAvailabilityInteractor(
- impl: NightDisplayTileDataInteractor
+ impl: NightDisplayTileDataInteractor
): QSTileAvailabilityInteractor
companion object {
@@ -165,6 +165,7 @@
const val REDUCE_BRIGHTNESS_TILE_SPEC = "reduce_brightness"
const val ONE_HANDED_TILE_SPEC = "onehanded"
const val NIGHT_DISPLAY_TILE_SPEC = "night"
+ const val HEARING_DEVICES_TILE_SPEC = "hearing_devices"
@Provides
@IntoMap
@@ -273,6 +274,20 @@
instanceId = uiEventLogger.getNewInstanceId(),
)
+ @Provides
+ @IntoMap
+ @StringKey(HEARING_DEVICES_TILE_SPEC)
+ fun provideHearingDevicesTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+ QSTileConfig(
+ tileSpec = TileSpec.create(HEARING_DEVICES_TILE_SPEC),
+ uiConfig =
+ QSTileUIConfig.Resource(
+ iconRes = R.drawable.qs_hearing_devices_icon,
+ labelRes = R.string.quick_settings_hearing_devices_label,
+ ),
+ instanceId = uiEventLogger.getNewInstanceId(),
+ )
+
/**
* Inject Reduce Bright Colors Tile into tileViewModelMap in QSModule. The tile is hidden
* behind a flag.
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
index a093f58..fd06bb1 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
@@ -29,7 +29,6 @@
import androidx.annotation.VisibleForTesting
import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger
-import com.android.internal.widget.LockPatternUtils
import com.android.systemui.Flags
import com.android.systemui.ambient.touch.TouchHandler.TouchSession
import com.android.systemui.ambient.touch.dagger.BouncerSwipeModule
@@ -37,8 +36,8 @@
import com.android.systemui.ambient.touch.scrim.ScrimManager
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserTracker
import com.android.systemui.shade.ShadeExpansionChangeEvent
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.phone.CentralSurfaces
@@ -63,8 +62,6 @@
private val notificationShadeWindowController: NotificationShadeWindowController,
private val valueAnimatorCreator: ValueAnimatorCreator,
private val velocityTrackerFactory: VelocityTrackerFactory,
- private val lockPatternUtils: LockPatternUtils,
- private val userTracker: UserTracker,
private val communalViewModel: CommunalViewModel,
@param:Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING)
private val flingAnimationUtils: FlingAnimationUtils,
@@ -75,7 +72,8 @@
@param:Named(BouncerSwipeModule.MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE)
private val minBouncerZoneScreenPercentage: Float,
private val uiEventLogger: UiEventLogger,
- private val activityStarter: ActivityStarter
+ private val activityStarter: ActivityStarter,
+ private val keyguardInteractor: KeyguardInteractor,
) : TouchHandler {
/** An interface for creating ValueAnimators. */
interface ValueAnimatorCreator {
@@ -148,7 +146,7 @@
// If scrolling up and keyguard is not locked, dismiss both keyguard and the
// dream since there's no bouncer to show.
- if (y > e2.y && !lockPatternUtils.isSecure(userTracker.userId)) {
+ if (y > e2.y && keyguardInteractor.isKeyguardDismissible.value) {
activityStarter.executeRunnableDismissingKeyguard(
{ centralSurfaces.get().awakenDreams() },
/* cancelAction= */ null,
@@ -331,8 +329,8 @@
return
}
- // Don't set expansion if the user doesn't have a pin/password set.
- if (!lockPatternUtils.isSecure(userTracker.userId)) {
+ // Don't set expansion if keyguard is dismissible (i.e. unlocked).
+ if (keyguardInteractor.isKeyguardDismissible.value) {
return
}
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 468737d..732a90d 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
@@ -32,11 +32,11 @@
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pin
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Sim
import com.android.systemui.authentication.shared.model.AuthenticationResultModel
+import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
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.SceneContainerFlag
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.onSubscriberAdded
@@ -254,7 +254,7 @@
override val hasLockoutOccurred: StateFlow<Boolean> = _hasLockoutOccurred.asStateFlow()
init {
- if (SceneContainerFlag.isEnabled) {
+ if (ComposeBouncerFlags.isComposeBouncerOrSceneContainerEnabled()) {
// Hydrate failedAuthenticationAttempts initially and whenever the selected user
// changes.
applicationScope.launch {
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 25d43d9..4c2fe07 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
@@ -941,16 +941,16 @@
private fun vibrateOnSuccess() {
_hapticsToPlay.value =
HapticsToPlay(
- HapticFeedbackConstants.CONFIRM,
- HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
+ HapticFeedbackConstants.BIOMETRIC_CONFIRM,
+ null,
)
}
private fun vibrateOnError() {
_hapticsToPlay.value =
HapticsToPlay(
- HapticFeedbackConstants.REJECT,
- HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
+ HapticFeedbackConstants.BIOMETRIC_REJECT,
+ null,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt
index 62ef365..a1111f6 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt
@@ -17,18 +17,17 @@
package com.android.systemui.bouncer.shared.flag
import com.android.systemui.Flags
-import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import dagger.Module
-import dagger.Provides
-interface ComposeBouncerFlags {
+object ComposeBouncerFlags {
/**
* Returns `true` if the Compose bouncer is enabled or if the scene container framework is
* enabled; `false` otherwise.
*/
- fun isComposeBouncerOrSceneContainerEnabled(): Boolean
+ fun isComposeBouncerOrSceneContainerEnabled(): Boolean {
+ return SceneContainerFlag.isEnabled || Flags.composeBouncer()
+ }
/**
* Returns `true` if only compose bouncer is enabled and scene container framework is not
@@ -39,30 +38,7 @@
"that includes compose bouncer in legacy keyguard.",
replaceWith = ReplaceWith("isComposeBouncerOrSceneContainerEnabled()")
)
- fun isOnlyComposeBouncerEnabled(): Boolean
-}
-
-class ComposeBouncerFlagsImpl() : ComposeBouncerFlags {
-
- override fun isComposeBouncerOrSceneContainerEnabled(): Boolean {
- return SceneContainerFlag.isEnabled || Flags.composeBouncer()
- }
-
- @Deprecated(
- "Avoid using this, this is meant to be used only by the glue code " +
- "that includes compose bouncer in legacy keyguard.",
- replaceWith = ReplaceWith("isComposeBouncerOrSceneContainerEnabled()")
- )
- override fun isOnlyComposeBouncerEnabled(): Boolean {
+ fun isOnlyComposeBouncerEnabled(): Boolean {
return !SceneContainerFlag.isEnabled && Flags.composeBouncer()
}
}
-
-@Module
-object ComposeBouncerFlagsModule {
- @Provides
- @SysUISingleton
- fun impl(): ComposeBouncerFlags {
- return ComposeBouncerFlagsImpl()
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
index ad93a25..cc8dce79 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
@@ -55,12 +55,11 @@
class BouncerViewBinder
@Inject
constructor(
- private val composeBouncerFlags: ComposeBouncerFlags,
private val legacyBouncerDependencies: Lazy<LegacyBouncerDependencies>,
private val composeBouncerDependencies: Lazy<ComposeBouncerDependencies>,
) {
fun bind(view: ViewGroup) {
- if (composeBouncerFlags.isOnlyComposeBouncerEnabled()) {
+ if (ComposeBouncerFlags.isOnlyComposeBouncerEnabled()) {
val deps = composeBouncerDependencies.get()
ComposeBouncerViewBinder.bind(
view,
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
index 102ae7a..c4bbd9c 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
@@ -8,14 +8,12 @@
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
-import com.android.compose.theme.PlatformTheme
import com.android.keyguard.ViewMediatorCallback
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.bouncer.ui.BouncerDialogFactory
-import com.android.systemui.bouncer.ui.composable.BouncerContent
+import com.android.systemui.bouncer.ui.composable.BouncerContainer
import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel
-import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import kotlinx.coroutines.flow.collectLatest
@@ -49,16 +47,7 @@
this@repeatWhenAttached.lifecycle
}
)
- setContent {
- PlatformTheme {
- BouncerContent(
- rememberViewModel("ComposeBouncerViewBinder") {
- viewModelFactory.create()
- },
- dialogFactory,
- )
- }
- }
+ setContent { BouncerContainer(viewModelFactory, dialogFactory) }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/composable/BouncerContainer.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/composable/BouncerContainer.kt
new file mode 100644
index 0000000..c05dcd5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/composable/BouncerContainer.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.ui.composable
+
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.android.compose.theme.PlatformTheme
+import com.android.systemui.bouncer.ui.BouncerDialogFactory
+import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel
+import com.android.systemui.compose.modifiers.sysuiResTag
+import com.android.systemui.lifecycle.rememberViewModel
+
+/** Container that includes the compose bouncer and is meant to be included in legacy keyguard. */
+@Composable
+fun BouncerContainer(
+ viewModelFactory: BouncerSceneContentViewModel.Factory,
+ dialogFactory: BouncerDialogFactory,
+) {
+ PlatformTheme {
+ val backgroundColor = MaterialTheme.colorScheme.surface
+
+ val bouncerViewModel = rememberViewModel("BouncerContainer") { viewModelFactory.create() }
+ Box {
+ Canvas(Modifier.fillMaxSize()) { drawRect(color = backgroundColor) }
+
+ // Separate the bouncer content into a reusable composable that
+ // doesn't have any SceneScope
+ // dependencies
+ BouncerContent(
+ bouncerViewModel,
+ dialogFactory,
+ Modifier.sysuiResTag(Bouncer.TestTags.Root).fillMaxSize()
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
index 05b4656..c383b8d 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
@@ -78,7 +78,6 @@
private val faceAuthInteractor: DeviceEntryFaceAuthInteractor,
private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
private val deviceEntryBiometricsAllowedInteractor: DeviceEntryBiometricsAllowedInteractor,
- private val flags: ComposeBouncerFlags,
) : ExclusiveActivatable() {
/**
* A message shown when the user has attempted the wrong credential too many times and now must
@@ -96,7 +95,7 @@
val message: MutableStateFlow<MessageViewModel?> = MutableStateFlow(null)
override suspend fun onActivated(): Nothing {
- if (!flags.isComposeBouncerOrSceneContainerEnabled()) {
+ if (!ComposeBouncerFlags.isComposeBouncerOrSceneContainerEnabled()) {
return awaitCancellation()
}
@@ -155,7 +154,7 @@
emptyFlow()
}
}
- .collectLatest { messageViewModel -> message.value = messageViewModel }
+ .collect { messageViewModel -> message.value = messageViewModel }
}
private suspend fun listenForSimBouncerEvents() {
@@ -170,7 +169,7 @@
emptyFlow()
}
}
- .collectLatest {
+ .collect {
if (it != null) {
message.value = it
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModel.kt
index 2a27271..2d57e5b 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModel.kt
@@ -25,7 +25,6 @@
import com.android.systemui.scene.ui.viewmodel.SceneActionsViewModel
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
-import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.map
/**
@@ -46,7 +45,7 @@
Swipe(SwipeDirection.Down) to UserActionResult(prevScene),
)
}
- .collectLatest { actions -> setActions(actions) }
+ .collect { actions -> setActions(actions) }
}
@AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
index b985fc4..0aada06 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
@@ -29,7 +29,6 @@
import com.android.systemui.authentication.shared.model.AuthenticationWipeModel
import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
-import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
@@ -57,7 +56,6 @@
private val authenticationInteractor: AuthenticationInteractor,
private val devicePolicyManager: DevicePolicyManager,
private val bouncerMessageViewModelFactory: BouncerMessageViewModel.Factory,
- private val flags: ComposeBouncerFlags,
private val userSwitcher: UserSwitcherViewModel,
private val actionButtonInteractor: BouncerActionButtonInteractor,
private val pinViewModelFactory: PinBouncerViewModel.Factory,
@@ -160,7 +158,7 @@
launch {
userSwitcher.selectedUser
.map { it.image.toBitmap() }
- .collectLatest { _selectedUserImage.value = it }
+ .collect { _selectedUserImage.value = it }
}
launch {
@@ -187,34 +185,32 @@
)
}
}
- .collectLatest { _userSwitcherDropdown.value = it }
+ .collect { _userSwitcherDropdown.value = it }
}
launch {
combine(wipeDialogMessage, lockoutDialogMessage) { _, _ -> createDialogViewModel() }
- .collectLatest { _dialogViewModel.value = it }
+ .collect { _dialogViewModel.value = it }
}
- launch {
- actionButtonInteractor.actionButton.collectLatest { _actionButton.value = it }
- }
+ launch { actionButtonInteractor.actionButton.collect { _actionButton.value = it } }
launch {
authMethodViewModel
.map { authMethod -> isSideBySideSupported(authMethod) }
- .collectLatest { _isSideBySideSupported.value = it }
+ .collect { _isSideBySideSupported.value = it }
}
launch {
authMethodViewModel
.map { authMethod -> isFoldSplitRequired(authMethod) }
- .collectLatest { _isFoldSplitRequired.value = it }
+ .collect { _isFoldSplitRequired.value = it }
}
launch {
message.isLockoutMessagePresent
.map { lockoutMessagePresent -> !lockoutMessagePresent }
- .collectLatest { _isInputEnabled.value = it }
+ .collect { _isInputEnabled.value = it }
}
awaitCancellation()
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 fc860e5..2493cf1 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
@@ -34,7 +34,6 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.receiveAsFlow
@@ -105,11 +104,9 @@
combine(isInputEnabled, isTextFieldFocused) { hasInput, hasFocus ->
hasInput && !hasFocus
}
- .collectLatest { _isTextFieldFocusRequested.value = it }
+ .collect { _isTextFieldFocusRequested.value = it }
}
- launch {
- selectedUserInteractor.selectedUser.collectLatest { _selectedUserId.value = it }
- }
+ launch { selectedUserInteractor.selectedUser.collect { _selectedUserId.value = it } }
launch {
// Re-fetch the currently-enabled IMEs whenever the selected user changes, and
// whenever
@@ -125,7 +122,7 @@
) { selectedUserId, _ ->
inputMethodInteractor.hasMultipleEnabledImesOrSubtypes(selectedUserId)
}
- .collectLatest { _isImeSwitcherButtonVisible.value = it }
+ .collect { _isImeSwitcherButtonVisible.value = it }
}
awaitCancellation()
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
index 60ec3019..0a866b4 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
@@ -34,7 +34,6 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
@@ -86,9 +85,7 @@
coroutineScope {
launch { super.onActivated() }
launch {
- selectedDotSet
- .map { it.toList() }
- .collectLatest { selectedDotList.value = it.toList() }
+ selectedDotSet.map { it.toList() }.collect { selectedDotList.value = it.toList() }
}
awaitCancellation()
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index db78a98..df6ca9b 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -42,7 +42,6 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
@@ -123,7 +122,7 @@
} else {
interactor.hintedPinLength
}
- .collectLatest { _hintedPinLength.value = it }
+ .collect { _hintedPinLength.value = it }
}
launch {
combine(
@@ -135,17 +134,17 @@
isAutoConfirmEnabled = isAutoConfirmEnabled,
)
}
- .collectLatest { _backspaceButtonAppearance.value = it }
+ .collect { _backspaceButtonAppearance.value = it }
}
launch {
interactor.isAutoConfirmEnabled
.map { if (it) ActionButtonAppearance.Hidden else ActionButtonAppearance.Shown }
- .collectLatest { _confirmButtonAppearance.value = it }
+ .collect { _confirmButtonAppearance.value = it }
}
launch {
interactor.isPinEnhancedPrivacyEnabled
.map { !it }
- .collectLatest { _isDigitButtonAnimationEnabled.value = it }
+ .collect { _isDigitButtonAnimationEnabled.value = it }
}
awaitCancellation()
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt
index 3cdb573..aef5f1f 100644
--- a/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt
@@ -38,5 +38,5 @@
}
/** Creates [Icon.Loaded] for a given drawable with an optional [contentDescription]. */
-fun Drawable.asIcon(contentDescription: ContentDescription? = null): Icon =
+fun Drawable.asIcon(contentDescription: ContentDescription? = null): Icon.Loaded =
Icon.Loaded(this, contentDescription)
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 9b96341..b570e14 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
@@ -61,6 +61,7 @@
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.phone.ManagedProfileController
import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
import com.android.systemui.util.kotlin.BooleanFlowOperators.not
import com.android.systemui.util.kotlin.emitOnStart
@@ -116,6 +117,7 @@
sceneInteractor: SceneInteractor,
@CommunalLog logBuffer: LogBuffer,
@CommunalTableLog tableLogBuffer: TableLogBuffer,
+ private val managedProfileController: ManagedProfileController
) {
private val logger = Logger(logBuffer, "CommunalInteractor")
@@ -401,12 +403,7 @@
/** Request to unpause work profile that is currently in quiet mode. */
fun unpauseWorkProfile() {
- userTracker.userProfiles
- .find { it.isManagedProfile }
- ?.userHandle
- ?.let { userHandle ->
- userManager.requestQuietModeEnabled(/* enableQuietMode */ false, userHandle)
- }
+ managedProfileController.setWorkModeEnabled(true)
}
/** Returns true if work profile is in quiet mode (disabled) for user handle. */
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
index d288cce..37c6e17 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -391,8 +391,10 @@
),
Pair(
if (SceneContainerFlag.isEnabled) {
- keyguardTransitionInteractor
- .isInTransitionWhere(toStatePredicate = { it == KeyguardState.UNDEFINED })
+ sceneInteractor
+ .get()
+ .transitionState
+ .map { it.isTransitioning(to = Scenes.Gone) || it.isIdle(Scenes.Gone) }
.isFalse()
} else {
keyguardRepository.isKeyguardGoingAway.isFalse()
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
index dff391a..cdd2b05 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
@@ -60,14 +60,23 @@
fun unregisterListener(listener: FaceAuthenticationListener)
fun onUdfpsSensorTouched()
+
fun onAssistantTriggeredOnLockScreen()
+
fun onDeviceLifted()
- fun onQsExpansionStared()
+
+ fun onShadeExpansionStarted()
+
fun onNotificationPanelClicked()
+
fun onSwipeUpOnBouncer()
+
fun onPrimaryBouncerUserInput()
+
fun onAccessibilityAction()
+
fun onWalletLaunched()
+
fun onDeviceUnfolded()
/** Whether face auth is considered class 3 */
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
index de5d0aa..9b8c2b1 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
@@ -47,6 +47,7 @@
override fun isFaceAuthEnabledAndEnrolled(): Boolean = false
override fun isFaceAuthStrong(): Boolean = false
+
override fun start() = Unit
override fun registerListener(listener: FaceAuthenticationListener) {}
@@ -59,13 +60,17 @@
override fun onDeviceLifted() {}
- override fun onQsExpansionStared() {}
+ override fun onShadeExpansionStarted() {}
override fun onNotificationPanelClicked() {}
override fun onSwipeUpOnBouncer() {}
+
override fun onPrimaryBouncerUserInput() {}
+
override fun onAccessibilityAction() {}
+
override fun onWalletLaunched() = Unit
+
override fun onDeviceUnfolded() {}
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
index 183e0e9..3b5d5a8 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
@@ -60,6 +60,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOn
@@ -214,6 +215,16 @@
}
}
.launchIn(applicationScope)
+
+ if (SceneContainerFlag.isEnabled) {
+ sceneInteractor
+ .get()
+ .transitionState
+ .filter { it.isTransitioning(from = Scenes.Lockscreen, to = Scenes.Shade) }
+ .distinctUntilChanged()
+ .onEach { onShadeExpansionStarted() }
+ .launchIn(applicationScope)
+ }
}
private val isBouncerVisible: Flow<Boolean> by lazy {
@@ -239,8 +250,8 @@
runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED, true)
}
- override fun onQsExpansionStared() {
- runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_QS_EXPANDED, true)
+ override fun onShadeExpansionStarted() {
+ runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_QS_EXPANDED, false)
}
override fun onDeviceLifted() {
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index 40e2f17..1f5878b 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -48,6 +48,7 @@
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.scan
import kotlinx.coroutines.flow.shareIn
@@ -184,6 +185,9 @@
if (Flags.enableEfficientDisplayRepository()) {
enabledDisplayIds
.mapElementsLazily { displayId -> getDisplay(displayId) }
+ .onEach {
+ if (it.isEmpty()) Log.wtf(TAG, "No enabled displays. This should never happen.")
+ }
.flowOn(backgroundCoroutineDispatcher)
.debugLog("enabledDisplays")
.stateIn(
@@ -194,7 +198,8 @@
// performance concerns.
// Ultimately, this is a trade-off between a one-time UI thread binder call and
// the constant overhead of sharedFlows.
- initialValue = getDisplays())
+ initialValue = getDisplays()
+ )
} else {
oldEnabledDisplays
}
@@ -380,9 +385,8 @@
val resultSet: Set<V>
)
- val initialState = State(emptySet<T>(), emptyMap(), emptySet<V>())
-
- return this.scan(initialState) { state, currentSet ->
+ val emptyInitialState = State(emptySet<T>(), emptyMap(), emptySet<V>())
+ return this.scan(emptyInitialState) { state, currentSet ->
if (currentSet == state.previousSet) {
state
} else {
@@ -397,6 +401,7 @@
State(currentSet, newMap, resultSet)
}
}
+ .filter { it != emptyInitialState }
.map { it.resultSet }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index c3bc24f..1c263ae 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -185,6 +185,7 @@
mShadeExpanded = expanded;
updateLifecycleStateLocked();
+ updateGestureBlockingLocked();
});
}
};
@@ -215,20 +216,127 @@
mBouncerShowing = bouncerShowing;
updateLifecycleStateLocked();
+ updateGestureBlockingLocked();
});
}
};
- private final DreamOverlayStateController.Callback mExitAnimationFinishedCallback =
- new DreamOverlayStateController.Callback() {
- @Override
- public void onStateChanged() {
- if (!mStateController.areExitAnimationsRunning()) {
- mStateController.removeCallback(mExitAnimationFinishedCallback);
- resetCurrentDreamOverlayLocked();
+ /**
+ * {@link ResetHandler} protects resetting {@link DreamOverlayService} by making sure reset
+ * requests are processed before subsequent actions proceed. Requests themselves are also
+ * ordered between each other as well to ensure actions are correctly sequenced.
+ */
+ private final class ResetHandler {
+ @FunctionalInterface
+ interface Callback {
+ void onComplete();
+ }
+
+ private record Info(Callback callback, String source) {}
+
+ private final ArrayList<Info> mPendingCallbacks = new ArrayList<>();
+
+ DreamOverlayStateController.Callback mStateCallback =
+ new DreamOverlayStateController.Callback() {
+ @Override
+ public void onStateChanged() {
+ process(true);
}
+ };
+
+ /**
+ * Called from places where there is no need to wait for the reset to complete. This still
+ * will defer the reset until it is okay to reset and also sequences the request with
+ * others.
+ */
+ public void reset(String source) {
+ reset(() -> {}, source);
+ }
+
+ /**
+ * Invoked to request a reset with a callback that will fire after reset if it is deferred.
+ *
+ * @return {@code true} if the reset happened immediately, {@code false} if it was deferred
+ * and will fire later, invoking the callback.
+ */
+ public boolean reset(Callback callback, String source) {
+ // Always add listener pre-emptively
+ if (mPendingCallbacks.isEmpty()) {
+ mStateController.addCallback(mStateCallback);
+ }
+
+ final Info info = new Info(callback, source);
+ mPendingCallbacks.add(info);
+ process(false);
+
+ boolean processed = !mPendingCallbacks.contains(info);
+
+ if (!processed) {
+ Log.d(TAG, "delayed resetting from: " + source);
+ }
+
+ return processed;
+ }
+
+ private void resetInternal() {
+ // This ensures the container view of the current dream is removed before
+ // the controller is potentially reset.
+ removeContainerViewFromParentLocked();
+
+ if (mStarted && mWindow != null) {
+ try {
+ mWindow.clearContentView();
+ mWindowManager.removeView(mWindow.getDecorView());
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Error removing decor view when resetting overlay", e);
}
- };
+ }
+
+ mStateController.setOverlayActive(false);
+ mStateController.setLowLightActive(false);
+ mStateController.setEntryAnimationsFinished(false);
+
+ if (mDreamOverlayContainerViewController != null) {
+ mDreamOverlayContainerViewController.destroy();
+ mDreamOverlayContainerViewController = null;
+ }
+
+ if (mTouchMonitor != null) {
+ mTouchMonitor.destroy();
+ mTouchMonitor = null;
+ }
+
+ mWindow = null;
+
+ // Always unregister the any set DreamActivity from being blocked from gestures.
+ mGestureInteractor.removeGestureBlockedMatcher(DREAM_TYPE_MATCHER,
+ GestureInteractor.Scope.Global);
+
+ mStarted = false;
+ }
+
+ private boolean canReset() {
+ return !mStateController.areExitAnimationsRunning();
+ }
+
+ private void process(boolean fromDelayedCallback) {
+ while (canReset() && !mPendingCallbacks.isEmpty()) {
+ final Info callbackInfo = mPendingCallbacks.removeFirst();
+ resetInternal();
+ callbackInfo.callback.onComplete();
+
+ if (fromDelayedCallback) {
+ Log.d(TAG, "reset overlay (delayed) for " + callbackInfo.source);
+ }
+ }
+
+ if (mPendingCallbacks.isEmpty()) {
+ mStateController.removeCallback(mStateCallback);
+ }
+ }
+ }
+
+ private final ResetHandler mResetHandler = new ResetHandler();
private final DreamOverlayStateController mStateController;
@@ -342,10 +450,8 @@
mExecutor.execute(() -> {
setLifecycleStateLocked(Lifecycle.State.DESTROYED);
-
- resetCurrentDreamOverlayLocked();
-
mDestroyed = true;
+ mResetHandler.reset("destroying");
});
mDispatcher.onServicePreSuperOnDestroy();
@@ -385,7 +491,10 @@
// Reset the current dream overlay before starting a new one. This can happen
// when two dreams overlap (briefly, for a smoother dream transition) and both
// dreams are bound to the dream overlay service.
- resetCurrentDreamOverlayLocked();
+ if (!mResetHandler.reset(() -> onStartDream(layoutParams),
+ "starting with dream already started")) {
+ return;
+ }
}
mDreamOverlayContainerViewController =
@@ -397,7 +506,7 @@
// If we are not able to add the overlay window, reset the overlay.
if (!addOverlayWindowLocked(layoutParams)) {
- resetCurrentDreamOverlayLocked();
+ mResetHandler.reset("couldn't add window while starting");
return;
}
@@ -418,11 +527,7 @@
mStarted = true;
updateRedirectWakeup();
-
- if (!isDreamInPreviewMode()) {
- mGestureInteractor.addGestureBlockedMatcher(DREAM_TYPE_MATCHER,
- GestureInteractor.Scope.Global);
- }
+ updateGestureBlockingLocked();
}
private void updateRedirectWakeup() {
@@ -435,7 +540,7 @@
@Override
public void onEndDream() {
- resetCurrentDreamOverlayLocked();
+ mResetHandler.reset("ending dream");
}
@Override
@@ -446,6 +551,18 @@
null);
}
+ private void updateGestureBlockingLocked() {
+ final boolean shouldBlock = !isDreamInPreviewMode() && !mShadeExpanded && !mBouncerShowing;
+
+ if (shouldBlock) {
+ mGestureInteractor.addGestureBlockedMatcher(DREAM_TYPE_MATCHER,
+ GestureInteractor.Scope.Global);
+ } else {
+ mGestureInteractor.removeGestureBlockedMatcher(DREAM_TYPE_MATCHER,
+ GestureInteractor.Scope.Global);
+ }
+ }
+
private Lifecycle.State getLifecycleStateLocked() {
return mLifecycleRegistry.getCurrentState();
}
@@ -566,46 +683,4 @@
Log.w(TAG, "Removing dream overlay container view parent!");
parentView.removeView(containerView);
}
-
- private void resetCurrentDreamOverlayLocked() {
- if (mStateController.areExitAnimationsRunning()) {
- mStateController.addCallback(mExitAnimationFinishedCallback);
- return;
- }
-
- // This ensures the container view of the current dream is removed before
- // the controller is potentially reset.
- removeContainerViewFromParentLocked();
-
- if (mStarted && mWindow != null) {
- try {
- mWindow.clearContentView();
- mWindowManager.removeView(mWindow.getDecorView());
- } catch (IllegalArgumentException e) {
- Log.e(TAG, "Error removing decor view when resetting overlay", e);
- }
- }
-
- mStateController.setOverlayActive(false);
- mStateController.setLowLightActive(false);
- mStateController.setEntryAnimationsFinished(false);
-
- if (mDreamOverlayContainerViewController != null) {
- mDreamOverlayContainerViewController.destroy();
- mDreamOverlayContainerViewController = null;
- }
-
- if (mTouchMonitor != null) {
- mTouchMonitor.destroy();
- mTouchMonitor = null;
- }
-
- mWindow = null;
-
- // Always unregister the any set DreamActivity from being blocked from gestures.
- mGestureInteractor.removeGestureBlockedMatcher(DREAM_TYPE_MATCHER,
- GestureInteractor.Scope.Global);
-
- mStarted = false;
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index cd0b3f9..6318dc0 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -41,7 +41,6 @@
import com.android.systemui.statusbar.notification.shared.NotificationAvalancheSuppression
import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
import com.android.systemui.statusbar.notification.shared.PriorityPeopleSection
import javax.inject.Inject
@@ -59,7 +58,6 @@
NotificationAvalancheSuppression.token dependsOn VisualInterruptionRefactor.token
PriorityPeopleSection.token dependsOn SortBySectionTimeFlag.token
NotificationMinimalismPrototype.token dependsOn NotificationThrottleHun.token
- NotificationsHeadsUpRefactor.token dependsOn NotificationThrottleHun.token
// SceneContainer dependencies
SceneContainerFlag.getFlagDependencies().forEach { (alpha, beta) -> alpha dependsOn beta }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 8c82900..d38c952 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -3568,12 +3568,16 @@
}
return;
}
- try {
- mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
- mContext.getPackageName(),
- mSelectedUserInteractor.getSelectedUserId());
- } catch (RemoteException e) {
- Log.d(TAG, "Failed to set disable flags: " + flags, e);
+
+ // Handled in StatusBarDisableFlagsInteractor.
+ if (!KeyguardWmStateRefactor.isEnabled()) {
+ try {
+ mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
+ mContext.getPackageName(),
+ mSelectedUserInteractor.getSelectedUserId());
+ } catch (RemoteException e) {
+ Log.d(TAG, "Failed to set disable flags: " + flags, e);
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
index 180afb2..e89594e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
@@ -23,7 +23,7 @@
import android.view.WindowManager
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardDismissTransitionInteractor
import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier
import com.android.systemui.statusbar.policy.KeyguardStateController
import java.util.concurrent.Executor
@@ -41,7 +41,7 @@
private val activityTaskManagerService: IActivityTaskManager,
private val keyguardStateController: KeyguardStateController,
private val keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier,
- private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ private val keyguardDismissTransitionInteractor: KeyguardDismissTransitionInteractor,
) {
/**
@@ -148,7 +148,7 @@
// a transition to GONE. This transition needs to start even if we're not provided an app
// animation target - it's possible the app is destroyed on creation, etc. but we'll still
// be unlocking.
- keyguardTransitionInteractor.startDismissKeyguardTransition(
+ keyguardDismissTransitionInteractor.startDismissKeyguardTransition(
reason = "Going away remote animation started"
)
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 1042ae3..e4b0f6e 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
@@ -35,7 +35,6 @@
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -49,7 +48,6 @@
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
-@ExperimentalCoroutinesApi
@SysUISingleton
class FromAlternateBouncerTransitionInteractor
@Inject
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissTransitionInteractor.kt
new file mode 100644
index 0000000..c19bbbc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissTransitionInteractor.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+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.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import javax.inject.Inject
+
+@SysUISingleton
+class KeyguardDismissTransitionInteractor
+@Inject
+constructor(
+ private val repository: KeyguardTransitionRepository,
+ private val fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor,
+ private val fromPrimaryBouncerTransitionInteractor: FromPrimaryBouncerTransitionInteractor,
+ private val fromAodTransitionInteractor: FromAodTransitionInteractor,
+ private val fromAlternateBouncerTransitionInteractor: FromAlternateBouncerTransitionInteractor,
+ private val fromDozingTransitionInteractor: FromDozingTransitionInteractor,
+ private val fromOccludedTransitionInteractor: FromOccludedTransitionInteractor,
+) {
+
+ /**
+ * Called to start a transition that will ultimately dismiss the keyguard from the current
+ * state.
+ *
+ * This is called exclusively by sources that can authoritatively say we should be unlocked,
+ * including KeyguardSecurityContainerController and WindowManager.
+ */
+ fun startDismissKeyguardTransition(reason: String = "") {
+ if (SceneContainerFlag.isEnabled) return
+ Log.d(TAG, "#startDismissKeyguardTransition(reason=$reason)")
+ when (val startedState = repository.currentTransitionInfoInternal.value.to) {
+ LOCKSCREEN -> fromLockscreenTransitionInteractor.dismissKeyguard()
+ PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.dismissPrimaryBouncer()
+ ALTERNATE_BOUNCER -> fromAlternateBouncerTransitionInteractor.dismissAlternateBouncer()
+ AOD -> fromAodTransitionInteractor.dismissAod()
+ DOZING -> fromDozingTransitionInteractor.dismissFromDozing()
+ KeyguardState.OCCLUDED -> fromOccludedTransitionInteractor.dismissFromOccluded()
+ KeyguardState.GONE ->
+ Log.i(
+ TAG,
+ "Already transitioning to GONE; ignoring startDismissKeyguardTransition."
+ )
+ else -> Log.e(TAG, "We don't know how to dismiss keyguard from state $startedState.")
+ }
+ }
+
+ companion object {
+ private val TAG = KeyguardDismissTransitionInteractor::class.simpleName
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
index 4aef808..44aafab 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
@@ -48,7 +48,7 @@
@Application scope: CoroutineScope,
val repository: KeyguardRepository,
val biometricSettingsRepository: BiometricSettingsRepository,
- transitionInteractor: KeyguardTransitionInteractor,
+ keyguardDismissTransitionInteractor: KeyguardDismissTransitionInteractor,
internalTransitionInteractor: InternalKeyguardTransitionInteractor,
) {
@@ -94,7 +94,9 @@
showKeyguardWhenReenabled
.filter { shouldDismiss -> shouldDismiss }
.collect {
- transitionInteractor.startDismissKeyguardTransition("keyguard disabled")
+ keyguardDismissTransitionInteractor.startDismissKeyguardTransition(
+ "keyguard disabled"
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 92e2a91..e19b72e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -25,20 +25,14 @@
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.Edge
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.KeyguardState.AOD
-import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
-import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
-import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
-import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
-import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.shared.model.KeyguardState.UNDEFINED
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.util.kotlin.WithPrev
import com.android.systemui.util.kotlin.pairwise
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -67,14 +61,6 @@
constructor(
@Application val scope: CoroutineScope,
private val repository: KeyguardTransitionRepository,
- private val fromLockscreenTransitionInteractor: dagger.Lazy<FromLockscreenTransitionInteractor>,
- private val fromPrimaryBouncerTransitionInteractor:
- dagger.Lazy<FromPrimaryBouncerTransitionInteractor>,
- private val fromAodTransitionInteractor: dagger.Lazy<FromAodTransitionInteractor>,
- private val fromAlternateBouncerTransitionInteractor:
- dagger.Lazy<FromAlternateBouncerTransitionInteractor>,
- private val fromDozingTransitionInteractor: dagger.Lazy<FromDozingTransitionInteractor>,
- private val fromOccludedTransitionInteractor: dagger.Lazy<FromOccludedTransitionInteractor>,
private val sceneInteractor: SceneInteractor,
) {
private val transitionMap = mutableMapOf<Edge.StateToState, MutableSharedFlow<TransitionStep>>()
@@ -103,6 +89,18 @@
val transitionState: StateFlow<TransitionStep> =
transitions.stateIn(scope, SharingStarted.Eagerly, TransitionStep())
+ private val sceneTransitionPair =
+ sceneInteractor.transitionState
+ .pairwise()
+ .stateIn(
+ scope,
+ SharingStarted.Eagerly,
+ WithPrev(
+ sceneInteractor.transitionState.value,
+ sceneInteractor.transitionState.value
+ )
+ )
+
/**
* A pair of the most recent STARTED step, and the transition step immediately preceding it. The
* transition framework enforces that the previous step is either a CANCELED or FINISHED step,
@@ -209,7 +207,7 @@
}
return if (SceneContainerFlag.isEnabled) {
- flow.filter {
+ flow.filter { step ->
val fromScene =
when (edge) {
is Edge.StateToState -> edge.from?.mapToSceneContainerScene()
@@ -226,8 +224,23 @@
fun SceneKey?.isLockscreenOrNull() = this == Scenes.Lockscreen || this == null
- return@filter (fromScene.isLockscreenOrNull() && toScene.isLockscreenOrNull()) ||
+ val isTransitioningBetweenLockscreenStates =
+ fromScene.isLockscreenOrNull() && toScene.isLockscreenOrNull()
+ val isTransitioningBetweenDesiredScenes =
sceneInteractor.transitionState.value.isTransitioning(fromScene, toScene)
+
+ // We can't compare the terminal step with the current sceneTransition because
+ // a) STL has no guarantee that it will settle in Idle() when finished/canceled
+ // b) Comparing to Idle(toScene) would make any other FINISHED step settling in
+ // toScene pass as well
+ val terminalStepBelongsToPreviousTransition =
+ (step.transitionState == TransitionState.FINISHED ||
+ step.transitionState == TransitionState.CANCELED) &&
+ sceneTransitionPair.value.previousValue.isTransitioning(fromScene, toScene)
+
+ return@filter isTransitioningBetweenLockscreenStates ||
+ isTransitioningBetweenDesiredScenes ||
+ terminalStepBelongsToPreviousTransition
}
} else {
flow
@@ -365,33 +378,6 @@
}
/**
- * Called to start a transition that will ultimately dismiss the keyguard from the current
- * state.
- *
- * This is called exclusively by sources that can authoritatively say we should be unlocked,
- * including KeyguardSecurityContainerController and WindowManager.
- */
- fun startDismissKeyguardTransition(reason: String = "") {
- if (SceneContainerFlag.isEnabled) return
- Log.d(TAG, "#startDismissKeyguardTransition(reason=$reason)")
- when (val startedState = repository.currentTransitionInfoInternal.value.to) {
- LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard()
- PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.get().dismissPrimaryBouncer()
- ALTERNATE_BOUNCER ->
- fromAlternateBouncerTransitionInteractor.get().dismissAlternateBouncer()
- AOD -> fromAodTransitionInteractor.get().dismissAod()
- DOZING -> fromDozingTransitionInteractor.get().dismissFromDozing()
- OCCLUDED -> fromOccludedTransitionInteractor.get().dismissFromOccluded()
- GONE ->
- Log.i(
- TAG,
- "Already transitioning to GONE; ignoring startDismissKeyguardTransition."
- )
- else -> Log.e(TAG, "We don't know how to dismiss keyguard from state $startedState.")
- }
- }
-
- /**
* Whether we're in a transition to and from the given [KeyguardState]s, but haven't yet
* completed it.
*
@@ -464,6 +450,17 @@
return finishedKeyguardState.map { it == state }.distinctUntilChanged()
}
+ fun isCurrentlyIn(scene: SceneKey, stateWithoutSceneContainer: KeyguardState): Flow<Boolean> {
+ return if (SceneContainerFlag.isEnabled) {
+ // In STL there is no difference between finished/currentState
+ isFinishedIn(scene, stateWithoutSceneContainer)
+ } else {
+ stateWithoutSceneContainer.checkValidState()
+ currentKeyguardState.map { it == stateWithoutSceneContainer }
+ }
+ .distinctUntilChanged()
+ }
+
fun getCurrentState(): KeyguardState {
return currentKeyguardState.replayCache.last()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
index f0bf402..9b8d9ea1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
@@ -37,6 +37,7 @@
import com.android.systemui.keyguard.shared.model.KeyguardState.Companion.deviceIsAwakeInState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.settings.SecureSettings
@@ -181,14 +182,20 @@
scope.launch {
powerInteractor.detailedWakefulness
.distinctUntilChangedBy { it.isAwake() }
- .sample(transitionInteractor.currentKeyguardState, ::Pair)
- .collect { (wakefulness, currentState) ->
+ .sample(
+ transitionInteractor.isCurrentlyIn(
+ Scenes.Gone,
+ stateWithoutSceneContainer = KeyguardState.GONE
+ ),
+ ::Pair
+ )
+ .collect { (wakefulness, finishedInGone) ->
// Save isAwake for use in onDreamingStarted/onDreamingStopped.
this@KeyguardWakeDirectlyToGoneInteractor.isAwake = wakefulness.isAwake()
// If we're sleeping from GONE, check the timeout and lock instantly settings.
// These are not relevant if we're coming from non-GONE states.
- if (!isAwake && currentState == KeyguardState.GONE) {
+ if (!isAwake && finishedInGone) {
val lockTimeoutDuration = getCanIgnoreAuthAndReturnToGoneDuration()
// If the screen timed out and went to sleep, and the lock timeout is > 0ms,
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 a7a8321..7899971 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
@@ -369,8 +369,7 @@
} else {
vibratorHelper.performHapticFeedback(
view,
- HapticFeedbackConstants.CONFIRM,
- HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
+ HapticFeedbackConstants.BIOMETRIC_CONFIRM,
)
}
}
@@ -390,8 +389,7 @@
} else {
vibratorHelper.performHapticFeedback(
view,
- HapticFeedbackConstants.REJECT,
- HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
+ HapticFeedbackConstants.BIOMETRIC_REJECT,
)
}
}
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 0032c2f..e2ad4635 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
@@ -46,6 +46,7 @@
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToPrimaryBouncerTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludedToAodTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.OccludedToDozingTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludedToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.OffToLockscreenTransitionViewModel
@@ -205,6 +206,12 @@
@Binds
@IntoSet
+ abstract fun occludedToDozing(
+ impl: OccludedToDozingTransitionViewModel
+ ): DeviceEntryIconTransition
+
+ @Binds
+ @IntoSet
abstract fun occludedToLockscreen(
impl: OccludedToLockscreenTransitionViewModel
): DeviceEntryIconTransition
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 55fc718..99160f8 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
@@ -127,6 +127,7 @@
// migrate addSmartspaceView from KeyguardClockSwitchController
constrainHeight(sharedR.id.bc_smartspace_view, ConstraintSet.WRAP_CONTENT)
+ constrainWidth(sharedR.id.bc_smartspace_view, ConstraintSet.MATCH_CONSTRAINT)
connect(
sharedR.id.bc_smartspace_view,
ConstraintSet.START,
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 6f8389f..9f68210 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
@@ -59,6 +59,7 @@
alternateBouncerToDozingTransitionViewModel: AlternateBouncerToDozingTransitionViewModel,
dreamingToAodTransitionViewModel: DreamingToAodTransitionViewModel,
primaryBouncerToLockscreenTransitionViewModel: PrimaryBouncerToLockscreenTransitionViewModel,
+ occludedToDozingTransitionViewModel: OccludedToDozingTransitionViewModel,
) {
val color: Flow<Int> =
deviceEntryIconViewModel.useBackgroundProtection.flatMapLatest { useBackground ->
@@ -103,6 +104,7 @@
dreamingToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
primaryBouncerToLockscreenTransitionViewModel
.deviceEntryBackgroundViewAlpha,
+ occludedToDozingTransitionViewModel.deviceEntryBackgroundViewAlpha,
)
.merge()
.onStart {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index ebdcaa0..eaa61a1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -34,20 +34,18 @@
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
-import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
-import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.ui.StateToValue
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.ui.viewmodel.NotificationShadeWindowModel
import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
-import com.android.systemui.util.kotlin.BooleanFlowOperators.any
import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.kotlin.sample
@@ -86,6 +84,7 @@
private val communalInteractor: CommunalInteractor,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
+ notificationShadeWindowModel: NotificationShadeWindowModel,
private val alternateBouncerToAodTransitionViewModel: AlternateBouncerToAodTransitionViewModel,
private val alternateBouncerToGoneTransitionViewModel:
AlternateBouncerToGoneTransitionViewModel,
@@ -197,37 +196,18 @@
.distinctUntilChanged()
/**
- * Keyguard states which should fully hide the keyguard.
- *
- * Note: [GONE] is not included as it is handled separately.
- */
- private val hiddenKeyguardStates = listOf(OCCLUDED, DREAMING, GLANCEABLE_HUB)
-
- /**
* Keyguard should not show if fully transitioned into a hidden keyguard state or if
* transitioning between hidden states.
*/
private val hideKeyguard: Flow<Boolean> =
- (hiddenKeyguardStates.map { state ->
- keyguardTransitionInteractor
- .transitionValue(state)
- .map { it == 1f }
- .onStart { emit(false) }
- } +
- listOf(
- communalInteractor.isIdleOnCommunal,
- keyguardTransitionInteractor
- .transitionValue(scene = Scenes.Gone, stateWithoutSceneContainer = GONE)
- .map { it == 1f }
- .onStart { emit(false) },
- keyguardTransitionInteractor
- .isInTransitionWhere(
- fromStatePredicate = { hiddenKeyguardStates.contains(it) },
- toStatePredicate = { hiddenKeyguardStates.contains(it) },
- )
- .onStart { emit(false) },
- ))
- .any()
+ anyOf(
+ notificationShadeWindowModel.isKeyguardOccluded,
+ communalInteractor.isIdleOnCommunal,
+ keyguardTransitionInteractor
+ .transitionValue(scene = Scenes.Gone, stateWithoutSceneContainer = GONE)
+ .map { it == 1f }
+ .onStart { emit(false) },
+ )
/** Last point that the root view was tapped */
val lastRootViewTapPosition: Flow<Point?> = keyguardInteractor.lastRootViewTapPosition
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index fcafd5e..adb63b7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -40,7 +40,6 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
@@ -91,13 +90,13 @@
end = end,
)
}
- .collectLatest { _unfoldTranslations.value = it }
+ .collect { _unfoldTranslations.value = it }
}
launch {
occlusionInteractor.isOccludingActivityShown
.map { !it }
- .collectLatest { _isContentVisible.value = it }
+ .collect { _isContentVisible.value = it }
}
awaitCancellation()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneActionsViewModel.kt
index 7383f57..2819e61 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneActionsViewModel.kt
@@ -35,7 +35,6 @@
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
@@ -100,7 +99,7 @@
}
}
}
- .collectLatest { setActions(it) }
+ .collect { setActions(it) }
}
private fun swipeDownFromTop(pointerCount: Int): Swipe {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModel.kt
index af01930..4fb2b9b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModel.kt
@@ -17,15 +17,19 @@
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.FromOccludedTransitionInteractor
import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
/**
* Breaks down OCCLUDED->DOZING transition into discrete steps for corresponding views to consume.
@@ -35,8 +39,9 @@
class OccludedToDozingTransitionViewModel
@Inject
constructor(
+ deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
-) {
+) : DeviceEntryIconTransition {
private val transitionAnimation =
animationFlow.setup(
duration = FromOccludedTransitionInteractor.TO_DOZING_DURATION,
@@ -50,4 +55,17 @@
duration = 250.milliseconds,
onStep = { it },
)
+
+ val deviceEntryBackgroundViewAlpha: Flow<Float> =
+ transitionAnimation.immediatelyTransitionTo(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/media/controls/domain/pipeline/MediaActions.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt
index 378a147..bcf748e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt
@@ -59,13 +59,14 @@
val playOrPause =
if (isConnectingState(state.state)) {
// Spinner needs to be animating to render anything. Start it here.
- val drawable = MediaControlDrawables.getProgress(context)
+ val drawable =
+ context.getDrawable(com.android.internal.R.drawable.progress_small_material)
(drawable as Animatable).start()
MediaAction(
drawable,
null, // no action to perform when clicked
context.getString(R.string.controls_media_button_connecting),
- MediaControlDrawables.getConnecting(context),
+ context.getDrawable(R.drawable.ic_media_connecting_container),
// Specify a rebind id to prevent the spinner from restarting on later binds.
com.android.internal.R.drawable.progress_small_material
)
@@ -153,18 +154,18 @@
return when (action) {
PlaybackState.ACTION_PLAY -> {
MediaAction(
- MediaControlDrawables.getPlayIcon(context),
+ context.getDrawable(R.drawable.ic_media_play),
{ controller.transportControls.play() },
context.getString(R.string.controls_media_button_play),
- MediaControlDrawables.getPlayBackground(context)
+ context.getDrawable(R.drawable.ic_media_play_container)
)
}
PlaybackState.ACTION_PAUSE -> {
MediaAction(
- MediaControlDrawables.getPauseIcon(context),
+ context.getDrawable(R.drawable.ic_media_pause),
{ controller.transportControls.pause() },
context.getString(R.string.controls_media_button_pause),
- MediaControlDrawables.getPauseBackground(context)
+ context.getDrawable(R.drawable.ic_media_pause_container)
)
}
PlaybackState.ACTION_SKIP_TO_PREVIOUS -> {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
index 415449f..4555810 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
@@ -71,7 +71,6 @@
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager.Companion.isMediaNotification
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.media.controls.domain.resume.ResumeMediaBrowser
-import com.android.systemui.media.controls.shared.MediaControlDrawables
import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_SOURCE
import com.android.systemui.media.controls.shared.model.EXTRA_VALUE_TRIGGER_PERIODIC
import com.android.systemui.media.controls.shared.model.MediaAction
@@ -1229,7 +1228,7 @@
.loadDrawable(context),
action,
context.getString(R.string.controls_media_resume),
- MediaControlDrawables.getPlayBackground(context)
+ context.getDrawable(R.drawable.ic_media_play_container)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaControlDrawables.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaControlDrawables.kt
index c78220e..95ca11c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaControlDrawables.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaControlDrawables.kt
@@ -17,20 +17,12 @@
package com.android.systemui.media.controls.shared
import android.content.Context
-import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.Drawable
import com.android.systemui.Flags.mediaControlsDrawablesReuse
import com.android.systemui.res.R
object MediaControlDrawables {
- // Play/Pause Button drawables.
- private var progress: Drawable? = null
- private var connecting: Drawable? = null
- private var playIcon: AnimatedVectorDrawable? = null
- private var playBackground: AnimatedVectorDrawable? = null
- private var pauseIcon: AnimatedVectorDrawable? = null
- private var pauseBackground: AnimatedVectorDrawable? = null
// Prev button.
private var prevIcon: Drawable? = null
// Next button.
@@ -40,81 +32,6 @@
private var antenna: Drawable? = null
private var groupDevice: Drawable? = null
private var homeDevices: Drawable? = null
- // Guts drawables.
- private var outline: Drawable? = null
- private var solid: Drawable? = null
-
- fun getProgress(context: Context): Drawable? {
- if (!mediaControlsDrawablesReuse()) {
- return context.getDrawable(com.android.internal.R.drawable.progress_small_material)
- }
- return progress?.mutate()
- ?: context.getDrawable(com.android.internal.R.drawable.progress_small_material).also {
- progress = it
- }
- }
-
- fun getConnecting(context: Context): Drawable? {
- if (!mediaControlsDrawablesReuse()) {
- return context.getDrawable(R.drawable.ic_media_connecting_container)
- }
- return connecting?.mutate()
- ?: context.getDrawable(R.drawable.ic_media_connecting_container).also {
- connecting = it
- }
- }
-
- fun getPlayIcon(context: Context): AnimatedVectorDrawable? {
- if (!mediaControlsDrawablesReuse()) {
- return context.getDrawable(R.drawable.ic_media_play) as AnimatedVectorDrawable?
- }
- return playIcon?.let {
- it.reset()
- it.mutate() as AnimatedVectorDrawable
- }
- ?: (context.getDrawable(R.drawable.ic_media_play) as AnimatedVectorDrawable?).also {
- playIcon = it
- }
- }
-
- fun getPlayBackground(context: Context): AnimatedVectorDrawable? {
- if (!mediaControlsDrawablesReuse()) {
- return context.getDrawable(R.drawable.ic_media_play_container)
- as AnimatedVectorDrawable?
- }
- return playBackground?.let {
- it.reset()
- it.mutate() as AnimatedVectorDrawable
- }
- ?: (context.getDrawable(R.drawable.ic_media_play_container) as AnimatedVectorDrawable?)
- .also { playBackground = it }
- }
-
- fun getPauseIcon(context: Context): AnimatedVectorDrawable? {
- if (!mediaControlsDrawablesReuse()) {
- return context.getDrawable(R.drawable.ic_media_pause) as AnimatedVectorDrawable?
- }
- return pauseIcon?.let {
- it.reset()
- it.mutate() as AnimatedVectorDrawable
- }
- ?: (context.getDrawable(R.drawable.ic_media_pause) as AnimatedVectorDrawable?).also {
- pauseIcon = it
- }
- }
-
- fun getPauseBackground(context: Context): AnimatedVectorDrawable? {
- if (!mediaControlsDrawablesReuse()) {
- return context.getDrawable(R.drawable.ic_media_pause_container)
- as AnimatedVectorDrawable?
- }
- return pauseBackground?.let {
- it.reset()
- it.mutate() as AnimatedVectorDrawable
- }
- ?: (context.getDrawable(R.drawable.ic_media_pause_container) as AnimatedVectorDrawable?)
- .also { pauseBackground = it }
- }
fun getNextIcon(context: Context): Drawable? {
if (!mediaControlsDrawablesReuse()) {
@@ -165,19 +82,4 @@
return homeDevices
?: context.getDrawable(R.drawable.ic_media_home_devices).also { homeDevices = it }
}
-
- fun getOutline(context: Context): Drawable? {
- if (!mediaControlsDrawablesReuse()) {
- return context.getDrawable(R.drawable.qs_media_outline_button)
- }
- return outline
- ?: context.getDrawable(R.drawable.qs_media_outline_button).also { outline = it }
- }
-
- fun getSolid(context: Context): Drawable? {
- if (!mediaControlsDrawablesReuse()) {
- return context.getDrawable(R.drawable.qs_media_solid_button)
- }
- return solid ?: context.getDrawable(R.drawable.qs_media_solid_button).also { solid = it }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
index f460134..64820e0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
@@ -30,7 +30,6 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaControlInteractor
-import com.android.systemui.media.controls.shared.MediaControlDrawables
import com.android.systemui.media.controls.shared.model.MediaAction
import com.android.systemui.media.controls.shared.model.MediaButton
import com.android.systemui.media.controls.shared.model.MediaControlModel
@@ -285,9 +284,9 @@
},
cancelTextBackground =
if (model.isDismissible) {
- MediaControlDrawables.getOutline(applicationContext)
+ applicationContext.getDrawable(R.drawable.qs_media_outline_button)
} else {
- MediaControlDrawables.getSolid(applicationContext)
+ applicationContext.getDrawable(R.drawable.qs_media_solid_button)
},
onSettingsClicked = {
logger.logLongPressSettings(model.uid, model.packageName, model.instanceId)
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
index c706c3e..e8c90c1 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
@@ -140,7 +140,6 @@
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
-import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.shared.rotation.RotationButtonController;
import com.android.systemui.shared.rotation.RotationPolicyUtil;
@@ -166,6 +165,7 @@
import com.android.systemui.util.ViewController;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.shared.handles.RegionSamplingHelper;
import dagger.Lazy;
diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt
new file mode 100644
index 0000000..db988f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.notifications.ui.viewmodel
+
+import com.android.compose.animation.scene.Back
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.scene.shared.model.TransitionKeys
+import com.android.systemui.scene.ui.viewmodel.SceneActionsViewModel
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeAlignment
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+/** Models the UI state for the user actions for navigating to other scenes or overlays. */
+class NotificationsShadeOverlayActionsViewModel
+@AssistedInject
+constructor(
+ private val shadeInteractor: ShadeInteractor,
+) : SceneActionsViewModel() {
+
+ override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
+ setActions(
+ mapOf(
+ if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
+ Swipe.Up to UserActionResult.HideOverlay(Overlays.NotificationsShade)
+ } else {
+ Swipe.Down to
+ UserActionResult.HideOverlay(
+ overlay = Overlays.NotificationsShade,
+ transitionKey = TransitionKeys.OpenBottomShade,
+ )
+ },
+ Back to UserActionResult.HideOverlay(Overlays.NotificationsShade),
+ )
+ )
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): NotificationsShadeOverlayActionsViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
index d948dfd..c75b601 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
@@ -16,7 +16,6 @@
package com.android.systemui.qs.panels.ui.compose
-import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.runtime.Composable
@@ -24,7 +23,6 @@
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.dimensionResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.panels.shared.model.SizedTileImpl
@@ -33,7 +31,6 @@
import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel
import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.res.R
import javax.inject.Inject
@SysUISingleton
@@ -64,7 +61,7 @@
Tile(
tile = sizedTiles[index].tile,
iconOnly = iconTilesViewModel.isIconTile(sizedTiles[index].tile.spec),
- modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
+ modifier = Modifier
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
index a9027ff..eeb55ca 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
@@ -16,18 +16,15 @@
package com.android.systemui.qs.panels.ui.compose
-import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.dimensionResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.qs.panels.ui.viewmodel.QuickQuickSettingsViewModel
-import com.android.systemui.res.R
@Composable
fun QuickQuickSettings(
@@ -54,11 +51,7 @@
key = { index -> sizedTiles[index].tile.spec.spec },
span = { index -> GridItemSpan(sizedTiles[index].width) }
) { index ->
- Tile(
- tile = tiles[index],
- iconOnly = sizedTiles[index].isIcon,
- modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
- )
+ Tile(tile = tiles[index], iconOnly = sizedTiles[index].isIcon, modifier = Modifier)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
index 79c2eb9..24af09d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
@@ -44,7 +44,6 @@
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -136,23 +135,23 @@
// TODO(b/361789146): Draw the shapes instead of clipping
val tileShape = TileDefaults.animateTileShape(uiState.state)
- val iconShape = TileDefaults.animateIconShape(uiState.state)
TileContainer(
colors = colors,
showLabels = showLabels,
label = uiState.label,
iconOnly = iconOnly,
- shape = if (iconOnly) iconShape else tileShape,
+ shape = tileShape,
clickEnabled = true,
onClick = tile::onClick,
onLongClick = tile::onLongClick,
- modifier = modifier,
+ modifier = modifier.height(tileHeight()),
) {
val icon = getTileIcon(icon = uiState.icon)
if (iconOnly) {
TileIcon(icon = icon, color = colors.icon, modifier = Modifier.align(Alignment.Center))
} else {
+ val iconShape = TileDefaults.animateIconShape(uiState.state)
LargeTileContent(
label = uiState.label,
secondaryLabel = uiState.secondaryLabel,
@@ -199,7 +198,7 @@
Expandable(
color = backgroundColor,
shape = shape,
- modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height)).clip(shape)
+ modifier = Modifier.height(tileHeight()).clip(shape)
) {
Box(
modifier =
@@ -246,7 +245,7 @@
// Icon
Box(
modifier =
- Modifier.fillMaxHeight().aspectRatio(1f).thenIf(toggleClickSupported) {
+ Modifier.size(TileDefaults.ToggleTargetSize).thenIf(toggleClickSupported) {
Modifier.clip(iconShape)
.background(colors.iconBackground, { 1f })
.combinedClickable(onClick = onClick, onLongClick = onLongClick)
@@ -673,7 +672,7 @@
animateToEnd: Boolean = false,
modifier: Modifier = Modifier,
) {
- val iconModifier = modifier.size(dimensionResource(id = R.dimen.qs_icon_size))
+ val iconModifier = modifier.size(TileDefaults.IconSize)
val context = LocalContext.current
val loadedDrawable =
remember(icon, context) {
@@ -710,17 +709,12 @@
}
}
-@Composable
private fun Modifier.tilePadding(): Modifier {
- return padding(dimensionResource(id = R.dimen.qs_label_container_margin))
+ return padding(TileDefaults.TilePadding)
}
-@Composable
private fun tileHorizontalArrangement(): Arrangement.Horizontal {
- return spacedBy(
- space = dimensionResource(id = R.dimen.qs_label_container_margin),
- alignment = Alignment.Start
- )
+ return spacedBy(space = TileDefaults.TileArrangementPadding, alignment = Alignment.Start)
}
@Composable
@@ -728,7 +722,7 @@
return if (iconWithLabel) {
TileDefaults.IconTileWithLabelHeight
} else {
- dimensionResource(id = R.dimen.qs_tile_height)
+ TileDefaults.TileHeight
}
}
@@ -749,6 +743,14 @@
val InactiveCornerRadius = 50.dp
val ActiveIconCornerRadius = 16.dp
val ActiveTileCornerRadius = 24.dp
+
+ val ToggleTargetSize = 56.dp
+ val IconSize = 24.dp
+
+ val TilePadding = 8.dp
+ val TileArrangementPadding = 6.dp
+
+ val TileHeight = 72.dp
val IconTileWithLabelHeight = 140.dp
/** An active tile without dual target uses the active color as background */
@@ -812,7 +814,7 @@
fun animateIconShape(state: Int): Shape {
return animateShape(
state = state,
- activeCornerRadius = ActiveTileCornerRadius,
+ activeCornerRadius = ActiveIconCornerRadius,
label = "QSTileCornerRadius",
)
}
@@ -821,7 +823,7 @@
fun animateTileShape(state: Int): Shape {
return animateShape(
state = state,
- activeCornerRadius = ActiveIconCornerRadius,
+ activeCornerRadius = ActiveTileCornerRadius,
label = "QSTileIconCornerRadius",
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
index 6d63d26..313cb30 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
@@ -21,6 +21,7 @@
import android.os.Handler
import android.os.Looper
import android.service.quicksettings.Tile
+import androidx.annotation.DrawableRes
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.coroutineScope
import androidx.lifecycle.repeatOnLifecycle
@@ -98,7 +99,7 @@
override fun newTileState(): QSTile.State {
return QSTile.State().apply {
label = mContext.getString(R.string.quick_settings_modes_label)
- icon = ResourceIcon.get(R.drawable.qs_dnd_icon_off)
+ icon = ResourceIcon.get(ICON_RES_ID)
state = Tile.STATE_INACTIVE
}
}
@@ -116,7 +117,7 @@
state?.apply {
this.state = tileState.activationState.legacyState
val tileStateIcon = tileState.icon()
- icon = tileStateIcon?.asQSTileIcon() ?: ResourceIcon.get(R.drawable.qs_dnd_icon_off)
+ icon = tileStateIcon?.asQSTileIcon() ?: ResourceIcon.get(ICON_RES_ID)
label = tileLabel
secondaryLabel = tileState.secondaryLabel
contentDescription = tileState.contentDescription
@@ -127,5 +128,6 @@
companion object {
const val TILE_SPEC = "dnd"
+ @DrawableRes val ICON_RES_ID = com.android.internal.R.drawable.ic_zen_priority_modes
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
index 71f8639..89b9eee 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
@@ -306,6 +306,8 @@
mInternetDialogController.isAirplaneModeEnabled() ? View.VISIBLE : View.GONE);
mWifiRecyclerView.setLayoutManager(new LinearLayoutManager(context));
mWifiRecyclerView.setAdapter(mAdapter);
+
+ updateDialogUI(getWifiNetworkContent());
}
@Override
@@ -315,6 +317,7 @@
}
mLifecycleRegistry.setCurrentState(Lifecycle.State.RESUMED);
+
mInternetDialogController.onStart(this, mCanConfigWifi);
if (!mCanConfigWifi) {
hideWifiViews();
@@ -402,10 +405,12 @@
internetContent.mShouldUpdateMobileNetwork = shouldUpdateMobileNetwork;
internetContent.mInternetDialogTitleString = getDialogTitleText();
internetContent.mInternetDialogSubTitle = getSubtitleText();
- internetContent.mActiveNetworkIsCellular =
- mInternetDialogController.activeNetworkIsCellular();
- internetContent.mIsCarrierNetworkActive =
- mInternetDialogController.isCarrierNetworkActive();
+ if (shouldUpdateMobileNetwork) {
+ internetContent.mActiveNetworkIsCellular =
+ mInternetDialogController.activeNetworkIsCellular();
+ internetContent.mIsCarrierNetworkActive =
+ mInternetDialogController.isCarrierNetworkActive();
+ }
internetContent.mIsAirplaneModeEnabled = mInternetDialogController.isAirplaneModeEnabled();
internetContent.mHasEthernet = mInternetDialogController.hasEthernet();
internetContent.mIsWifiEnabled = mInternetDialogController.isWifiEnabled();
@@ -416,6 +421,15 @@
return internetContent;
}
+ private InternetContent getWifiNetworkContent() {
+ InternetContent internetContent = new InternetContent();
+ internetContent.mInternetDialogTitleString = getDialogTitleText();
+ internetContent.mInternetDialogSubTitle = getSubtitleText();
+ internetContent.mIsWifiEnabled = mInternetDialogController.isWifiEnabled();
+ internetContent.mIsDeviceLocked = mInternetDialogController.isDeviceLocked();
+ return internetContent;
+ }
+
private void setOnClickListener(SystemUIDialog dialog) {
mMobileNetworkLayout.setOnClickListener(v -> {
int autoSwitchNonDdsSubId = mInternetDialogController.getActiveAutoSwitchNonDdsSubId();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
index 3f18fc2..6173091 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
@@ -20,10 +20,12 @@
import android.content.Context
import android.os.UserHandle
import com.android.app.tracing.coroutines.flow.map
+import com.android.systemui.common.shared.model.asIcon
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
+import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -52,18 +54,32 @@
*/
fun tileData() =
zenModeInteractor.activeModes
- .map { modes ->
- ModesTileModel(
- isActivated = modes.isNotEmpty(),
- icon =
- if (Flags.modesApi() && Flags.modesUi() && Flags.modesUiIcons())
- zenModeInteractor.getActiveModeIcon(modes)
- else null,
- activeModes = modes.map { it.name }
- )
+ .map { activeModes ->
+ val modesIconResId = com.android.internal.R.drawable.ic_zen_priority_modes
+
+ if (usesModeIcons()) {
+ val mainModeDrawable = activeModes.mainMode?.icon?.drawable
+ val iconResId = if (mainModeDrawable == null) modesIconResId else null
+
+ ModesTileModel(
+ isActivated = activeModes.isAnyActive(),
+ icon = (mainModeDrawable ?: context.getDrawable(modesIconResId)!!).asIcon(),
+ iconResId = iconResId,
+ activeModes = activeModes.modeNames
+ )
+ } else {
+ ModesTileModel(
+ isActivated = activeModes.isAnyActive(),
+ icon = context.getDrawable(modesIconResId)!!.asIcon(),
+ iconResId = modesIconResId,
+ activeModes = activeModes.modeNames
+ )
+ }
}
.flowOn(bgDispatcher)
.distinctUntilChanged()
override fun availability(user: UserHandle): Flow<Boolean> = flowOf(Flags.modesUi())
+
+ private fun usesModeIcons() = Flags.modesApi() && Flags.modesUi() && Flags.modesUiIcons()
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt
index 904ff3a..db48123 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt
@@ -21,5 +21,12 @@
data class ModesTileModel(
val isActivated: Boolean,
val activeModes: List<String>,
- val icon: Icon? = null
+ val icon: Icon.Loaded,
+
+ /**
+ * Resource id corresponding to [icon]. Will only be present if it's know to correspond to a
+ * resource with a known id in SystemUI (such as resources from `android.R`,
+ * `com.android.internal.R`, or `com.android.systemui.res` itself).
+ */
+ val iconResId: Int? = null
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
index 83c3335..7f571b1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
@@ -16,11 +16,9 @@
package com.android.systemui.qs.tiles.impl.modes.ui
-import android.app.Flags
import android.content.res.Resources
import android.icu.text.MessageFormat
import android.widget.Button
-import com.android.systemui.common.shared.model.asIcon
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
@@ -38,15 +36,10 @@
) : QSTileDataToStateMapper<ModesTileModel> {
override fun map(config: QSTileConfig, data: ModesTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
- if (Flags.modesApi() && Flags.modesUi() && Flags.modesUiIcons() && data.icon != null) {
- icon = { data.icon }
- } else {
- val iconRes =
- if (data.isActivated) R.drawable.qs_dnd_icon_on else R.drawable.qs_dnd_icon_off
- val icon = resources.getDrawable(iconRes, theme).asIcon()
- this.iconRes = iconRes
- this.icon = { icon }
+ if (!android.app.Flags.modesUiIcons()) {
+ iconRes = data.iconResId
}
+ icon = { data.icon }
activationState =
if (data.isActivated) {
QSTileState.ActivationState.ACTIVE
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneActionsViewModel.kt
index af55f5a..2bb5dc66 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneActionsViewModel.kt
@@ -31,7 +31,6 @@
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
@@ -78,7 +77,7 @@
}
}
}
- .collectLatest { actions -> setActions(actions) }
+ .collect { actions -> setActions(actions) }
}
@AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModel.kt
index 93bf73f..a264f51 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModel.kt
@@ -17,16 +17,24 @@
package com.android.systemui.qs.ui.viewmodel
import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import java.util.concurrent.atomic.AtomicBoolean
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
/**
* Models UI state needed for rendering the content of the quick settings scene.
@@ -43,7 +51,9 @@
private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
private val footerActionsController: FooterActionsController,
val mediaCarouselInteractor: MediaCarouselInteractor,
-) {
+ private val shadeInteractor: ShadeInteractor,
+ private val sceneInteractor: SceneInteractor,
+) : ExclusiveActivatable() {
val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasAnyMediaOrRecommendation
@@ -56,6 +66,19 @@
return footerActionsViewModelFactory.create(lifecycleOwner)
}
+ override suspend fun onActivated(): Nothing {
+ coroutineScope {
+ launch {
+ shadeInteractor.shadeMode.collect { shadeMode ->
+ if (shadeMode == ShadeMode.Split) {
+ sceneInteractor.snapToScene(Scenes.Shade, "Unfold while on QS")
+ }
+ }
+ }
+ awaitCancellation()
+ }
+ }
+
@AssistedFactory
interface Factory {
fun create(): QuickSettingsSceneContentViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModel.kt
index d2967b8..9956a46 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModel.kt
@@ -29,7 +29,6 @@
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.map
/**
@@ -62,7 +61,7 @@
}
}
}
- .collectLatest { actions -> setActions(actions) }
+ .collect { actions -> setActions(actions) }
}
@AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index fe5cbb1..000781a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -894,11 +894,21 @@
return;
}
mHandler.removeCallbacks(mConnectionRunnable);
+
+ // Avoid creating TouchInteractionService because the System user in HSUM mode does not
+ // interact with UI elements
+ UserHandle currentUser = UserHandle.of(mUserTracker.getUserId());
+ if (UserManager.isHeadlessSystemUserMode() && currentUser.isSystem()) {
+ Log.w(TAG_OPS,
+ "Skipping connection to TouchInteractionService for the System user in HSUM "
+ + "mode.");
+ return;
+ }
try {
mBound = mContext.bindServiceAsUser(mQuickStepIntent,
mOverviewServiceConnection,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
- UserHandle.of(mUserTracker.getUserId()));
+ currentUser);
} catch (SecurityException e) {
Log.e(TAG_OPS, "Unable to bind because of security error", e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
index 6e89973..98cf941 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
@@ -27,6 +27,7 @@
import com.android.systemui.scene.domain.startable.SceneContainerStartable
import com.android.systemui.scene.domain.startable.ScrimStartable
import com.android.systemui.scene.domain.startable.StatusBarStartable
+import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.shared.flag.DualShade
@@ -42,6 +43,7 @@
[
EmptySceneModule::class,
GoneSceneModule::class,
+ NotificationsShadeOverlayModule::class,
NotificationsShadeSceneModule::class,
NotificationsShadeSessionModule::class,
QuickSettingsSceneModule::class,
@@ -99,6 +101,10 @@
Scenes.Shade.takeUnless { DualShade.isEnabled },
),
initialSceneKey = Scenes.Gone,
+ overlayKeys =
+ listOfNotNull(
+ Overlays.NotificationsShade.takeIf { DualShade.isEnabled },
+ ),
navigationDistances =
mapOf(
Scenes.Gone to 0,
diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
index 7d63b4c..8fc896c 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
@@ -17,7 +17,6 @@
package com.android.systemui.scene
import com.android.systemui.CoreStartable
-import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlagsModule
import com.android.systemui.notifications.ui.composable.NotificationsShadeSessionModule
import com.android.systemui.scene.domain.SceneDomainModule
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
@@ -28,6 +27,7 @@
import com.android.systemui.scene.domain.startable.SceneContainerStartable
import com.android.systemui.scene.domain.startable.ScrimStartable
import com.android.systemui.scene.domain.startable.StatusBarStartable
+import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.shared.flag.DualShade
@@ -43,13 +43,13 @@
[
BouncerSceneModule::class,
CommunalSceneModule::class,
- ComposeBouncerFlagsModule::class,
EmptySceneModule::class,
GoneSceneModule::class,
LockscreenSceneModule::class,
QuickSettingsSceneModule::class,
ShadeSceneModule::class,
QuickSettingsShadeSceneModule::class,
+ NotificationsShadeOverlayModule::class,
NotificationsShadeSceneModule::class,
NotificationsShadeSessionModule::class,
SceneDomainModule::class,
@@ -108,6 +108,10 @@
Scenes.Shade.takeUnless { DualShade.isEnabled },
),
initialSceneKey = Scenes.Lockscreen,
+ overlayKeys =
+ listOfNotNull(
+ Overlays.NotificationsShade.takeIf { DualShade.isEnabled },
+ ),
navigationDistances =
mapOf(
Scenes.Gone to 0,
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt
index c176cca..2d40845 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt
@@ -58,29 +58,20 @@
fun onSceneChange(from: SceneKey, to: SceneKey) {
check(from != to) { "from == to, from=${from.debugName}, to=${to.debugName}" }
- when (stackOperation(from, to)) {
- Clear -> {
- _backStack.value = sceneStackOf()
- }
- Push -> {
- _backStack.update { s -> s.push(from) }
- }
- Pop -> {
- _backStack.update { s ->
- checkNotNull(s.pop()) { "Cannot pop ${from.debugName} when stack is empty" }
- .also {
- val popped = s.peek()
- check(popped == to) {
- "Expected to pop ${to.debugName} but instead popped ${popped?.debugName}"
- }
- }
- }
+
+ _backStack.update { stack ->
+ when (stackOperation(from, to, stack)) {
+ null -> stack
+ Clear -> sceneStackOf()
+ Push -> stack.push(from)
+ Pop ->
+ checkNotNull(stack.pop()) { "Cannot pop ${from.debugName} when stack is empty" }
}
}
logger.logSceneBackStack(backStack.value.asIterable())
}
- private fun stackOperation(from: SceneKey, to: SceneKey): StackOperation {
+ private fun stackOperation(from: SceneKey, to: SceneKey, stack: SceneStack): StackOperation? {
val fromDistance =
checkNotNull(sceneContainerConfig.navigationDistances[from]) {
"No distance mapping for scene \"${from.debugName}\"!"
@@ -93,6 +84,7 @@
return when {
toDistance == 0 -> Clear
toDistance > fromDistance -> Push
+ stack.peek() != to -> null
toDistance < fromDistance -> Pop
else ->
error(
@@ -103,7 +95,10 @@
}
private sealed interface StackOperation
+
private data object Clear : StackOperation
+
private data object Push : StackOperation
+
private data object Pop : StackOperation
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
index 6c63c97..751448f 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
@@ -27,7 +27,7 @@
import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.shared.ComposeLockscreen
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
import com.android.systemui.statusbar.phone.PredictiveBackSysUiFlag
/** Helper for reading or using the scene container flag state. */
@@ -43,7 +43,7 @@
KeyguardBottomAreaRefactor.isEnabled &&
KeyguardWmStateRefactor.isEnabled &&
MigrateClocksToBlueprint.isEnabled &&
- NotificationsHeadsUpRefactor.isEnabled &&
+ NotificationThrottleHun.isEnabled &&
PredictiveBackSysUiFlag.isEnabled &&
DeviceEntryUdfpsRefactor.isEnabled
@@ -59,7 +59,7 @@
KeyguardBottomAreaRefactor.token,
KeyguardWmStateRefactor.token,
MigrateClocksToBlueprint.token,
- NotificationsHeadsUpRefactor.token,
+ NotificationThrottleHun.token,
PredictiveBackSysUiFlag.token,
DeviceEntryUdfpsRefactor.token,
// NOTE: Changes should also be made in isEnabled and @EnableSceneContainer
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Overlays.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Overlays.kt
new file mode 100644
index 0000000..0bb02e9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Overlays.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.shared.model
+
+import com.android.compose.animation.scene.OverlayKey
+
+/**
+ * Keys of all known overlays.
+ *
+ * PLEASE KEEP THE KEYS SORTED ALPHABETICALLY.
+ */
+object Overlays {
+ /**
+ * The notifications shade overlay primarily shows a scrollable list of notifications.
+ *
+ * It's used only in the dual shade configuration, where there are two separate shades: one for
+ * notifications (this overlay) and another for [QuickSettingsShade].
+ *
+ * It's not used in the single/accordion configuration (swipe down once to reveal the shade,
+ * swipe down again the to expand quick settings) or in the "split" shade configuration (on
+ * large screens or unfolded foldables, where notifications and quick settings are shown
+ * side-by-side in their own columns).
+ */
+ @JvmField val NotificationsShade = OverlayKey("notifications_shade")
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneActionsViewModel.kt
index b707a5a..88d4c4f 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneActionsViewModel.kt
@@ -29,7 +29,6 @@
import com.android.systemui.shade.shared.model.ShadeMode
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
-import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.map
class GoneSceneActionsViewModel
@@ -82,7 +81,7 @@
}
}
}
- .collectLatest { setActions(it) }
+ .collect { setActions(it) }
}
@AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt
index 368e4fa..0766130 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt
@@ -32,6 +32,7 @@
* need to worry about resetting the value of [actions] when the view-model is deactivated/canceled,
* this base class takes care of it.
*/
+// TODO(b/363206563): Rename to UserActionsViewModel.
abstract class SceneActionsViewModel : ExclusiveActivatable() {
private val _actions = MutableStateFlow<Map<UserAction, UserActionResult>>(emptyMap())
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
index 474afa8b..56afb79 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
@@ -9,7 +9,6 @@
import android.view.ViewTreeObserver
import android.view.animation.AccelerateDecelerateInterpolator
import androidx.constraintlayout.widget.Guideline
-import com.android.systemui.Flags.screenshotPrivateProfileBehaviorFix
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.res.R
import com.android.systemui.screenshot.message.ProfileMessageController
@@ -49,44 +48,19 @@
}
fun onScreenshotTaken(screenshot: ScreenshotData) {
- if (screenshotPrivateProfileBehaviorFix()) {
- mainScope.launch {
- val profileData = profileMessageController.onScreenshotTaken(screenshot.userHandle)
- var notifiedApps: List<CharSequence> =
- screenshotDetectionController.maybeNotifyOfScreenshot(screenshot)
-
- // If profile first run needs to show, bias towards that, otherwise show screenshot
- // detection notification if needed.
- if (profileData != null) {
- workProfileFirstRunView.visibility = View.VISIBLE
- detectionNoticeView.visibility = View.GONE
- profileMessageController.bindView(workProfileFirstRunView, profileData) {
- animateOutMessageContainer()
- }
- animateInMessageContainer()
- } else if (notifiedApps.isNotEmpty()) {
- detectionNoticeView.visibility = View.VISIBLE
- workProfileFirstRunView.visibility = View.GONE
- screenshotDetectionController.populateView(detectionNoticeView, notifiedApps)
- animateInMessageContainer()
- }
- }
- } else {
- val workProfileData =
- workProfileMessageController.onScreenshotTaken(screenshot.userHandle)
+ mainScope.launch {
+ val profileData = profileMessageController.onScreenshotTaken(screenshot.userHandle)
var notifiedApps: List<CharSequence> =
screenshotDetectionController.maybeNotifyOfScreenshot(screenshot)
- // If work profile first run needs to show, bias towards that, otherwise show screenshot
+ // If profile first run needs to show, bias towards that, otherwise show screenshot
// detection notification if needed.
- if (workProfileData != null) {
+ if (profileData != null) {
workProfileFirstRunView.visibility = View.VISIBLE
detectionNoticeView.visibility = View.GONE
- workProfileMessageController.populateView(
- workProfileFirstRunView,
- workProfileData,
- this::animateOutMessageContainer
- )
+ profileMessageController.bindView(workProfileFirstRunView, profileData) {
+ animateOutMessageContainer()
+ }
animateInMessageContainer()
} else if (notifiedApps.isNotEmpty()) {
detectionNoticeView.visibility = View.VISIBLE
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
deleted file mode 100644
index 922997d..0000000
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.screenshot
-
-import android.util.Log
-import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
-
-/** Implementation of [ScreenshotRequestProcessor] */
-class RequestProcessor(
- private val capture: ImageCapture,
- private val policy: ScreenshotPolicy,
-) : ScreenshotRequestProcessor {
-
- override suspend fun process(screenshot: ScreenshotData): ScreenshotData {
- var result = screenshot
-
- // Apply work profile screenshots policy:
- //
- // If the focused app belongs to a work profile, transforms a full screen
- // (or partial) screenshot request to a task snapshot (provided image) screenshot.
-
- // Whenever displayContentInfo is fetched, the topComponent is also populated
- // regardless of the managed profile status.
-
- if (screenshot.type != TAKE_SCREENSHOT_PROVIDED_IMAGE) {
- val info = policy.findPrimaryContent(screenshot.displayId)
- Log.d(TAG, "findPrimaryContent: $info")
- result.taskId = info.taskId
- result.topComponent = info.component
- result.userHandle = info.user
-
- if (policy.isManagedProfile(info.user.identifier)) {
- val image =
- capture.captureTask(info.taskId)
- ?: throw RequestProcessorException("Task snapshot returned a null Bitmap!")
-
- // Provide the task snapshot as the screenshot
- result.type = TAKE_SCREENSHOT_PROVIDED_IMAGE
- result.bitmap = image
- result.screenBounds = info.bounds
- }
- }
-
- return result
- }
-}
-
-private const val TAG = "RequestProcessor"
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt
index 3ad4075a..ee1008d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt
@@ -27,5 +27,5 @@
suspend fun process(original: ScreenshotData): ScreenshotData
}
-/** Exception thrown by [RequestProcessor] if something goes wrong. */
+/** Exception thrown by [ScreenshotRequestProcessor] if something goes wrong. */
class RequestProcessorException(message: String) : IllegalStateException(message)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
index 9db1f24..ad5e772 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
@@ -71,7 +71,9 @@
import com.android.systemui.screenshot.scroll.CropView;
import com.android.systemui.settings.UserTracker;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
@@ -344,10 +346,63 @@
// Set up the dropdown when multiple backlinks are available.
if (backlinksData.size() > 1) {
- setUpListPopupWindow(backlinksData, mBacklinksDataTextView);
+ setUpListPopupWindow(updateBacklinkLabelsWithDuplicateNames(backlinksData),
+ mBacklinksDataTextView);
}
}
+ /**
+ * If there are more than 1 backlinks that have the same app name, then this method appends
+ * a numerical suffix to such backlinks to help users distinguish.
+ */
+ private List<InternalBacklinksData> updateBacklinkLabelsWithDuplicateNames(
+ List<InternalBacklinksData> backlinksData) {
+ // Check if there are multiple backlinks with same name.
+ Map<String, Integer> duplicateNamedBacklinksCountMap = new HashMap<>();
+ for (InternalBacklinksData data : backlinksData) {
+ if (duplicateNamedBacklinksCountMap.containsKey(data.getDisplayLabel())) {
+ int duplicateCount = duplicateNamedBacklinksCountMap.get(data.getDisplayLabel());
+ if (duplicateCount == 0) {
+ // If this is the first time the loop is coming across a duplicate name, set the
+ // count to 2. This way the count starts from 1 for all duplicate named
+ // backlinks.
+ duplicateNamedBacklinksCountMap.put(data.getDisplayLabel(), 2);
+ } else {
+ // For all duplicate named backlinks, increase the duplicate count by 1.
+ duplicateNamedBacklinksCountMap.put(data.getDisplayLabel(), duplicateCount + 1);
+ }
+ } else {
+ // This is the first time the loop is coming across a backlink with this name. Set
+ // its count to 0. The loop will increase its count by 1 when a duplicate is found.
+ duplicateNamedBacklinksCountMap.put(data.getDisplayLabel(), 0);
+ }
+ }
+
+ // Go through the backlinks in reverse order as it is easier to assign the numerical suffix
+ // in descending order of frequency using the duplicate map that was built earlier. For
+ // example, if "App A" is present 3 times, then we assign display label "App A (3)" first
+ // and then "App A (2)", lastly "App A (1)".
+ for (InternalBacklinksData data : backlinksData.reversed()) {
+ String originalBacklinkLabel = data.getDisplayLabel();
+ int duplicateCount = duplicateNamedBacklinksCountMap.get(originalBacklinkLabel);
+
+ // The display label should only be updated if there are multiple backlinks with the
+ // same name.
+ if (duplicateCount > 0) {
+ // Update the display label to: "App name (count)"
+ data.setDisplayLabel(
+ getString(R.string.backlinks_duplicate_label_format, originalBacklinkLabel,
+ duplicateCount));
+
+ // Decrease the duplicate count and update the map.
+ duplicateCount--;
+ duplicateNamedBacklinksCountMap.put(originalBacklinkLabel, duplicateCount);
+ }
+ }
+
+ return backlinksData;
+ }
+
private void setUpListPopupWindow(List<InternalBacklinksData> backlinksData, View anchor) {
ListPopupWindow listPopupWindow = new ListPopupWindow(this);
listPopupWindow.setAnchorView(anchor);
@@ -365,7 +420,7 @@
public View getView(int position, @Nullable View convertView, ViewGroup parent) {
TextView itemView = (TextView) super.getView(position, convertView, parent);
InternalBacklinksData data = backlinksData.get(position);
- itemView.setText(data.getClipData().getDescription().getLabel());
+ itemView.setText(data.getDisplayLabel());
Drawable icon = data.getAppIcon();
icon.setBounds(createBacklinksTextViewDrawableBounds());
@@ -387,7 +442,7 @@
* expected to be already set when this method is called.
*/
private void updateBacklinksTextView(InternalBacklinksData backlinksData) {
- mBacklinksDataTextView.setText(backlinksData.getClipData().getDescription().getLabel());
+ mBacklinksDataTextView.setText(backlinksData.getDisplayLabel());
Drawable appIcon = backlinksData.getAppIcon();
Rect compoundDrawableBounds = createBacklinksTextViewDrawableBounds();
appIcon.setBounds(compoundDrawableBounds);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/InternalBacklinksData.kt b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/InternalBacklinksData.kt
index 0e312f9..30c33c5 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/InternalBacklinksData.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/InternalBacklinksData.kt
@@ -20,4 +20,6 @@
import android.graphics.drawable.Drawable
/** A class to hold the [ClipData] for backlinks and the corresponding app's [Drawable] icon. */
-internal data class InternalBacklinksData(val clipData: ClipData, val appIcon: Drawable)
+internal data class InternalBacklinksData(val clipData: ClipData, val appIcon: Drawable) {
+ var displayLabel: String = clipData.description.label.toString()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt
index 44f767a..2cb9fe7 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt
@@ -19,14 +19,11 @@
import android.content.ComponentName
import android.content.Context
import android.os.Process
-import com.android.systemui.Flags.screenshotPrivateProfileBehaviorFix
import com.android.systemui.SystemUIService
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.screenshot.ImageCapture
-import com.android.systemui.screenshot.RequestProcessor
-import com.android.systemui.screenshot.ScreenshotPolicy
import com.android.systemui.screenshot.ScreenshotRequestProcessor
import com.android.systemui.screenshot.data.repository.DisplayContentRepository
import com.android.systemui.screenshot.data.repository.DisplayContentRepositoryImpl
@@ -68,23 +65,18 @@
@Application context: Context,
@Background background: CoroutineDispatcher,
imageCapture: ImageCapture,
- policyProvider: Provider<ScreenshotPolicy>,
- displayContentRepoProvider: Provider<DisplayContentRepository>,
+ displayContentRepo: DisplayContentRepository,
policyListProvider: Provider<List<CapturePolicy>>,
): ScreenshotRequestProcessor {
- return if (screenshotPrivateProfileBehaviorFix()) {
- PolicyRequestProcessor(
- background = background,
- capture = imageCapture,
- displayTasks = displayContentRepoProvider.get(),
- policies = policyListProvider.get(),
- defaultOwner = Process.myUserHandle(),
- defaultComponent =
- ComponentName(context.packageName, SystemUIService::class.java.toString())
- )
- } else {
- RequestProcessor(imageCapture, policyProvider.get())
- }
+ return PolicyRequestProcessor(
+ background = background,
+ capture = imageCapture,
+ displayTasks = displayContentRepo,
+ policies = policyListProvider.get(),
+ defaultOwner = Process.myUserHandle(),
+ defaultComponent =
+ ComponentName(context.packageName, SystemUIService::class.java.toString())
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt
index e7ee961..edff4bf 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt
@@ -17,6 +17,7 @@
package com.android.systemui.settings
import android.view.Display
+import com.android.systemui.util.annotations.WeaklyReferencedCallback
import java.util.concurrent.Executor
/**
@@ -52,6 +53,7 @@
fun getDisplay(displayId: Int): Display
/** Ćallback for notifying of changes. */
+ @WeaklyReferencedCallback
interface Callback {
/** Notifies that a display has been added. */
diff --git a/packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java b/packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java
index 05f19ef..b9f9b92 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java
@@ -16,7 +16,6 @@
package com.android.systemui.settings;
-import android.app.ActivityManager;
import android.app.IActivityManager;
import android.content.Context;
import android.hardware.display.DisplayManager;
@@ -67,7 +66,7 @@
@Background CoroutineDispatcher backgroundDispatcher,
@Background Handler handler
) {
- int startingUser = ActivityManager.getCurrentUser();
+ int startingUser = userManager.getBootUser().getIdentifier();
UserTrackerImpl tracker = new UserTrackerImpl(context, featureFlagsProvider, userManager,
iActivityManager, dumpManager, appScope, backgroundDispatcher, handler);
tracker.initialize(startingUser);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index 4639e22..3bb494b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -203,6 +203,13 @@
*/
private var shadeConsumingTouches = false
+ /**
+ * True if the shade is showing at all.
+ *
+ * Inverse of [ShadeInteractor.isShadeFullyCollapsed]
+ */
+ private var shadeShowing = false
+
/** True if the keyguard transition state is finished on [KeyguardState.LOCKSCREEN]. */
private var onLockscreen = false
@@ -414,6 +421,7 @@
),
{ (isFullyExpanded, isUserInteracting, isShadeFullyCollapsed) ->
shadeConsumingTouches = isUserInteracting
+ shadeShowing = !isShadeFullyCollapsed
val expandedAndNotInteractive = isFullyExpanded && !isUserInteracting
// If we ever are fully expanded and not interacting, capture this state as we
@@ -529,7 +537,7 @@
val isMove = ev.actionMasked == MotionEvent.ACTION_MOVE
val isCancel = ev.actionMasked == MotionEvent.ACTION_CANCEL
- val hubOccluded = anyBouncerShowing || shadeShowingAndConsumingTouches
+ val hubOccluded = anyBouncerShowing || shadeConsumingTouches || shadeShowing
if ((isDown || isMove) && !hubOccluded) {
if (isDown) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index c023b83..31813b2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -191,12 +191,10 @@
import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
-import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor;
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -439,7 +437,6 @@
private boolean mExpandingFromHeadsUp;
private boolean mCollapsedOnDown;
private boolean mClosingWithAlphaFadeOut;
- private boolean mHeadsUpVisible;
private boolean mHeadsUpAnimatingAway;
private final FalsingManager mFalsingManager;
private final FalsingCollector mFalsingCollector;
@@ -610,7 +607,6 @@
private final PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
private final SharedNotificationContainerInteractor mSharedNotificationContainerInteractor;
private final ActiveNotificationsInteractor mActiveNotificationsInteractor;
- private final HeadsUpNotificationInteractor mHeadsUpNotificationInteractor;
private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
private final KeyguardInteractor mKeyguardInteractor;
private final PowerInteractor mPowerInteractor;
@@ -776,7 +772,6 @@
ActivityStarter activityStarter,
SharedNotificationContainerInteractor sharedNotificationContainerInteractor,
ActiveNotificationsInteractor activeNotificationsInteractor,
- HeadsUpNotificationInteractor headsUpNotificationInteractor,
ShadeAnimationInteractor shadeAnimationInteractor,
KeyguardViewConfigurator keyguardViewConfigurator,
DeviceEntryFaceAuthInteractor deviceEntryFaceAuthInteractor,
@@ -811,7 +806,6 @@
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
mSharedNotificationContainerInteractor = sharedNotificationContainerInteractor;
mActiveNotificationsInteractor = activeNotificationsInteractor;
- mHeadsUpNotificationInteractor = headsUpNotificationInteractor;
mKeyguardInteractor = keyguardInteractor;
mPowerInteractor = powerInteractor;
mKeyguardViewConfigurator = keyguardViewConfigurator;
@@ -1222,11 +1216,6 @@
}
},
mMainDispatcher);
-
- if (NotificationsHeadsUpRefactor.isEnabled()) {
- collectFlow(mView, mHeadsUpNotificationInteractor.isHeadsUpOrAnimatingAway(),
- setHeadsUpVisible(), mMainDispatcher);
- }
}
@VisibleForTesting
@@ -3077,21 +3066,7 @@
mPanelAlphaEndAction = r;
}
- private Consumer<Boolean> setHeadsUpVisible() {
- return (Boolean isHeadsUpVisible) -> {
- mHeadsUpVisible = isHeadsUpVisible;
-
- if (isHeadsUpVisible) {
- updateNotificationTranslucency();
- }
- updateExpansionAndVisibility();
- updateGestureExclusionRect();
- mKeyguardStatusBarViewController.updateForHeadsUp();
- };
- }
-
private void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
- NotificationsHeadsUpRefactor.assertInLegacyMode();
mHeadsUpAnimatingAway = headsUpAnimatingAway;
mNotificationStackScrollLayoutController.setHeadsUpAnimatingAway(headsUpAnimatingAway);
updateVisibility();
@@ -3107,16 +3082,13 @@
}
private boolean shouldPanelBeVisible() {
- boolean headsUpVisible = NotificationsHeadsUpRefactor.isEnabled() ? mHeadsUpVisible
- : (mHeadsUpAnimatingAway || mHeadsUpPinnedMode);
+ boolean headsUpVisible = mHeadsUpAnimatingAway || mHeadsUpPinnedMode;
return headsUpVisible || isExpanded() || mBouncerShowing;
}
private void setHeadsUpManager(HeadsUpManager headsUpManager) {
mHeadsUpManager = headsUpManager;
- if (!NotificationsHeadsUpRefactor.isEnabled()) {
- mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
- }
+ mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
mHeadsUpTouchHelper = new HeadsUpTouchHelper(
headsUpManager,
mStatusBarService,
@@ -3204,8 +3176,7 @@
}
private boolean isPanelVisibleBecauseOfHeadsUp() {
- boolean headsUpVisible = NotificationsHeadsUpRefactor.isEnabled() ? mHeadsUpVisible
- : (mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway);
+ boolean headsUpVisible = mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway;
return headsUpVisible && mBarState == StatusBarState.SHADE;
}
@@ -3521,7 +3492,6 @@
ipw.print("mExpandingFromHeadsUp="); ipw.println(mExpandingFromHeadsUp);
ipw.print("mCollapsedOnDown="); ipw.println(mCollapsedOnDown);
ipw.print("mClosingWithAlphaFadeOut="); ipw.println(mClosingWithAlphaFadeOut);
- ipw.print("mHeadsUpVisible="); ipw.println(mHeadsUpVisible);
ipw.print("mHeadsUpAnimatingAway="); ipw.println(mHeadsUpAnimatingAway);
ipw.print("mShowIconsWhenExpanded="); ipw.println(mShowIconsWhenExpanded);
ipw.print("mIndicationBottomPadding="); ipw.println(mIndicationBottomPadding);
@@ -4446,8 +4416,6 @@
private final class ShadeHeadsUpChangedListener implements OnHeadsUpChangedListener {
@Override
public void onHeadsUpPinnedModeChanged(final boolean inPinnedMode) {
- NotificationsHeadsUpRefactor.assertInLegacyMode();
-
if (inPinnedMode) {
mHeadsUpExistenceChangedRunnable.run();
updateNotificationTranslucency();
@@ -4464,8 +4432,6 @@
@Override
public void onHeadsUpPinned(NotificationEntry entry) {
- NotificationsHeadsUpRefactor.assertInLegacyMode();
-
if (!isKeyguardShowing()) {
mNotificationStackScrollLayoutController.generateHeadsUpAnimation(entry, true);
}
@@ -4473,8 +4439,6 @@
@Override
public void onHeadsUpUnPinned(NotificationEntry entry) {
- NotificationsHeadsUpRefactor.assertInLegacyMode();
-
// When we're unpinning the notification via active edge they remain heads-upped,
// we need to make sure that an animation happens in this case, otherwise the
// notification
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index 16aef65..f1d9764 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -1005,7 +1005,7 @@
// When expanding QS, let's authenticate the user if possible,
// this will speed up notification actions.
if (height == 0 && !mKeyguardStateController.canDismissLockScreen()) {
- mDeviceEntryFaceAuthInteractor.onQsExpansionStared();
+ mDeviceEntryFaceAuthInteractor.onShadeExpansionStarted();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt
index c210fc5..abf1f4c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt
@@ -28,7 +28,6 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.collectLatest
/**
* Models UI state and handles user input for the overlay shade UI, which shows a shade as an
@@ -48,7 +47,7 @@
val panelAlignment = shadeInteractor.shadeAlignment
override suspend fun onActivated(): Nothing {
- sceneInteractor.resolveSceneFamily(SceneFamilies.Home).collectLatest { sceneKey ->
+ sceneInteractor.resolveSceneFamily(SceneFamilies.Home).collect { sceneKey ->
_backgroundScene.value = sceneKey
}
awaitCancellation()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
index c8abb1f..a154e91 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
@@ -46,7 +46,6 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
@@ -132,12 +131,10 @@
launch {
mobileIconsInteractor.filteredSubscriptions
.map { list -> list.map { it.subscriptionId } }
- .collectLatest { _mobileSubIds.value = it }
+ .collect { _mobileSubIds.value = it }
}
- launch {
- shadeInteractor.isQsEnabled.map { !it }.collectLatest { _isDisabled.value = it }
- }
+ launch { shadeInteractor.isQsEnabled.map { !it }.collect { _isDisabled.value = it } }
awaitCancellation()
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModel.kt
index bdc0fdb..ab71913 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModel.kt
@@ -29,7 +29,6 @@
import com.android.systemui.shade.shared.model.ShadeMode
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
-import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
/**
@@ -67,7 +66,7 @@
}
}
}
- .collectLatest { actions -> setActions(actions) }
+ .collect { actions -> setActions(actions) }
}
@AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt
index 7a79a22..7c70759 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt
@@ -35,12 +35,10 @@
import dagger.assisted.AssistedInject
import java.util.concurrent.atomic.AtomicBoolean
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.collectLatest
/**
* Models UI state used to render the content of the shade scene.
@@ -75,11 +73,9 @@
private val footerActionsControllerInitialized = AtomicBoolean(false)
override suspend fun onActivated(): Nothing {
- deviceEntryInteractor.isDeviceEntered.collectLatest { isDeviceEntered ->
+ deviceEntryInteractor.isDeviceEntered.collect { isDeviceEntered ->
_isEmptySpaceClickable.value = !isDeviceEntered
}
-
- awaitCancellation()
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 696e222..d523bc1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -255,8 +255,8 @@
}
final float stackBottom = SceneContainerFlag.isEnabled()
- ? ambientState.getStackTop() + ambientState.getStackHeight()
- : ambientState.getStackY() + ambientState.getStackHeight();
+ ? ambientState.getStackTop() + ambientState.getInterpolatedStackHeight()
+ : ambientState.getStackY() + ambientState.getInterpolatedStackHeight();
if (viewState.hidden) {
// if the shelf is hidden, position it at the end of the stack (plus the clip
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 6eadd26..2b44c2f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -58,6 +58,7 @@
import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.StatusBarIcon;
+import com.android.internal.statusbar.StatusBarIcon.Shape;
import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.Flags;
import com.android.systemui.res.R;
@@ -211,16 +212,19 @@
/** Should always be preceded by {@link #reloadDimens()} */
@VisibleForTesting
public void maybeUpdateIconScaleDimens() {
- // We do not resize and scale system icons (on the right), only notification icons (on the
- // left).
- if (isNotification()) {
- updateIconScaleForNotifications();
+ // We scale notification icons (on the left) plus icons on the right that explicitly
+ // want FIXED_SPACE.
+ boolean useNonSystemIconScaling = isNotification()
+ || (usesModeIcons() && mIcon != null && mIcon.shape == Shape.FIXED_SPACE);
+
+ if (useNonSystemIconScaling) {
+ updateIconScaleForNonSystemIcons();
} else {
updateIconScaleForSystemIcons();
}
}
- private void updateIconScaleForNotifications() {
+ private void updateIconScaleForNonSystemIcons() {
float iconScale;
// we need to scale the image size to be same as the original size
// (fit mOriginalStatusBarIconSize), then we can scale it with mScaleToFitNewIconSize
@@ -411,7 +415,9 @@
if (!levelEquals) {
setImageLevel(icon.iconLevel);
}
-
+ if (usesModeIcons()) {
+ setScaleType(icon.shape == Shape.FIXED_SPACE ? ScaleType.FIT_CENTER : ScaleType.CENTER);
+ }
if (!visibilityEquals) {
setVisibility(icon.visible && !mBlocked ? VISIBLE : GONE);
}
@@ -501,7 +507,12 @@
@Nullable
private Drawable loadDrawable(Context context, StatusBarIcon statusBarIcon) {
if (usesModeIcons() && statusBarIcon.preloadedIcon != null) {
- return statusBarIcon.preloadedIcon.mutate();
+ Drawable.ConstantState cached = statusBarIcon.preloadedIcon.getConstantState();
+ if (cached != null) {
+ return cached.newDrawable(mContext.getResources()).mutate();
+ } else {
+ return statusBarIcon.preloadedIcon.mutate();
+ }
} else {
int userId = statusBarIcon.user.getIdentifier();
if (userId == UserHandle.USER_ALL) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt
index 173ff37..be733d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt
@@ -16,14 +16,24 @@
package com.android.systemui.statusbar.chips
+import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.DemoRonChipViewModel
+import dagger.Binds
import dagger.Module
import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
@Module
abstract class StatusBarChipsModule {
+ @Binds
+ @IntoMap
+ @ClassKey(DemoRonChipViewModel::class)
+ abstract fun binds(impl: DemoRonChipViewModel): CoreStartable
+
companion object {
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
index 18ea0b4..e825258 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
@@ -69,7 +69,7 @@
state.notificationIconView
)
} else {
- OngoingActivityChipModel.ChipIcon.Basic(phoneIcon)
+ OngoingActivityChipModel.ChipIcon.SingleColorIcon(phoneIcon)
}
// This block mimics OngoingCallController#updateChip.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
index cf4e707..d4ad6ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
@@ -190,7 +190,7 @@
): OngoingActivityChipModel.Shown {
return OngoingActivityChipModel.Shown.Timer(
icon =
- OngoingActivityChipModel.ChipIcon.Basic(
+ OngoingActivityChipModel.ChipIcon.SingleColorIcon(
Icon.Resource(
CAST_TO_OTHER_DEVICE_ICON,
// This string is "Casting screen"
@@ -215,7 +215,7 @@
private fun createIconOnlyCastChip(deviceName: String?): OngoingActivityChipModel.Shown {
return OngoingActivityChipModel.Shown.IconOnly(
icon =
- OngoingActivityChipModel.ChipIcon.Basic(
+ OngoingActivityChipModel.ChipIcon.SingleColorIcon(
Icon.Resource(
CAST_TO_OTHER_DEVICE_ICON,
// This string is just "Casting"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModel.kt
new file mode 100644
index 0000000..cce9a16
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModel.kt
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel
+
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.NameNotFoundException
+import android.graphics.drawable.Drawable
+import com.android.systemui.CoreStartable
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.chips.ron.shared.StatusBarRonChips
+import com.android.systemui.statusbar.chips.ui.model.ColorsModel
+import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.statusbar.commandline.ParseableCommand
+import com.android.systemui.statusbar.commandline.Type
+import com.android.systemui.util.time.SystemClock
+import java.io.PrintWriter
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * A view model that will emit demo RON chips (rich ongoing notification chips) from [chip] based on
+ * adb commands sent by the user.
+ *
+ * Example adb commands:
+ *
+ * To show a chip with the SysUI icon and custom text and color:
+ * ```
+ * adb shell cmd statusbar demo-ron -p com.android.systemui -t 10min -c "\\#434343"
+ * ```
+ *
+ * To hide the chip:
+ * ```
+ * adb shell cmd statusbar demo-ron --hide
+ * ```
+ *
+ * See [DemoRonCommand] for more information on the adb command spec.
+ */
+@SysUISingleton
+class DemoRonChipViewModel
+@Inject
+constructor(
+ private val commandRegistry: CommandRegistry,
+ private val packageManager: PackageManager,
+ private val systemClock: SystemClock,
+) : OngoingActivityChipViewModel, CoreStartable {
+ override fun start() {
+ commandRegistry.registerCommand("demo-ron") { DemoRonCommand() }
+ }
+
+ private val _chip =
+ MutableStateFlow<OngoingActivityChipModel>(OngoingActivityChipModel.Hidden())
+ override val chip: StateFlow<OngoingActivityChipModel> = _chip.asStateFlow()
+
+ private inner class DemoRonCommand : ParseableCommand("demo-ron") {
+ private val packageName: String? by
+ param(
+ longName = "packageName",
+ shortName = "p",
+ description = "The package name for the demo RON app",
+ valueParser = Type.String,
+ )
+
+ private val text: String? by
+ param(
+ longName = "text",
+ shortName = "t",
+ description = "Text to display in the chip",
+ valueParser = Type.String,
+ )
+
+ private val backgroundColor: Int? by
+ param(
+ longName = "color",
+ shortName = "c",
+ description =
+ "The color to show as the chip background color. " +
+ "You can either just write a basic color like 'red' or 'green', " +
+ "or you can include a #RRGGBB string in this format: \"\\\\#434343\".",
+ valueParser = Type.Color,
+ )
+
+ private val hide by
+ flag(
+ longName = "hide",
+ description = "Hides any existing demo RON chip",
+ )
+
+ override fun execute(pw: PrintWriter) {
+ if (!StatusBarRonChips.isEnabled) {
+ pw.println(
+ "Error: com.android.systemui.status_bar_ron_chips must be enabled " +
+ "before using this demo feature"
+ )
+ return
+ }
+
+ if (hide) {
+ _chip.value = OngoingActivityChipModel.Hidden()
+ return
+ }
+
+ val currentPackageName = packageName
+ if (currentPackageName == null) {
+ pw.println("--packageName (or -p) must be included")
+ return
+ }
+
+ val appIcon = getAppIcon(currentPackageName)
+ if (appIcon == null) {
+ pw.println("Package $currentPackageName could not be found")
+ return
+ }
+
+ val colors =
+ if (backgroundColor != null) {
+ ColorsModel.Custom(backgroundColorInt = backgroundColor!!)
+ } else {
+ ColorsModel.Themed
+ }
+
+ val currentText = text
+ if (currentText != null) {
+ _chip.value =
+ OngoingActivityChipModel.Shown.Text(
+ icon = appIcon,
+ colors = colors,
+ text = currentText,
+ )
+ } else {
+ _chip.value =
+ OngoingActivityChipModel.Shown.Timer(
+ icon = appIcon,
+ colors = colors,
+ startTimeMs = systemClock.elapsedRealtime(),
+ onClickListener = null,
+ )
+ }
+ }
+
+ private fun getAppIcon(packageName: String): OngoingActivityChipModel.ChipIcon? {
+ lateinit var iconDrawable: Drawable
+ try {
+ // Note: For the real implementation, we should check if applicationInfo exists
+ // before fetching the icon, so that we either don't show the chip or show a good
+ // backup icon in case the app info can't be found for some reason.
+ iconDrawable = packageManager.getApplicationIcon(packageName)
+ } catch (e: NameNotFoundException) {
+ return null
+ }
+ return OngoingActivityChipModel.ChipIcon.FullColorAppIcon(
+ Icon.Loaded(drawable = iconDrawable, contentDescription = null),
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsHeadsUpRefactor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/shared/StatusBarRonChips.kt
similarity index 72%
copy from packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsHeadsUpRefactor.kt
copy to packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/shared/StatusBarRonChips.kt
index 62641fe..4ef1909 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsHeadsUpRefactor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/shared/StatusBarRonChips.kt
@@ -14,17 +14,17 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.shared
+package com.android.systemui.statusbar.chips.ron.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 heads up refactor flag state. */
+/** Helper for reading or using the status bar RON chips flag state. */
@Suppress("NOTHING_TO_INLINE")
-object NotificationsHeadsUpRefactor {
+object StatusBarRonChips {
/** The aconfig flag name */
- const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_HEADS_UP_REFACTOR
+ const val FLAG_NAME = Flags.FLAG_STATUS_BAR_RON_CHIPS
/** A token used for dependency declaration */
val token: FlagToken
@@ -33,7 +33,7 @@
/** Is the refactor enabled */
@JvmStatic
inline val isEnabled
- get() = Flags.notificationsHeadsUpRefactor()
+ get() = Flags.statusBarRonChips()
/**
* Called to ensure code is only run when the flag is enabled. This protects users from the
@@ -46,6 +46,14 @@
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is not enabled to ensure that the refactor author catches issues in testing.
+ * Caution!! Using this check incorrectly will cause crashes in nextfood builds!
+ */
+ @JvmStatic
+ inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(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
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
index 9e6cacb..eb73521 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
@@ -80,7 +80,7 @@
is ScreenRecordChipModel.Recording -> {
OngoingActivityChipModel.Shown.Timer(
icon =
- OngoingActivityChipModel.ChipIcon.Basic(
+ OngoingActivityChipModel.ChipIcon.SingleColorIcon(
Icon.Resource(
ICON,
ContentDescription.Resource(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
index 7897f93..d99a916 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
@@ -110,7 +110,7 @@
): OngoingActivityChipModel.Shown {
return OngoingActivityChipModel.Shown.Timer(
icon =
- OngoingActivityChipModel.ChipIcon.Basic(
+ OngoingActivityChipModel.ChipIcon.SingleColorIcon(
Icon.Resource(
SHARE_TO_APP_ICON,
ContentDescription.Resource(R.string.share_to_app_chip_accessibility_label),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt
index 8a5165d8..4b0fc5a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt
@@ -39,6 +39,24 @@
Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorPrimary)
}
+ /**
+ * The chip should have the given background color, and text color that matches dark/light
+ * theme.
+ */
+ data class Custom(val backgroundColorInt: Int) : ColorsModel {
+ override fun background(context: Context): ColorStateList =
+ ColorStateList.valueOf(backgroundColorInt)
+
+ // TODO(b/361346412): When dark theme changes, the chip should automatically re-render with
+ // the right text color. Right now, it has the right text color when the chip is first
+ // created but the color doesn't update if dark theme changes.
+ override fun text(context: Context) =
+ Utils.getColorAttrDefaultColor(
+ context,
+ com.android.internal.R.attr.materialColorOnSurface,
+ )
+ }
+
/** The chip should have a red background with white text. */
data object Red : ColorsModel {
override fun background(context: Context): ColorStateList {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
index 26a2f91..62622a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
@@ -89,6 +89,16 @@
) : Shown(icon = null, colors, onClickListener = null) {
override val logName = "Shown.Countdown"
}
+
+ /** This chip shows the specified [text] in the chip. */
+ data class Text(
+ override val icon: ChipIcon,
+ override val colors: ColorsModel,
+ // TODO(b/361346412): Enforce a max length requirement?
+ val text: String,
+ ) : Shown(icon, colors, onClickListener = null) {
+ override val logName = "Shown.Text"
+ }
}
/** Represents an icon to show on the chip. */
@@ -106,7 +116,13 @@
}
}
- /** The icon is a basic resource or drawable icon that System UI created internally. */
- data class Basic(val impl: Icon) : ChipIcon
+ /**
+ * This icon is a single color and it came from basic resource or drawable icon that System
+ * UI created internally.
+ */
+ data class SingleColorIcon(val impl: Icon) : ChipIcon
+
+ /** This icon is an app icon in full color (so it should not get tinted in any way). */
+ data class FullColorAppIcon(val impl: Icon) : ChipIcon
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
index b0d897d..04c4516 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
@@ -23,6 +23,8 @@
import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.call.ui.viewmodel.CallChipViewModel
import com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel.CastToOtherDeviceChipViewModel
+import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.DemoRonChipViewModel
+import com.android.systemui.statusbar.chips.ron.shared.StatusBarRonChips
import com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel.ScreenRecordChipViewModel
import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.ShareToAppChipViewModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
@@ -51,6 +53,7 @@
shareToAppChipViewModel: ShareToAppChipViewModel,
castToOtherDeviceChipViewModel: CastToOtherDeviceChipViewModel,
callChipViewModel: CallChipViewModel,
+ demoRonChipViewModel: DemoRonChipViewModel,
@StatusBarChipsLog private val logger: LogBuffer,
) {
private enum class ChipType {
@@ -58,6 +61,8 @@
ShareToApp,
CastToOtherDevice,
Call,
+ /** A demo of a RON chip (rich ongoing notification chip), used just for testing. */
+ DemoRon,
}
/** Model that helps us internally track the various chip states from each of the types. */
@@ -78,6 +83,7 @@
val shareToApp: OngoingActivityChipModel.Hidden,
val castToOtherDevice: OngoingActivityChipModel.Hidden,
val call: OngoingActivityChipModel.Hidden,
+ val demoRon: OngoingActivityChipModel.Hidden,
) : InternalChipModel
}
@@ -87,7 +93,8 @@
shareToAppChipViewModel.chip,
castToOtherDeviceChipViewModel.chip,
callChipViewModel.chip,
- ) { screenRecord, shareToApp, castToOtherDevice, call ->
+ demoRonChipViewModel.chip,
+ ) { screenRecord, shareToApp, castToOtherDevice, call, demoRon ->
logger.log(
TAG,
LogLevel.INFO,
@@ -98,7 +105,15 @@
},
{ "Chips: ScreenRecord=$str1 > ShareToApp=$str2 > CastToOther=$str3..." },
)
- logger.log(TAG, LogLevel.INFO, { str1 = call.logName }, { "... > Call=$str1" })
+ logger.log(
+ TAG,
+ LogLevel.INFO,
+ {
+ str1 = call.logName
+ str2 = demoRon.logName
+ },
+ { "... > Call=$str1 > DemoRon=$str2" }
+ )
// This `when` statement shows the priority order of the chips.
when {
// Screen recording also activates the media projection APIs, so whenever the
@@ -113,17 +128,23 @@
InternalChipModel.Shown(ChipType.CastToOtherDevice, castToOtherDevice)
call is OngoingActivityChipModel.Shown ->
InternalChipModel.Shown(ChipType.Call, call)
+ demoRon is OngoingActivityChipModel.Shown -> {
+ StatusBarRonChips.assertInNewMode()
+ InternalChipModel.Shown(ChipType.DemoRon, demoRon)
+ }
else -> {
// We should only get here if all chip types are hidden
check(screenRecord is OngoingActivityChipModel.Hidden)
check(shareToApp is OngoingActivityChipModel.Hidden)
check(castToOtherDevice is OngoingActivityChipModel.Hidden)
check(call is OngoingActivityChipModel.Hidden)
+ check(demoRon is OngoingActivityChipModel.Hidden)
InternalChipModel.Hidden(
screenRecord = screenRecord,
shareToApp = shareToApp,
castToOtherDevice = castToOtherDevice,
call = call,
+ demoRon = demoRon,
)
}
}
@@ -154,6 +175,7 @@
ChipType.ShareToApp -> new.shareToApp
ChipType.CastToOtherDevice -> new.castToOtherDevice
ChipType.Call -> new.call
+ ChipType.DemoRon -> new.demoRon
}
} else if (new is InternalChipModel.Shown) {
// If we have a chip to show, always show it.
@@ -179,6 +201,7 @@
shareToApp = OngoingActivityChipModel.Hidden(),
castToOtherDevice = OngoingActivityChipModel.Hidden(),
call = OngoingActivityChipModel.Hidden(),
+ demoRon = OngoingActivityChipModel.Hidden(),
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ValueParser.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ValueParser.kt
index 01083d9..412c8c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ValueParser.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ValueParser.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.commandline
+import androidx.core.graphics.toColorInt
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
@@ -164,10 +165,23 @@
?: Result.failure(ArgParseError("Failed to parse $value as a float"))
}
+// See https://developer.android.com/reference/android/graphics/Color#parseColor(java.lang.String)
+// for the supported formats of the color string. tl;dr: #RRGGBB, #AARRGGBB, or a basic color name
+// like "red" or "green". For the RRGGBB values, the `#` needs to be escaped. Use `"\\#RRGGBB"` in
+// the command to escape the `#` correctly.
+private val parseColor: ValueParser<Int> = ValueParser { value ->
+ try {
+ Result.success(value.toColorInt())
+ } catch (e: IllegalArgumentException) {
+ Result.failure(ArgParseError("Failed to parse $value as a color: $e"))
+ }
+}
+
/** Default parsers that can be use as-is, or [map]ped to another type */
object Type {
val Boolean = parseBoolean
val Int = parseInt
val Float = parseFloat
val String = parseString
+ val Color = parseColor
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index ecb6d7f..406a664 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -22,6 +22,7 @@
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.statusbar.data.StatusBarDataLayerModule
import com.android.systemui.statusbar.phone.LightBarController
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog
import com.android.systemui.statusbar.ui.SystemBarUtilsProxyImpl
@@ -51,6 +52,11 @@
@ClassKey(LightBarController::class)
abstract fun bindLightBarController(impl: LightBarController): CoreStartable
+ @Binds
+ @IntoMap
+ @ClassKey(StatusBarSignalPolicy::class)
+ abstract fun bindStatusBarSignalPolicy(impl: StatusBarSignalPolicy): CoreStartable
+
companion object {
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
index 74ec7ed..aa203d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
@@ -21,11 +21,11 @@
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository
import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository
import com.android.systemui.statusbar.notification.shared.HeadsUpRowKey
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -58,7 +58,7 @@
/** Set of currently pinned top-level heads up rows to be displayed. */
val pinnedHeadsUpRows: Flow<Set<HeadsUpRowKey>> by lazy {
- if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
flowOf(emptySet())
} else {
headsUpRepository.activeHeadsUpRows.flatMapLatest { repositories ->
@@ -80,7 +80,7 @@
/** Are there any pinned heads up rows to display? */
val hasPinnedRows: Flow<Boolean> by lazy {
- if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
flowOf(false)
} else {
headsUpRepository.activeHeadsUpRows.flatMapLatest { rows ->
@@ -95,7 +95,7 @@
}
val isHeadsUpOrAnimatingAway: Flow<Boolean> by lazy {
- if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
flowOf(false)
} else {
combine(hasPinnedRows, headsUpRepository.isHeadsUpAnimatingAway) {
@@ -123,7 +123,7 @@
}
val showHeadsUpStatusBar: Flow<Boolean> by lazy {
- if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
flowOf(false)
} else {
combine(hasPinnedRows, canShowHeadsUp) { hasPinnedRows, canShowHeadsUp ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 4be638f..2f3719a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -173,7 +173,8 @@
}
/**
- * @return Height of the notifications panel without top padding when expansion completes.
+ * @return Height of the available space for the notification content, when the shade
+ * expansion completes.
*/
public float getStackEndHeight() {
return mStackEndHeight;
@@ -276,17 +277,18 @@
}
/**
- * @see #getStackHeight()
+ * @return Height of the notification content returned by {@link #getStackEndHeight()}, but
+ * interpolated by the shade expansion fraction.
*/
- public void setStackHeight(float stackHeight) {
- mStackHeight = stackHeight;
+ public float getInterpolatedStackHeight() {
+ return mStackHeight;
}
/**
- * @return Height of notifications panel interpolated by the expansion fraction.
+ * @see #getInterpolatedStackHeight()
*/
- public float getStackHeight() {
- return mStackHeight;
+ public void setInterpolatedStackHeight(float stackHeight) {
+ mStackHeight = stackHeight;
}
@Inject
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 77c26cb..925ebf3 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
@@ -114,7 +114,6 @@
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling;
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds;
@@ -790,7 +789,6 @@
private void onJustBeforeDraw() {
if (SceneContainerFlag.isEnabled()) {
if (mChildrenUpdateRequested) {
- updateForcedScroll();
updateChildren();
mChildrenUpdateRequested = false;
}
@@ -875,7 +873,7 @@
y = (int) (mAmbientState.getStackY());
drawDebugInfo(canvas, y, Color.CYAN, /* label= */ "mAmbientState.getStackY() = " + y);
- y = (int) (mAmbientState.getStackY() + mAmbientState.getStackHeight());
+ y = (int) (mAmbientState.getStackY() + mAmbientState.getInterpolatedStackHeight());
drawDebugInfo(canvas, y, Color.LTGRAY,
/* label= */ "mAmbientState.getStackY() + mAmbientState.getStackHeight() = " + y);
@@ -1124,11 +1122,13 @@
@Override
public void addStackHeightChangedListener(@NonNull Runnable runnable) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
mStackHeightChangedListeners.addIfAbsent(runnable);
}
@Override
public void removeStackHeightChangedListener(@NonNull Runnable runnable) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
mStackHeightChangedListeners.remove(runnable);
}
@@ -1234,7 +1234,6 @@
if (mAmbientState.getStackTop() != stackTop) {
mAmbientState.setStackTop(stackTop);
onTopPaddingChanged(/* animate = */ isAddOrRemoveAnimationPending());
- setExpandedHeight(mExpandedHeight);
}
}
@@ -1477,7 +1476,7 @@
@VisibleForTesting
public void updateStackEndHeightAndStackHeight(float fraction) {
- final float oldStackHeight = mAmbientState.getStackHeight();
+ final float oldStackHeight = mAmbientState.getInterpolatedStackHeight();
if (SceneContainerFlag.isEnabled()) {
final float endHeight;
if (!shouldSkipHeightUpdate()) {
@@ -1485,20 +1484,20 @@
} else {
endHeight = mAmbientState.getStackEndHeight();
}
- updateStackHeight(endHeight, fraction);
+ updateInterpolatedStackHeight(endHeight, fraction);
} else {
if (mQsExpansionFraction <= 0 && !shouldSkipHeightUpdate()) {
final float endHeight = updateStackEndHeight(
getHeight(), getEmptyBottomMarginInternal(), getTopPadding());
- updateStackHeight(endHeight, fraction);
+ updateInterpolatedStackHeight(endHeight, fraction);
} else {
// Always updateStackHeight to prevent jumps in the stack height when this fraction
// suddenly reapplies after a freeze.
final float endHeight = mAmbientState.getStackEndHeight();
- updateStackHeight(endHeight, fraction);
+ updateInterpolatedStackHeight(endHeight, fraction);
}
}
- if (oldStackHeight != mAmbientState.getStackHeight()) {
+ if (oldStackHeight != mAmbientState.getInterpolatedStackHeight()) {
requestChildrenUpdate();
}
}
@@ -1532,7 +1531,7 @@
}
@VisibleForTesting
- public void updateStackHeight(float endHeight, float fraction) {
+ public void updateInterpolatedStackHeight(float endHeight, float fraction) {
if (!newAodTransition()) {
// During the (AOD<=>LS) transition where dozeAmount is changing,
// apply dozeAmount to stack height instead of expansionFraction
@@ -1542,7 +1541,7 @@
fraction = 1f - dozeAmount;
}
}
- mAmbientState.setStackHeight(
+ mAmbientState.setInterpolatedStackHeight(
MathUtils.lerp(endHeight * StackScrollAlgorithm.START_FRACTION,
endHeight, fraction));
}
@@ -1571,8 +1570,11 @@
// Update the expand progress between started/stopped events
mAmbientState.setExpansionFraction(expandFraction);
- // TODO(b/332577544): don't convert to height which then converts to the fraction again
- setExpandedHeight(expandFraction * getHeight());
+
+ if (!shouldSkipHeightUpdate()) {
+ updateStackEndHeightAndStackHeight(expandFraction);
+ updateExpandedHeight(expandFraction);
+ }
// expansion stopped event requires that the expandFraction has already been updated
if (!nowExpanding && wasExpanding) {
@@ -1581,6 +1583,19 @@
}
}
+ private void updateExpandedHeight(float expandFraction) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+ float expandedHeight = expandFraction * getHeight();
+ setIsExpanded(expandedHeight > 0);
+
+ if (mExpandedHeight != expandedHeight) {
+ mExpandedHeight = expandedHeight;
+ updateAlgorithmHeightAndPadding();
+ requestChildrenUpdate();
+ notifyAppearChangedListeners();
+ }
+ }
+
@Override
public void setQsExpandFraction(float expandFraction) {
if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
@@ -1593,6 +1608,7 @@
* @param height the expanded height of the panel
*/
public void setExpandedHeight(float height) {
+ SceneContainerFlag.assertInLegacyMode();
final boolean skipHeightUpdate = shouldSkipHeightUpdate();
updateStackPosition();
@@ -1724,6 +1740,7 @@
* Measured relative to the resting position.
*/
private float getExpandTranslationStart() {
+ SceneContainerFlag.assertInLegacyMode();
return -getTopPadding() + getMinExpansionHeight() - mShelf.getIntrinsicHeight();
}
@@ -1732,6 +1749,7 @@
* Measured in absolute height.
*/
private float getAppearStartPosition() {
+ SceneContainerFlag.assertInLegacyMode();
if (isHeadsUpTransition()) {
final NotificationSection firstVisibleSection = getFirstVisibleSection();
final int pinnedHeight = firstVisibleSection != null
@@ -1787,6 +1805,7 @@
* have the shelf on its own)
*/
private float getAppearEndPosition() {
+ SceneContainerFlag.assertInLegacyMode();
if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
return getAppearEndPositionLegacy();
}
@@ -1847,7 +1866,7 @@
*/
@FloatRange(from = -1.0, to = 1.0)
public float calculateAppearFraction(float height) {
- if (isHeadsUpTransition()) {
+ if (isHeadsUpTransition() && !SceneContainerFlag.isEnabled()) {
// HUN is a special case because fraction can go negative if swiping up. And for now
// it must go negative as other pieces responsible for proper translation up assume
// negative value for HUN going up.
@@ -1978,7 +1997,8 @@
}
public void lockScrollTo(View v) {
- if (mForcedScroll == v) {
+ // NSSL shouldn't handle scrolling with SceneContainer enabled.
+ if (mForcedScroll == v || SceneContainerFlag.isEnabled()) {
return;
}
mForcedScroll = v;
@@ -1986,6 +2006,10 @@
}
public boolean scrollTo(View v) {
+ // NSSL shouldn't handle scrolling with SceneContainer enabled.
+ if (SceneContainerFlag.isEnabled()) {
+ return false;
+ }
ExpandableView expandableView = (ExpandableView) v;
int positionInLinearLayout = getPositionInLinearLayout(v);
int targetScroll = targetScrollForView(expandableView, positionInLinearLayout);
@@ -2007,6 +2031,7 @@
* the IME.
*/
private int targetScrollForView(ExpandableView v, int positionInLinearLayout) {
+ SceneContainerFlag.assertInLegacyMode();
return positionInLinearLayout + v.getIntrinsicHeight() +
getImeInset() - getHeight()
+ ((!isExpanded() && isPinnedHeadsUp(v)) ? mHeadsUpInset : getTopPadding());
@@ -2523,10 +2548,33 @@
}
@VisibleForTesting
- void updateContentHeight() {
+ void updateStackHeight() {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+
+ final int shelfIntrinsicHeight = mShelf != null ? mShelf.getIntrinsicHeight() : 0;
+ final int footerIntrinsicHeight =
+ mFooterView != null ? mFooterView.getIntrinsicHeight() : 0;
+ final int notificationsHeight = (int) mNotificationStackSizeCalculator.computeHeight(
+ /* notificationStackScrollLayout= */ this,
+ mMaxDisplayedNotifications,
+ shelfIntrinsicHeight
+ );
+ mIntrinsicContentHeight = notificationsHeight;
+ final int fullStackHeight = notificationsHeight + footerIntrinsicHeight + mBottomPadding;
+ if (mScrollViewFields.getIntrinsicStackHeight() != fullStackHeight) {
+ mScrollViewFields.setIntrinsicStackHeight(fullStackHeight);
+ notifyStackHeightChangedListeners();
+ }
+ }
+
+ private void updateContentHeight() {
+ if (SceneContainerFlag.isEnabled()) {
+ updateStackHeight();
+ return;
+ }
+
final float scrimTopPadding = getScrimTopPaddingOrZero();
final int shelfIntrinsicHeight = mShelf != null ? mShelf.getIntrinsicHeight() : 0;
- final int footerIntrinsicHeight = mFooterView != null ? mFooterView.getIntrinsicHeight() : 0;
final float height =
(int) scrimTopPadding + (int) mNotificationStackSizeCalculator.computeHeight(
/* notificationStackScrollLayout= */ this, mMaxDisplayedNotifications,
@@ -2537,19 +2585,15 @@
// state the maxPanelHeight and the contentHeight should be bigger
mContentHeight =
(int) (height + Math.max(getIntrinsicPadding(), getTopPadding()) + mBottomPadding);
- mScrollViewFields.setIntrinsicStackHeight(
- (int) (getIntrinsicPadding() + mIntrinsicContentHeight + footerIntrinsicHeight
- + mBottomPadding));
updateScrollability();
clampScrollPosition();
updateStackPosition();
mAmbientState.setContentHeight(mContentHeight);
-
- notifyStackHeightChangedListeners();
}
@Override
public int getIntrinsicStackHeight() {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return 0;
return mScrollViewFields.getIntrinsicStackHeight();
}
@@ -4133,6 +4177,11 @@
*/
@Override
public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
+ // Don't handle scroll accessibility events from the NSSL, when SceneContainer enabled.
+ if (SceneContainerFlag.isEnabled()) {
+ return super.performAccessibilityActionInternal(action, arguments);
+ }
+
if (super.performAccessibilityActionInternal(action, arguments)) {
return true;
}
@@ -4304,7 +4353,7 @@
// Resetting headsUpAnimatingAway on Shade expansion avoids delays caused by
// waiting for all child animations to finish.
// TODO(b/328390331) Do we need to reset this on QS expanded as well?
- if (NotificationsHeadsUpRefactor.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
setHeadsUpAnimatingAway(false);
}
} else {
@@ -4415,7 +4464,7 @@
void onChildAnimationFinished() {
setAnimationRunning(false);
- if (NotificationsHeadsUpRefactor.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
setHeadsUpAnimatingAway(false);
}
requestChildrenUpdate();
@@ -4894,6 +4943,11 @@
@Override
public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
super.onInitializeAccessibilityEventInternal(event);
+ // Don't handle scroll accessibility events from the NSSL, when SceneContainer enabled.
+ if (SceneContainerFlag.isEnabled()) {
+ return;
+ }
+
event.setScrollable(mScrollable);
event.setMaxScrollX(mScrollX);
event.setScrollY(mOwnScrollY);
@@ -4903,6 +4957,11 @@
@Override
public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfoInternal(info);
+ // Don't handle scroll accessibility events from the NSSL, when SceneContainer enabled.
+ if (SceneContainerFlag.isEnabled()) {
+ return;
+ }
+
if (mScrollable) {
info.setScrollable(true);
if (mBackwardScrollable) {
@@ -4961,7 +5020,7 @@
}
public void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) {
- NotificationsHeadsUpRefactor.assertInLegacyMode();
+ SceneContainerFlag.assertInLegacyMode();
ExpandableNotificationRow row = entry.getHeadsUpAnimationView();
generateHeadsUpAnimation(row, isHeadsUp);
}
@@ -5004,7 +5063,7 @@
mNeedsAnimation = true;
if (!mIsExpanded && !mWillExpand && !isHeadsUp) {
row.setHeadsUpAnimatingAway(true);
- if (NotificationsHeadsUpRefactor.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
setHeadsUpAnimatingAway(true);
}
}
@@ -5198,7 +5257,7 @@
updateClipping();
}
- /** TODO(b/328390331) make this private, when {@link NotificationsHeadsUpRefactor} is removed */
+ /** TODO(b/328390331) make this private, when {@link SceneContainerFlag} is removed */
public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
if (mHeadsUpAnimatingAway != headsUpAnimatingAway) {
mHeadsUpAnimatingAway = headsUpAnimatingAway;
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 4e73529..e112c99 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
@@ -127,7 +127,6 @@
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.NotificationSnooze;
import com.android.systemui.statusbar.notification.shared.GroupHunAnimationFix;
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.HeadsUpNotificationViewControllerEmptyImpl;
@@ -686,13 +685,13 @@
new OnHeadsUpChangedListener() {
@Override
public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) {
- NotificationsHeadsUpRefactor.assertInLegacyMode();
+ SceneContainerFlag.assertInLegacyMode();
mView.setInHeadsUpPinnedMode(inPinnedMode);
}
@Override
public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
- NotificationsHeadsUpRefactor.assertInLegacyMode();
+ SceneContainerFlag.assertInLegacyMode();
NotificationEntry topEntry = mHeadsUpManager.getTopEntry();
mView.setTopHeadsUpRow(topEntry != null ? topEntry.getRow() : null);
generateHeadsUpAnimation(entry, isHeadsUp);
@@ -880,7 +879,7 @@
});
}
- if (!NotificationsHeadsUpRefactor.isEnabled()) {
+ if (!SceneContainerFlag.isEnabled()) {
mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
}
mHeadsUpManager.setAnimationStateHandler(mView::setHeadsUpGoingAwayAnimationsAllowed);
@@ -1409,6 +1408,7 @@
}
public float calculateAppearFraction(float height) {
+ SceneContainerFlag.assertInLegacyMode();
return mView.calculateAppearFraction(height);
}
@@ -1508,7 +1508,7 @@
}
public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
- NotificationsHeadsUpRefactor.assertInLegacyMode();
+ SceneContainerFlag.assertInLegacyMode();
mView.setHeadsUpAnimatingAway(headsUpAnimatingAway);
}
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 0c2b5ae..ef1bcfc 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
@@ -575,7 +575,8 @@
final float shelfHeight = showingShelf ? ambientState.getShelf().getIntrinsicHeight() : 0f;
final float scrimPadding = getScrimTopPaddingOrZero(ambientState);
- final float stackHeight = ambientState.getStackHeight() - shelfHeight - scrimPadding;
+ final float stackHeight =
+ ambientState.getInterpolatedStackHeight() - shelfHeight - scrimPadding;
final float stackEndHeight = ambientState.getStackEndHeight() - shelfHeight - scrimPadding;
if (stackEndHeight == 0f) {
// This should not happen, since even when the shade is empty we show EmptyShadeView
@@ -734,7 +735,7 @@
|| ambientState.getDozeAmount() == 1f
|| bypassPulseNotExpanding
? ambientState.getInnerHeight()
- : ambientState.getStackHeight();
+ : ambientState.getInterpolatedStackHeight();
final float shelfStart = stackBottom
- ambientState.getShelf().getIntrinsicHeight()
- mPaddingBetweenElements;
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 5572f8e..d770b20 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
@@ -27,6 +27,7 @@
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.NotificationShelf
import com.android.systemui.statusbar.notification.NotificationActivityStarter
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController
@@ -36,7 +37,6 @@
import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterViewBinder
import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerShelfViewBinder
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinder
import com.android.systemui.statusbar.notification.stack.DisplaySwitchNotificationsHiderTracker
@@ -93,7 +93,7 @@
view.repeatWhenAttached {
lifecycleScope.launch {
- if (NotificationsHeadsUpRefactor.isEnabled) {
+ if (SceneContainerFlag.isEnabled) {
launch { hunBinder.bindHeadsUpNotifications(view) }
}
launch { bindShelf(shelf) }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index 5fba615..e55492e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -18,6 +18,7 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
@@ -26,7 +27,6 @@
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel
import com.android.systemui.statusbar.notification.shared.HeadsUpRowKey
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackInteractor
import com.android.systemui.statusbar.policy.domain.interactor.UserSetupInteractor
@@ -256,7 +256,7 @@
}
val topHeadsUpRow: Flow<HeadsUpRowKey?> by lazy {
- if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
flowOf(null)
} else {
headsUpNotificationInteractor.topHeadsUpRow.dumpWhileCollecting("topHeadsUpRow")
@@ -264,7 +264,7 @@
}
val pinnedHeadsUpRows: Flow<Set<HeadsUpRowKey>> by lazy {
- if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
flowOf(emptySet())
} else {
headsUpNotificationInteractor.pinnedHeadsUpRows.dumpWhileCollecting("pinnedHeadsUpRows")
@@ -272,7 +272,7 @@
}
val headsUpAnimationsEnabled: Flow<Boolean> by lazy {
- if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
flowOf(false)
} else {
flowOf(true).dumpWhileCollecting("headsUpAnimationsEnabled")
@@ -280,7 +280,7 @@
}
val hasPinnedHeadsUpRow: Flow<Boolean> by lazy {
- if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
flowOf(false)
} else {
headsUpNotificationInteractor.hasPinnedRows.dumpWhileCollecting("hasPinnedHeadsUpRow")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index e3242d1..7227b93 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -31,6 +31,7 @@
import static com.android.systemui.Flags.lightRevealMigration;
import static com.android.systemui.Flags.newAodTransition;
import static com.android.systemui.Flags.relockWithPowerButtonImmediately;
+import static com.android.systemui.Flags.statusBarSignalPolicyRefactor;
import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL;
import static com.android.systemui.flags.Flags.SHORTCUT_LIST_SEARCH_LAYOUT;
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
@@ -870,7 +871,10 @@
mBubblesOptional.ifPresent(this::initBubbles);
mKeyguardBypassController.listenForQsExpandedChange();
- mStatusBarSignalPolicy.init();
+ if (!statusBarSignalPolicyRefactor()) {
+ mStatusBarSignalPolicy.init();
+ }
+
mKeyguardIndicationController.init();
mColorExtractor.addOnColorsChangedListener(mOnColorsChangedListener);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 0ea28a7..1efad3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -47,7 +47,6 @@
import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.AnimationStateHandler;
import com.android.systemui.statusbar.policy.AvalancheController;
@@ -284,7 +283,7 @@
private void onShadeOrQsExpanded(Boolean isExpanded) {
if (isExpanded != mIsExpanded) {
mIsExpanded = isExpanded;
- if (!NotificationsHeadsUpRefactor.isEnabled() && isExpanded) {
+ if (!SceneContainerFlag.isEnabled() && isExpanded) {
mHeadsUpAnimatingAway.setValue(false);
}
}
@@ -517,7 +516,7 @@
@Nullable
private HeadsUpEntryPhone getTopHeadsUpEntryPhone() {
- if (NotificationsHeadsUpRefactor.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
return (HeadsUpEntryPhone) mTopHeadsUpRow.getValue();
} else {
return (HeadsUpEntryPhone) getTopHeadsUpEntry();
@@ -711,7 +710,7 @@
}
private NotificationEntry requireEntry() {
- /* check if */ NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode();
+ /* check if */ SceneContainerFlag.isUnexpectedlyInLegacyMode();
return Objects.requireNonNull(mEntry);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
index 56ea00c..7ef1e41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
@@ -22,6 +22,7 @@
import androidx.annotation.NonNull;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.settings.UserTracker;
@@ -43,17 +44,20 @@
private final UserManager mUserManager;
private final UserTracker mUserTracker;
private final LinkedList<UserInfo> mProfiles;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private boolean mListening;
private int mCurrentUser;
@Inject
public ManagedProfileControllerImpl(Context context, @Main Executor mainExecutor,
- UserTracker userTracker, UserManager userManager) {
+ UserTracker userTracker, UserManager userManager,
+ KeyguardUpdateMonitor keyguardUpdateMonitor) {
mContext = context;
mMainExecutor = mainExecutor;
mUserManager = userManager;
mUserTracker = userTracker;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mProfiles = new LinkedList<>();
}
@@ -80,6 +84,7 @@
StatusBarManager statusBarManager = (StatusBarManager) mContext
.getSystemService(android.app.Service.STATUS_BAR_SERVICE);
statusBarManager.collapsePanels();
+ mKeyguardUpdateMonitor.awakenFromDream();
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 05bd1a7..ba39c3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -44,7 +44,7 @@
import androidx.lifecycle.Observer;
-import com.android.settingslib.notification.modes.ZenMode;
+import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.Flags;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.DisplayId;
@@ -80,6 +80,7 @@
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor;
+import com.android.systemui.statusbar.policy.domain.model.ZenModeInfo;
import com.android.systemui.util.RingerModeTracker;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.time.DateFormatUtil;
@@ -363,7 +364,7 @@
// Note that we're not fully replacing ZenModeController with ZenModeInteractor, so
// we listen for the extra event here but still add the ZMC callback.
mJavaAdapter.alwaysCollectFlow(mZenModeInteractor.getMainActiveMode(),
- this::onActiveModeChanged);
+ this::onMainActiveModeChanged);
}
mZenController.addCallback(mZenControllerCallback);
if (!Flags.statusBarScreenSharingChips()) {
@@ -395,20 +396,23 @@
() -> mResources.getString(R.string.accessibility_managed_profile));
}
- private void onActiveModeChanged(@Nullable ZenMode mode) {
+ private void onMainActiveModeChanged(@Nullable ZenModeInfo mainActiveMode) {
if (!usesModeIcons()) {
- Log.wtf(TAG, "onActiveModeChanged shouldn't be called if MODES_UI_ICONS is disabled");
+ Log.wtf(TAG, "onMainActiveModeChanged shouldn't run if MODES_UI_ICONS is disabled");
return;
}
- boolean visible = mode != null;
- if (visible) {
- // TODO: b/360399800 - Get the resource id, package, and cached drawable from the mode;
- // this is a shortcut for testing.
- String resPackage = mode.getIconKey().resPackage();
- int iconResId = mode.getIconKey().resId();
- mIconController.setResourceIcon(mSlotZen, resPackage, iconResId,
- /* preloadedIcon= */ null, mode.getName());
+ boolean visible = mainActiveMode != null;
+ if (visible) {
+ // Shape=FIXED_SPACE because mode icons can be from 3P packages and may not be square;
+ // we don't want to allow apps to set incredibly wide icons and take up too much space
+ // in the status bar.
+ mIconController.setResourceIcon(mSlotZen,
+ mainActiveMode.getIcon().key().resPackage(),
+ mainActiveMode.getIcon().key().resId(),
+ mainActiveMode.getIcon().drawable(),
+ mainActiveMode.getName(),
+ StatusBarIcon.Shape.FIXED_SPACE);
}
if (visible != mZenVisible) {
mIconController.setIconVisibility(mSlotZen, visible);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
index da5877b..8f2d4f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
@@ -19,12 +19,12 @@
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
@@ -112,7 +112,7 @@
}
private void setHeadsAnimatingAway(boolean headsUpAnimatingAway) {
- if (!NotificationsHeadsUpRefactor.isEnabled()) {
+ if (!SceneContainerFlag.isEnabled()) {
mHeadsUpManager.setHeadsUpAnimatingAway(headsUpAnimatingAway);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 09b6b68..43f9af6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -68,13 +68,11 @@
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dreams.DreamOverlayStateController;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.keyguard.KeyguardWmStateRefactor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardDismissTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
import com.android.systemui.keyguard.shared.model.DismissAction;
import com.android.systemui.keyguard.shared.model.Edge;
import com.android.systemui.keyguard.shared.model.KeyguardDone;
@@ -363,8 +361,6 @@
}
}
};
- private Lazy<WindowManagerLockscreenVisibilityInteractor> mWmLockscreenVisibilityInteractor;
- private Lazy<KeyguardSurfaceBehindInteractor> mSurfaceBehindInteractor;
private Lazy<KeyguardDismissActionInteractor> mKeyguardDismissActionInteractor;
private final JavaAdapter mJavaAdapter;
private StatusBarKeyguardViewManagerInteractor mStatusBarKeyguardViewManagerInteractor;
@@ -394,11 +390,10 @@
UdfpsOverlayInteractor udfpsOverlayInteractor,
ActivityStarter activityStarter,
KeyguardTransitionInteractor keyguardTransitionInteractor,
+ KeyguardDismissTransitionInteractor keyguardDismissTransitionInteractor,
@Main CoroutineDispatcher mainDispatcher,
- Lazy<WindowManagerLockscreenVisibilityInteractor> wmLockscreenVisibilityInteractor,
Lazy<KeyguardDismissActionInteractor> keyguardDismissActionInteractorLazy,
SelectedUserInteractor selectedUserInteractor,
- Lazy<KeyguardSurfaceBehindInteractor> surfaceBehindInteractor,
JavaAdapter javaAdapter,
Lazy<SceneInteractor> sceneInteractorLazy,
StatusBarKeyguardViewManagerInteractor statusBarKeyguardViewManagerInteractor,
@@ -432,11 +427,10 @@
mUdfpsOverlayInteractor = udfpsOverlayInteractor;
mActivityStarter = activityStarter;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
+ mKeyguardDismissTransitionInteractor = keyguardDismissTransitionInteractor;
mMainDispatcher = mainDispatcher;
- mWmLockscreenVisibilityInteractor = wmLockscreenVisibilityInteractor;
mKeyguardDismissActionInteractor = keyguardDismissActionInteractorLazy;
mSelectedUserInteractor = selectedUserInteractor;
- mSurfaceBehindInteractor = surfaceBehindInteractor;
mJavaAdapter = javaAdapter;
mSceneInteractorLazy = sceneInteractorLazy;
mStatusBarKeyguardViewManagerInteractor = statusBarKeyguardViewManagerInteractor;
@@ -445,6 +439,7 @@
}
KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+ KeyguardDismissTransitionInteractor mKeyguardDismissTransitionInteractor;
CoroutineDispatcher mMainDispatcher;
@Override
@@ -1616,7 +1611,7 @@
}
if (KeyguardWmStateRefactor.isEnabled()) {
- mKeyguardTransitionInteractor.startDismissKeyguardTransition(
+ mKeyguardDismissTransitionInteractor.startDismissKeyguardTransition(
"SBKVM#keyguardAuthenticated");
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index ba59398..d5fafe2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.Flags.statusBarSignalPolicyRefactor;
+
import android.annotation.NonNull;
import android.content.Context;
import android.os.Handler;
@@ -23,16 +25,19 @@
import android.util.Log;
import com.android.settingslib.mobile.TelephonyIcons;
+import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.connectivity.IconState;
import com.android.systemui.statusbar.connectivity.NetworkController;
import com.android.systemui.statusbar.connectivity.SignalCallback;
import com.android.systemui.statusbar.phone.ui.StatusBarIconController;
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor;
import com.android.systemui.statusbar.policy.SecurityController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
import com.android.systemui.util.CarrierConfigTracker;
+import com.android.systemui.util.kotlin.JavaAdapter;
import java.util.ArrayList;
import java.util.List;
@@ -40,10 +45,13 @@
import javax.inject.Inject;
-/** Controls the signal policies for icons shown in the statusbar. **/
+/** Controls the signal policies for icons shown in the statusbar. */
@SysUISingleton
-public class StatusBarSignalPolicy implements SignalCallback,
- SecurityController.SecurityControllerCallback, Tunable {
+public class StatusBarSignalPolicy
+ implements SignalCallback,
+ SecurityController.SecurityControllerCallback,
+ Tunable,
+ CoreStartable {
private static final String TAG = "StatusBarSignalPolicy";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -61,16 +69,15 @@
private final Handler mHandler = Handler.getMain();
private final CarrierConfigTracker mCarrierConfigTracker;
private final TunerService mTunerService;
+ private final JavaAdapter mJavaAdapter;
+ private final AirplaneModeInteractor mAirplaneModeInteractor;
private boolean mHideAirplane;
private boolean mHideMobile;
private boolean mHideEthernet;
- private boolean mActivityEnabled;
+ private final boolean mActivityEnabled;
- // Track as little state as possible, and only for padding purposes
- private boolean mIsAirplaneMode = false;
-
- private ArrayList<CallIndicatorIconState> mCallIndicatorStates = new ArrayList<>();
+ private final ArrayList<CallIndicatorIconState> mCallIndicatorStates = new ArrayList<>();
private boolean mInitialized;
@Inject
@@ -80,15 +87,19 @@
CarrierConfigTracker carrierConfigTracker,
NetworkController networkController,
SecurityController securityController,
- TunerService tunerService
+ TunerService tunerService,
+ JavaAdapter javaAdapter,
+ AirplaneModeInteractor airplaneModeInteractor
) {
mContext = context;
mIconController = iconController;
mCarrierConfigTracker = carrierConfigTracker;
+ mJavaAdapter = javaAdapter;
mNetworkController = networkController;
mSecurityController = securityController;
mTunerService = tunerService;
+ mAirplaneModeInteractor = airplaneModeInteractor;
mSlotAirplane = mContext.getString(com.android.internal.R.string.status_bar_airplane);
mSlotMobile = mContext.getString(com.android.internal.R.string.status_bar_mobile);
@@ -100,15 +111,35 @@
mActivityEnabled = mContext.getResources().getBoolean(R.bool.config_showActivity);
}
+ @Override
+ public void start() {
+ if (!statusBarSignalPolicyRefactor()) {
+ return;
+ }
+
+ mTunerService.addTunable(this, StatusBarIconController.ICON_HIDE_LIST);
+ mNetworkController.addCallback(this);
+ mSecurityController.addCallback(this);
+
+ mJavaAdapter.alwaysCollectFlow(
+ mAirplaneModeInteractor.isAirplaneMode(), this::updateAirplaneModeIcon);
+ }
+
/** Call to initialize and register this class with the system. */
public void init() {
- if (mInitialized) {
+ if (mInitialized || statusBarSignalPolicyRefactor()) {
return;
}
mInitialized = true;
mTunerService.addTunable(this, StatusBarIconController.ICON_HIDE_LIST);
mNetworkController.addCallback(this);
mSecurityController.addCallback(this);
+
+ if (statusBarSignalPolicyRefactor()) {
+ mJavaAdapter.alwaysCollectFlow(
+ mAirplaneModeInteractor.isAirplaneMode(),
+ this::updateAirplaneModeIcon);
+ }
}
public void destroy() {
@@ -222,15 +253,19 @@
@Override
public void setIsAirplaneMode(IconState icon) {
+ if (statusBarSignalPolicyRefactor()) {
+ return;
+ }
+
if (DEBUG) {
Log.d(TAG, "setIsAirplaneMode: "
+ "icon = " + (icon == null ? "" : icon.toString()));
}
- mIsAirplaneMode = icon.visible && !mHideAirplane;
+ boolean isAirplaneMode = icon.visible && !mHideAirplane;
int resId = icon.icon;
String description = icon.contentDescription;
- if (mIsAirplaneMode && resId > 0) {
+ if (isAirplaneMode && resId > 0) {
mIconController.setIcon(mSlotAirplane, resId, description);
mIconController.setIconVisibility(mSlotAirplane, true);
} else {
@@ -238,6 +273,21 @@
}
}
+ public void updateAirplaneModeIcon(boolean isAirplaneModeOn) {
+ if (StatusBarSignalPolicyRefactor.isUnexpectedlyInLegacyMode()) {
+ return;
+ }
+
+ boolean isAirplaneMode = isAirplaneModeOn && !mHideAirplane;
+ mIconController.setIconVisibility(mSlotAirplane, isAirplaneMode);
+ if (isAirplaneMode) {
+ mIconController.setIcon(
+ mSlotAirplane,
+ TelephonyIcons.FLIGHT_MODE_ICON,
+ mContext.getString(R.string.accessibility_airplane_mode));
+ }
+ }
+
/**
* Stores the statusbar state for no Calling & SMS.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsHeadsUpRefactor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicyRefactor.kt
similarity index 84%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsHeadsUpRefactor.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicyRefactor.kt
index 62641fe..0577f49 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsHeadsUpRefactor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicyRefactor.kt
@@ -14,17 +14,17 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.shared
+package com.android.systemui.statusbar.phone
import com.android.systemui.Flags
import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
-/** Helper for reading or using the notifications heads up refactor flag state. */
+/** Helper for reading or using the status_bar_signal_policy_refactor flag state. */
@Suppress("NOTHING_TO_INLINE")
-object NotificationsHeadsUpRefactor {
+object StatusBarSignalPolicyRefactor {
/** The aconfig flag name */
- const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_HEADS_UP_REFACTOR
+ const val FLAG_NAME = Flags.FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR
/** A token used for dependency declaration */
val token: FlagToken
@@ -33,7 +33,7 @@
/** Is the refactor enabled */
@JvmStatic
inline val isEnabled
- get() = Flags.notificationsHeadsUpRefactor()
+ get() = Flags.statusBarSignalPolicyRefactor()
/**
* Called to ensure code is only run when the flag is enabled. This protects users from the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/DarkIconManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/DarkIconManager.java
index 8871dae..6c30330 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/DarkIconManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/DarkIconManager.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.phone.ui;
-import android.view.ViewGroup;
import android.widget.LinearLayout;
import com.android.internal.statusbar.StatusBarIcon;
@@ -64,9 +63,8 @@
}
@Override
- protected LinearLayout.LayoutParams onCreateLayoutParams() {
- LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
+ protected LinearLayout.LayoutParams onCreateLayoutParams(StatusBarIcon.Shape shape) {
+ LinearLayout.LayoutParams lp = super.onCreateLayoutParams(shape);
lp.setMargins(mIconHorizontalMargin, 0, mIconHorizontalMargin, 0);
return lp;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/IconManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/IconManager.java
index 5ad7376..91ead61 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/IconManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/IconManager.java
@@ -20,6 +20,7 @@
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE_NEW;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI_NEW;
+import static com.android.systemui.statusbar.phone.ui.StatusBarIconControllerImpl.usesModeIcons;
import android.annotation.Nullable;
import android.content.Context;
@@ -27,9 +28,8 @@
import android.view.ViewGroup;
import android.widget.LinearLayout;
-import androidx.annotation.VisibleForTesting;
-
import com.android.internal.statusbar.StatusBarIcon;
+import com.android.internal.statusbar.StatusBarIcon.Shape;
import com.android.systemui.demomode.DemoModeCommandReceiver;
import com.android.systemui.statusbar.BaseStatusBarFrameLayout;
import com.android.systemui.statusbar.StatusBarIconView;
@@ -155,12 +155,11 @@
};
}
- @VisibleForTesting
protected StatusBarIconView addIcon(int index, String slot, boolean blocked,
StatusBarIcon icon) {
StatusBarIconView view = onCreateStatusBarIconView(slot, blocked);
view.set(icon);
- mGroup.addView(view, index, onCreateLayoutParams());
+ mGroup.addView(view, index, onCreateLayoutParams(icon.shape));
return view;
}
@@ -174,7 +173,7 @@
int index) {
mBindableIcons.put(holder.getSlot(), holder);
ModernStatusBarView view = holder.getInitializer().createAndBind(mContext);
- mGroup.addView(view, index, onCreateLayoutParams());
+ mGroup.addView(view, index, onCreateLayoutParams(Shape.WRAP_CONTENT));
if (mIsInDemoMode) {
mDemoStatusIcons.addBindableIcon(holder);
}
@@ -183,7 +182,7 @@
protected StatusIconDisplayable addNewWifiIcon(int index, String slot) {
ModernStatusBarWifiView view = onCreateModernStatusBarWifiView(slot);
- mGroup.addView(view, index, onCreateLayoutParams());
+ mGroup.addView(view, index, onCreateLayoutParams(Shape.WRAP_CONTENT));
if (mIsInDemoMode) {
mDemoStatusIcons.addModernWifiView(mWifiViewModel);
@@ -199,7 +198,7 @@
int subId
) {
BaseStatusBarFrameLayout view = onCreateModernStatusBarMobileView(slot, subId);
- mGroup.addView(view, index, onCreateLayoutParams());
+ mGroup.addView(view, index, onCreateLayoutParams(Shape.WRAP_CONTENT));
if (mIsInDemoMode) {
Context mobileContext = mMobileContextProvider
@@ -233,8 +232,12 @@
);
}
- protected LinearLayout.LayoutParams onCreateLayoutParams() {
- return new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
+ protected LinearLayout.LayoutParams onCreateLayoutParams(Shape shape) {
+ int width = usesModeIcons() && shape == StatusBarIcon.Shape.FIXED_SPACE
+ ? mIconSize
+ : ViewGroup.LayoutParams.WRAP_CONTENT;
+
+ return new LinearLayout.LayoutParams(width, mIconSize);
}
protected void destroy() {
@@ -256,6 +259,13 @@
/** Called once an icon has been set. */
public void onSetIcon(int viewIndex, StatusBarIcon icon) {
StatusBarIconView view = (StatusBarIconView) mGroup.getChildAt(viewIndex);
+ if (usesModeIcons()) {
+ ViewGroup.LayoutParams current = view.getLayoutParams();
+ ViewGroup.LayoutParams desired = onCreateLayoutParams(icon.shape);
+ if (desired.width != current.width || desired.height != current.height) {
+ view.setLayoutParams(desired);
+ }
+ }
view.set(icon);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconController.java
index ee528e9..0459b97 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconController.java
@@ -70,7 +70,8 @@
* @param preloadedIcon optional drawable corresponding to {@code iconResId}, if known
*/
void setResourceIcon(String slot, @Nullable String resPackage, @DrawableRes int iconResId,
- @Nullable Drawable preloadedIcon, CharSequence contentDescription);
+ @Nullable Drawable preloadedIcon, CharSequence contentDescription,
+ StatusBarIcon.Shape shape);
/**
* Sets up a wifi icon using the new data pipeline. No effect if the wifi icon has already been
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java
index ad3a9e3..9b6d32b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java
@@ -234,13 +234,14 @@
Icon.createWithResource(mContext, resourceId),
/* preloadedIcon= */ null,
contentDescription,
- StatusBarIcon.Type.SystemIcon);
+ StatusBarIcon.Type.SystemIcon,
+ StatusBarIcon.Shape.WRAP_CONTENT);
}
@Override
public void setResourceIcon(String slot, @Nullable String resPackage,
@DrawableRes int iconResId, @Nullable Drawable preloadedIcon,
- CharSequence contentDescription) {
+ CharSequence contentDescription, StatusBarIcon.Shape shape) {
if (!usesModeIcons()) {
Log.wtf("TAG",
"StatusBarIconController.setResourceIcon() should not be called without "
@@ -260,12 +261,13 @@
icon,
preloadedIcon,
contentDescription,
- StatusBarIcon.Type.ResourceIcon);
+ StatusBarIcon.Type.ResourceIcon,
+ shape);
}
private void setResourceIconInternal(String slot, Icon resourceIcon,
@Nullable Drawable preloadedIcon, CharSequence contentDescription,
- StatusBarIcon.Type type) {
+ StatusBarIcon.Type type, StatusBarIcon.Shape shape) {
checkArgument(resourceIcon.getType() == Icon.TYPE_RESOURCE,
"Expected Icon of TYPE_RESOURCE, but got " + resourceIcon.getType());
String resPackage = resourceIcon.getResPackage();
@@ -277,7 +279,7 @@
if (holder == null) {
StatusBarIcon icon = new StatusBarIcon(UserHandle.SYSTEM, resPackage,
resourceIcon, /* iconLevel= */ 0, /* number=*/ 0,
- contentDescription, type);
+ contentDescription, type, shape);
icon.preloadedIcon = preloadedIcon;
holder = StatusBarIconHolder.fromIcon(icon);
setIcon(slot, holder);
@@ -286,6 +288,7 @@
holder.getIcon().icon = resourceIcon;
holder.getIcon().contentDescription = contentDescription;
holder.getIcon().type = type;
+ holder.getIcon().shape = shape;
holder.getIcon().preloadedIcon = preloadedIcon;
handleSet(slot, holder);
}
@@ -578,7 +581,7 @@
}
}
- private static boolean usesModeIcons() {
+ static boolean usesModeIcons() {
return android.app.Flags.modesApi() && android.app.Flags.modesUi()
&& android.app.Flags.modesUiIcons();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ServiceStateModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ServiceStateModel.kt
deleted file mode 100644
index cce3eb0..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ServiceStateModel.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline.mobile.data.model
-
-import android.telephony.ServiceState
-
-/**
- * Simplified representation of a [ServiceState] for use in SystemUI. Add any fields that we need to
- * extract from service state here for consumption downstream
- */
-data class ServiceStateModel(val isEmergencyOnly: Boolean) {
- companion object {
- fun fromServiceState(serviceState: ServiceState): ServiceStateModel {
- return ServiceStateModel(isEmergencyOnly = serviceState.isEmergencyOnly)
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
index 5ad8bf1..32e9c85 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
@@ -21,7 +21,6 @@
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.MobileMappings.Config
-import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
@@ -93,17 +92,15 @@
val defaultMobileIconGroup: Flow<MobileIconGroup>
/**
- * [deviceServiceState] is equivalent to the last [Intent.ACTION_SERVICE_STATE] broadcast with a
- * subscriptionId of -1 (aka [SubscriptionManager.INVALID_SUBSCRIPTION_ID]).
+ * Can the device make emergency calls using the device-based service state? This field is only
+ * useful when all known active subscriptions are OOS and not emergency call capable.
*
- * While each [MobileConnectionsRepository] listens for the service state of each subscription,
- * there is potentially a service state associated with the device itself. This value can be
- * used to calculate e.g., the emergency calling capability of the device (as opposed to the
- * emergency calling capability of an individual mobile connection)
+ * Specifically, this checks every [ServiceState] of the device, and looks for any that report
+ * [ServiceState.isEmergencyOnly].
*
- * Note: this is a [StateFlow] using an eager sharing strategy.
+ * This is an eager flow, and re-evaluates whenever ACTION_SERVICE_STATE is sent for subId = -1.
*/
- val deviceServiceState: StateFlow<ServiceStateModel?>
+ val isDeviceEmergencyCallCapable: StateFlow<Boolean>
/**
* If any active SIM on the device is in
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
index b068152..b247da4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
@@ -25,7 +25,6 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.demomode.DemoMode
import com.android.systemui.demomode.DemoModeController
-import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryImpl
@@ -152,16 +151,17 @@
override val defaultMobileIconGroup: Flow<SignalIcon.MobileIconGroup> =
activeRepo.flatMapLatest { it.defaultMobileIconGroup }
- override val deviceServiceState: StateFlow<ServiceStateModel?> =
+ override val isDeviceEmergencyCallCapable: StateFlow<Boolean> =
activeRepo
- .flatMapLatest { it.deviceServiceState }
+ .flatMapLatest { it.isDeviceEmergencyCallCapable }
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
- realRepository.deviceServiceState.value
+ realRepository.isDeviceEmergencyCallCapable.value
)
override val isAnySimSecure: Flow<Boolean> = activeRepo.flatMapLatest { it.isAnySimSecure }
+
override fun getIsAnySimSecure(): Boolean = activeRepo.value.getIsAnySimSecure()
override val defaultDataSubId: StateFlow<Int> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
index a944e91..3a79f3f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
@@ -27,7 +27,6 @@
import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
-import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
@@ -137,10 +136,11 @@
override val defaultMobileIconGroup = flowOf(TelephonyIcons.THREE_G)
- // TODO(b/339023069): demo command for device-based connectivity state
- override val deviceServiceState: StateFlow<ServiceStateModel?> = MutableStateFlow(null)
+ // TODO(b/339023069): demo command for device-based emergency calls state
+ override val isDeviceEmergencyCallCapable: StateFlow<Boolean> = MutableStateFlow(false)
override val isAnySimSecure: Flow<Boolean> = flowOf(getIsAnySimSecure())
+
override fun getIsAnySimSecure(): Boolean = false
override val defaultMobileIconMapping = MutableStateFlow(TelephonyIcons.ICON_NAME_TO_ICON)
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 261258a..b756a05 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
@@ -21,7 +21,6 @@
import android.content.Intent
import android.content.IntentFilter
import android.telephony.CarrierConfigManager
-import android.telephony.ServiceState
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
@@ -49,7 +48,6 @@
import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog
import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
-import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
@@ -72,7 +70,6 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
@@ -175,8 +172,8 @@
}
.flowOn(bgDispatcher)
- /** Note that this flow is eager, so we don't miss any state */
- override val deviceServiceState: StateFlow<ServiceStateModel?> =
+ /** Turn ACTION_SERVICE_STATE (for subId = -1) into an event */
+ private val serviceStateChangedEvent: Flow<Unit> =
broadcastDispatcher
.broadcastFlow(IntentFilter(Intent.ACTION_SERVICE_STATE)) { intent, _ ->
val subId =
@@ -185,24 +182,34 @@
INVALID_SUBSCRIPTION_ID
)
- val extras = intent.extras
- if (extras == null) {
- logger.logTopLevelServiceStateBroadcastMissingExtras(subId)
- return@broadcastFlow null
- }
-
- val serviceState = ServiceState.newFromBundle(extras)
- logger.logTopLevelServiceStateBroadcastEmergencyOnly(subId, serviceState)
+ // Only emit if the subId is not associated with an active subscription
if (subId == INVALID_SUBSCRIPTION_ID) {
- // Assume that -1 here is the device's service state. We don't care about
- // other ones.
- ServiceStateModel.fromServiceState(serviceState)
- } else {
- null
+ Unit
}
}
- .filterNotNull()
- .stateIn(scope, SharingStarted.Eagerly, null)
+ // Emit on start so that we always check the state at least once
+ .onStart { emit(Unit) }
+
+ /** Eager flow to determine the device-based emergency calls only state */
+ override val isDeviceEmergencyCallCapable: StateFlow<Boolean> =
+ serviceStateChangedEvent
+ .mapLatest {
+ val modems = telephonyManager.activeModemCount
+ // Check the service state for every modem. If any state reports emergency calling
+ // capable, then consider the device to have emergency call capabilities
+ (0..<modems)
+ .map { telephonyManager.getServiceStateForSlot(it) }
+ .any { it?.isEmergencyOnly == true }
+ }
+ .flowOn(bgDispatcher)
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLogger,
+ columnPrefix = LOGGING_PREFIX,
+ columnName = "deviceEmergencyOnly",
+ initialValue = false,
+ )
+ .stateIn(scope, SharingStarted.Eagerly, false)
/**
* State flow that emits the set of mobile data subscriptions, each represented by its own
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index 26553e6..28fff4e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -385,15 +385,7 @@
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val isDeviceInEmergencyCallsOnlyMode: Flow<Boolean> =
- mobileConnectionsRepo.deviceServiceState
- .map { it?.isEmergencyOnly ?: false }
- .distinctUntilChanged()
- .logDiffsForTable(
- tableLogger,
- columnPrefix = LOGGING_PREFIX,
- columnName = "deviceEmergencyOnly",
- initialValue = false,
- )
+ mobileConnectionsRepo.isDeviceEmergencyCallCapable
/** Vends out new [MobileIconInteractor] for a particular subId */
override fun getMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
index 1a55f7d..f5cfc8c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
@@ -31,7 +31,7 @@
import androidx.annotation.ArrayRes
import androidx.annotation.VisibleForTesting
import com.android.systemui.Dumpable
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
@@ -47,6 +47,7 @@
import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel.Wifi
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.getMainOrUnderlyingWifiInfo
import com.android.systemui.tuner.TunerService
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -252,7 +253,10 @@
}
// Only CELLULAR networks may have underlying wifi information that's relevant to SysUI,
// so skip the underlying network check if it's not CELLULAR.
- if (!this.hasTransport(TRANSPORT_CELLULAR)) {
+ if (
+ !this.hasTransport(TRANSPORT_CELLULAR) &&
+ !Flags.statusBarAlwaysCheckUnderlyingNetworks()
+ ) {
return mainWifiInfo
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
index d46aaf4..c24d694 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
@@ -35,6 +35,7 @@
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.chips.ron.shared.StatusBarRonChips
import com.android.systemui.statusbar.chips.ui.binder.ChipChronometerBinder
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
@@ -124,10 +125,6 @@
// Colors
val textColor = chipModel.colors.text(chipContext)
- chipDefaultIconView.imageTintList =
- ColorStateList.valueOf(textColor)
- chipBackgroundView.getCustomIconView()?.imageTintList =
- ColorStateList.valueOf(textColor)
chipTimeView.setTextColor(textColor)
chipTextView.setTextColor(textColor)
(chipBackgroundView.background as GradientDrawable).color =
@@ -173,13 +170,22 @@
// it.
backgroundView.removeView(backgroundView.getCustomIconView())
+ val iconTint = chipModel.colors.text(defaultIconView.context)
+
when (val icon = chipModel.icon) {
null -> {
defaultIconView.visibility = View.GONE
}
- is OngoingActivityChipModel.ChipIcon.Basic -> {
+ is OngoingActivityChipModel.ChipIcon.SingleColorIcon -> {
IconViewBinder.bind(icon.impl, defaultIconView)
defaultIconView.visibility = View.VISIBLE
+ defaultIconView.tintView(iconTint)
+ }
+ is OngoingActivityChipModel.ChipIcon.FullColorAppIcon -> {
+ StatusBarRonChips.assertInNewMode()
+ IconViewBinder.bind(icon.impl, defaultIconView)
+ defaultIconView.visibility = View.VISIBLE
+ defaultIconView.untintView()
}
is OngoingActivityChipModel.ChipIcon.StatusBarView -> {
// Hide the default icon since we'll show this custom icon instead.
@@ -194,6 +200,7 @@
// maybe include the app name.
contentDescription =
context.resources.getString(R.string.ongoing_phone_call_content_description)
+ tintView(iconTint)
}
// 2. If we just reinflated the view, we may need to detach the icon view from the
@@ -219,6 +226,14 @@
return this.findViewById(CUSTOM_ICON_VIEW_ID)
}
+ private fun ImageView.tintView(color: Int) {
+ this.imageTintList = ColorStateList.valueOf(color)
+ }
+
+ private fun ImageView.untintView() {
+ this.imageTintList = null
+ }
+
private fun generateCustomIconLayoutParams(iconView: ImageView): FrameLayout.LayoutParams {
val customIconSize =
iconView.context.resources.getDimensionPixelSize(
@@ -237,10 +252,13 @@
chipTextView.text = chipModel.secondsUntilStarted.toString()
chipTextView.visibility = View.VISIBLE
- // The Chronometer should be stopped to prevent leaks -- see b/192243808 and
- // [Chronometer.start].
- chipTimeView.stop()
- chipTimeView.visibility = View.GONE
+ chipTimeView.hide()
+ }
+ is OngoingActivityChipModel.Shown.Text -> {
+ chipTextView.text = chipModel.text
+ chipTextView.visibility = View.VISIBLE
+
+ chipTimeView.hide()
}
is OngoingActivityChipModel.Shown.Timer -> {
ChipChronometerBinder.bind(chipModel.startTimeMs, chipTimeView)
@@ -250,14 +268,18 @@
}
is OngoingActivityChipModel.Shown.IconOnly -> {
chipTextView.visibility = View.GONE
- // The Chronometer should be stopped to prevent leaks -- see b/192243808 and
- // [Chronometer.start].
- chipTimeView.stop()
- chipTimeView.visibility = View.GONE
+ chipTimeView.hide()
}
}
}
+ private fun ChipChronometer.hide() {
+ // The Chronometer should be stopped to prevent leaks -- see b/192243808 and
+ // [Chronometer.start].
+ this.stop()
+ this.visibility = View.GONE
+ }
+
private fun updateChipPadding(
chipModel: OngoingActivityChipModel.Shown,
backgroundView: View,
@@ -356,6 +378,7 @@
chipView.accessibilityLiveRegion = View.ACCESSIBILITY_LIVE_REGION_ASSERTIVE
}
is OngoingActivityChipModel.Shown.Timer,
+ is OngoingActivityChipModel.Shown.Text,
is OngoingActivityChipModel.Shown.IconOnly -> {
chipView.accessibilityLiveRegion = View.ACCESSIBILITY_LIVE_REGION_NONE
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
index 21ec14f..591d7af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
@@ -403,7 +403,7 @@
tileSpec = TileSpec.create(DND_TILE_SPEC),
uiConfig =
QSTileUIConfig.Resource(
- iconRes = R.drawable.qs_dnd_icon_off,
+ iconRes = com.android.internal.R.drawable.ic_zen_priority_modes,
labelRes = R.string.quick_settings_modes_label,
),
instanceId = uiEventLogger.getNewInstanceId(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index 71bcdfcb..6cebcbd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -22,8 +22,10 @@
import com.android.internal.R;
import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
+import com.android.settingslib.notification.modes.ZenIconLoader;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.LogBufferFactory;
import com.android.systemui.settings.UserTracker;
@@ -79,6 +81,7 @@
import dagger.Provides;
import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
import javax.inject.Named;
@@ -236,4 +239,12 @@
static LogBuffer provideCastControllerLog(LogBufferFactory factory) {
return factory.create("CastControllerLog", 50);
}
+
+ /** Provides a {@link ZenIconLoader} that fetches icons in a background thread. */
+ @Provides
+ @SysUISingleton
+ static ZenIconLoader provideZenIconLoader(
+ @UiBackground ExecutorService backgroundExecutorService) {
+ return new ZenIconLoader(backgroundExecutorService);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
index a67b47a..93c631f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
@@ -23,16 +23,20 @@
import android.util.Log
import androidx.concurrent.futures.await
import com.android.settingslib.notification.data.repository.ZenModeRepository
+import com.android.settingslib.notification.modes.ZenIcon
import com.android.settingslib.notification.modes.ZenIconLoader
import com.android.settingslib.notification.modes.ZenMode
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.common.shared.model.asIcon
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
+import com.android.systemui.statusbar.policy.domain.model.ActiveZenModes
+import com.android.systemui.statusbar.policy.domain.model.ZenModeInfo
import java.time.Duration
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
/**
@@ -45,9 +49,9 @@
private val context: Context,
private val zenModeRepository: ZenModeRepository,
private val notificationSettingsRepository: NotificationSettingsRepository,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ private val iconLoader: ZenIconLoader,
) {
- private val iconLoader: ZenIconLoader = ZenIconLoader.getInstance()
-
val isZenModeEnabled: Flow<Boolean> =
zenModeRepository.globalZenMode
.map {
@@ -76,34 +80,27 @@
val modes: Flow<List<ZenMode>> = zenModeRepository.modes
- val activeModes: Flow<List<ZenMode>> =
- modes.map { modes -> modes.filter { mode -> mode.isActive } }.distinctUntilChanged()
+ /** Flow returning the currently active mode(s), if any. */
+ val activeModes: Flow<ActiveZenModes> =
+ modes
+ .map { modes ->
+ val activeModesList =
+ modes
+ .filter { mode -> mode.isActive }
+ .sortedWith(ZenMode.PRIORITIZING_COMPARATOR)
+ val mainActiveMode =
+ activeModesList.firstOrNull()?.let { ZenModeInfo(it.name, getModeIcon(it)) }
- /** Flow returning the most prioritized of the active modes, if any. */
- val mainActiveMode: Flow<ZenMode?> =
- activeModes.map { modes -> getMainActiveMode(modes) }.distinctUntilChanged()
+ ActiveZenModes(activeModesList.map { m -> m.name }, mainActiveMode)
+ }
+ .flowOn(bgDispatcher)
+ .distinctUntilChanged()
- /**
- * Given the list of modes (which may include zero or more currently active modes), returns the
- * most prioritized of the active modes, if any.
- */
- private fun getMainActiveMode(modes: List<ZenMode>): ZenMode? {
- return modes.sortedWith(ZenMode.PRIORITIZING_COMPARATOR).firstOrNull { it.isActive }
- }
+ val mainActiveMode: Flow<ZenModeInfo?> =
+ activeModes.map { a -> a.mainMode }.distinctUntilChanged()
- suspend fun getModeIcon(mode: ZenMode): Icon {
- return iconLoader.getIcon(context, mode).await().drawable().asIcon()
- }
-
- /**
- * Given the list of modes (which may include zero or more currently active modes), returns an
- * icon representing the active mode, if any (or, if multiple modes are active, to the most
- * prioritized one). This icon is suitable for use in the status bar or lockscreen (uses the
- * standard DND icon for implicit modes, instead of the launcher icon of the associated
- * package).
- */
- suspend fun getActiveModeIcon(modes: List<ZenMode>): Icon? {
- return getMainActiveMode(modes)?.let { m -> getModeIcon(m) }
+ suspend fun getModeIcon(mode: ZenMode): ZenIcon {
+ return iconLoader.getIcon(context, mode).await()
}
fun activateMode(zenMode: ZenMode) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/model/ActiveZenModes.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/model/ActiveZenModes.kt
new file mode 100644
index 0000000..569e517
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/model/ActiveZenModes.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy.domain.model
+
+import com.android.settingslib.notification.modes.ZenMode
+
+/**
+ * Represents the list of [ZenMode] instances that are currently active.
+ *
+ * @property modeNames Names of all the active modes, sorted by their priority.
+ * @property mainMode The most prioritized active mode, if any modes active. Guaranteed to be
+ * non-null if [modeNames] is not empty.
+ */
+data class ActiveZenModes(val modeNames: List<String>, val mainMode: ZenModeInfo?) {
+ fun isAnyActive(): Boolean = modeNames.isNotEmpty()
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/model/ZenModeInfo.kt
similarity index 69%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt
copy to packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/model/ZenModeInfo.kt
index 60d97d1..5004f4c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/model/ZenModeInfo.kt
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.bouncer.shared.flag
+package com.android.systemui.statusbar.policy.domain.model
-import com.android.systemui.kosmos.Kosmos
+import com.android.settingslib.notification.modes.ZenIcon
+import com.android.settingslib.notification.modes.ZenMode
-var Kosmos.fakeComposeBouncerFlags by Kosmos.Fixture { FakeComposeBouncerFlags() }
-val Kosmos.composeBouncerFlags by Kosmos.Fixture<ComposeBouncerFlags> { fakeComposeBouncerFlags }
+/** Name and icon of a [ZenMode] */
+data class ZenModeInfo(val name: String, val icon: ZenIcon)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
index 3fffd9f..cacb384 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
@@ -33,6 +33,10 @@
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.stateDescription
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.android.systemui.common.ui.compose.Icon
@@ -56,9 +60,11 @@
modifier =
Modifier.combinedClickable(
onClick = viewModel.onClick,
- onLongClick = viewModel.onLongClick
+ onLongClick = viewModel.onLongClick,
+ onLongClickLabel = viewModel.onLongClickLabel
)
- .padding(20.dp),
+ .padding(20.dp)
+ .semantics { stateDescription = viewModel.stateDescription },
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement =
Arrangement.spacedBy(
@@ -76,7 +82,10 @@
Text(
viewModel.subtext,
fontWeight = FontWeight.W400,
- modifier = Modifier.tileMarquee().testTag("state")
+ modifier =
+ Modifier.tileMarquee().testTag("state").clearAndSetSemantics {
+ contentDescription = viewModel.subtextDescription
+ }
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModeTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModeTileViewModel.kt
index 7c1cb6a..abd2453 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModeTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModeTileViewModel.kt
@@ -28,7 +28,10 @@
val icon: Icon,
val text: String,
val subtext: String,
+ val subtextDescription: String, // version of subtext without "on"/"off" for screen readers
val enabled: Boolean,
+ val stateDescription: String, // "on"/"off" state of the tile, for screen readers
val onClick: () -> Unit,
val onLongClick: () -> Unit,
+ val onLongClickLabel: String, // for screen readers
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
index be90bec..6764839c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
@@ -23,6 +23,7 @@
import android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID
import com.android.settingslib.notification.modes.EnableZenModeDialog
import com.android.settingslib.notification.modes.ZenMode
+import com.android.systemui.common.shared.model.asIcon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.qs.tiles.dialog.QSZenModeDialogMetricsLogger
@@ -88,10 +89,15 @@
modesList.map { mode ->
ModeTileViewModel(
id = mode.id,
- icon = zenModeInteractor.getModeIcon(mode),
+ icon = zenModeInteractor.getModeIcon(mode).drawable().asIcon(),
text = mode.name,
subtext = getTileSubtext(mode),
+ subtextDescription = getModeDescription(mode) ?: "",
enabled = mode.isActive,
+ stateDescription =
+ context.getString(
+ if (mode.isActive) R.string.zen_mode_on else R.string.zen_mode_off
+ ),
onClick = {
if (!mode.rule.isEnabled) {
openSettings(mode)
@@ -112,7 +118,9 @@
}
}
},
- onLongClick = { openSettings(mode) }
+ onLongClick = { openSettings(mode) },
+ onLongClickLabel =
+ context.resources.getString(R.string.accessibility_long_click_tile)
)
}
}
@@ -127,23 +135,36 @@
dialogDelegate.launchFromDialog(intent)
}
- private fun getTileSubtext(mode: ZenMode): String {
+ /**
+ * Returns a description of the mode, which is:
+ * * a prompt to set up the mode if it is not enabled
+ * * if it cannot be manually activated, text that says so
+ * * otherwise, the trigger description of the mode if it exists...
+ * * ...or null if it doesn't
+ *
+ * This description is used directly for the content description of a mode tile for screen
+ * readers, and for the tile subtext will be augmented with the current status of the mode.
+ */
+ private fun getModeDescription(mode: ZenMode): String? {
if (!mode.rule.isEnabled) {
return context.resources.getString(R.string.zen_mode_set_up)
}
if (!mode.rule.isManualInvocationAllowed && !mode.isActive) {
return context.resources.getString(R.string.zen_mode_no_manual_invocation)
}
+ return mode.getDynamicDescription(context)
+ }
- val modeSubtext = mode.getDynamicDescription(context)
+ private fun getTileSubtext(mode: ZenMode): String {
+ val modeDescription = getModeDescription(mode)
return if (mode.isActive) {
- if (modeSubtext != null) {
- context.getString(R.string.zen_mode_on_with_details, modeSubtext)
+ if (modeDescription != null) {
+ context.getString(R.string.zen_mode_on_with_details, modeDescription)
} else {
context.getString(R.string.zen_mode_on)
}
} else {
- modeSubtext ?: context.getString(R.string.zen_mode_off)
+ modeDescription ?: context.getString(R.string.zen_mode_off)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
index 89227cf..fe1d647 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
@@ -21,10 +21,10 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
import javax.inject.Inject
@@ -58,7 +58,7 @@
) {
private val showingHeadsUpStatusBar: Flow<Boolean> =
- if (NotificationsHeadsUpRefactor.isEnabled) {
+ if (SceneContainerFlag.isEnabled) {
headsUpNotificationInteractor.showHeadsUpStatusBar
} else {
flowOf(false)
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java
index ecf1165..70774f13 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java
@@ -28,6 +28,7 @@
import dagger.Provides;
import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.inject.Singleton;
@@ -81,6 +82,18 @@
@Singleton
@UiBackground
public static Executor provideUiBackgroundExecutor() {
+ return provideUiBackgroundExecutorService();
+ }
+
+ /**
+ * Provide an ExecutorService specifically for running UI operations on a separate thread.
+ *
+ * <p>Keep submitted runnables short and to the point, just as with any other UI code.
+ */
+ @Provides
+ @Singleton
+ @UiBackground
+ public static ExecutorService provideUiBackgroundExecutorService() {
return Executors.newSingleThreadExecutor();
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt
index 4f77cd0..73728e6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt
@@ -75,7 +75,7 @@
}
.map { it ?: AudioOutputDevice.Unknown }
.flowOn(backgroundCoroutineContext)
- .stateIn(scope, SharingStarted.Eagerly, AudioOutputDevice.Unknown)
+ .stateIn(scope, SharingStarted.Eagerly, AudioOutputDevice.Unavailable)
private fun AudioDeviceInfo.toAudioOutputDevice(): AudioOutputDevice {
if (
@@ -120,6 +120,11 @@
name = name,
icon = icon,
)
+ deviceType == MediaDeviceType.TYPE_CAST_DEVICE ->
+ AudioOutputDevice.Remote(
+ name = name,
+ icon = icon,
+ )
else ->
AudioOutputDevice.BuiltIn(
name = name,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/model/AudioOutputDevice.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/model/AudioOutputDevice.kt
index ba0b082..0e4cac0b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/domain/model/AudioOutputDevice.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/domain/model/AudioOutputDevice.kt
@@ -31,6 +31,12 @@
override val icon: Drawable?,
) : AudioOutputDevice
+ /** Models a cast audio output device. */
+ data class Remote(
+ override val name: String,
+ override val icon: Drawable?,
+ ) : AudioOutputDevice
+
/** Models a wired audio output device. */
data class Wired(
override val name: String,
@@ -52,4 +58,16 @@
override val icon: Drawable
get() = error("Unsupported for unknown device")
}
+
+ /**
+ * Models a state when current audio output device is not loaded yet or the system failed to
+ * load it.
+ */
+ data object Unavailable : AudioOutputDevice {
+ override val name: String
+ get() = error("Unsupported for unavailable device")
+
+ override val icon: Drawable
+ get() = error("Unsupported for unavailable device")
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt
index a270d5f..f94cbda 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt
@@ -74,34 +74,51 @@
)
private val currentAudioDevice: Flow<AudioOutputDevice> =
- audioOutputInteractor.currentAudioDevice.filter { it !is AudioOutputDevice.Unknown }
+ audioOutputInteractor.currentAudioDevice.filter { it !is AudioOutputDevice.Unavailable }
+ /**
+ * Model for the Media Output component in the Volume Panel. It's guaranteed to have an
+ * available device if it's loaded.
+ */
val mediaOutputModel: StateFlow<Result<MediaOutputComponentModel>> =
- audioModeInteractor.isOngoingCall
- .flatMapLatest { isOngoingCall ->
- audioSharingInteractor.isInAudioSharing.flatMapLatest { isInAudioSharing ->
- if (isOngoingCall) {
- currentAudioDevice.map {
- MediaOutputComponentModel.Calling(it, isInAudioSharing)
- }
- } else {
- combine(sessionWithPlaybackState.filterData(), currentAudioDevice) {
- sessionWithPlaybackState,
- currentAudioDevice ->
- if (sessionWithPlaybackState == null) {
- MediaOutputComponentModel.Idle(currentAudioDevice, isInAudioSharing)
- } else {
- MediaOutputComponentModel.MediaSession(
- sessionWithPlaybackState.session,
- sessionWithPlaybackState.isPlaybackActive,
- currentAudioDevice,
- isInAudioSharing,
- )
- }
+ combine(
+ audioSharingInteractor.isInAudioSharing,
+ audioModeInteractor.isOngoingCall,
+ currentAudioDevice,
+ ) { isInAudioSharing, isOngoingCall, currentAudioDevice ->
+ if (isOngoingCall) {
+ flowOf(
+ MediaOutputComponentModel.Calling(
+ device = currentAudioDevice,
+ isInAudioSharing = isInAudioSharing,
+ canOpenAudioSwitcher = false,
+ )
+ )
+ } else {
+ sessionWithPlaybackState.filterData().map { sessionWithPlaybackState ->
+ if (sessionWithPlaybackState == null) {
+ MediaOutputComponentModel.Idle(
+ device = currentAudioDevice,
+ isInAudioSharing = isInAudioSharing,
+ canOpenAudioSwitcher =
+ !isInAudioSharing &&
+ currentAudioDevice !is AudioOutputDevice.Unknown,
+ )
+ } else {
+ MediaOutputComponentModel.MediaSession(
+ session = sessionWithPlaybackState.session,
+ isPlaybackActive = sessionWithPlaybackState.isPlaybackActive,
+ device = currentAudioDevice,
+ isInAudioSharing = isInAudioSharing,
+ canOpenAudioSwitcher =
+ !isInAudioSharing &&
+ currentAudioDevice !is AudioOutputDevice.Unknown,
+ )
}
}
}
}
+ .flatMapLatest { it }
.wrapInResult()
.stateIn(coroutineScope, SharingStarted.Eagerly, Result.Loading())
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
index 31a8977..aa07cfd 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
@@ -179,9 +179,7 @@
return MediaDeviceSession(
packageName = packageName,
sessionToken = sessionToken,
- canAdjustVolume =
- playbackInfo != null &&
- playbackInfo?.volumeControl != VolumeProvider.VOLUME_CONTROL_FIXED,
+ canAdjustVolume = playbackInfo.volumeControl != VolumeProvider.VOLUME_CONTROL_FIXED,
appLabel = getApplicationLabel(packageName) ?: return null
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaOutputComponentModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaOutputComponentModel.kt
index 220fb2b..6588b44 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaOutputComponentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaOutputComponentModel.kt
@@ -24,11 +24,13 @@
val device: AudioOutputDevice
val isInAudioSharing: Boolean
+ val canOpenAudioSwitcher: Boolean
/** There is an ongoing call on the device. */
data class Calling(
override val device: AudioOutputDevice,
override val isInAudioSharing: Boolean,
+ override val canOpenAudioSwitcher: Boolean,
) : MediaOutputComponentModel
/** There is media playing on the device. */
@@ -37,11 +39,13 @@
val isPlaybackActive: Boolean,
override val device: AudioOutputDevice,
override val isInAudioSharing: Boolean,
+ override val canOpenAudioSwitcher: Boolean,
) : MediaOutputComponentModel
/** There is nothing playing on the device. */
data class Idle(
override val device: AudioOutputDevice,
override val isInAudioSharing: Boolean,
+ override val canOpenAudioSwitcher: Boolean,
) : MediaOutputComponentModel
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/ConnectedDeviceViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/ConnectedDeviceViewModel.kt
index 8ba672d..42f88b4 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/ConnectedDeviceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/ConnectedDeviceViewModel.kt
@@ -16,11 +16,15 @@
package com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel
+import com.android.systemui.common.shared.model.Color
+
/**
* Models part of the Media Session Volume Panel component that displays connected device
* information.
*/
data class ConnectedDeviceViewModel(
val label: CharSequence,
+ val labelColor: Color,
val deviceName: CharSequence?,
+ val deviceNameColor: Color,
)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
index 36b42f2..e565de5 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
@@ -75,12 +75,25 @@
}
}
ConnectedDeviceViewModel(
- label,
- if (mediaOutputModel.isInAudioSharing) {
- context.getString(R.string.audio_sharing_description)
- } else {
- mediaOutputModel.device.name
- },
+ label = label,
+ labelColor =
+ Color.Attribute(com.android.internal.R.attr.materialColorOnSurfaceVariant),
+ deviceName =
+ if (mediaOutputModel.isInAudioSharing) {
+ context.getString(R.string.audio_sharing_description)
+ } else {
+ mediaOutputModel.device
+ .takeIf { it !is AudioOutputDevice.Unknown }
+ ?.name ?: context.getString(R.string.media_seamless_other_device)
+ },
+ deviceNameColor =
+ if (mediaOutputModel.canOpenAudioSwitcher) {
+ Color.Attribute(com.android.internal.R.attr.materialColorOnSurface)
+ } else {
+ Color.Attribute(
+ com.android.internal.R.attr.materialColorOnSurfaceVariant
+ )
+ },
)
}
.stateIn(
@@ -107,19 +120,39 @@
DeviceIconViewModel.IsPlaying(
icon = icon,
iconColor =
- Color.Attribute(com.android.internal.R.attr.materialColorSurface),
+ if (mediaOutputModel.canOpenAudioSwitcher) {
+ Color.Attribute(com.android.internal.R.attr.materialColorSurface)
+ } else {
+ Color.Attribute(
+ com.android.internal.R.attr.materialColorSurfaceContainerHighest
+ )
+ },
backgroundColor =
- Color.Attribute(com.android.internal.R.attr.materialColorSecondary),
+ if (mediaOutputModel.canOpenAudioSwitcher) {
+ Color.Attribute(com.android.internal.R.attr.materialColorSecondary)
+ } else {
+ Color.Attribute(com.android.internal.R.attr.materialColorOutline)
+ },
)
} else {
DeviceIconViewModel.IsNotPlaying(
icon = icon,
iconColor =
- Color.Attribute(
- com.android.internal.R.attr.materialColorOnSurfaceVariant
- ),
+ if (mediaOutputModel.canOpenAudioSwitcher) {
+ Color.Attribute(
+ com.android.internal.R.attr.materialColorOnSurfaceVariant
+ )
+ } else {
+ Color.Attribute(com.android.internal.R.attr.materialColorOutline)
+ },
backgroundColor =
- Color.Attribute(com.android.internal.R.attr.materialColorSurface),
+ if (mediaOutputModel.canOpenAudioSwitcher) {
+ Color.Attribute(com.android.internal.R.attr.materialColorSurface)
+ } else {
+ Color.Attribute(
+ com.android.internal.R.attr.materialColorSurfaceContainerHighest
+ )
+ },
)
}
}
@@ -132,7 +165,7 @@
val enabled: StateFlow<Boolean> =
mediaOutputComponentInteractor.mediaOutputModel
.filterData()
- .map { !it.isInAudioSharing }
+ .map { it.canOpenAudioSwitcher }
.stateIn(
coroutineScope,
SharingStarted.Eagerly,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
index cfcd6b1..56d0bce 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
@@ -63,13 +63,7 @@
private val changes = MutableSharedFlow<Unit>()
private val currentAudioDeviceAttributes: StateFlow<AudioDeviceAttributes?> =
audioOutputInteractor.currentAudioDevice
- .map { audioDevice ->
- if (audioDevice is AudioOutputDevice.Unknown) {
- builtinSpeaker
- } else {
- audioDevice.getAudioDeviceAttributes()
- }
- }
+ .map { audioDevice -> audioDevice.getAudioDeviceAttributes() }
.stateIn(coroutineScope, SharingStarted.WhileSubscribed(), builtinSpeaker)
/**
@@ -185,7 +179,10 @@
.firstOrNull { spatializerInteractor.isSpatialAudioAvailable(it) }
}
}
- else -> null
+ is AudioOutputDevice.Wired -> null
+ is AudioOutputDevice.Remote -> null
+ is AudioOutputDevice.Unknown -> builtinSpeaker
+ is AudioOutputDevice.Unavailable -> builtinSpeaker
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java b/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java
index 4841c78..ea213cb 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java
@@ -23,11 +23,19 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.qs.QsEventLogger;
+import com.android.systemui.qs.pipeline.shared.TileSpec;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.qs.tiles.QuickAccessWalletTile;
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig;
+import com.android.systemui.qs.tiles.viewmodel.QSTilePolicy;
+import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig;
+import com.android.systemui.res.R;
import com.android.systemui.wallet.controller.WalletContextualLocationsService;
import com.android.systemui.wallet.ui.WalletActivity;
+import java.util.concurrent.Executor;
+
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
@@ -35,14 +43,14 @@
import dagger.multibindings.IntoMap;
import dagger.multibindings.StringKey;
-import java.util.concurrent.Executor;
-
/**
* Module for injecting classes in Wallet.
*/
@Module
public abstract class WalletModule {
+ public static final String WALLET_TILE_SPEC = "wallet";
+
@Binds
@IntoMap
@ClassKey(WalletContextualLocationsService.class)
@@ -69,4 +77,21 @@
@StringKey(QuickAccessWalletTile.TILE_SPEC)
public abstract QSTileImpl<?> bindQuickAccessWalletTile(
QuickAccessWalletTile quickAccessWalletTile);
+
+ @Provides
+ @IntoMap
+ @StringKey(WALLET_TILE_SPEC)
+ public static QSTileConfig provideQuickAccessWalletTileConfig(QsEventLogger uiEventLogger) {
+ TileSpec tileSpec = TileSpec.create(WALLET_TILE_SPEC);
+ return new QSTileConfig(
+ tileSpec,
+ new QSTileUIConfig.Resource(
+ R.drawable.ic_wallet_lockscreen,
+ R.string.wallet_title
+ ),
+ uiEventLogger.getNewInstanceId(),
+ tileSpec.getSpec(),
+ QSTilePolicy.NoRestrictions.INSTANCE
+ );
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index e724c60..487432e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -16,6 +16,8 @@
package com.android.keyguard;
+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.eq;
@@ -27,6 +29,7 @@
import static org.mockito.Mockito.when;
import android.os.SystemClock;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.testing.TestableLooper.RunWithLooper;
import android.view.KeyEvent;
@@ -43,9 +46,13 @@
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.haptics.msdl.FakeMSDLPlayer;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.res.R;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.google.android.msdl.data.model.MSDLToken;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -86,6 +93,8 @@
@Mock
private SelectedUserInteractor mSelectedUserInteractor;
private KeyguardAbsKeyInputViewController mKeyguardAbsKeyInputViewController;
+ private KosmosJavaAdapter mKosmosJavaAdapter = new KosmosJavaAdapter(this);
+ private final FakeMSDLPlayer mMSDLPlayer = mKosmosJavaAdapter.getMsdlPlayer();
@Before
public void setup() {
@@ -108,7 +117,7 @@
return new KeyguardAbsKeyInputViewController(mAbsKeyInputView,
mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
mKeyguardMessageAreaControllerFactory, mLatencyTracker, mFalsingCollector,
- mEmergencyButtonController, mFeatureFlags, mSelectedUserInteractor) {
+ mEmergencyButtonController, mFeatureFlags, mSelectedUserInteractor, mMSDLPlayer) {
@Override
void resetState() {
}
@@ -197,4 +206,32 @@
verify(mAbsKeyInputView, never()).setPasswordEntryInputEnabled(true);
verify(mAbsKeyInputView, never()).setPasswordEntryEnabled(true);
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ public void onPasswordChecked_withMSDLFeedback_withMatch_playsUnlockToken() {
+ mKeyguardAbsKeyInputViewController.onPasswordChecked(0, true, 100, true);
+ assertThat(mMSDLPlayer.getLatestTokenPlayed()).isEqualTo(MSDLToken.UNLOCK);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ public void onPasswordChecked_withoutMSDLFeedback_withMatch_doesNotPlayToken() {
+ mKeyguardAbsKeyInputViewController.onPasswordChecked(0, true, 100, true);
+ assertThat(mMSDLPlayer.getLatestTokenPlayed()).isNull();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ public void onPasswordChecked_withMSDLFeedback_withoutMatch_playsFailureToken() {
+ mKeyguardAbsKeyInputViewController.onPasswordChecked(0, false, 100, true);
+ assertThat(mMSDLPlayer.getLatestTokenPlayed()).isEqualTo(MSDLToken.FAILURE);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ public void onPasswordChecked_withoutMSDLFeedback_withoutMatch_doesNotPlayToken() {
+ mKeyguardAbsKeyInputViewController.onPasswordChecked(0, false, 100, true);
+ assertThat(mMSDLPlayer.getLatestTokenPlayed()).isNull();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 36d4d12..c43a184 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -149,7 +149,8 @@
featureFlags,
mSelectedUserInteractor,
uiEventLogger,
- keyguardKeyboardInteractor
+ keyguardKeyboardInteractor,
+ null,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
index 7151c42..ea6c1bc 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
@@ -101,7 +101,8 @@
emergencyButtonController,
fakeFeatureFlags,
mSelectedUserInteractor,
- keyguardKeyboardInteractor
+ keyguardKeyboardInteractor,
+ null,
)
underTest.init()
underTest.onViewAttached()
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
index acae913..c26365d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
@@ -96,7 +96,8 @@
emergencyButtonController,
fakeFeatureFlags,
mSelectedUserInteractor,
- keyguardKeyboardInteractor
+ keyguardKeyboardInteractor,
+ null,
)
underTest.init()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
index 113a8c0..5e37d4c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
@@ -28,6 +28,7 @@
import android.content.Context;
import android.content.ContextWrapper;
import android.hardware.display.DisplayManager;
+import android.os.Handler;
import android.os.UserHandle;
import android.provider.Settings;
import android.testing.TestableLooper;
@@ -80,6 +81,7 @@
private AccessibilityManager mAccessibilityManager;
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private AccessibilityFloatingMenuController mController;
+ private TestableLooper mTestableLooper;
@Mock
private AccessibilityButtonTargetsObserver mTargetsObserver;
@Mock
@@ -108,6 +110,7 @@
mViewCaptureAwareWindowManager = new ViewCaptureAwareWindowManager(mWindowManager,
mLazyViewCapture, /* isViewCaptureEnabled= */ false);
mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
+ mTestableLooper = TestableLooper.get(this);
when(mTargetsObserver.getCurrentAccessibilityButtonTargets())
.thenReturn(Settings.Secure.getStringForUser(mContextWrapper.getContentResolver(),
@@ -231,7 +234,8 @@
mKeyguardCallback.onKeyguardVisibilityChanged(false);
mKeyguardCallback.onUserSwitching(fakeUserId);
- mKeyguardCallback.onUserSwitchComplete(fakeUserId);
+ mController.mUserInitializationCompleteCallback.onUserInitializationComplete(1);
+ mTestableLooper.processAllMessages();
assertThat(mController.mFloatingMenu).isNotNull();
}
@@ -346,7 +350,8 @@
new AccessibilityFloatingMenuController(mContextWrapper, windowManager,
viewCaptureAwareWindowManager, displayManager, mAccessibilityManager,
mTargetsObserver, mModeObserver, mKeyguardUpdateMonitor, mSecureSettings,
- displayTracker, mNavigationModeController);
+ displayTracker, mNavigationModeController, new Handler(
+ mTestableLooper.getLooper()));
controller.init();
return controller;
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 6047e7d..4fc4166 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
@@ -717,13 +717,9 @@
assertThat(confirmHaptics?.hapticFeedbackConstant)
.isEqualTo(
if (expectConfirmation) HapticFeedbackConstants.NO_HAPTICS
- else HapticFeedbackConstants.CONFIRM
+ else HapticFeedbackConstants.BIOMETRIC_CONFIRM
)
- assertThat(confirmHaptics?.flag)
- .isEqualTo(
- if (expectConfirmation) null
- else HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING
- )
+ assertThat(confirmHaptics?.flag).isNull()
if (expectConfirmation) {
kosmos.promptViewModel.confirmAuthenticated()
@@ -731,9 +727,8 @@
val confirmedHaptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
assertThat(confirmedHaptics?.hapticFeedbackConstant)
- .isEqualTo(HapticFeedbackConstants.CONFIRM)
- assertThat(confirmedHaptics?.flag)
- .isEqualTo(HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING)
+ .isEqualTo(HapticFeedbackConstants.BIOMETRIC_CONFIRM)
+ assertThat(confirmedHaptics?.flag).isNull()
}
@Test
@@ -747,9 +742,8 @@
val currentHaptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
assertThat(currentHaptics?.hapticFeedbackConstant)
- .isEqualTo(HapticFeedbackConstants.CONFIRM)
- assertThat(currentHaptics?.flag)
- .isEqualTo(HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING)
+ .isEqualTo(HapticFeedbackConstants.BIOMETRIC_CONFIRM)
+ assertThat(currentHaptics?.flag).isNull()
}
@Test
@@ -757,9 +751,9 @@
kosmos.promptViewModel.showTemporaryError("test", "messageAfterError", false)
val currentHaptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
- assertThat(currentHaptics?.hapticFeedbackConstant).isEqualTo(HapticFeedbackConstants.REJECT)
- assertThat(currentHaptics?.flag)
- .isEqualTo(HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING)
+ assertThat(currentHaptics?.hapticFeedbackConstant)
+ .isEqualTo(HapticFeedbackConstants.BIOMETRIC_REJECT)
+ assertThat(currentHaptics?.flag).isNull()
}
// biometricprompt_sfps_fingerprint_authenticating reused across rotations
@@ -870,8 +864,9 @@
)
val haptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
- assertThat(haptics?.hapticFeedbackConstant).isEqualTo(HapticFeedbackConstants.REJECT)
- assertThat(haptics?.flag).isEqualTo(HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING)
+ assertThat(haptics?.hapticFeedbackConstant)
+ .isEqualTo(HapticFeedbackConstants.BIOMETRIC_REJECT)
+ assertThat(haptics?.flag).isNull()
}
@Test
@@ -901,10 +896,12 @@
val haptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
if (expectConfirmation) {
- assertThat(haptics?.hapticFeedbackConstant).isEqualTo(HapticFeedbackConstants.REJECT)
- assertThat(haptics?.flag).isEqualTo(HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING)
+ assertThat(haptics?.hapticFeedbackConstant)
+ .isEqualTo(HapticFeedbackConstants.BIOMETRIC_REJECT)
+ assertThat(haptics?.flag).isNull()
} else {
- assertThat(haptics?.hapticFeedbackConstant).isEqualTo(HapticFeedbackConstants.CONFIRM)
+ assertThat(haptics?.hapticFeedbackConstant)
+ .isEqualTo(HapticFeedbackConstants.BIOMETRIC_CONFIRM)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
index 6aecc0e..40b2a08 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
@@ -63,6 +63,7 @@
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -361,15 +362,88 @@
}
@Test
- fun faceAuthIsRequestedWhenQsExpansionStared() =
+ fun faceAuthIsRequestedWhenShadeExpansionStarted() =
testScope.runTest {
underTest.start()
- underTest.onQsExpansionStared()
+ underTest.onShadeExpansionStarted()
runCurrent()
assertThat(faceAuthRepository.runningAuthRequest.value)
- .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_QS_EXPANDED, true))
+ .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_QS_EXPANDED, false))
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun faceAuthIsRequestedWhenShadeExpansionIsStarted() =
+ testScope.runTest {
+ underTest.start()
+ faceAuthRepository.canRunFaceAuth.value = true
+ kosmos.sceneInteractor.snapToScene(toScene = Scenes.Lockscreen, "for-test")
+ runCurrent()
+
+ kosmos.sceneInteractor.changeScene(toScene = Scenes.Shade, loggingReason = "for-test")
+ kosmos.sceneInteractor.setTransitionState(
+ MutableStateFlow(
+ ObservableTransitionState.Transition(
+ fromScene = Scenes.Lockscreen,
+ toScene = Scenes.Shade,
+ currentScene = flowOf(Scenes.Lockscreen),
+ progress = MutableStateFlow(0.2f),
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+ )
+
+ runCurrent()
+ assertThat(faceAuthRepository.runningAuthRequest.value)
+ .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_QS_EXPANDED, false))
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun faceAuthIsRequestedOnlyOnceWhenShadeExpansionStarts() =
+ testScope.runTest {
+ underTest.start()
+ faceAuthRepository.canRunFaceAuth.value = true
+ kosmos.sceneInteractor.snapToScene(toScene = Scenes.Lockscreen, "for-test")
+ runCurrent()
+
+ kosmos.sceneInteractor.changeScene(toScene = Scenes.Shade, loggingReason = "for-test")
+ kosmos.sceneInteractor.setTransitionState(
+ MutableStateFlow(
+ ObservableTransitionState.Transition(
+ fromScene = Scenes.Lockscreen,
+ toScene = Scenes.Shade,
+ currentScene = flowOf(Scenes.Lockscreen),
+ progress = MutableStateFlow(0.2f),
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+ )
+
+ runCurrent()
+ assertThat(faceAuthRepository.runningAuthRequest.value)
+ .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_QS_EXPANDED, false))
+ faceAuthRepository.runningAuthRequest.value = null
+
+ // expansion progress shouldn't trigger face auth again
+ kosmos.sceneInteractor.setTransitionState(
+ MutableStateFlow(
+ ObservableTransitionState.Transition(
+ fromScene = Scenes.Lockscreen,
+ toScene = Scenes.Shade,
+ currentScene = flowOf(Scenes.Lockscreen),
+ progress = MutableStateFlow(0.5f),
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+ )
+
+ assertThat(faceAuthRepository.runningAuthRequest.value).isNull()
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
index 0ac04b6..76539d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
@@ -467,6 +467,16 @@
assertThat(values.toIdSets()).containsExactly(setOf(0, 1, 2))
}
+ @Test
+ fun displayFlow_onlyDefaultDisplayAvailable_neverEmitsEmptySet() =
+ testScope.runTest {
+ setDisplays(0)
+
+ val values: List<Set<Display>> by collectValues(displayRepository.displays)
+
+ assertThat(values.toIdSets()).containsExactly(setOf(0))
+ }
+
private fun Iterable<Display>.ids(): List<Int> = map { it.displayId }
private fun Iterable<Set<Display>>.toIdSets(): List<Set<Int>> = map { it.ids().toSet() }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
index 4e1b12f..43c7ed6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
@@ -21,7 +21,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardDismissTransitionInteractor
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
@@ -44,7 +44,8 @@
@Mock private lateinit var activityTaskManagerService: IActivityTaskManager
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier
- @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+ @Mock
+ private lateinit var keyguardDismissTransitionInteractor: KeyguardDismissTransitionInteractor
@Before
fun setUp() {
@@ -57,7 +58,7 @@
activityTaskManagerService = activityTaskManagerService,
keyguardStateController = keyguardStateController,
keyguardSurfaceBehindAnimator = keyguardSurfaceBehindAnimator,
- keyguardTransitionInteractor = keyguardTransitionInteractor,
+ keyguardDismissTransitionInteractor = keyguardDismissTransitionInteractor,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
index 9eccd9f..3459645 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
@@ -1835,10 +1835,6 @@
assertThat(userEntries).hasSize(1)
val secondSemanticActions = userEntries?.values?.toList()?.get(0)?.semanticActions!!
- assertThat(secondSemanticActions.playOrPause?.icon)
- .isEqualTo(firstSemanticActions.playOrPause?.icon)
- assertThat(secondSemanticActions.playOrPause?.background)
- .isEqualTo(firstSemanticActions.playOrPause?.background)
assertThat(secondSemanticActions.nextOrCustom?.icon)
.isEqualTo(firstSemanticActions.nextOrCustom?.icon)
assertThat(secondSemanticActions.prevOrCustom?.icon)
@@ -1873,11 +1869,6 @@
assertThat(userEntries).hasSize(1)
val secondSemanticActions = userEntries?.values?.toList()?.get(0)?.semanticActions!!
-
- assertThat(secondSemanticActions.playOrPause?.icon)
- .isNotEqualTo(firstSemanticActions.playOrPause?.icon)
- assertThat(secondSemanticActions.playOrPause?.background)
- .isNotEqualTo(firstSemanticActions.playOrPause?.background)
assertThat(secondSemanticActions.nextOrCustom?.icon)
.isNotEqualTo(firstSemanticActions.nextOrCustom?.icon)
assertThat(secondSemanticActions.prevOrCustom?.icon)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
index 19735e2..d2dcf4d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
@@ -16,7 +16,6 @@
package com.android.systemui.qs.tiles
-import android.graphics.drawable.TestStubDrawable
import android.os.Handler
import android.platform.test.annotations.EnableFlags
import android.service.quicksettings.Tile
@@ -25,9 +24,10 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
-import com.android.settingslib.notification.data.repository.FakeZenModeRepository
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
@@ -41,16 +41,15 @@
import com.android.systemui.qs.tiles.viewmodel.QSTileConfigTestBuilder
import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
import com.android.systemui.res.R
-import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
-import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
+import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.settings.SecureSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.After
@@ -59,7 +58,6 @@
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
-import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@@ -68,6 +66,9 @@
@RunWith(AndroidJUnit4::class)
@RunWithLooper(setAsMainLooper = true)
class ModesTileTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val testDispatcher = kosmos.testDispatcher
@Mock private lateinit var qsHost: QSHost
@@ -85,25 +86,13 @@
@Mock private lateinit var dialogDelegate: ModesDialogDelegate
- private val testDispatcher = UnconfinedTestDispatcher()
- private val testScope = TestScope(testDispatcher)
-
private val inputHandler = FakeQSTileIntentUserInputHandler()
- private val zenModeRepository = FakeZenModeRepository()
+ private val zenModeRepository = kosmos.zenModeRepository
private val tileDataInteractor =
- ModesTileDataInteractor(
- context,
- ZenModeInteractor(context, zenModeRepository, mock<NotificationSettingsRepository>()),
- testDispatcher
- )
+ ModesTileDataInteractor(context, kosmos.zenModeInteractor, testDispatcher)
private val mapper =
ModesTileMapper(
- context.orCreateTestableResources
- .apply {
- addOverride(R.drawable.qs_dnd_icon_on, TestStubDrawable())
- addOverride(R.drawable.qs_dnd_icon_off, TestStubDrawable())
- }
- .resources,
+ context.resources,
context.theme,
)
@@ -127,7 +116,7 @@
QSTileConfigTestBuilder.build {
uiConfig =
QSTileUIConfig.Resource(
- iconRes = R.drawable.qs_dnd_icon_off,
+ iconRes = ModesTile.ICON_RES_ID,
labelRes = R.string.quick_settings_modes_label,
)
}
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 b31d21e..15da77d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
@@ -2,8 +2,6 @@
import android.graphics.drawable.Drawable
import android.os.UserHandle
-import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
@@ -90,20 +88,6 @@
}
@Test
- @DisableFlags(com.android.systemui.Flags.FLAG_SCREENSHOT_PRIVATE_PROFILE_BEHAVIOR_FIX)
- fun testOnScreenshotTakenUserHandle_withWorkProfileFirstRun() {
- whenever(workProfileMessageController.onScreenshotTaken(eq(userHandle)))
- .thenReturn(workProfileData)
- messageContainer.onScreenshotTaken(screenshotData)
-
- verify(workProfileMessageController)
- .populateView(eq(workProfileFirstRunView), eq(workProfileData), any())
- assertEquals(View.VISIBLE, workProfileFirstRunView.visibility)
- assertEquals(View.GONE, detectionNoticeView.visibility)
- }
-
- @Test
- @EnableFlags(com.android.systemui.Flags.FLAG_SCREENSHOT_PRIVATE_PROFILE_BEHAVIOR_FIX)
fun testOnScreenshotTakenUserHandle_withProfileProfileFirstRun() = runTest {
val profileData =
ProfileMessageController.ProfileFirstRunData(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
deleted file mode 100644
index 0847f01..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.screenshot
-
-import android.content.ComponentName
-import android.graphics.Bitmap
-import android.graphics.ColorSpace
-import android.graphics.Insets
-import android.graphics.Rect
-import android.hardware.HardwareBuffer
-import android.os.UserHandle
-import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER
-import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER
-import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
-import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
-import com.android.internal.util.ScreenshotRequest
-import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.runBlocking
-import org.junit.Assert
-import org.junit.Test
-
-private const val USER_ID = 1
-private const val TASK_ID = 1
-
-class RequestProcessorTest {
- private val imageCapture = FakeImageCapture()
- private val component = ComponentName("android.test", "android.test.Component")
- private val bounds = Rect(25, 25, 75, 75)
-
- private val policy = FakeScreenshotPolicy()
-
- @Test
- fun testFullScreenshot() = runBlocking {
- // Indicate that the primary content belongs to a normal user
- policy.setManagedProfile(USER_ID, false)
- policy.setDisplayContentInfo(
- policy.getDefaultDisplayId(),
- DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID)
- )
-
- val request =
- ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER).build()
- val processor = RequestProcessor(imageCapture, policy)
-
- val processedData = processor.process(ScreenshotData.fromRequest(request))
-
- // Request has topComponent added, but otherwise unchanged.
- assertThat(processedData.type).isEqualTo(TAKE_SCREENSHOT_FULLSCREEN)
- assertThat(processedData.source).isEqualTo(SCREENSHOT_OTHER)
- assertThat(processedData.topComponent).isEqualTo(component)
- }
-
- @Test
- fun testFullScreenshot_managedProfile() = runBlocking {
- // Provide a fake task bitmap when asked
- val bitmap = makeHardwareBitmap(100, 100)
- imageCapture.image = bitmap
-
- // Indicate that the primary content belongs to a manged profile
- policy.setManagedProfile(USER_ID, true)
- policy.setDisplayContentInfo(
- policy.getDefaultDisplayId(),
- DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID)
- )
-
- val request =
- ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
- val processor = RequestProcessor(imageCapture, policy)
-
- val processedData = processor.process(ScreenshotData.fromRequest(request))
-
- // Expect a task snapshot is taken, overriding the full screen mode
- assertThat(processedData.type).isEqualTo(TAKE_SCREENSHOT_PROVIDED_IMAGE)
- assertThat(processedData.bitmap).isEqualTo(bitmap)
- assertThat(processedData.screenBounds).isEqualTo(bounds)
- assertThat(processedData.insets).isEqualTo(Insets.NONE)
- assertThat(processedData.taskId).isEqualTo(TASK_ID)
- assertThat(imageCapture.requestedTaskId).isEqualTo(TASK_ID)
- }
-
- @Test
- fun testFullScreenshot_managedProfile_nullBitmap() {
- // Provide a null task bitmap when asked
- imageCapture.image = null
-
- // Indicate that the primary content belongs to a manged profile
- policy.setManagedProfile(USER_ID, true)
- policy.setDisplayContentInfo(
- policy.getDefaultDisplayId(),
- DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID)
- )
-
- val request =
- ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
- val processor = RequestProcessor(imageCapture, policy)
-
- Assert.assertThrows(IllegalStateException::class.java) {
- runBlocking { processor.process(ScreenshotData.fromRequest(request)) }
- }
- }
-
- @Test
- fun testProvidedImageScreenshot() = runBlocking {
- val bounds = Rect(50, 50, 150, 150)
- val processor = RequestProcessor(imageCapture, policy)
-
- policy.setManagedProfile(USER_ID, false)
-
- val bitmap = makeHardwareBitmap(100, 100)
-
- val request =
- ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
- .setTopComponent(component)
- .setTaskId(TASK_ID)
- .setUserId(USER_ID)
- .setBitmap(bitmap)
- .setBoundsOnScreen(bounds)
- .setInsets(Insets.NONE)
- .build()
-
- val screenshotData = ScreenshotData.fromRequest(request)
- val processedData = processor.process(screenshotData)
-
- assertThat(processedData).isEqualTo(screenshotData)
- }
-
- @Test
- fun testProvidedImageScreenshot_managedProfile() = runBlocking {
- val bounds = Rect(50, 50, 150, 150)
- val processor = RequestProcessor(imageCapture, policy)
-
- // Indicate that the screenshot belongs to a manged profile
- policy.setManagedProfile(USER_ID, true)
-
- val bitmap = makeHardwareBitmap(100, 100)
-
- val request =
- ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
- .setTopComponent(component)
- .setTaskId(TASK_ID)
- .setUserId(USER_ID)
- .setBitmap(bitmap)
- .setBoundsOnScreen(bounds)
- .setInsets(Insets.NONE)
- .build()
-
- val screenshotData = ScreenshotData.fromRequest(request)
- val processedData = processor.process(screenshotData)
-
- // Work profile, but already a task snapshot, so no changes
- assertThat(processedData).isEqualTo(screenshotData)
- }
-
- private fun makeHardwareBitmap(width: Int, height: Int): Bitmap {
- val buffer =
- HardwareBuffer.create(
- width,
- height,
- HardwareBuffer.RGBA_8888,
- 1,
- HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
- )
- return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!!
- }
-
- private fun Bitmap.equalsHardwareBitmap(bitmap: Bitmap): Boolean {
- return bitmap.hardwareBuffer == this.hardwareBuffer && bitmap.colorSpace == this.colorSpace
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
index a8d5008..eb1a04d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
@@ -308,6 +308,40 @@
assertThat(backlinksData.getCompoundDrawablesRelative()[2]).isNotNull();
}
+ @Test
+ @EnableFlags(Flags.FLAG_APP_CLIPS_BACKLINKS)
+ public void appClipsLaunched_backlinks_multipleBacklinksAvailable_duplicateName()
+ throws RemoteException {
+ // Set up mocking for multiple backlinks.
+ ResolveInfo resolveInfo1 = createBacklinksTaskResolveInfo();
+
+ ResolveInfo resolveInfo2 = createBacklinksTaskResolveInfo();
+ RunningTaskInfo runningTaskInfo2 = createTaskInfoForBacklinksTask();
+ int taskId2 = BACKLINKS_TASK_ID + 2;
+ runningTaskInfo2.taskId = taskId2;
+
+ when(mAtmService.getTasks(eq(Integer.MAX_VALUE), eq(false), eq(false),
+ mDisplayIdCaptor.capture()))
+ .thenReturn(List.of(TASK_THAT_SUPPORTS_BACKLINKS, runningTaskInfo2));
+ when(mPackageManager.resolveActivity(any(Intent.class), anyInt())).thenReturn(resolveInfo1,
+ resolveInfo1, resolveInfo1, resolveInfo2, resolveInfo2, resolveInfo2);
+ when(mPackageManager.loadItemIcon(any(), any())).thenReturn(FAKE_DRAWABLE);
+
+ // Using same AssistContent data for both tasks.
+ mockForAssistContent(ASSIST_CONTENT_FOR_BACKLINKS_TASK, BACKLINKS_TASK_ID);
+ mockForAssistContent(ASSIST_CONTENT_FOR_BACKLINKS_TASK, taskId2);
+
+ // Mocking complete, trigger backlinks.
+ launchActivity();
+ waitForIdleSync();
+
+ // Verify default backlink shown to user has the numerical suffix.
+ TextView backlinksData = mActivity.findViewById(R.id.backlinks_data);
+ assertThat(backlinksData.getText().toString()).isEqualTo(
+ mContext.getString(R.string.backlinks_duplicate_label_format,
+ BACKLINKS_TASK_APP_NAME, 1));
+ }
+
private void setUpMocksForBacklinks() throws RemoteException {
when(mAtmService.getTasks(eq(Integer.MAX_VALUE), eq(false), eq(false),
mDisplayIdCaptor.capture()))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
index 774aa51..2e2ac3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
@@ -33,9 +33,11 @@
import com.android.systemui.flags.Flags
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.TruthJUnit.assume
+import java.util.concurrent.Executor
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
@@ -54,10 +56,7 @@
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
-import java.util.concurrent.Executor
-
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -71,27 +70,19 @@
fun isBackgroundUserTrackerEnabled(): Iterable<Boolean> = listOf(true, false)
}
- @Mock
- private lateinit var context: Context
+ @Mock private lateinit var context: Context
- @Mock
- private lateinit var userManager: UserManager
+ @Mock private lateinit var userManager: UserManager
- @Mock
- private lateinit var iActivityManager: IActivityManager
+ @Mock private lateinit var iActivityManager: IActivityManager
- @Mock
- private lateinit var userSwitchingReply: IRemoteCallback
+ @Mock private lateinit var userSwitchingReply: IRemoteCallback
- @Mock(stubOnly = true)
- private lateinit var dumpManager: DumpManager
+ @Mock(stubOnly = true) private lateinit var dumpManager: DumpManager
- @Mock(stubOnly = true)
- private lateinit var handler: Handler
+ @Mock(stubOnly = true) private lateinit var handler: Handler
- @Parameterized.Parameter
- @JvmField
- var isBackgroundUserTrackerEnabled: Boolean = false
+ @Parameterized.Parameter @JvmField var isBackgroundUserTrackerEnabled: Boolean = false
private val testScope = TestScope()
private val testDispatcher = StandardTestDispatcher(testScope.testScheduler)
@@ -104,365 +95,379 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- `when`(context.userId).thenReturn(UserHandle.USER_SYSTEM)
- `when`(context.user).thenReturn(UserHandle.SYSTEM)
- `when`(context.createContextAsUser(any(), anyInt())).thenAnswer { invocation ->
+ whenever(context.userId).thenReturn(UserHandle.USER_SYSTEM)
+ whenever(context.user).thenReturn(UserHandle.SYSTEM)
+ whenever(context.createContextAsUser(any(), anyInt())).thenAnswer { invocation ->
val user = invocation.getArgument<UserHandle>(0)
- `when`(context.user).thenReturn(user)
- `when`(context.userId).thenReturn(user.identifier)
+ whenever(context.user).thenReturn(user)
+ whenever(context.userId).thenReturn(user.identifier)
context
}
- `when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
+ whenever(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
val info = UserInfo(invocation.getArgument<Int>(0), "", UserInfo.FLAG_FULL)
listOf(info)
}
featureFlags.set(Flags.USER_TRACKER_BACKGROUND_CALLBACKS, isBackgroundUserTrackerEnabled)
tracker =
- UserTrackerImpl(
- context,
- { featureFlags },
- userManager,
- iActivityManager,
- dumpManager,
- testScope.backgroundScope,
- testDispatcher,
- handler,
- )
+ UserTrackerImpl(
+ context,
+ { featureFlags },
+ userManager,
+ iActivityManager,
+ dumpManager,
+ testScope.backgroundScope,
+ testDispatcher,
+ handler,
+ )
}
+ @Test fun testNotInitialized() = testScope.runTest { assertThat(tracker.initialized).isFalse() }
+
+ @Test(expected = IllegalStateException::class)
+ fun testGetUserIdBeforeInitThrowsException() = testScope.runTest { tracker.userId }
+
+ @Test(expected = IllegalStateException::class)
+ fun testGetUserHandleBeforeInitThrowsException() = testScope.runTest { tracker.userHandle }
+
+ @Test(expected = IllegalStateException::class)
+ fun testGetUserContextBeforeInitThrowsException() = testScope.runTest { tracker.userContext }
+
+ @Test(expected = IllegalStateException::class)
+ fun testGetUserContentResolverBeforeInitThrowsException() =
+ testScope.runTest { tracker.userContentResolver }
+
+ @Test(expected = IllegalStateException::class)
+ fun testGetUserProfilesBeforeInitThrowsException() = testScope.runTest { tracker.userProfiles }
+
@Test
- fun testNotInitialized() = testScope.runTest {
- assertThat(tracker.initialized).isFalse()
- }
+ fun testInitialize() =
+ testScope.runTest {
+ tracker.initialize(0)
- @Test(expected = IllegalStateException::class)
- fun testGetUserIdBeforeInitThrowsException() = testScope.runTest {
- tracker.userId
- }
-
- @Test(expected = IllegalStateException::class)
- fun testGetUserHandleBeforeInitThrowsException() = testScope.runTest {
- tracker.userHandle
- }
-
- @Test(expected = IllegalStateException::class)
- fun testGetUserContextBeforeInitThrowsException() = testScope.runTest {
- tracker.userContext
- }
-
- @Test(expected = IllegalStateException::class)
- fun testGetUserContentResolverBeforeInitThrowsException() = testScope.runTest {
- tracker.userContentResolver
- }
-
- @Test(expected = IllegalStateException::class)
- fun testGetUserProfilesBeforeInitThrowsException() = testScope.runTest {
- tracker.userProfiles
- }
+ assertThat(tracker.initialized).isTrue()
+ }
@Test
- fun testInitialize() = testScope.runTest {
- tracker.initialize(0)
+ fun testReceiverRegisteredOnInitialize() =
+ testScope.runTest {
+ tracker.initialize(0)
- assertThat(tracker.initialized).isTrue()
- }
+ val captor = ArgumentCaptor.forClass(IntentFilter::class.java)
- @Test
- fun testReceiverRegisteredOnInitialize() = testScope.runTest {
- tracker.initialize(0)
-
- val captor = ArgumentCaptor.forClass(IntentFilter::class.java)
-
- verify(context)
+ verify(context)
.registerReceiverForAllUsers(eq(tracker), capture(captor), isNull(), eq(handler))
- with(captor.value) {
- assertThat(countActions()).isEqualTo(11)
- assertThat(hasAction(Intent.ACTION_LOCALE_CHANGED)).isTrue()
- assertThat(hasAction(Intent.ACTION_USER_INFO_CHANGED)).isTrue()
- assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)).isTrue()
- assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)).isTrue()
- assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_ADDED)).isTrue()
- assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_REMOVED)).isTrue()
- assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED)).isTrue()
- assertThat(hasAction(Intent.ACTION_PROFILE_ADDED)).isTrue()
- assertThat(hasAction(Intent.ACTION_PROFILE_REMOVED)).isTrue()
- assertThat(hasAction(Intent.ACTION_PROFILE_AVAILABLE)).isTrue()
- assertThat(hasAction(Intent.ACTION_PROFILE_UNAVAILABLE)).isTrue()
- }
- }
-
- @Test
- fun testInitialValuesSet() = testScope.runTest {
- val testID = 4
- tracker.initialize(testID)
-
- verify(userManager).getProfiles(testID)
-
- assertThat(tracker.userId).isEqualTo(testID)
- assertThat(tracker.userHandle).isEqualTo(UserHandle.of(testID))
- assertThat(tracker.userContext.userId).isEqualTo(testID)
- assertThat(tracker.userContext.user).isEqualTo(UserHandle.of(testID))
- assertThat(tracker.userProfiles).hasSize(1)
-
- val info = tracker.userProfiles[0]
- assertThat(info.id).isEqualTo(testID)
- }
-
- @Test
- fun testUserSwitch() = testScope.runTest {
- tracker.initialize(0)
- val newID = 5
-
- val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
- verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
- captor.value.onBeforeUserSwitching(newID)
- captor.value.onUserSwitching(newID, userSwitchingReply)
- runCurrent()
- verify(userSwitchingReply).sendResult(any())
-
- verify(userManager).getProfiles(newID)
-
- assertThat(tracker.userId).isEqualTo(newID)
- assertThat(tracker.userHandle).isEqualTo(UserHandle.of(newID))
- assertThat(tracker.userContext.userId).isEqualTo(newID)
- assertThat(tracker.userContext.user).isEqualTo(UserHandle.of(newID))
- assertThat(tracker.userProfiles).hasSize(1)
-
- val info = tracker.userProfiles[0]
- assertThat(info.id).isEqualTo(newID)
- }
-
- @Test
- fun testManagedProfileAvailable() = testScope.runTest {
- tracker.initialize(0)
- val profileID = tracker.userId + 10
-
- `when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
- val id = invocation.getArgument<Int>(0)
- val info = UserInfo(id, "", UserInfo.FLAG_FULL)
- val infoProfile = UserInfo(
- id + 10,
- "",
- "",
- UserInfo.FLAG_MANAGED_PROFILE,
- UserManager.USER_TYPE_PROFILE_MANAGED
- )
- infoProfile.profileGroupId = id
- listOf(info, infoProfile)
+ with(captor.value) {
+ assertThat(countActions()).isEqualTo(11)
+ assertThat(hasAction(Intent.ACTION_LOCALE_CHANGED)).isTrue()
+ assertThat(hasAction(Intent.ACTION_USER_INFO_CHANGED)).isTrue()
+ assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)).isTrue()
+ assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)).isTrue()
+ assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_ADDED)).isTrue()
+ assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_REMOVED)).isTrue()
+ assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED)).isTrue()
+ assertThat(hasAction(Intent.ACTION_PROFILE_ADDED)).isTrue()
+ assertThat(hasAction(Intent.ACTION_PROFILE_REMOVED)).isTrue()
+ assertThat(hasAction(Intent.ACTION_PROFILE_AVAILABLE)).isTrue()
+ assertThat(hasAction(Intent.ACTION_PROFILE_UNAVAILABLE)).isTrue()
+ }
}
- val intent = Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
- .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
- tracker.onReceive(context, intent)
-
- assertThat(tracker.userProfiles.map { it.id }).containsExactly(tracker.userId, profileID)
- }
-
@Test
- fun testManagedProfileUnavailable() = testScope.runTest {
- tracker.initialize(0)
- val profileID = tracker.userId + 10
+ fun testInitialValuesSet() =
+ testScope.runTest {
+ val testID = 4
+ tracker.initialize(testID)
- `when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
- val id = invocation.getArgument<Int>(0)
- val info = UserInfo(id, "", UserInfo.FLAG_FULL)
- val infoProfile = UserInfo(
- id + 10,
- "",
- "",
- UserInfo.FLAG_MANAGED_PROFILE or UserInfo.FLAG_QUIET_MODE,
- UserManager.USER_TYPE_PROFILE_MANAGED
- )
- infoProfile.profileGroupId = id
- listOf(info, infoProfile)
+ verify(userManager).getProfiles(testID)
+
+ assertThat(tracker.userId).isEqualTo(testID)
+ assertThat(tracker.userHandle).isEqualTo(UserHandle.of(testID))
+ assertThat(tracker.userContext.userId).isEqualTo(testID)
+ assertThat(tracker.userContext.user).isEqualTo(UserHandle.of(testID))
+ assertThat(tracker.userProfiles).hasSize(1)
+
+ val info = tracker.userProfiles[0]
+ assertThat(info.id).isEqualTo(testID)
}
- val intent = Intent(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
- .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
- tracker.onReceive(context, intent)
-
- assertThat(tracker.userProfiles.map { it.id }).containsExactly(tracker.userId, profileID)
- }
-
@Test
- fun testManagedProfileStartedAndRemoved() = testScope.runTest {
- tracker.initialize(0)
- val profileID = tracker.userId + 10
+ fun testUserSwitch() =
+ testScope.runTest {
+ tracker.initialize(0)
+ val newID = 5
- `when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
- val id = invocation.getArgument<Int>(0)
- val info = UserInfo(id, "", UserInfo.FLAG_FULL)
- val infoProfile = UserInfo(
- id + 10,
- "",
- "",
- UserInfo.FLAG_MANAGED_PROFILE,
- UserManager.USER_TYPE_PROFILE_MANAGED
- )
- infoProfile.profileGroupId = id
- listOf(info, infoProfile)
+ val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
+ verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
+ captor.value.onBeforeUserSwitching(newID)
+ captor.value.onUserSwitching(newID, userSwitchingReply)
+ runCurrent()
+ verify(userSwitchingReply).sendResult(any())
+
+ verify(userManager).getProfiles(newID)
+
+ assertThat(tracker.userId).isEqualTo(newID)
+ assertThat(tracker.userHandle).isEqualTo(UserHandle.of(newID))
+ assertThat(tracker.userContext.userId).isEqualTo(newID)
+ assertThat(tracker.userContext.user).isEqualTo(UserHandle.of(newID))
+ assertThat(tracker.userProfiles).hasSize(1)
+
+ val info = tracker.userProfiles[0]
+ assertThat(info.id).isEqualTo(newID)
}
- // Managed profile started
- val intent = Intent(Intent.ACTION_MANAGED_PROFILE_UNLOCKED)
- .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
- tracker.onReceive(context, intent)
+ @Test
+ fun testManagedProfileAvailable() =
+ testScope.runTest {
+ tracker.initialize(0)
+ val profileID = tracker.userId + 10
- assertThat(tracker.userProfiles.map { it.id }).containsExactly(tracker.userId, profileID)
+ whenever(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
+ val id = invocation.getArgument<Int>(0)
+ val info = UserInfo(id, "", UserInfo.FLAG_FULL)
+ val infoProfile =
+ UserInfo(
+ id + 10,
+ "",
+ "",
+ UserInfo.FLAG_MANAGED_PROFILE,
+ UserManager.USER_TYPE_PROFILE_MANAGED
+ )
+ infoProfile.profileGroupId = id
+ listOf(info, infoProfile)
+ }
- `when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
- listOf(UserInfo(invocation.getArgument(0), "", UserInfo.FLAG_FULL))
+ val intent =
+ Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
+ .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
+ tracker.onReceive(context, intent)
+
+ assertThat(tracker.userProfiles.map { it.id })
+ .containsExactly(tracker.userId, profileID)
}
- val intent2 = Intent(Intent.ACTION_MANAGED_PROFILE_REMOVED)
- .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
- tracker.onReceive(context, intent2)
-
- assertThat(tracker.userProfiles.map { it.id }).containsExactly(tracker.userId)
- }
-
@Test
- fun testCallbackNotCalledOnAdd() = testScope.runTest {
- tracker.initialize(0)
- val callback = TestCallback()
+ fun testManagedProfileUnavailable() =
+ testScope.runTest {
+ tracker.initialize(0)
+ val profileID = tracker.userId + 10
- tracker.addCallback(callback, executor)
+ whenever(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
+ val id = invocation.getArgument<Int>(0)
+ val info = UserInfo(id, "", UserInfo.FLAG_FULL)
+ val infoProfile =
+ UserInfo(
+ id + 10,
+ "",
+ "",
+ UserInfo.FLAG_MANAGED_PROFILE or UserInfo.FLAG_QUIET_MODE,
+ UserManager.USER_TYPE_PROFILE_MANAGED
+ )
+ infoProfile.profileGroupId = id
+ listOf(info, infoProfile)
+ }
- assertThat(callback.calledOnProfilesChanged).isEqualTo(0)
- assertThat(callback.calledOnUserChanged).isEqualTo(0)
- }
+ val intent =
+ Intent(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
+ .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
+ tracker.onReceive(context, intent)
- @Test
- fun testCallbackCalledOnUserChanging() = testScope.runTest {
- tracker.initialize(0)
- val callback = TestCallback()
- tracker.addCallback(callback, executor)
-
- val newID = 5
-
- val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
- verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
- captor.value.onBeforeUserSwitching(newID)
- captor.value.onUserSwitching(newID, userSwitchingReply)
- runCurrent()
-
- verify(userSwitchingReply).sendResult(any())
- assertThat(callback.calledOnUserChanging).isEqualTo(1)
- assertThat(callback.lastUser).isEqualTo(newID)
- assertThat(callback.lastUserContext?.userId).isEqualTo(newID)
- }
-
- @Test
- fun testAsyncCallbackWaitsUserToChange() = testScope.runTest {
- // Skip this test for CountDownLatch variation. The problem is that there would be a
- // deadlock if the callbacks processing runs on the same thread as the callback (which
- // is blocked by the latch). Before the change it works because the callbacks are
- // processed on a binder thread which is always distinct.
- // This is the issue that this feature addresses.
- assume().that(isBackgroundUserTrackerEnabled).isTrue()
-
- tracker.initialize(0)
- val callback = TestCallback()
- val callbackExecutor = FakeExecutor(FakeSystemClock())
- tracker.addCallback(callback, callbackExecutor)
-
- val newID = 5
-
- val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
- verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
- captor.value.onUserSwitching(newID, userSwitchingReply)
-
- assertThat(callback.calledOnUserChanging).isEqualTo(0)
- verify(userSwitchingReply, never()).sendResult(any())
-
- FakeExecutor.exhaustExecutors(callbackExecutor)
- runCurrent()
- FakeExecutor.exhaustExecutors(callbackExecutor)
- runCurrent()
-
- assertThat(callback.calledOnUserChanging).isEqualTo(1)
- verify(userSwitchingReply).sendResult(any())
- }
-
- @Test
- fun testCallbackCalledOnUserChanged() = testScope.runTest {
- tracker.initialize(0)
- val callback = TestCallback()
- tracker.addCallback(callback, executor)
-
- val newID = 5
-
- val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
- verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
- captor.value.onBeforeUserSwitching(newID)
- captor.value.onUserSwitchComplete(newID)
- runCurrent()
-
- assertThat(callback.calledOnUserChanged).isEqualTo(1)
- assertThat(callback.lastUser).isEqualTo(newID)
- assertThat(callback.lastUserContext?.userId).isEqualTo(newID)
- assertThat(callback.calledOnProfilesChanged).isEqualTo(1)
- assertThat(callback.lastUserProfiles.map { it.id }).containsExactly(newID)
- }
-
- @Test
- fun testCallbackCalledOnUserInfoChanged() = testScope.runTest {
- tracker.initialize(0)
- val callback = TestCallback()
- tracker.addCallback(callback, executor)
- val profileID = tracker.userId + 10
-
- `when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
- val id = invocation.getArgument<Int>(0)
- val info = UserInfo(id, "", UserInfo.FLAG_FULL)
- val infoProfile = UserInfo(
- id + 10,
- "",
- "",
- UserInfo.FLAG_MANAGED_PROFILE,
- UserManager.USER_TYPE_PROFILE_MANAGED
- )
- infoProfile.profileGroupId = id
- listOf(info, infoProfile)
+ assertThat(tracker.userProfiles.map { it.id })
+ .containsExactly(tracker.userId, profileID)
}
- val intent = Intent(Intent.ACTION_USER_INFO_CHANGED)
- .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
+ @Test
+ fun testManagedProfileStartedAndRemoved() =
+ testScope.runTest {
+ tracker.initialize(0)
+ val profileID = tracker.userId + 10
- tracker.onReceive(context, intent)
+ whenever(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
+ val id = invocation.getArgument<Int>(0)
+ val info = UserInfo(id, "", UserInfo.FLAG_FULL)
+ val infoProfile =
+ UserInfo(
+ id + 10,
+ "",
+ "",
+ UserInfo.FLAG_MANAGED_PROFILE,
+ UserManager.USER_TYPE_PROFILE_MANAGED
+ )
+ infoProfile.profileGroupId = id
+ listOf(info, infoProfile)
+ }
- assertThat(callback.calledOnUserChanged).isEqualTo(0)
- assertThat(callback.calledOnProfilesChanged).isEqualTo(1)
- assertThat(callback.lastUserProfiles.map { it.id }).containsExactly(0, profileID)
- }
+ // Managed profile started
+ val intent =
+ Intent(Intent.ACTION_MANAGED_PROFILE_UNLOCKED)
+ .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
+ tracker.onReceive(context, intent)
+
+ assertThat(tracker.userProfiles.map { it.id })
+ .containsExactly(tracker.userId, profileID)
+
+ whenever(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
+ listOf(UserInfo(invocation.getArgument(0), "", UserInfo.FLAG_FULL))
+ }
+
+ val intent2 =
+ Intent(Intent.ACTION_MANAGED_PROFILE_REMOVED)
+ .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
+ tracker.onReceive(context, intent2)
+
+ assertThat(tracker.userProfiles.map { it.id }).containsExactly(tracker.userId)
+ }
@Test
- fun testCallbackRemoved() = testScope.runTest {
- tracker.initialize(0)
- val newID = 5
- val profileID = newID + 10
+ fun testCallbackNotCalledOnAdd() =
+ testScope.runTest {
+ tracker.initialize(0)
+ val callback = TestCallback()
- val callback = TestCallback()
- tracker.addCallback(callback, executor)
- tracker.removeCallback(callback)
+ tracker.addCallback(callback, executor)
- val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
- verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
- captor.value.onUserSwitching(newID, userSwitchingReply)
- runCurrent()
- verify(userSwitchingReply).sendResult(any())
- captor.value.onUserSwitchComplete(newID)
+ assertThat(callback.calledOnProfilesChanged).isEqualTo(0)
+ assertThat(callback.calledOnUserChanged).isEqualTo(0)
+ }
- val intentProfiles = Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
- .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
+ @Test
+ fun testCallbackCalledOnUserChanging() =
+ testScope.runTest {
+ tracker.initialize(0)
+ val callback = TestCallback()
+ tracker.addCallback(callback, executor)
- tracker.onReceive(context, intentProfiles)
+ val newID = 5
- assertThat(callback.calledOnUserChanging).isEqualTo(0)
- assertThat(callback.calledOnUserChanged).isEqualTo(0)
- assertThat(callback.calledOnProfilesChanged).isEqualTo(0)
- }
+ val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
+ verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
+ captor.value.onBeforeUserSwitching(newID)
+ captor.value.onUserSwitching(newID, userSwitchingReply)
+ runCurrent()
+
+ verify(userSwitchingReply).sendResult(any())
+ assertThat(callback.calledOnUserChanging).isEqualTo(1)
+ assertThat(callback.lastUser).isEqualTo(newID)
+ assertThat(callback.lastUserContext?.userId).isEqualTo(newID)
+ }
+
+ @Test
+ fun testAsyncCallbackWaitsUserToChange() =
+ testScope.runTest {
+ // Skip this test for CountDownLatch variation. The problem is that there would be a
+ // deadlock if the callbacks processing runs on the same thread as the callback (which
+ // is blocked by the latch). Before the change it works because the callbacks are
+ // processed on a binder thread which is always distinct.
+ // This is the issue that this feature addresses.
+ assume().that(isBackgroundUserTrackerEnabled).isTrue()
+
+ tracker.initialize(0)
+ val callback = TestCallback()
+ val callbackExecutor = FakeExecutor(FakeSystemClock())
+ tracker.addCallback(callback, callbackExecutor)
+
+ val newID = 5
+
+ val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
+ verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
+ captor.value.onUserSwitching(newID, userSwitchingReply)
+
+ assertThat(callback.calledOnUserChanging).isEqualTo(0)
+ verify(userSwitchingReply, never()).sendResult(any())
+
+ FakeExecutor.exhaustExecutors(callbackExecutor)
+ runCurrent()
+ FakeExecutor.exhaustExecutors(callbackExecutor)
+ runCurrent()
+
+ assertThat(callback.calledOnUserChanging).isEqualTo(1)
+ verify(userSwitchingReply).sendResult(any())
+ }
+
+ @Test
+ fun testCallbackCalledOnUserChanged() =
+ testScope.runTest {
+ tracker.initialize(0)
+ val callback = TestCallback()
+ tracker.addCallback(callback, executor)
+
+ val newID = 5
+
+ val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
+ verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
+ captor.value.onBeforeUserSwitching(newID)
+ captor.value.onUserSwitchComplete(newID)
+ runCurrent()
+
+ assertThat(callback.calledOnUserChanged).isEqualTo(1)
+ assertThat(callback.lastUser).isEqualTo(newID)
+ assertThat(callback.lastUserContext?.userId).isEqualTo(newID)
+ assertThat(callback.calledOnProfilesChanged).isEqualTo(1)
+ assertThat(callback.lastUserProfiles.map { it.id }).containsExactly(newID)
+ }
+
+ @Test
+ fun testCallbackCalledOnUserInfoChanged() =
+ testScope.runTest {
+ tracker.initialize(0)
+ val callback = TestCallback()
+ tracker.addCallback(callback, executor)
+ val profileID = tracker.userId + 10
+
+ whenever(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
+ val id = invocation.getArgument<Int>(0)
+ val info = UserInfo(id, "", UserInfo.FLAG_FULL)
+ val infoProfile =
+ UserInfo(
+ id + 10,
+ "",
+ "",
+ UserInfo.FLAG_MANAGED_PROFILE,
+ UserManager.USER_TYPE_PROFILE_MANAGED
+ )
+ infoProfile.profileGroupId = id
+ listOf(info, infoProfile)
+ }
+
+ val intent =
+ Intent(Intent.ACTION_USER_INFO_CHANGED)
+ .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
+
+ tracker.onReceive(context, intent)
+
+ assertThat(callback.calledOnUserChanged).isEqualTo(0)
+ assertThat(callback.calledOnProfilesChanged).isEqualTo(1)
+ assertThat(callback.lastUserProfiles.map { it.id }).containsExactly(0, profileID)
+ }
+
+ @Test
+ fun testCallbackRemoved() =
+ testScope.runTest {
+ tracker.initialize(0)
+ val newID = 5
+ val profileID = newID + 10
+
+ val callback = TestCallback()
+ tracker.addCallback(callback, executor)
+ tracker.removeCallback(callback)
+
+ val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
+ verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
+ captor.value.onUserSwitching(newID, userSwitchingReply)
+ runCurrent()
+ verify(userSwitchingReply).sendResult(any())
+ captor.value.onUserSwitchComplete(newID)
+
+ val intentProfiles =
+ Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
+ .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
+
+ tracker.onReceive(context, intentProfiles)
+
+ assertThat(callback.calledOnUserChanging).isEqualTo(0)
+ assertThat(callback.calledOnUserChanged).isEqualTo(0)
+ assertThat(callback.calledOnProfilesChanged).isEqualTo(0)
+ }
private class TestCallback : UserTracker.Callback {
var calledOnUserChanging = 0
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 5a5cdcd..3ba1447e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -61,7 +61,6 @@
import com.android.systemui.media.controls.controller.keyguardMediaController
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.sceneDataSourceDelegator
-import com.android.systemui.shade.data.repository.fakeShadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.lockscreen.lockscreenSmartspaceController
import com.android.systemui.statusbar.notification.stack.notificationStackScrollLayoutController
@@ -417,13 +416,17 @@
// Communal is open.
goToScene(CommunalScenes.Communal)
- // Shade shows up.
- shadeTestUtil.setQsExpansion(0.5f)
- testableLooper.processAllMessages()
+ // Touch starts and ends.
assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
assertThat(underTest.onTouchEvent(CANCEL_EVENT)).isTrue()
+
+ // Up event is no longer processed
assertThat(underTest.onTouchEvent(UP_EVENT)).isFalse()
+
+ // Move event can still be processed
assertThat(underTest.onTouchEvent(MOVE_EVENT)).isTrue()
+ assertThat(underTest.onTouchEvent(MOVE_EVENT)).isTrue()
+ assertThat(underTest.onTouchEvent(UP_EVENT)).isTrue()
}
}
@@ -702,7 +705,7 @@
verify(containerView).onTouchEvent(DOWN_EVENT)
// User is interacting with shade on lockscreen.
- fakeShadeRepository.setLegacyLockscreenShadeTracking(true)
+ shadeTestUtil.setLockscreenShadeTracking(true)
testableLooper.processAllMessages()
// A move event is ignored while the user is already interacting.
@@ -716,6 +719,30 @@
}
@Test
+ fun onTouchEvent_shadeExpanding_touchesNotDispatched() =
+ with(kosmos) {
+ testScope.runTest {
+ // On lockscreen.
+ goToScene(CommunalScenes.Blank)
+ whenever(
+ notificationStackScrollLayoutController.isBelowLastNotification(
+ any(),
+ any()
+ )
+ )
+ .thenReturn(true)
+
+ // Shade is open slightly.
+ shadeTestUtil.setShadeExpansion(0.01f)
+ testableLooper.processAllMessages()
+
+ // Touches are not consumed.
+ assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
+ verify(containerView, never()).onTouchEvent(DOWN_EVENT)
+ }
+ }
+
+ @Test
fun onTouchEvent_bouncerInteracting_movesNotDispatched() =
with(kosmos) {
testScope.runTest {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 523d15c..9481e5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -155,7 +155,6 @@
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinatorLogger;
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.HeadsUpNotificationInteractor;
import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.AmbientState;
@@ -163,7 +162,6 @@
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator;
-import com.android.systemui.statusbar.notification.stack.data.repository.FakeHeadsUpNotificationRepository;
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
@@ -365,13 +363,6 @@
protected TestScope mTestScope = mKosmos.getTestScope();
protected ShadeInteractor mShadeInteractor;
protected PowerInteractor mPowerInteractor;
- protected FakeHeadsUpNotificationRepository mFakeHeadsUpNotificationRepository =
- new FakeHeadsUpNotificationRepository();
- protected NotificationsKeyguardViewStateRepository mNotificationsKeyguardViewStateRepository =
- new NotificationsKeyguardViewStateRepository();
- protected NotificationsKeyguardInteractor mNotificationsKeyguardInteractor =
- new NotificationsKeyguardInteractor(mNotificationsKeyguardViewStateRepository);
- protected HeadsUpNotificationInteractor mHeadsUpNotificationInteractor;
protected NotificationPanelViewController.TouchHandler mTouchHandler;
protected ConfigurationController mConfigurationController;
protected SysuiStatusBarStateController mStatusBarStateController;
@@ -689,12 +680,6 @@
when(longPressHandlingView.getResources()).thenReturn(longPressHandlingViewRes);
when(longPressHandlingViewRes.getString(anyInt())).thenReturn("");
-
- mHeadsUpNotificationInteractor =
- new HeadsUpNotificationInteractor(mFakeHeadsUpNotificationRepository,
- mDeviceEntryFaceAuthInteractor, mKeyguardTransitionInteractor,
- mNotificationsKeyguardInteractor, mShadeInteractor);
-
mNotificationPanelViewController = new NotificationPanelViewController(
mView,
mMainHandler,
@@ -769,7 +754,6 @@
mActivityStarter,
mSharedNotificationContainerInteractor,
mActiveNotificationsInteractor,
- mHeadsUpNotificationInteractor,
mShadeAnimationInteractor,
mKeyguardViewConfigurator,
mDeviceEntryFaceAuthInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 905cc4c..a7fd160 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -58,13 +58,13 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.DejankUtils;
+import com.android.systemui.flags.DisableSceneContainer;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.KeyguardClockPositionAlgorithm;
@@ -1375,7 +1375,7 @@
}
@Test
- @DisableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+ @DisableSceneContainer
public void shadeExpanded_whenHunIsPresent() {
when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
assertThat(mNotificationPanelViewController.isExpanded()).isTrue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
index 64eadb7..90655c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
@@ -19,7 +19,6 @@
package com.android.systemui.shade
import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
import android.testing.TestableLooper
import android.view.HapticFeedbackConstants
import android.view.View
@@ -35,7 +34,6 @@
import com.android.systemui.statusbar.StatusBarState.SHADE
import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -243,36 +241,4 @@
val bottomAreaAlpha by collectLastValue(mFakeKeyguardRepository.bottomAreaAlpha)
assertThat(bottomAreaAlpha).isEqualTo(1f)
}
-
- @Test
- @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
- fun shadeExpanded_whenHunIsPresent() = runTest {
- launch(mainDispatcher) {
- givenViewAttached()
-
- // WHEN a pinned heads up is present
- mFakeHeadsUpNotificationRepository.setNotifications(
- FakeHeadsUpRowRepository("key", isPinned = true)
- )
- }
- advanceUntilIdle()
-
- // THEN the panel should be visible
- assertThat(mNotificationPanelViewController.isExpanded).isTrue()
- }
-
- @Test
- @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
- fun shadeExpanded_whenHunIsAnimatingAway() = runTest {
- launch(mainDispatcher) {
- givenViewAttached()
-
- // WHEN a heads up is animating away
- mFakeHeadsUpNotificationRepository.isHeadsUpAnimatingAway.value = true
- }
- advanceUntilIdle()
-
- // THEN the panel should be visible
- assertThat(mNotificationPanelViewController.isExpanded).isTrue()
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarSignalPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarSignalPolicyTest.kt
new file mode 100644
index 0000000..593f873
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarSignalPolicyTest.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar
+
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.Flags.FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
+import com.android.systemui.statusbar.connectivity.IconState
+import com.android.systemui.statusbar.connectivity.NetworkController
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy_Factory
+import com.android.systemui.statusbar.phone.ui.StatusBarIconController
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.airplaneModeInteractor
+import com.android.systemui.statusbar.policy.SecurityController
+import com.android.systemui.tuner.TunerService
+import com.android.systemui.util.CarrierConfigTracker
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.util.mockito.mock
+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.runner.RunWith
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.verifyZeroInteractions
+import kotlin.test.Test
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class StatusBarSignalPolicyTest : SysuiTestCase() {
+ private val kosmos = Kosmos().also { it.testCase = this }
+
+ private lateinit var underTest: StatusBarSignalPolicy
+
+ private val testScope = TestScope()
+
+ private val javaAdapter = JavaAdapter(testScope.backgroundScope)
+ private val airplaneModeInteractor = kosmos.airplaneModeInteractor
+
+ private val securityController = mock<SecurityController>()
+ private val tunerService = mock<TunerService>()
+ private val statusBarIconController = mock<StatusBarIconController>()
+ private val networkController = mock<NetworkController>()
+ private val carrierConfigTracker = mock<CarrierConfigTracker>()
+
+ private var slotAirplane: String? = null
+
+ @Before
+ fun setup() {
+ underTest =
+ StatusBarSignalPolicy_Factory.newInstance(
+ mContext,
+ statusBarIconController,
+ carrierConfigTracker,
+ networkController,
+ securityController,
+ tunerService,
+ javaAdapter,
+ airplaneModeInteractor,
+ )
+
+ slotAirplane = mContext.getString(R.string.status_bar_airplane)
+ }
+
+ @Test
+ @EnableFlags(FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR)
+ fun airplaneModeViaInteractor_statusBarSignalPolicyRefactorFlagEnabled_iconUpdated() =
+ testScope.runTest {
+ underTest.start()
+ airplaneModeInteractor.setIsAirplaneMode(true)
+ runCurrent()
+ verify(statusBarIconController).setIconVisibility(slotAirplane, true)
+
+ airplaneModeInteractor.setIsAirplaneMode(false)
+ runCurrent()
+ verify(statusBarIconController).setIconVisibility(slotAirplane, false)
+ }
+
+ @Test
+ @EnableFlags(FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR)
+ fun airplaneModeViaSignalCallback_statusBarSignalPolicyRefactorFlagEnabled_iconNotUpdated() =
+ testScope.runTest {
+ underTest.start()
+ runCurrent()
+ clearInvocations(statusBarIconController)
+
+ // Make sure the legacy code path does not change airplane mode when the refactor
+ // flag is enabled.
+ underTest.setIsAirplaneMode(IconState(true, TelephonyIcons.FLIGHT_MODE_ICON, ""))
+ runCurrent()
+ verifyZeroInteractions(statusBarIconController)
+
+ underTest.setIsAirplaneMode(IconState(false, TelephonyIcons.FLIGHT_MODE_ICON, ""))
+ runCurrent()
+ verifyZeroInteractions(statusBarIconController)
+ }
+
+ @Test
+ @EnableFlags(FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR)
+ fun statusBarSignalPolicyInitialization_statusBarSignalPolicyRefactorFlagEnabled_initNoOp() =
+ testScope.runTest {
+ // Make sure StatusBarSignalPolicy.init does no initialization when
+ // the refactor flag is disabled.
+ underTest.init()
+ verifyZeroInteractions(securityController, networkController, tunerService)
+ }
+
+ @Test
+ @DisableFlags(FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR)
+ fun airplaneModeViaSignalCallback_statusBarSignalPolicyRefactorFlagDisabled_iconUpdated() =
+ testScope.runTest {
+ underTest.init()
+
+ underTest.setIsAirplaneMode(IconState(true, TelephonyIcons.FLIGHT_MODE_ICON, ""))
+ runCurrent()
+ verify(statusBarIconController).setIconVisibility(slotAirplane, true)
+
+ underTest.setIsAirplaneMode(IconState(false, TelephonyIcons.FLIGHT_MODE_ICON, ""))
+ runCurrent()
+ verify(statusBarIconController).setIconVisibility(slotAirplane, false)
+ }
+
+ @Test
+ @DisableFlags(FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR)
+ fun airplaneModeViaInteractor_statusBarSignalPolicyRefactorFlagDisabled_iconNotUpdated() =
+ testScope.runTest {
+ underTest.init()
+
+ // Make sure changing airplane mode from airplaneModeRepository does nothing
+ // if the StatusBarSignalPolicyRefactor is not enabled.
+ airplaneModeInteractor.setIsAirplaneMode(true)
+ runCurrent()
+ verifyZeroInteractions(statusBarIconController)
+
+ airplaneModeInteractor.setIsAirplaneMode(false)
+ runCurrent()
+ verifyZeroInteractions(statusBarIconController)
+ }
+
+ @Test
+ @DisableFlags(FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR)
+ fun statusBarSignalPolicyInitialization_statusBarSignalPolicyRefactorFlagDisabled_startNoOp() =
+ testScope.runTest {
+ // Make sure StatusBarSignalPolicy.start does no initialization when
+ // the refactor flag is disabled.
+ underTest.start()
+ verifyZeroInteractions(securityController, networkController, tunerService)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
index ce79fbd..7bc6d4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
@@ -132,10 +132,10 @@
)
assertThat((latest as OngoingActivityChipModel.Shown).icon)
- .isInstanceOf(OngoingActivityChipModel.ChipIcon.Basic::class.java)
+ .isInstanceOf(OngoingActivityChipModel.ChipIcon.SingleColorIcon::class.java)
val icon =
(((latest as OngoingActivityChipModel.Shown).icon)
- as OngoingActivityChipModel.ChipIcon.Basic)
+ as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
.impl as Icon.Resource
assertThat(icon.res).isEqualTo(com.android.internal.R.drawable.ic_phone)
assertThat(icon.contentDescription).isNotNull()
@@ -170,10 +170,10 @@
)
assertThat((latest as OngoingActivityChipModel.Shown).icon)
- .isInstanceOf(OngoingActivityChipModel.ChipIcon.Basic::class.java)
+ .isInstanceOf(OngoingActivityChipModel.ChipIcon.SingleColorIcon::class.java)
val icon =
(((latest as OngoingActivityChipModel.Shown).icon)
- as OngoingActivityChipModel.ChipIcon.Basic)
+ as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
.impl as Icon.Resource
assertThat(icon.res).isEqualTo(com.android.internal.R.drawable.ic_phone)
assertThat(icon.contentDescription).isNotNull()
@@ -206,10 +206,10 @@
repo.setOngoingCallState(inCallModel(startTimeMs = 1000, notificationIcon = null))
assertThat((latest as OngoingActivityChipModel.Shown).icon)
- .isInstanceOf(OngoingActivityChipModel.ChipIcon.Basic::class.java)
+ .isInstanceOf(OngoingActivityChipModel.ChipIcon.SingleColorIcon::class.java)
val icon =
(((latest as OngoingActivityChipModel.Shown).icon)
- as OngoingActivityChipModel.ChipIcon.Basic)
+ as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
.impl as Icon.Resource
assertThat(icon.res).isEqualTo(com.android.internal.R.drawable.ic_phone)
assertThat(icon.contentDescription).isNotNull()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
index a8d2c5b..77992db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
@@ -127,7 +127,7 @@
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
val icon =
(((latest as OngoingActivityChipModel.Shown).icon)
- as OngoingActivityChipModel.ChipIcon.Basic)
+ as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
.impl as Icon.Resource
assertThat(icon.res).isEqualTo(R.drawable.ic_cast_connected)
assertThat((icon.contentDescription as ContentDescription.Resource).res)
@@ -146,7 +146,7 @@
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
val icon =
(((latest as OngoingActivityChipModel.Shown).icon)
- as OngoingActivityChipModel.ChipIcon.Basic)
+ as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
.impl as Icon.Resource
assertThat(icon.res).isEqualTo(R.drawable.ic_cast_connected)
assertThat((icon.contentDescription as ContentDescription.Resource).res)
@@ -184,7 +184,7 @@
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java)
val icon =
(((latest as OngoingActivityChipModel.Shown).icon)
- as OngoingActivityChipModel.ChipIcon.Basic)
+ as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
.impl as Icon.Resource
assertThat(icon.res).isEqualTo(R.drawable.ic_cast_connected)
// This content description is just generic "Casting", not "Casting screen"
@@ -214,7 +214,7 @@
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
val icon =
(((latest as OngoingActivityChipModel.Shown).icon)
- as OngoingActivityChipModel.ChipIcon.Basic)
+ as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
.impl as Icon.Resource
assertThat(icon.res).isEqualTo(R.drawable.ic_cast_connected)
// MediaProjection == screen casting, so this content description reflects that we're
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt
new file mode 100644
index 0000000..118dea6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel
+
+import android.content.packageManager
+import android.graphics.drawable.BitmapDrawable
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_STATUS_BAR_RON_CHIPS
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.chips.ui.model.ColorsModel
+import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.statusbar.commandline.commandRegistry
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
+import kotlin.test.Test
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.mockito.kotlin.any
+import org.mockito.kotlin.whenever
+
+@SmallTest
+class DemoRonChipViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val commandRegistry = kosmos.commandRegistry
+ private val pw = PrintWriter(StringWriter())
+
+ private val underTest = kosmos.demoRonChipViewModel
+
+ @Before
+ fun setUp() {
+ underTest.start()
+ whenever(kosmos.packageManager.getApplicationIcon(any<String>()))
+ .thenReturn(BitmapDrawable())
+ }
+
+ @Test
+ @DisableFlags(FLAG_STATUS_BAR_RON_CHIPS)
+ fun chip_flagOff_hidden() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ addDemoRonChip()
+
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
+ }
+
+ @Test
+ @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS)
+ fun chip_noPackage_hidden() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ commandRegistry.onShellCommand(pw, arrayOf("demo-ron"))
+
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
+ }
+
+ @Test
+ @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS)
+ fun chip_hasPackage_shown() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ commandRegistry.onShellCommand(pw, arrayOf("demo-ron", "-p", "com.android.systemui"))
+
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+ }
+
+ @Test
+ @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS)
+ fun chip_hasText_shownWithText() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ commandRegistry.onShellCommand(
+ pw,
+ arrayOf("demo-ron", "-p", "com.android.systemui", "-t", "test")
+ )
+
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Text::class.java)
+ }
+
+ @Test
+ @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS)
+ fun chip_supportsColor() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ commandRegistry.onShellCommand(
+ pw,
+ arrayOf("demo-ron", "-p", "com.android.systemui", "-c", "#434343")
+ )
+
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+ assertThat((latest as OngoingActivityChipModel.Shown).colors)
+ .isInstanceOf(ColorsModel.Custom::class.java)
+ }
+
+ @Test
+ @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS)
+ fun chip_hasHideArg_hidden() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ // First, show a chip
+ addDemoRonChip()
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+
+ // Then, hide the chip
+ commandRegistry.onShellCommand(pw, arrayOf("demo-ron", "--hide"))
+
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
+ }
+
+ private fun addDemoRonChip() {
+ Companion.addDemoRonChip(commandRegistry, pw)
+ }
+
+ companion object {
+ fun addDemoRonChip(commandRegistry: CommandRegistry, pw: PrintWriter) {
+ commandRegistry.onShellCommand(pw, arrayOf("demo-ron", "-p", "com.android.systemui"))
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
index 804eb5c..16101bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
@@ -150,7 +150,7 @@
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
val icon =
(((latest as OngoingActivityChipModel.Shown).icon)
- as OngoingActivityChipModel.ChipIcon.Basic)
+ as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
.impl as Icon.Resource
assertThat(icon.res).isEqualTo(R.drawable.ic_screenrecord)
assertThat(icon.contentDescription).isNotNull()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
index a2ef599..791a21d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
@@ -135,7 +135,7 @@
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
val icon =
(((latest as OngoingActivityChipModel.Shown).icon)
- as OngoingActivityChipModel.ChipIcon.Basic)
+ as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
.impl as Icon.Resource
assertThat(icon.res).isEqualTo(R.drawable.ic_present_to_all)
assertThat(icon.contentDescription).isNotNull()
@@ -152,7 +152,7 @@
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
val icon =
(((latest as OngoingActivityChipModel.Shown).icon)
- as OngoingActivityChipModel.ChipIcon.Basic)
+ as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
.impl as Icon.Resource
assertThat(icon.res).isEqualTo(R.drawable.ic_present_to_all)
assertThat(icon.contentDescription).isNotNull()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
index a724cfaa..4977c548 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
@@ -154,5 +154,7 @@
}
private fun createIcon(@DrawableRes drawable: Int) =
- OngoingActivityChipModel.ChipIcon.Basic(Icon.Resource(drawable, contentDescription = null))
+ OngoingActivityChipModel.ChipIcon.SingleColorIcon(
+ Icon.Resource(drawable, contentDescription = null)
+ )
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
index 556ec6a..bd5df07 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
@@ -19,9 +19,12 @@
import android.content.DialogInterface
import android.content.packageManager
import android.content.pm.PackageManager
+import android.graphics.drawable.BitmapDrawable
+import android.platform.test.annotations.EnableFlags
import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_STATUS_BAR_RON_CHIPS
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
@@ -36,8 +39,11 @@
import com.android.systemui.screenrecord.data.repository.screenRecordRepository
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.NORMAL_PACKAGE
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection
+import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.DemoRonChipViewModelTest.Companion.addDemoRonChip
+import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.demoRonChipViewModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
+import com.android.systemui.statusbar.commandline.commandRegistry
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
@@ -45,6 +51,8 @@
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel
import com.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -68,11 +76,14 @@
private val kosmos = Kosmos().also { it.testCase = this }
private val testScope = kosmos.testScope
private val systemClock = kosmos.fakeSystemClock
+ private val commandRegistry = kosmos.commandRegistry
private val screenRecordState = kosmos.screenRecordRepository.screenRecordState
private val mediaProjectionState = kosmos.fakeMediaProjectionRepository.mediaProjectionState
private val callRepo = kosmos.ongoingCallRepository
+ private val pw = PrintWriter(StringWriter())
+
private val mockSystemUIDialog = mock<SystemUIDialog>()
private val chipBackgroundView = mock<ChipBackgroundContainer>()
private val chipView =
@@ -90,6 +101,9 @@
@Before
fun setUp() {
setUpPackageManagerForMediaProjection(kosmos)
+ kosmos.demoRonChipViewModel.start()
+ whenever(kosmos.packageManager.getApplicationIcon(any<String>()))
+ .thenReturn(BitmapDrawable())
}
@Test
@@ -169,15 +183,24 @@
}
@Test
+ @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS)
fun chip_higherPriorityChipAdded_lowerPriorityChipReplaced() =
testScope.runTest {
- // Start with just the lower priority call chip
- callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
+ // Start with just the lowest priority chip shown
+ addDemoRonChip(commandRegistry, pw)
+ // And everything else hidden
+ callRepo.setOngoingCallState(OngoingCallModel.NoCall)
mediaProjectionState.value = MediaProjectionState.NotProjecting
screenRecordState.value = ScreenRecordModel.DoingNothing
val latest by collectLastValue(underTest.chip)
+ assertIsDemoRonChip(latest)
+
+ // WHEN the higher priority call chip is added
+ callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
+
+ // THEN the higher priority call chip is used
assertIsCallChip(latest)
// WHEN the higher priority media projection chip is added
@@ -199,14 +222,15 @@
}
@Test
+ @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS)
fun chip_highestPriorityChipRemoved_showsNextPriorityChip() =
testScope.runTest {
// WHEN all chips are active
screenRecordState.value = ScreenRecordModel.Recording
mediaProjectionState.value =
MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
-
callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
+ addDemoRonChip(commandRegistry, pw)
val latest by collectLastValue(underTest.chip)
@@ -224,6 +248,12 @@
// THEN the lower priority call is used
assertIsCallChip(latest)
+
+ // WHEN the higher priority call is removed
+ callRepo.setOngoingCallState(OngoingCallModel.NoCall)
+
+ // THEN the lower priority demo RON is used
+ assertIsDemoRonChip(latest)
}
/** Regression test for b/347726238. */
@@ -338,7 +368,7 @@
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
val icon =
(((latest as OngoingActivityChipModel.Shown).icon)
- as OngoingActivityChipModel.ChipIcon.Basic)
+ as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
.impl as Icon.Resource
assertThat(icon.res).isEqualTo(R.drawable.ic_screenrecord)
}
@@ -347,7 +377,7 @@
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
val icon =
(((latest as OngoingActivityChipModel.Shown).icon)
- as OngoingActivityChipModel.ChipIcon.Basic)
+ as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
.impl as Icon.Resource
assertThat(icon.res).isEqualTo(R.drawable.ic_present_to_all)
}
@@ -356,9 +386,15 @@
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
val icon =
(((latest as OngoingActivityChipModel.Shown).icon)
- as OngoingActivityChipModel.ChipIcon.Basic)
+ as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
.impl as Icon.Resource
assertThat(icon.res).isEqualTo(com.android.internal.R.drawable.ic_phone)
}
+
+ fun assertIsDemoRonChip(latest: OngoingActivityChipModel?) {
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+ assertThat((latest as OngoingActivityChipModel.Shown).icon)
+ .isInstanceOf(OngoingActivityChipModel.ChipIcon.FullColorAppIcon::class.java)
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt
index 8cf7473..1efb3f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt
@@ -40,6 +40,15 @@
}
@Test
+ fun parseColor() {
+ assertThat(Type.Color.parseValue("#434343").isSuccess).isTrue()
+ assertThat(Type.Color.parseValue("#aa123456").isSuccess).isTrue()
+ assertThat(Type.Color.parseValue("red").isSuccess).isTrue()
+
+ assertThat(Type.Color.parseValue("not a color").isFailure).isTrue()
+ }
+
+ @Test
fun mapToComplexType() {
val parseSquare = Type.Int.map { Rect(it, it, it, it) }
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 e4945fc..1a1af2e 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
@@ -342,7 +342,7 @@
val stackTop = 200f
val stackHeight = 800f
whenever(ambientState.stackTop).thenReturn(stackTop)
- whenever(ambientState.stackHeight).thenReturn(stackHeight)
+ whenever(ambientState.interpolatedStackHeight).thenReturn(stackHeight)
val shelfTop = stackTop + stackHeight - shelf.height
val stackScrollAlgorithmState = StackScrollAlgorithmState()
val viewInShelf = mock(ExpandableView::class.java)
@@ -378,7 +378,7 @@
val stackTop = 200f
val stackHeight = 800f
whenever(ambientState.stackTop).thenReturn(stackTop)
- whenever(ambientState.stackHeight).thenReturn(stackHeight)
+ whenever(ambientState.interpolatedStackHeight).thenReturn(stackHeight)
val paddingBetweenElements =
context.resources.getDimensionPixelSize(R.dimen.notification_divider_height)
whenever(ambientState.isShadeExpanded).thenReturn(true)
@@ -404,7 +404,7 @@
fun updateState_withNullLastVisibleBackgroundChild_hideShelf() {
// GIVEN
whenever(ambientState.stackY).thenReturn(100f)
- whenever(ambientState.stackHeight).thenReturn(100f)
+ whenever(ambientState.interpolatedStackHeight).thenReturn(100f)
val paddingBetweenElements =
context.resources.getDimensionPixelSize(R.dimen.notification_divider_height)
val endOfStack = 200f + paddingBetweenElements
@@ -433,7 +433,7 @@
val stackTop = 200f
val stackHeight = 800f
whenever(ambientState.stackTop).thenReturn(stackTop)
- whenever(ambientState.stackHeight).thenReturn(stackHeight)
+ whenever(ambientState.interpolatedStackHeight).thenReturn(stackHeight)
val paddingBetweenElements =
context.resources.getDimensionPixelSize(R.dimen.notification_divider_height)
whenever(ambientState.isShadeExpanded).thenReturn(true)
@@ -459,7 +459,7 @@
fun updateState_withNullFirstViewInShelf_hideShelf() {
// GIVEN
whenever(ambientState.stackY).thenReturn(100f)
- whenever(ambientState.stackHeight).thenReturn(100f)
+ whenever(ambientState.interpolatedStackHeight).thenReturn(100f)
val paddingBetweenElements =
context.resources.getDimensionPixelSize(R.dimen.notification_divider_height)
val endOfStack = 200f + paddingBetweenElements
@@ -488,7 +488,7 @@
val stackTop = 200f
val stackHeight = 800f
whenever(ambientState.stackTop).thenReturn(stackTop)
- whenever(ambientState.stackHeight).thenReturn(stackHeight)
+ whenever(ambientState.interpolatedStackHeight).thenReturn(stackHeight)
val paddingBetweenElements =
context.resources.getDimensionPixelSize(R.dimen.notification_divider_height)
val lastVisibleBackgroundChild = mock<ExpandableView>()
@@ -514,7 +514,7 @@
fun updateState_withCollapsedShade_hideShelf() {
// GIVEN
whenever(ambientState.stackY).thenReturn(100f)
- whenever(ambientState.stackHeight).thenReturn(100f)
+ whenever(ambientState.interpolatedStackHeight).thenReturn(100f)
val paddingBetweenElements =
context.resources.getDimensionPixelSize(R.dimen.notification_divider_height)
val endOfStack = 200f + paddingBetweenElements
@@ -543,7 +543,7 @@
val stackTop = 200f
val stackHeight = 800f
whenever(ambientState.stackTop).thenReturn(stackTop)
- whenever(ambientState.stackHeight).thenReturn(stackHeight)
+ whenever(ambientState.interpolatedStackHeight).thenReturn(stackHeight)
val paddingBetweenElements =
context.resources.getDimensionPixelSize(R.dimen.notification_divider_height)
whenever(ambientState.isShadeExpanded).thenReturn(true)
@@ -583,7 +583,7 @@
fun updateState_withHiddenSectionBeforeShelf_hideShelf() {
// GIVEN
whenever(ambientState.stackY).thenReturn(100f)
- whenever(ambientState.stackHeight).thenReturn(100f)
+ whenever(ambientState.interpolatedStackHeight).thenReturn(100f)
val paddingBetweenElements =
context.resources.getDimensionPixelSize(R.dimen.notification_divider_height)
val endOfStack = 200f + paddingBetweenElements
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 1717f4c..a18de68 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
@@ -95,7 +95,6 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -246,26 +245,12 @@
when(mStackSizeCalculator.computeHeight(eq(mStackScroller), anyInt(), anyFloat()))
.thenReturn((float) stackHeight);
- mStackScroller.updateContentHeight();
+ mStackScroller.updateStackHeight();
assertThat(mStackScroller.getIntrinsicStackHeight()).isEqualTo(stackHeight);
}
@Test
- @DisableSceneContainer
- public void testIntrinsicStackHeight_includesTopScrimPadding() {
- int stackHeight = 300;
- int topScrimPadding = px(R.dimen.notification_side_paddings);
- when(mStackSizeCalculator.computeHeight(eq(mStackScroller), anyInt(), anyFloat()))
- .thenReturn((float) stackHeight);
-
- mStackScroller.updateContentHeight();
-
- assertThat(mStackScroller.getIntrinsicStackHeight())
- .isEqualTo(stackHeight + topScrimPadding);
- }
-
- @Test
@DisableSceneContainer // TODO(b/312473478): address disabled test
public void testUpdateStackHeight_qsExpansionZero() {
final float expansionFraction = 0.2f;
@@ -290,8 +275,8 @@
endHeight * StackScrollAlgorithm.START_FRACTION,
endHeight, expansionFraction);
- mStackScroller.updateStackHeight(endHeight, expansionFraction);
- assertThat(mAmbientState.getStackHeight()).isEqualTo(expected);
+ mStackScroller.updateInterpolatedStackHeight(endHeight, expansionFraction);
+ assertThat(mAmbientState.getInterpolatedStackHeight()).isEqualTo(expected);
}
@Test
@@ -310,7 +295,7 @@
// THEN stackHeight and stackEndHeight are the same
verify(mAmbientState).setStackEndHeight(stackEndHeight);
- verify(mAmbientState).setStackHeight(stackEndHeight);
+ verify(mAmbientState).setInterpolatedStackHeight(stackEndHeight);
}
@Test
@@ -330,7 +315,7 @@
// THEN stackHeight is changed by the expansion frac
verify(mAmbientState).setStackEndHeight(stackEndHeight);
- verify(mAmbientState).setStackHeight(stackEndHeight * 0.75f);
+ verify(mAmbientState).setInterpolatedStackHeight(stackEndHeight * 0.75f);
}
@Test
@@ -350,7 +335,7 @@
// THEN stackHeight is measured from the stack top
verify(mAmbientState).setStackEndHeight(stackEndHeight);
- verify(mAmbientState).setStackHeight(stackEndHeight);
+ verify(mAmbientState).setInterpolatedStackHeight(stackEndHeight);
}
@Test
@@ -364,7 +349,7 @@
clearInvocations(mAmbientState);
mStackScroller.updateStackEndHeightAndStackHeight(1f);
- verify(mAmbientState).setStackHeight(eq(300f));
+ verify(mAmbientState).setInterpolatedStackHeight(eq(300f));
}
@Test
@@ -377,7 +362,7 @@
clearInvocations(mAmbientState);
mStackScroller.updateStackEndHeightAndStackHeight(expansionFraction);
verify(mAmbientState, never()).setStackEndHeight(anyFloat());
- verify(mAmbientState).setStackHeight(anyFloat());
+ verify(mAmbientState).setInterpolatedStackHeight(anyFloat());
}
@Test
@@ -392,14 +377,14 @@
clearInvocations(mAmbientState);
mStackScroller.updateStackEndHeightAndStackHeight(expansionFraction);
verify(mAmbientState, never()).setStackEndHeight(anyFloat());
- verify(mAmbientState).setStackHeight(anyFloat());
+ verify(mAmbientState).setInterpolatedStackHeight(anyFloat());
// Validate that when the animation ends the stackEndHeight is recalculated immediately
clearInvocations(mAmbientState);
mStackScroller.setPanelFlinging(false);
verify(mAmbientState).setFlinging(eq(false));
verify(mAmbientState).setStackEndHeight(anyFloat());
- verify(mAmbientState).setStackHeight(anyFloat());
+ verify(mAmbientState).setInterpolatedStackHeight(anyFloat());
}
@Test
@@ -441,6 +426,86 @@
}
@Test
+ @EnableSceneContainer
+ public void setExpandFraction_fullyCollapsed() {
+ // Given: NSSL has a height
+ when(mStackScroller.getHeight()).thenReturn(1200);
+ // And: stack bounds are set
+ float expandFraction = 0.0f;
+ float stackTop = 100;
+ float stackCutoff = 1100;
+ float stackHeight = stackCutoff - stackTop;
+ mStackScroller.setStackTop(stackTop);
+ mStackScroller.setStackCutoff(stackCutoff);
+
+ // When: panel is fully collapsed
+ mStackScroller.setExpandFraction(expandFraction);
+
+ // Then
+ assertThat(mAmbientState.getExpansionFraction()).isEqualTo(expandFraction);
+ assertThat(mAmbientState.isExpansionChanging()).isFalse();
+ assertThat(mAmbientState.getStackEndHeight()).isEqualTo(stackHeight);
+ assertThat(mAmbientState.getInterpolatedStackHeight()).isEqualTo(
+ stackHeight * StackScrollAlgorithm.START_FRACTION);
+ assertThat(mAmbientState.isShadeExpanded()).isFalse();
+ assertThat(mStackScroller.getExpandedHeight()).isZero();
+ }
+
+ @Test
+ @EnableSceneContainer
+ public void setExpandFraction_expanding() {
+ // Given: NSSL has a height
+ when(mStackScroller.getHeight()).thenReturn(1200);
+ // And: stack bounds are set
+ float expandFraction = 0.6f;
+ float stackTop = 100;
+ float stackCutoff = 1100;
+ float stackHeight = stackCutoff - stackTop;
+ mStackScroller.setStackTop(stackTop);
+ mStackScroller.setStackCutoff(stackCutoff);
+
+ // When: panel is expanding
+ mStackScroller.setExpandFraction(expandFraction);
+
+ // Then
+ assertThat(mAmbientState.getExpansionFraction()).isEqualTo(expandFraction);
+ assertThat(mAmbientState.isExpansionChanging()).isTrue();
+ assertThat(mAmbientState.getStackEndHeight()).isEqualTo(stackHeight);
+ assertThat(mAmbientState.getInterpolatedStackHeight()).isGreaterThan(
+ stackHeight * StackScrollAlgorithm.START_FRACTION);
+ assertThat(mAmbientState.getInterpolatedStackHeight()).isLessThan(stackHeight);
+ assertThat(mStackScroller.getExpandedHeight()).isGreaterThan(0f);
+ assertThat(mAmbientState.isShadeExpanded()).isTrue();
+ }
+
+ @Test
+ @EnableSceneContainer
+ public void setExpandFraction_fullyExpanded() {
+ // Given: NSSL has a height
+ int viewHeight = 1200;
+ when(mStackScroller.getHeight()).thenReturn(viewHeight);
+ // And: stack bounds are set
+ float expandFraction = 1.0f;
+ float stackTop = 100;
+ float stackCutoff = 1100;
+ float stackHeight = stackCutoff - stackTop;
+ mStackScroller.setStackTop(stackTop);
+ mStackScroller.setStackCutoff(stackCutoff);
+
+ // When: panel is fully expanded
+ mStackScroller.setExpandFraction(expandFraction);
+
+ // Then
+ assertThat(mAmbientState.getExpansionFraction()).isEqualTo(expandFraction);
+ assertThat(mAmbientState.isExpansionChanging()).isFalse();
+ assertThat(mAmbientState.getStackEndHeight()).isEqualTo(stackHeight);
+ assertThat(mAmbientState.getInterpolatedStackHeight()).isEqualTo(stackHeight);
+ assertThat(mStackScroller.getExpandedHeight()).isEqualTo(viewHeight);
+ assertThat(mAmbientState.isShadeExpanded()).isTrue();
+ }
+
+ @Test
+ @DisableSceneContainer
public void testSetExpandedHeight_listenerReceivedCallbacks() {
final float expectedHeight = 0f;
@@ -467,6 +532,7 @@
}
@Test
+ @DisableSceneContainer
public void testSetExpandedHeight_withSplitShade_doesntInterpolateStackHeight() {
mTestableResources
.addOverride(R.bool.config_use_split_notification_shade, /* value= */ true);
@@ -1206,7 +1272,7 @@
@Test
- @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+ @EnableSceneContainer
public void testGenerateHeadsUpDisappearEvent_setsHeadsUpAnimatingAway() {
// GIVEN NSSL is ready for HUN animations
Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
@@ -1222,7 +1288,7 @@
}
@Test
- @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+ @EnableSceneContainer
public void testGenerateHeadsUpDisappearEvent_stackExpanded_headsUpAnimatingAwayNotSet() {
// GIVEN NSSL would be ready for HUN animations, BUT it is expanded
Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
@@ -1241,7 +1307,7 @@
}
@Test
- @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+ @EnableSceneContainer
public void testGenerateHeadsUpDisappearEvent_pendingAppearEvent_headsUpAnimatingAwayNotSet() {
// GIVEN NSSL is ready for HUN animations
Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
@@ -1259,7 +1325,7 @@
}
@Test
- @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+ @EnableSceneContainer
public void testGenerateHeadsUpAppearEvent_headsUpAnimatingAwayNotSet() {
// GIVEN NSSL is ready for HUN animations
Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
@@ -1295,7 +1361,7 @@
}
@Test
- @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+ @EnableSceneContainer
public void testOnChildAnimationsFinished_resetsheadsUpAnimatingAway() {
// GIVEN NSSL is ready for HUN animations
Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
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 7e79019..3e8bf47 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
@@ -1114,6 +1114,7 @@
}
@Test
+ @DisableSceneContainer
fun shadeOpened_hunDoesNotOverlapQQS_hunShouldHaveNoShadow() {
// 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,
@@ -1144,6 +1145,7 @@
}
@Test
+ @DisableSceneContainer
fun shadeClosed_hunShouldHaveFullShadow() {
// Given: shade is closed, ambientState.stackTranslation == -ambientState.topPadding,
// the height of HUN is equal to the height of QQS Panel,
@@ -1172,6 +1174,7 @@
}
@Test
+ @DisableSceneContainer
fun draggingHunToOpenShade_hunShouldHavePartialShadow() {
// Given: shade is closed when HUN pops up,
// now drags down the HUN to open shade
@@ -1447,7 +1450,7 @@
// set stackEndHeight and stackHeight
// ExpansionFractionWithoutShelf == stackHeight / stackEndHeight
ambientState.stackEndHeight = 100f
- ambientState.stackHeight = ambientState.stackEndHeight * fraction
+ ambientState.interpolatedStackHeight = ambientState.stackEndHeight * fraction
}
private fun resetViewStates_hunYTranslationIs(expected: Float) {
@@ -1531,7 +1534,7 @@
// shade is fully open
ambientState.expansionFraction = 1.0f
with(fullStackHeight) {
- ambientState.stackHeight = this
+ ambientState.interpolatedStackHeight = this
ambientState.stackEndHeight = this
}
stackScrollAlgorithm.setIsExpanded(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt
index 2f81027..c7919df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt
@@ -20,6 +20,7 @@
import android.os.UserManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.SysuiTestCase
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.concurrency.FakeExecutor
@@ -35,6 +36,7 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.never
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -46,12 +48,20 @@
@Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var userManager: UserManager
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- controller = ManagedProfileControllerImpl(context, mainExecutor, userTracker, userManager)
+ controller =
+ ManagedProfileControllerImpl(
+ context,
+ mainExecutor,
+ userTracker,
+ userManager,
+ keyguardUpdateMonitor
+ )
}
@Test
@@ -107,6 +117,24 @@
captor.value.onProfilesChanged(userManager.getEnabledProfiles(1))
}
+ @Test
+ fun hasWorkingProfile_setWorkModeEnabled_callsAwakenFromDream() {
+ `when`(userTracker.userId).thenReturn(1)
+ setupWorkingProfile(1)
+ `when`(userManager.requestQuietModeEnabled(any(), any())).thenReturn(false)
+ controller.hasActiveProfile()
+
+ controller.isWorkModeEnabled = true
+
+ verify(keyguardUpdateMonitor).awakenFromDream()
+ }
+
+ @Test
+ fun noWorkingProfile_setWorkModeEnabled_NoAwakenFromDreamCall() {
+ controller.isWorkModeEnabled = true
+ verify(keyguardUpdateMonitor, never()).awakenFromDream()
+ }
+
private fun setupWorkingProfile(userId: Int) {
`when`(userManager.getEnabledProfiles(userId))
.thenReturn(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
index 76dc65c..2ed3473 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
@@ -34,6 +34,7 @@
import android.testing.TestableLooper.RunWithLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.internal.statusbar.StatusBarIcon
import com.android.settingslib.notification.modes.TestModeBuilder
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
@@ -41,6 +42,7 @@
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
+import com.android.systemui.kosmos.testScope
import com.android.systemui.privacy.PrivacyItemController
import com.android.systemui.privacy.logging.PrivacyLogger
import com.android.systemui.screenrecord.RecordingController
@@ -71,9 +73,7 @@
import com.android.systemui.util.time.FakeSystemClock
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -145,7 +145,7 @@
private lateinit var alarmCallbackCaptor:
ArgumentCaptor<NextAlarmController.NextAlarmChangeCallback>
- private val testScope = TestScope(UnconfinedTestDispatcher())
+ private val testScope = kosmos.testScope
private val fakeConnectedDisplayStateProvider = FakeConnectedDisplayStateProvider()
private val zenModeController = FakeZenModeController()
@@ -249,7 +249,7 @@
statusBarPolicy.init()
clearInvocations(iconController)
- fakeConnectedDisplayStateProvider.emit(State.CONNECTED)
+ fakeConnectedDisplayStateProvider.setState(State.CONNECTED)
runCurrent()
verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true)
@@ -261,7 +261,8 @@
statusBarPolicy.init()
clearInvocations(iconController)
- fakeConnectedDisplayStateProvider.emit(State.DISCONNECTED)
+ fakeConnectedDisplayStateProvider.setState(State.DISCONNECTED)
+ runCurrent()
verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, false)
}
@@ -272,9 +273,12 @@
statusBarPolicy.init()
clearInvocations(iconController)
- fakeConnectedDisplayStateProvider.emit(State.CONNECTED)
- fakeConnectedDisplayStateProvider.emit(State.DISCONNECTED)
- fakeConnectedDisplayStateProvider.emit(State.CONNECTED)
+ fakeConnectedDisplayStateProvider.setState(State.CONNECTED)
+ runCurrent()
+ fakeConnectedDisplayStateProvider.setState(State.DISCONNECTED)
+ runCurrent()
+ fakeConnectedDisplayStateProvider.setState(State.CONNECTED)
+ runCurrent()
inOrder(iconController).apply {
verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true)
@@ -289,7 +293,8 @@
statusBarPolicy.init()
clearInvocations(iconController)
- fakeConnectedDisplayStateProvider.emit(State.CONNECTED_SECURE)
+ fakeConnectedDisplayStateProvider.setState(State.CONNECTED_SECURE)
+ runCurrent()
verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true)
}
@@ -390,7 +395,7 @@
}
@Test
- @EnableFlags(android.app.Flags.FLAG_MODES_UI_ICONS)
+ @EnableFlags(android.app.Flags.FLAG_MODES_UI, android.app.Flags.FLAG_MODES_UI_ICONS)
fun zenModeInteractorActiveModeChanged_showsModeIcon() =
testScope.runTest {
statusBarPolicy.init()
@@ -403,8 +408,8 @@
.setName("Bedtime Mode")
.setType(AutomaticZenRule.TYPE_BEDTIME)
.setActive(true)
- .setPackage("some.package")
- .setIconResId(123)
+ .setPackage(mContext.packageName)
+ .setIconResId(android.R.drawable.ic_lock_lock)
.build(),
TestModeBuilder()
.setId("other")
@@ -412,7 +417,7 @@
.setType(AutomaticZenRule.TYPE_OTHER)
.setActive(true)
.setPackage(SystemZenRules.PACKAGE_ANDROID)
- .setIconResId(456)
+ .setIconResId(android.R.drawable.ic_media_play)
.build(),
)
)
@@ -422,17 +427,25 @@
verify(iconController)
.setResourceIcon(
eq(ZEN_SLOT),
- eq("some.package"),
- eq(123),
- eq(null),
- eq("Bedtime Mode")
+ eq(mContext.packageName),
+ eq(android.R.drawable.ic_lock_lock),
+ any(), // non-null
+ eq("Bedtime Mode"),
+ eq(StatusBarIcon.Shape.FIXED_SPACE)
)
zenModeRepository.deactivateMode("bedtime")
runCurrent()
verify(iconController)
- .setResourceIcon(eq(ZEN_SLOT), eq(null), eq(456), eq(null), eq("Other Mode"))
+ .setResourceIcon(
+ eq(ZEN_SLOT),
+ eq(null),
+ eq(android.R.drawable.ic_media_play),
+ any(), // non-null
+ eq("Other Mode"),
+ eq(StatusBarIcon.Shape.FIXED_SPACE)
+ )
zenModeRepository.deactivateMode("other")
runCurrent()
@@ -441,7 +454,7 @@
}
@Test
- @EnableFlags(android.app.Flags.FLAG_MODES_UI_ICONS)
+ @EnableFlags(android.app.Flags.FLAG_MODES_UI, android.app.Flags.FLAG_MODES_UI_ICONS)
fun zenModeControllerOnGlobalZenChanged_doesNotUpdateDndIcon() {
statusBarPolicy.init()
reset(iconController)
@@ -450,7 +463,8 @@
verify(iconController, never()).setIconVisibility(eq(ZEN_SLOT), any())
verify(iconController, never()).setIcon(eq(ZEN_SLOT), anyInt(), any())
- verify(iconController, never()).setResourceIcon(eq(ZEN_SLOT), any(), any(), any(), any())
+ verify(iconController, never())
+ .setResourceIcon(eq(ZEN_SLOT), any(), any(), any(), any(), any())
}
@Test
@@ -466,7 +480,7 @@
verify(iconController, never()).setIconVisibility(eq(ZEN_SLOT), any())
verify(iconController, never()).setIcon(eq(ZEN_SLOT), anyInt(), any())
verify(iconController, never())
- .setResourceIcon(eq(ZEN_SLOT), any(), any(), any(), any())
+ .setResourceIcon(eq(ZEN_SLOT), any(), any(), any(), any(), any())
}
@Test
@@ -529,9 +543,11 @@
}
private class FakeConnectedDisplayStateProvider : ConnectedDisplayInteractor {
- private val flow = MutableSharedFlow<State>()
+ private val flow = MutableStateFlow(State.DISCONNECTED)
- suspend fun emit(value: State) = flow.emit(value)
+ fun setState(value: State) {
+ flow.value = value
+ }
override val connectedDisplayState: Flow<State>
get() = flow
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 54c03e8..3e3c046 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -84,10 +84,9 @@
import com.android.systemui.flags.DisableSceneContainer;
import com.android.systemui.flags.EnableSceneContainer;
import com.android.systemui.keyguard.DismissCallbackRegistry;
+import com.android.systemui.keyguard.domain.interactor.KeyguardDismissTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
@@ -235,11 +234,10 @@
mUdfpsOverlayInteractor,
mActivityStarter,
mKeyguardTransitionInteractor,
+ mock(KeyguardDismissTransitionInteractor.class),
StandardTestDispatcher(null, null),
- () -> mock(WindowManagerLockscreenVisibilityInteractor.class),
() -> mock(KeyguardDismissActionInteractor.class),
mSelectedUserInteractor,
- () -> mock(KeyguardSurfaceBehindInteractor.class),
mock(JavaAdapter.class),
() -> mSceneInteractor,
mock(StatusBarKeyguardViewManagerInteractor.class),
@@ -759,11 +757,10 @@
mUdfpsOverlayInteractor,
mActivityStarter,
mock(KeyguardTransitionInteractor.class),
+ mock(KeyguardDismissTransitionInteractor.class),
StandardTestDispatcher(null, null),
- () -> mock(WindowManagerLockscreenVisibilityInteractor.class),
() -> mock(KeyguardDismissActionInteractor.class),
mSelectedUserInteractor,
- () -> mock(KeyguardSurfaceBehindInteractor.class),
mock(JavaAdapter.class),
() -> mSceneInteractor,
mock(StatusBarKeyguardViewManagerInteractor.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/IconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/IconManagerTest.kt
new file mode 100644
index 0000000..90732d01
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/IconManagerTest.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone.ui
+
+import android.app.Flags
+import android.graphics.drawable.Icon
+import android.os.UserHandle
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.LinearLayout
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.statusbar.StatusBarIcon
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider
+import com.android.systemui.statusbar.phone.StatusBarLocation
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter
+import com.android.systemui.statusbar.pipeline.wifi.ui.WifiUiAdapter
+import com.android.systemui.util.Assert
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.RETURNS_DEEP_STUBS
+import org.mockito.kotlin.mock
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class IconManagerTest : SysuiTestCase() {
+
+ private lateinit var underTest: IconManager
+ private lateinit var viewGroup: ViewGroup
+
+ @Before
+ fun setUp() {
+ Assert.setTestThread(Thread.currentThread())
+ viewGroup = LinearLayout(context)
+ underTest =
+ IconManager(
+ viewGroup,
+ StatusBarLocation.HOME,
+ mock<WifiUiAdapter>(defaultAnswer = RETURNS_DEEP_STUBS),
+ mock<MobileUiAdapter>(defaultAnswer = RETURNS_DEEP_STUBS),
+ mock<MobileContextProvider>(defaultAnswer = RETURNS_DEEP_STUBS),
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS)
+ fun addIcon_shapeWrapContent_addsIconViewWithVariableWidth() {
+ val sbIcon = newStatusBarIcon(StatusBarIcon.Shape.WRAP_CONTENT)
+
+ underTest.addIcon(0, "slot", false, sbIcon)
+
+ assertThat(viewGroup.childCount).isEqualTo(1)
+ val iconView = viewGroup.getChildAt(0) as StatusBarIconView
+ assertThat(iconView).isNotNull()
+
+ assertThat(iconView.layoutParams.width).isEqualTo(ViewGroup.LayoutParams.WRAP_CONTENT)
+ assertThat(iconView.scaleType).isEqualTo(ImageView.ScaleType.CENTER)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS)
+ fun addIcon_shapeFixedSpace_addsIconViewWithFixedWidth() {
+ val sbIcon = newStatusBarIcon(StatusBarIcon.Shape.FIXED_SPACE)
+
+ underTest.addIcon(0, "slot", false, sbIcon)
+
+ assertThat(viewGroup.childCount).isEqualTo(1)
+ val iconView = viewGroup.getChildAt(0) as StatusBarIconView
+ assertThat(iconView).isNotNull()
+
+ assertThat(iconView.layoutParams.width).isNotEqualTo(ViewGroup.LayoutParams.WRAP_CONTENT)
+ assertThat(iconView.layoutParams.width).isEqualTo(iconView.layoutParams.height)
+ assertThat(iconView.scaleType).isEqualTo(ImageView.ScaleType.FIT_CENTER)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MODES_UI_ICONS)
+ fun addIcon_iconsFlagOff_addsIconViewWithVariableWidth() {
+ val sbIcon = newStatusBarIcon(StatusBarIcon.Shape.FIXED_SPACE)
+
+ underTest.addIcon(0, "slot", false, sbIcon)
+
+ assertThat(viewGroup.childCount).isEqualTo(1)
+ val iconView = viewGroup.getChildAt(0) as StatusBarIconView
+ assertThat(iconView).isNotNull()
+
+ assertThat(iconView.layoutParams.width).isEqualTo(ViewGroup.LayoutParams.WRAP_CONTENT)
+ assertThat(iconView.scaleType).isEqualTo(ImageView.ScaleType.CENTER)
+ }
+
+ private fun newStatusBarIcon(shape: StatusBarIcon.Shape) =
+ StatusBarIcon(
+ UserHandle.CURRENT,
+ context.packageName,
+ Icon.createWithResource(context, android.R.drawable.ic_media_next),
+ 0,
+ 0,
+ "",
+ StatusBarIcon.Type.ResourceIcon,
+ shape,
+ )
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImplTest.kt
index 26a57e4..50a13b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImplTest.kt
@@ -424,7 +424,14 @@
@EnableFlags(android.app.Flags.FLAG_MODES_UI, android.app.Flags.FLAG_MODES_UI_ICONS)
fun setResourceIcon_setsIconAndPreloadedIconInHolder() {
val drawable = ColorDrawable(1)
- underTest.setResourceIcon("slot", "some.package", 123, drawable, "description")
+ underTest.setResourceIcon(
+ "slot",
+ "some.package",
+ 123,
+ drawable,
+ "description",
+ StatusBarIcon.Shape.FIXED_SPACE
+ )
val iconHolder = iconList.getIconHolder("slot", 0)
assertThat(iconHolder).isNotNull()
@@ -432,6 +439,7 @@
assertThat(iconHolder?.icon?.icon?.resId).isEqualTo(123)
assertThat(iconHolder?.icon?.icon?.resPackage).isEqualTo("some.package")
assertThat(iconHolder?.icon?.contentDescription).isEqualTo("description")
+ assertThat(iconHolder?.icon?.shape).isEqualTo(StatusBarIcon.Shape.FIXED_SPACE)
assertThat(iconHolder?.icon?.preloadedIcon).isEqualTo(drawable)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 76982ae..6de2caa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -28,7 +28,6 @@
import android.net.vcn.VcnTransportInfo
import android.net.wifi.WifiInfo
import android.net.wifi.WifiManager
-import android.os.Bundle
import android.os.ParcelUuid
import android.telephony.CarrierConfigManager
import android.telephony.ServiceState
@@ -56,7 +55,6 @@
import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
-import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
@@ -74,7 +72,6 @@
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.android.wifitrackerlib.MergedCarrierEntry
import com.android.wifitrackerlib.WifiEntry
@@ -98,6 +95,7 @@
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.whenever
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@@ -602,47 +600,85 @@
@SuppressLint("UnspecifiedRegisterReceiverFlag")
@Test
- fun testDeviceServiceStateFromBroadcast_eagerlyWatchesBroadcast() =
+ fun testDeviceEmergencyCallState_eagerlyChecksState() =
testScope.runTest {
- // Value starts out empty (null)
- assertThat(underTest.deviceServiceState.value).isNull()
+ // Value starts out false
+ assertThat(underTest.isDeviceEmergencyCallCapable.value).isFalse()
+ whenever(telephonyManager.activeModemCount).thenReturn(1)
+ whenever(telephonyManager.getServiceStateForSlot(any())).thenAnswer { _ ->
+ ServiceState().apply { isEmergencyOnly = true }
+ }
// WHEN an appropriate intent gets sent out
- val intent = serviceStateIntent(subId = -1, emergencyOnly = false)
+ val intent = serviceStateIntent(subId = -1)
fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
context,
intent,
)
runCurrent()
- // THEN the repo's state is updated
- val expected = ServiceStateModel(isEmergencyOnly = false)
- assertThat(underTest.deviceServiceState.value).isEqualTo(expected)
+ // THEN the repo's state is updated despite no listeners
+ assertThat(underTest.isDeviceEmergencyCallCapable.value).isEqualTo(true)
}
@Test
- fun testDeviceServiceStateFromBroadcast_followsSubIdNegativeOne() =
+ fun testDeviceEmergencyCallState_aggregatesAcrossSlots_oneTrue() =
testScope.runTest {
- // device based state tracks -1
- val intent = serviceStateIntent(subId = -1, emergencyOnly = false)
+ val latest by collectLastValue(underTest.isDeviceEmergencyCallCapable)
+
+ // GIVEN there are multiple slots
+ whenever(telephonyManager.activeModemCount).thenReturn(4)
+ // GIVEN only one of them reports ECM
+ whenever(telephonyManager.getServiceStateForSlot(any())).thenAnswer { invocation ->
+ when (invocation.getArgument(0) as Int) {
+ 0 -> ServiceState().apply { isEmergencyOnly = false }
+ 1 -> ServiceState().apply { isEmergencyOnly = false }
+ 2 -> ServiceState().apply { isEmergencyOnly = true }
+ 3 -> ServiceState().apply { isEmergencyOnly = false }
+ else -> null
+ }
+ }
+
+ // GIVEN a broadcast goes out for the appropriate subID
+ val intent = serviceStateIntent(subId = -1)
fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
context,
intent,
)
runCurrent()
- val deviceBasedState = ServiceStateModel(isEmergencyOnly = false)
- assertThat(underTest.deviceServiceState.value).isEqualTo(deviceBasedState)
+ // THEN the device is in ECM, because one of the service states is
+ assertThat(latest).isTrue()
+ }
- // ... and ignores any other subId
- val intent2 = serviceStateIntent(subId = 1, emergencyOnly = true)
+ @Test
+ fun testDeviceEmergencyCallState_aggregatesAcrossSlots_allFalse() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isDeviceEmergencyCallCapable)
+
+ // GIVEN there are multiple slots
+ whenever(telephonyManager.activeModemCount).thenReturn(4)
+ // GIVEN only one of them reports ECM
+ whenever(telephonyManager.getServiceStateForSlot(any())).thenAnswer { invocation ->
+ when (invocation.getArgument(0) as Int) {
+ 0 -> ServiceState().apply { isEmergencyOnly = false }
+ 1 -> ServiceState().apply { isEmergencyOnly = false }
+ 2 -> ServiceState().apply { isEmergencyOnly = false }
+ 3 -> ServiceState().apply { isEmergencyOnly = false }
+ else -> null
+ }
+ }
+
+ // GIVEN a broadcast goes out for the appropriate subID
+ val intent = serviceStateIntent(subId = -1)
fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
context,
- intent2,
+ intent,
)
runCurrent()
- assertThat(underTest.deviceServiceState.value).isEqualTo(deviceBasedState)
+ // THEN the device is in ECM, because one of the service states is
+ assertThat(latest).isFalse()
}
@Test
@@ -1549,15 +1585,8 @@
*/
private fun serviceStateIntent(
subId: Int,
- emergencyOnly: Boolean = false,
): Intent {
- val serviceState = ServiceState().apply { isEmergencyOnly = emergencyOnly }
-
- val bundle = Bundle()
- serviceState.fillInNotifierBundle(bundle)
-
return Intent(Intent.ACTION_SERVICE_STATE).apply {
- putExtras(bundle)
putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index cc0eae7..e218fba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -29,7 +29,6 @@
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
@@ -897,13 +896,11 @@
testScope.runTest {
val latest by collectLastValue(underTest.isDeviceInEmergencyCallsOnlyMode)
- connectionsRepository.deviceServiceState.value =
- ServiceStateModel(isEmergencyOnly = true)
+ connectionsRepository.isDeviceEmergencyCallCapable.value = true
assertThat(latest).isTrue()
- connectionsRepository.deviceServiceState.value =
- ServiceStateModel(isEmergencyOnly = false)
+ connectionsRepository.isDeviceEmergencyCallCapable.value = false
assertThat(latest).isFalse()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
index f486787..0945742 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
@@ -21,60 +21,61 @@
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
+import android.net.NetworkCapabilities.TRANSPORT_VPN
import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.VpnTransportInfo
import android.net.vcn.VcnTransportInfo
import android.net.wifi.WifiInfo
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_STATUS_BAR_ALWAYS_CHECK_UNDERLYING_NETWORKS
import com.android.systemui.SysuiTestCase
-import com.android.systemui.dump.DumpManager
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.core.FakeLogBuffer
import com.android.systemui.statusbar.pipeline.shared.ConnectivityInputLogger
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlots
-import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.DEFAULT_HIDDEN_ICONS_RESOURCE
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.HIDDEN_ICONS_TUNABLE_KEY
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.getMainOrUnderlyingWifiInfo
+import com.android.systemui.testKosmos
import com.android.systemui.tuner.TunerService
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import kotlinx.coroutines.yield
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class ConnectivityRepositoryImplTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
private lateinit var underTest: ConnectivityRepositoryImpl
- @Mock private lateinit var connectivityManager: ConnectivityManager
- @Mock private lateinit var connectivitySlots: ConnectivitySlots
- @Mock private lateinit var dumpManager: DumpManager
- @Mock private lateinit var logger: ConnectivityInputLogger
- private lateinit var testScope: TestScope
- @Mock private lateinit var tunerService: TunerService
+ private val connectivityManager = mock<ConnectivityManager>()
+ private val connectivitySlots = mock<ConnectivitySlots>()
+ private val dumpManager = kosmos.dumpManager
+ private val logger = ConnectivityInputLogger(FakeLogBuffer.Factory.create())
+ private val testScope = kosmos.testScope
+ private val tunerService = mock<TunerService>()
@Before
fun setUp() {
- MockitoAnnotations.initMocks(this)
- testScope = TestScope(UnconfinedTestDispatcher())
createAndSetRepo()
}
@@ -89,12 +90,10 @@
// config_statusBarIconsToExclude when it's first constructed
createAndSetRepo()
- var latest: Set<ConnectivitySlot>? = null
- val job = underTest.forceHiddenSlots.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.forceHiddenSlots)
+ runCurrent()
assertThat(latest).containsExactly(ConnectivitySlot.ETHERNET, ConnectivitySlot.WIFI)
-
- job.cancel()
}
@Test
@@ -102,14 +101,12 @@
testScope.runTest {
setUpEthernetWifiMobileSlotNames()
- var latest: Set<ConnectivitySlot>? = null
- val job = underTest.forceHiddenSlots.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.forceHiddenSlots)
+ runCurrent()
getTunable().onTuningChanged(HIDDEN_ICONS_TUNABLE_KEY, SLOT_MOBILE)
assertThat(latest).containsExactly(ConnectivitySlot.MOBILE)
-
- job.cancel()
}
@Test
@@ -117,19 +114,16 @@
testScope.runTest {
setUpEthernetWifiMobileSlotNames()
- var latest: Set<ConnectivitySlot>? = null
- val job = underTest.forceHiddenSlots.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.forceHiddenSlots)
+ runCurrent()
getTunable().onTuningChanged(HIDDEN_ICONS_TUNABLE_KEY, SLOT_MOBILE)
// WHEN onTuningChanged with the wrong key
getTunable().onTuningChanged("wrongKey", SLOT_WIFI)
- yield()
// THEN we didn't update our value and still have the old one
assertThat(latest).containsExactly(ConnectivitySlot.MOBILE)
-
- job.cancel()
}
@Test
@@ -143,8 +137,8 @@
// config_statusBarIconsToExclude when it's first constructed
createAndSetRepo()
- var latest: Set<ConnectivitySlot>? = null
- val job = underTest.forceHiddenSlots.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.forceHiddenSlots)
+ runCurrent()
// First, update the slots
getTunable().onTuningChanged(HIDDEN_ICONS_TUNABLE_KEY, SLOT_MOBILE)
@@ -152,19 +146,16 @@
// WHEN we update to a null value
getTunable().onTuningChanged(HIDDEN_ICONS_TUNABLE_KEY, null)
- yield()
// THEN we go back to our default value
assertThat(latest).containsExactly(ConnectivitySlot.ETHERNET, ConnectivitySlot.WIFI)
-
- job.cancel()
}
@Test
fun forceHiddenSlots_someInvalidSlotNames_flowHasValidSlotsOnly() =
testScope.runTest {
- var latest: Set<ConnectivitySlot>? = null
- val job = underTest.forceHiddenSlots.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.forceHiddenSlots)
+ runCurrent()
whenever(connectivitySlots.getSlotFromName(SLOT_WIFI)).thenReturn(ConnectivitySlot.WIFI)
whenever(connectivitySlots.getSlotFromName(SLOT_MOBILE)).thenReturn(null)
@@ -172,8 +163,6 @@
getTunable().onTuningChanged(HIDDEN_ICONS_TUNABLE_KEY, "$SLOT_WIFI,$SLOT_MOBILE")
assertThat(latest).containsExactly(ConnectivitySlot.WIFI)
-
- job.cancel()
}
@Test
@@ -181,23 +170,21 @@
testScope.runTest {
setUpEthernetWifiMobileSlotNames()
- var latest: Set<ConnectivitySlot>? = null
- val job = underTest.forceHiddenSlots.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.forceHiddenSlots)
+ runCurrent()
// WHEN there's empty and blank slot names
getTunable().onTuningChanged(HIDDEN_ICONS_TUNABLE_KEY, "$SLOT_MOBILE, ,,$SLOT_WIFI")
// THEN we skip that slot but still process the other ones
assertThat(latest).containsExactly(ConnectivitySlot.WIFI, ConnectivitySlot.MOBILE)
-
- job.cancel()
}
@Test
fun forceHiddenSlots_allInvalidOrEmptySlotNames_flowHasEmpty() =
testScope.runTest {
- var latest: Set<ConnectivitySlot>? = null
- val job = underTest.forceHiddenSlots.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.forceHiddenSlots)
+ runCurrent()
whenever(connectivitySlots.getSlotFromName(SLOT_WIFI)).thenReturn(null)
whenever(connectivitySlots.getSlotFromName(SLOT_ETHERNET)).thenReturn(null)
@@ -210,8 +197,6 @@
)
assertThat(latest).isEmpty()
-
- job.cancel()
}
@Test
@@ -219,29 +204,25 @@
testScope.runTest {
setUpEthernetWifiMobileSlotNames()
- var latest1: Set<ConnectivitySlot>? = null
- val job1 = underTest.forceHiddenSlots.onEach { latest1 = it }.launchIn(this)
+ val latest1 by collectLastValue(underTest.forceHiddenSlots)
+ runCurrent()
getTunable().onTuningChanged(HIDDEN_ICONS_TUNABLE_KEY, "$SLOT_WIFI,$SLOT_ETHERNET")
assertThat(latest1).containsExactly(ConnectivitySlot.WIFI, ConnectivitySlot.ETHERNET)
// WHEN we add a second subscriber after having already emitted a value
- var latest2: Set<ConnectivitySlot>? = null
- val job2 = underTest.forceHiddenSlots.onEach { latest2 = it }.launchIn(this)
+ val latest2 by collectLastValue(underTest.forceHiddenSlots)
+ runCurrent()
// THEN the second subscribe receives the already-emitted value
assertThat(latest2).containsExactly(ConnectivitySlot.WIFI, ConnectivitySlot.ETHERNET)
-
- job1.cancel()
- job2.cancel()
}
@Test
fun defaultConnections_noTransports_nothingIsDefault() =
testScope.runTest {
- var latest: DefaultConnectionModel? = null
- val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.defaultConnections)
val capabilities =
mock<NetworkCapabilities>().also {
@@ -256,15 +237,12 @@
assertThat(latest!!.wifi.isDefault).isFalse()
assertThat(latest!!.ethernet.isDefault).isFalse()
assertThat(latest!!.carrierMerged.isDefault).isFalse()
-
- job.cancel()
}
@Test
fun defaultConnections_cellularTransport_mobileIsDefault() =
testScope.runTest {
- var latest: DefaultConnectionModel? = null
- val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.defaultConnections)
val capabilities =
mock<NetworkCapabilities>().also {
@@ -279,15 +257,12 @@
assertThat(latest!!.wifi.isDefault).isFalse()
assertThat(latest!!.ethernet.isDefault).isFalse()
assertThat(latest!!.carrierMerged.isDefault).isFalse()
-
- job.cancel()
}
@Test
fun defaultConnections_wifiTransport_wifiIsDefault() =
testScope.runTest {
- var latest: DefaultConnectionModel? = null
- val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.defaultConnections)
val capabilities =
mock<NetworkCapabilities>().also {
@@ -302,15 +277,12 @@
assertThat(latest!!.ethernet.isDefault).isFalse()
assertThat(latest!!.carrierMerged.isDefault).isFalse()
assertThat(latest!!.mobile.isDefault).isFalse()
-
- job.cancel()
}
@Test
fun defaultConnections_ethernetTransport_ethernetIsDefault() =
testScope.runTest {
- var latest: DefaultConnectionModel? = null
- val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.defaultConnections)
val capabilities =
mock<NetworkCapabilities>().also {
@@ -325,15 +297,12 @@
assertThat(latest!!.wifi.isDefault).isFalse()
assertThat(latest!!.carrierMerged.isDefault).isFalse()
assertThat(latest!!.mobile.isDefault).isFalse()
-
- job.cancel()
}
@Test
fun defaultConnections_carrierMergedViaWifi_wifiAndCarrierMergedDefault() =
testScope.runTest {
- var latest: DefaultConnectionModel? = null
- val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.defaultConnections)
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
@@ -350,15 +319,12 @@
assertThat(latest!!.wifi.isDefault).isTrue()
assertThat(latest!!.carrierMerged.isDefault).isTrue()
assertThat(latest!!.mobile.isDefault).isFalse()
-
- job.cancel()
}
@Test
fun defaultConnections_carrierMergedViaMobile_mobileCarrierMergedWifiDefault() =
testScope.runTest {
- var latest: DefaultConnectionModel? = null
- val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.defaultConnections)
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
@@ -375,15 +341,12 @@
assertThat(latest!!.mobile.isDefault).isTrue()
assertThat(latest!!.carrierMerged.isDefault).isTrue()
assertThat(latest!!.wifi.isDefault).isTrue()
-
- job.cancel()
}
@Test
fun defaultConnections_carrierMergedViaWifiWithVcnTransport_wifiAndCarrierMergedDefault() =
testScope.runTest {
- var latest: DefaultConnectionModel? = null
- val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.defaultConnections)
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
@@ -400,15 +363,13 @@
assertThat(latest!!.wifi.isDefault).isTrue()
assertThat(latest!!.carrierMerged.isDefault).isTrue()
assertThat(latest!!.mobile.isDefault).isFalse()
-
- job.cancel()
}
+ /** VCN over W+ (aka VCN over carrier merged). See b/352162710#comment27 scenario #1. */
@Test
fun defaultConnections_carrierMergedViaMobileWithVcnTransport_mobileCarrierMergedWifiDefault() =
testScope.runTest {
- var latest: DefaultConnectionModel? = null
- val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.defaultConnections)
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
@@ -425,15 +386,48 @@
assertThat(latest!!.mobile.isDefault).isTrue()
assertThat(latest!!.carrierMerged.isDefault).isTrue()
assertThat(latest!!.wifi.isDefault).isTrue()
+ }
- job.cancel()
+ /** VPN over W+ (aka VPN over carrier merged). See b/352162710#comment27 scenario #2. */
+ @Test
+ @EnableFlags(FLAG_STATUS_BAR_ALWAYS_CHECK_UNDERLYING_NETWORKS)
+ fun defaultConnections_vpnOverCarrierMerged_carrierMergedDefault() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.defaultConnections)
+
+ // Underlying carrier merged network
+ val underlyingCarrierMergedNetwork = mock<Network>()
+ val carrierMergedInfo =
+ mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+ val underlyingCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(carrierMergedInfo)
+ }
+ whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
+ .thenReturn(underlyingCapabilities)
+
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(false)
+ // Transports are WIFI|VPN, *not* CELLULAR.
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.hasTransport(TRANSPORT_VPN)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(VpnTransportInfo(0, null, false, false))
+ whenever(it.underlyingNetworks)
+ .thenReturn(listOf(underlyingCarrierMergedNetwork))
+ }
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+
+ assertThat(latest!!.carrierMerged.isDefault).isTrue()
}
@Test
fun defaultConnections_notCarrierMergedViaWifi_carrierMergedNotDefault() =
testScope.runTest {
- var latest: DefaultConnectionModel? = null
- val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.defaultConnections)
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(false) }
@@ -448,15 +442,12 @@
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
assertThat(latest!!.carrierMerged.isDefault).isFalse()
-
- job.cancel()
}
@Test
fun defaultConnections_notCarrierMergedViaMobile_carrierMergedNotDefault() =
testScope.runTest {
- var latest: DefaultConnectionModel? = null
- val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.defaultConnections)
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(false) }
@@ -471,15 +462,12 @@
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
assertThat(latest!!.carrierMerged.isDefault).isFalse()
-
- job.cancel()
}
@Test
fun defaultConnections_transportInfoNotWifi_wifiNotDefault() =
testScope.runTest {
- var latest: DefaultConnectionModel? = null
- val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.defaultConnections)
val capabilities =
mock<NetworkCapabilities>().also {
@@ -492,8 +480,6 @@
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
assertThat(latest!!.wifi.isDefault).isFalse()
-
- job.cancel()
}
@Test
@@ -531,8 +517,7 @@
@Test
fun defaultConnections_cellular_underlyingCarrierMergedViaWifi_allDefault() =
testScope.runTest {
- var latest: DefaultConnectionModel? = null
- val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.defaultConnections)
// Underlying carrier merged network
val underlyingCarrierMergedNetwork = mock<Network>()
@@ -560,16 +545,17 @@
assertThat(latest!!.mobile.isDefault).isTrue()
assertThat(latest!!.carrierMerged.isDefault).isTrue()
assertThat(latest!!.wifi.isDefault).isTrue()
-
- job.cancel()
}
- /** Test for b/225902574. */
+ /**
+ * Test for b/225902574: VPN over VCN over W+ (aka VPN over VCN over carrier merged).
+ *
+ * Also see b/352162710#comment27 scenario #3 and b/352162710#comment30.
+ */
@Test
fun defaultConnections_cellular_underlyingCarrierMergedViaMobileWithVcnTransport_allDefault() =
testScope.runTest {
- var latest: DefaultConnectionModel? = null
- val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.defaultConnections)
// Underlying carrier merged network
val underlyingCarrierMergedNetwork = mock<Network>()
@@ -587,6 +573,7 @@
val mainCapabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.hasTransport(TRANSPORT_VPN)).thenReturn(true)
whenever(it.transportInfo).thenReturn(null)
whenever(it.underlyingNetworks)
.thenReturn(listOf(underlyingCarrierMergedNetwork))
@@ -597,15 +584,12 @@
assertThat(latest!!.mobile.isDefault).isTrue()
assertThat(latest!!.carrierMerged.isDefault).isTrue()
assertThat(latest!!.wifi.isDefault).isTrue()
-
- job.cancel()
}
@Test
fun defaultConnections_multipleTransports_multipleDefault() =
testScope.runTest {
- var latest: DefaultConnectionModel? = null
- val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.defaultConnections)
val capabilities =
mock<NetworkCapabilities>().also {
@@ -619,15 +603,12 @@
assertThat(latest!!.mobile.isDefault).isTrue()
assertThat(latest!!.ethernet.isDefault).isTrue()
assertThat(latest!!.wifi.isDefault).isTrue()
-
- job.cancel()
}
@Test
fun defaultConnections_hasValidated_isValidatedTrue() =
testScope.runTest {
- var latest: DefaultConnectionModel? = null
- val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.defaultConnections)
val capabilities =
mock<NetworkCapabilities>().also {
@@ -638,14 +619,12 @@
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
assertThat(latest!!.isValidated).isTrue()
- job.cancel()
}
@Test
fun defaultConnections_noValidated_isValidatedFalse() =
testScope.runTest {
- var latest: DefaultConnectionModel? = null
- val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.defaultConnections)
val capabilities =
mock<NetworkCapabilities>().also {
@@ -656,7 +635,6 @@
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
assertThat(latest!!.isValidated).isFalse()
- job.cancel()
}
@Test
@@ -669,8 +647,7 @@
testScope.runTest {
val vcnInfo = VcnTransportInfo(SUB_1_ID)
- var latest: Int? = null
- val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.vcnSubId)
val capabilities =
mock<NetworkCapabilities>().also {
@@ -681,7 +658,6 @@
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
assertThat(latest).isEqualTo(SUB_1_ID)
- job.cancel()
}
@Test
@@ -689,8 +665,7 @@
testScope.runTest {
val vcnInfo = VcnTransportInfo(INVALID_SUBSCRIPTION_ID)
- var latest: Int? = null
- val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.vcnSubId)
val capabilities =
mock<NetworkCapabilities>().also {
@@ -701,14 +676,12 @@
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
assertThat(latest).isNull()
- job.cancel()
}
@Test
fun vcnSubId_nullIfNoTransportInfo() =
testScope.runTest {
- var latest: Int? = null
- val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.vcnSubId)
val capabilities =
mock<NetworkCapabilities>().also {
@@ -719,7 +692,6 @@
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
assertThat(latest).isNull()
- job.cancel()
}
@Test
@@ -728,8 +700,7 @@
// If the underlying network of the VCN is a WiFi network, then there is no subId that
// could disagree with telephony's active data subscription id.
- var latest: Int? = null
- val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.vcnSubId)
val wifiInfo = mock<WifiInfo>()
val vcnInfo = VcnTransportInfo(wifiInfo)
@@ -742,14 +713,12 @@
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
assertThat(latest).isNull()
- job.cancel()
}
@Test
fun vcnSubId_changingVcnInfoIsTracked() =
testScope.runTest {
- var latest: Int? = null
- val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.vcnSubId)
val wifiInfo = mock<WifiInfo>()
val wifiVcnInfo = VcnTransportInfo(wifiInfo)
@@ -788,8 +757,6 @@
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
assertThat(latest).isNull()
-
- job.cancel()
}
@Test
@@ -862,6 +829,7 @@
}
@Test
+ @DisableFlags(FLAG_STATUS_BAR_ALWAYS_CHECK_UNDERLYING_NETWORKS)
fun getMainOrUnderlyingWifiInfo_notCellular_underlyingWifi_noInfo() {
val underlyingNetwork = mock<Network>()
val underlyingWifiInfo = mock<WifiInfo>()
@@ -916,6 +884,7 @@
}
@Test
+ @DisableFlags(FLAG_STATUS_BAR_ALWAYS_CHECK_UNDERLYING_NETWORKS)
fun getMainOrUnderlyingWifiInfo_notCellular_underlyingVcnWithWifi_noInfo() {
val underlyingNetwork = mock<Network>()
val underlyingVcnInfo = VcnTransportInfo(mock<WifiInfo>())
@@ -1076,12 +1045,13 @@
testScope.backgroundScope,
tunerService,
)
+ testScope.runCurrent()
}
private fun getTunable(): TunerService.Tunable {
val callbackCaptor = argumentCaptor<TunerService.Tunable>()
verify(tunerService).addTunable(callbackCaptor.capture(), any())
- return callbackCaptor.value!!
+ return callbackCaptor.firstValue
}
private fun setUpEthernetWifiMobileSlotNames() {
@@ -1094,7 +1064,7 @@
private fun getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback {
val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture())
- return callbackCaptor.value!!
+ return callbackCaptor.firstValue
}
private companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
index 12cfdcf..e396b56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
@@ -16,11 +16,11 @@
package com.android.systemui.statusbar.ui.viewmodel
-import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -33,7 +33,6 @@
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.domain.interactor.keyguardStatusBarInteractor
import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.policy.BatteryController
@@ -127,7 +126,7 @@
}
@Test
- @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+ @EnableSceneContainer
fun isVisible_headsUpStatusBarShown_false() =
testScope.runTest {
val latest by collectLastValue(underTest.isVisible)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/settingslib/notification/modes/ZenIconLoaderKosmos.kt
similarity index 72%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/settingslib/notification/modes/ZenIconLoaderKosmos.kt
index 60d97d1..8541d77 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/settingslib/notification/modes/ZenIconLoaderKosmos.kt
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.bouncer.shared.flag
+package com.android.settingslib.notification.modes
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.google.common.util.concurrent.MoreExecutors
-var Kosmos.fakeComposeBouncerFlags by Kosmos.Fixture { FakeComposeBouncerFlags() }
-val Kosmos.composeBouncerFlags by Kosmos.Fixture<ComposeBouncerFlags> { fakeComposeBouncerFlags }
+val Kosmos.zenIconLoader by Fixture { ZenIconLoader(MoreExecutors.newDirectExecutorService()) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/FakeComposeBouncerFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/FakeComposeBouncerFlags.kt
deleted file mode 100644
index 7482c0f..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/FakeComposeBouncerFlags.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.bouncer.shared.flag
-
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
-
-class FakeComposeBouncerFlags(var composeBouncerEnabled: Boolean = false) : ComposeBouncerFlags {
- override fun isComposeBouncerOrSceneContainerEnabled(): Boolean {
- return SceneContainerFlag.isEnabled || composeBouncerEnabled
- }
-
- @Deprecated(
- "Avoid using this, this is meant to be used only by the glue code " +
- "that includes compose bouncer in legacy keyguard.",
- replaceWith = ReplaceWith("isComposeBouncerOrSceneContainerEnabled()")
- )
- override fun isOnlyComposeBouncerEnabled(): Boolean = composeBouncerEnabled
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt
index e8612d08..5c5969d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt
@@ -22,7 +22,6 @@
import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
-import com.android.systemui.bouncer.shared.flag.composeBouncerFlags
import com.android.systemui.deviceentry.domain.interactor.biometricMessageInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryBiometricsAllowedInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
@@ -45,7 +44,6 @@
faceAuthInteractor = deviceEntryFaceAuthInteractor,
deviceUnlockedInteractor = deviceUnlockedInteractor,
deviceEntryBiometricsAllowedInteractor = deviceEntryBiometricsAllowedInteractor,
- flags = composeBouncerFlags,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
index e405d17..171be97 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
@@ -25,7 +25,6 @@
import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor
import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
-import com.android.systemui.bouncer.shared.flag.composeBouncerFlags
import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -55,7 +54,6 @@
authenticationInteractor = authenticationInteractor,
devicePolicyManager = devicePolicyManager,
bouncerMessageViewModelFactory = bouncerMessageViewModelFactory,
- flags = composeBouncerFlags,
userSwitcher = userSwitcherViewModel,
actionButtonInteractor = bouncerActionButtonInteractor,
pinViewModelFactory = pinBouncerViewModelFactory,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index 4ad046c..629fda6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -36,6 +36,7 @@
import com.android.systemui.plugins.activityStarter
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.settings.userTracker
+import com.android.systemui.statusbar.phone.fakeManagedProfileController
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.mock
@@ -61,6 +62,7 @@
sceneInteractor = sceneInteractor,
logBuffer = logcatLogBuffer("CommunalInteractor"),
tableLogBuffer = mock(),
+ managedProfileController = fakeManagedProfileController
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
index 1107971..c252924 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
@@ -22,7 +22,7 @@
import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
-import com.android.systemui.Flags.FLAG_NOTIFICATIONS_HEADS_UP_REFACTOR
+import com.android.systemui.Flags.FLAG_NOTIFICATION_AVALANCHE_THROTTLE_HUN
import com.android.systemui.Flags.FLAG_PREDICTIVE_BACK_SYSUI
import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
@@ -35,7 +35,7 @@
FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
FLAG_KEYGUARD_WM_STATE_REFACTOR,
FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
- FLAG_NOTIFICATIONS_HEADS_UP_REFACTOR,
+ FLAG_NOTIFICATION_AVALANCHE_THROTTLE_HUN,
FLAG_PREDICTIVE_BACK_SYSUI,
FLAG_SCENE_CONTAINER,
FLAG_DEVICE_ENTRY_UDFPS_REFACTOR,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
index e96aeada..5753c6c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
@@ -27,7 +27,6 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterNotNull
@SysUISingleton
@@ -37,38 +36,41 @@
private val _authenticationStatus = MutableStateFlow<FaceAuthenticationStatus?>(null)
override val authenticationStatus: Flow<FaceAuthenticationStatus> =
_authenticationStatus.filterNotNull()
+
fun setAuthenticationStatus(status: FaceAuthenticationStatus) {
_authenticationStatus.value = status
}
+
private val _detectionStatus = MutableStateFlow<FaceDetectionStatus?>(null)
override val detectionStatus: Flow<FaceDetectionStatus>
get() = _detectionStatus.filterNotNull()
+
fun setDetectionStatus(status: FaceDetectionStatus) {
_detectionStatus.value = status
}
private val _isLockedOut = MutableStateFlow(false)
override val isLockedOut = _isLockedOut
- private val _runningAuthRequest = MutableStateFlow<Pair<FaceAuthUiEvent, Boolean>?>(null)
- val runningAuthRequest: StateFlow<Pair<FaceAuthUiEvent, Boolean>?> =
- _runningAuthRequest.asStateFlow()
+ val runningAuthRequest: MutableStateFlow<Pair<FaceAuthUiEvent, Boolean>?> =
+ MutableStateFlow(null)
private val _isAuthRunning = MutableStateFlow(false)
override val isAuthRunning: StateFlow<Boolean> = _isAuthRunning
override val isBypassEnabled = MutableStateFlow(false)
+
override fun setLockedOut(isLockedOut: Boolean) {
_isLockedOut.value = isLockedOut
}
override fun requestAuthenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
- _runningAuthRequest.value = uiEvent to fallbackToDetection
+ runningAuthRequest.value = uiEvent to fallbackToDetection
_isAuthRunning.value = true
}
override fun cancel() {
_isAuthRunning.value = false
- _runningAuthRequest.value = null
+ runningAuthRequest.value = null
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/DismissKeyguardInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/DismissKeyguardInteractor.kt
new file mode 100644
index 0000000..82a5311
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/DismissKeyguardInteractor.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.keyguardDismissTransitionInteractor: KeyguardDismissTransitionInteractor by
+ Kosmos.Fixture {
+ KeyguardDismissTransitionInteractor(
+ repository = keyguardTransitionRepository,
+ fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractor,
+ fromPrimaryBouncerTransitionInteractor = fromPrimaryBouncerTransitionInteractor,
+ fromAodTransitionInteractor = fromAodTransitionInteractor,
+ fromAlternateBouncerTransitionInteractor = fromAlternateBouncerTransitionInteractor,
+ fromDozingTransitionInteractor = fromDozingTransitionInteractor,
+ fromOccludedTransitionInteractor = fromOccludedTransitionInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractorKosmos.kt
index c6b5ed0..007d229 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractorKosmos.kt
@@ -27,7 +27,7 @@
applicationCoroutineScope,
keyguardRepository,
biometricSettingsRepository,
- keyguardTransitionInteractor,
+ keyguardDismissTransitionInteractor,
internalTransitionInteractor = internalKeyguardTransitionInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
index 8e8f4b6..aa94c36 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
@@ -16,7 +16,6 @@
package com.android.systemui.keyguard.domain.interactor
-import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
@@ -27,12 +26,6 @@
KeyguardTransitionInteractor(
scope = applicationCoroutineScope,
repository = keyguardTransitionRepository,
- fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor },
- fromPrimaryBouncerTransitionInteractor = { fromPrimaryBouncerTransitionInteractor },
- fromAodTransitionInteractor = { fromAodTransitionInteractor },
- fromAlternateBouncerTransitionInteractor = { fromAlternateBouncerTransitionInteractor },
- fromDozingTransitionInteractor = { fromDozingTransitionInteractor },
- fromOccludedTransitionInteractor = { fromOccludedTransitionInteractor },
sceneInteractor = sceneInteractor
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index b9443bc..7cf4083 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -25,6 +25,7 @@
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.ui.viewmodel.notificationShadeWindowModel
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
import com.android.systemui.statusbar.phone.dozeParameters
import com.android.systemui.statusbar.phone.screenOffAnimationController
@@ -39,6 +40,7 @@
communalInteractor = communalInteractor,
keyguardTransitionInteractor = keyguardTransitionInteractor,
notificationsKeyguardInteractor = notificationsKeyguardInteractor,
+ notificationShadeWindowModel = notificationShadeWindowModel,
alternateBouncerToAodTransitionViewModel = alternateBouncerToAodTransitionViewModel,
alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
alternateBouncerToLockscreenTransitionViewModel =
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModelKosmos.kt
index a05e606..4196e54 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModelKosmos.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -25,6 +26,7 @@
val Kosmos.occludedToDozingTransitionViewModel by Fixture {
OccludedToDozingTransitionViewModel(
+ deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
animationFlow = keyguardTransitionAnimationFlow,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 953363d..851a378 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -36,6 +36,7 @@
import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.globalactions.domain.interactor.globalActionsInteractor
+import com.android.systemui.haptics.msdl.msdlPlayer
import com.android.systemui.haptics.qs.qsLongPressEffect
import com.android.systemui.jank.interactionJankMonitor
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -154,4 +155,5 @@
val scrimController by lazy { kosmos.scrimController }
val scrimStartable by lazy { kosmos.scrimStartable }
val sceneContainerOcclusionInteractor by lazy { kosmos.sceneContainerOcclusionInteractor }
+ val msdlPlayer by lazy { kosmos.msdlPlayer }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
index 7dfe802..7bc2483 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
@@ -1,9 +1,9 @@
package com.android.systemui.scene
-import com.android.compose.animation.scene.OverlayKey
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.scene.shared.model.FakeScene
+import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.FakeOverlay
@@ -26,8 +26,8 @@
val Kosmos.initialSceneKey by Fixture { Scenes.Lockscreen }
var Kosmos.overlayKeys by Fixture {
- listOf<OverlayKey>(
- // TODO(b/356596436): Add overlays here when we have them.
+ listOf(
+ Overlays.NotificationsShade,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayActionsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayActionsViewModelKosmos.kt
new file mode 100644
index 0000000..24481bf
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayActionsViewModelKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeOverlayActionsViewModel
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+
+val Kosmos.notificationsShadeOverlayActionsViewModel:
+ NotificationsShadeOverlayActionsViewModel by Fixture {
+ NotificationsShadeOverlayActionsViewModel(
+ shadeInteractor = shadeInteractor,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelKosmos.kt
new file mode 100644
index 0000000..c0d65a0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel
+
+import android.content.packageManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.commandline.commandRegistry
+import com.android.systemui.util.time.fakeSystemClock
+
+val Kosmos.demoRonChipViewModel: DemoRonChipViewModel by
+ Kosmos.Fixture {
+ DemoRonChipViewModel(
+ commandRegistry = commandRegistry,
+ packageManager = packageManager,
+ systemClock = fakeSystemClock,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt
index 16e288f..5382c1c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt
@@ -20,6 +20,7 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.chips.call.ui.viewmodel.callChipViewModel
import com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel.castToOtherDeviceChipViewModel
+import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.demoRonChipViewModel
import com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel.screenRecordChipViewModel
import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.shareToAppChipViewModel
import com.android.systemui.statusbar.chips.statusBarChipsLogger
@@ -32,6 +33,7 @@
shareToAppChipViewModel = shareToAppChipViewModel,
castToOtherDeviceChipViewModel = castToOtherDeviceChipViewModel,
callChipViewModel = callChipViewModel,
+ demoRonChipViewModel = demoRonChipViewModel,
logger = statusBarChipsLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/commandline/CommandRegistryKosmos.kt
similarity index 64%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/commandline/CommandRegistryKosmos.kt
index 60d97d1..14777b4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/commandline/CommandRegistryKosmos.kt
@@ -14,9 +14,16 @@
* limitations under the License.
*/
-package com.android.systemui.bouncer.shared.flag
+package com.android.systemui.statusbar.commandline
+import android.content.applicationContext
import com.android.systemui.kosmos.Kosmos
-var Kosmos.fakeComposeBouncerFlags by Kosmos.Fixture { FakeComposeBouncerFlags() }
-val Kosmos.composeBouncerFlags by Kosmos.Fixture<ComposeBouncerFlags> { fakeComposeBouncerFlags }
+val Kosmos.commandRegistry: CommandRegistry by
+ Kosmos.Fixture {
+ CommandRegistry(
+ context = applicationContext,
+ // Immediately run anything that comes in
+ mainExecutor = { command -> command.run() },
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ManagedProfileControllerKosmos.kt
similarity index 62%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ManagedProfileControllerKosmos.kt
index 60d97d1..ef04b9d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ManagedProfileControllerKosmos.kt
@@ -14,9 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.bouncer.shared.flag
+@file:OptIn(ExperimentalCoroutinesApi::class)
+package com.android.systemui.statusbar.phone
+
+import android.testing.LeakCheck
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.utils.leaks.FakeManagedProfileController
+import kotlinx.coroutines.ExperimentalCoroutinesApi
-var Kosmos.fakeComposeBouncerFlags by Kosmos.Fixture { FakeComposeBouncerFlags() }
-val Kosmos.composeBouncerFlags by Kosmos.Fixture<ComposeBouncerFlags> { fakeComposeBouncerFlags }
+val Kosmos.fakeManagedProfileController by Fixture { FakeManagedProfileController(LeakCheck()) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
index 8229575..e7be639 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
@@ -23,7 +23,6 @@
import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
@@ -94,9 +93,10 @@
private val _defaultMobileIconGroup = MutableStateFlow(DEFAULT_ICON)
override val defaultMobileIconGroup = _defaultMobileIconGroup
- override val deviceServiceState = MutableStateFlow<ServiceStateModel?>(null)
+ override val isDeviceEmergencyCallCapable = MutableStateFlow(false)
override val isAnySimSecure = MutableStateFlow(false)
+
override fun getIsAnySimSecure(): Boolean = isAnySimSecure.value
private var isInEcmMode: Boolean = false
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
index 66be7e7..61b53c9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
@@ -17,8 +17,10 @@
package com.android.systemui.statusbar.policy.domain.interactor
import android.content.testableContext
+import com.android.settingslib.notification.modes.zenIconLoader
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.shared.notifications.data.repository.notificationSettingsRepository
import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
@@ -27,5 +29,7 @@
context = testableContext,
zenModeRepository = zenModeRepository,
notificationSettingsRepository = notificationSettingsRepository,
+ bgDispatcher = testDispatcher,
+ iconLoader = zenIconLoader,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
index 2dbac67..0089199 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
@@ -64,7 +64,8 @@
@Override
public void setResourceIcon(String slot, @Nullable String resPackage, int iconResId,
- @Nullable Drawable preloadedIcon, CharSequence contentDescription) {
+ @Nullable Drawable preloadedIcon, CharSequence contentDescription,
+ StatusBarIcon.Shape shape) {
}
@Override
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/TestMediaDevicesFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/TestMediaDevicesFactory.kt
index 83adc79..ad1292e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/TestMediaDevicesFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/TestMediaDevicesFactory.kt
@@ -38,6 +38,7 @@
): MediaDevice = mock {
whenever(name).thenReturn(deviceName)
whenever(icon).thenReturn(deviceIcon)
+ whenever(deviceType).thenReturn(MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE)
}
fun wiredMediaDevice(
@@ -77,6 +78,18 @@
whenever(name).thenReturn(deviceName)
whenever(icon).thenReturn(deviceIcon)
whenever(cachedDevice).thenReturn(cachedBluetoothDevice)
+ whenever(deviceType).thenReturn(MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE)
+ }
+ }
+
+ fun remoteMediaDevice(
+ deviceName: String = "remote_media",
+ deviceIcon: Drawable? = TestStubDrawable(),
+ ): MediaDevice {
+ return mock<MediaDevice> {
+ whenever(name).thenReturn(deviceName)
+ whenever(icon).thenReturn(deviceIcon)
+ whenever(deviceType).thenReturn(MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE)
}
}
}
diff --git a/ravenwood/.gitignore b/ravenwood/.gitignore
new file mode 100644
index 0000000..751553b
--- /dev/null
+++ b/ravenwood/.gitignore
@@ -0,0 +1 @@
+*.bak
diff --git a/ravenwood/TEST_MAPPING b/ravenwood/TEST_MAPPING
index 7e2ee3e..469759b 100644
--- a/ravenwood/TEST_MAPPING
+++ b/ravenwood/TEST_MAPPING
@@ -1,17 +1,12 @@
-// Keep the following two TEST_MAPPINGs in sync:
-// frameworks/base/ravenwood/TEST_MAPPING
-// frameworks/base/tools/hoststubgen/TEST_MAPPING
{
"presubmit": [
{ "name": "tiny-framework-dump-test" },
{ "name": "hoststubgentest" },
+ { "name": "hoststubgen-test-tiny-test" },
{ "name": "hoststubgen-invoke-test" },
- {
- "name": "RavenwoodMockitoTest_device"
- },
- {
- "name": "RavenwoodBivalentTest_device"
- },
+ { "name": "RavenwoodMockitoTest_device" },
+ { "name": "RavenwoodBivalentTest_device" },
+
// The sysui tests should match vendor/unbundled_google/packages/SystemUIGoogle/TEST_MAPPING
{
"name": "SystemUIGoogleTests",
@@ -39,6 +34,113 @@
}
],
"ravenwood-presubmit": [
+ // AUTO-GENERATED-START
+ // DO NOT MODIFY MANUALLY
+ // Use scripts/update-test-mapping.sh to update it.
+ {
+ "name": "AdServicesSharedLibrariesUnitTestsRavenwood",
+ "host": true
+ },
+ {
+ "name": "android.test.mock.ravenwood.tests",
+ "host": true
+ },
+ {
+ "name": "CarLibHostUnitTest",
+ "host": true
+ },
+ {
+ "name": "CarServiceHostUnitTest",
+ "host": true
+ },
+ {
+ "name": "CarSystemUIRavenTests",
+ "host": true
+ },
+ {
+ "name": "CtsAccountManagerTestCasesRavenwood",
+ "host": true
+ },
+ {
+ "name": "CtsAppTestCasesRavenwood",
+ "host": true
+ },
+ {
+ "name": "CtsContentTestCasesRavenwood",
+ "host": true
+ },
+ {
+ "name": "CtsDatabaseTestCasesRavenwood",
+ "host": true
+ },
+ {
+ "name": "CtsGraphicsTestCasesRavenwood",
+ "host": true
+ },
+ {
+ "name": "CtsIcuTestCasesRavenwood",
+ "host": true
+ },
+ {
+ "name": "CtsInputMethodTestCasesRavenwood",
+ "host": true
+ },
+ {
+ "name": "CtsOsTestCasesRavenwood",
+ "host": true
+ },
+ {
+ "name": "CtsProtoTestCasesRavenwood",
+ "host": true
+ },
+ {
+ "name": "CtsResourcesTestCasesRavenwood",
+ "host": true
+ },
+ {
+ "name": "CtsTextTestCasesRavenwood",
+ "host": true
+ },
+ {
+ "name": "CtsUtilTestCasesRavenwood",
+ "host": true
+ },
+ {
+ "name": "FrameworksCoreSystemPropertiesTestsRavenwood",
+ "host": true
+ },
+ {
+ "name": "FrameworksCoreTestsRavenwood",
+ "host": true
+ },
+ {
+ "name": "FrameworksInputMethodSystemServerTestsRavenwood",
+ "host": true
+ },
+ {
+ "name": "FrameworksMockingServicesTestsRavenwood",
+ "host": true
+ },
+ {
+ "name": "FrameworksServicesTestsRavenwood",
+ "host": true
+ },
+ {
+ "name": "FrameworksUtilTestsRavenwood",
+ "host": true
+ },
+ {
+ "name": "InternalTestsRavenwood",
+ "host": true
+ },
+ {
+ "name": "PowerStatsTestsRavenwood",
+ "host": true
+ },
+ {
+ "name": "RavenwoodBivalentTest",
+ "host": true
+ },
{
"name": "RavenwoodMinimumTest",
"host": true
@@ -48,16 +150,21 @@
"host": true
},
{
- "name": "CtsUtilTestCasesRavenwood",
- "host": true
- },
- {
"name": "RavenwoodResApkTest",
"host": true
},
{
- "name": "RavenwoodBivalentTest",
+ "name": "RavenwoodRuntimeTest",
+ "host": true
+ },
+ {
+ "name": "RavenwoodServicesTest",
+ "host": true
+ },
+ {
+ "name": "SystemUiRavenTests",
"host": true
}
+ // AUTO-GENERATED-END
]
}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/CallTracker.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/CallTracker.java
index 8dadd39..09a0aa8 100644
--- a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/CallTracker.java
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/CallTracker.java
@@ -64,7 +64,7 @@
/**
* Check the number of calls stored in {@link #mNumCalled}.
*/
- protected void assertCalls(Object... methodNameAndCountPairs) {
+ public void assertCalls(Object... methodNameAndCountPairs) {
// Create a local copy
HashMap<String, Integer> counts = new HashMap<>(mNumCalled);
for (int i = 0; i < methodNameAndCountPairs.length - 1; i += 2) {
@@ -95,7 +95,7 @@
* Same as {@link #assertCalls(Object...)} but it kills the process if it fails.
* Only use in @AfterClass.
*/
- protected void assertCallsOrDie(Object... methodNameAndCountPairs) {
+ public void assertCallsOrDie(Object... methodNameAndCountPairs) {
try {
assertCalls(methodNameAndCountPairs);
} catch (Throwable th) {
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodNoRavenizerTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodNoRavenizerTest.java
new file mode 100644
index 0000000..9d878f4
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodNoRavenizerTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.bivalenttest.ravenizer;
+
+import android.platform.test.annotations.NoRavenizer;
+import android.platform.test.ravenwood.RavenwoodAwareTestRunner.RavenwoodTestRunnerInitializing;
+
+import org.junit.Test;
+
+/**
+ * Test for {@link android.platform.test.annotations.NoRavenizer}
+ */
+@NoRavenizer
+public class RavenwoodNoRavenizerTest {
+ public static final String TAG = "RavenwoodNoRavenizerTest";
+
+ private static final CallTracker sCallTracker = new CallTracker();
+
+ /**
+ * With @NoRavenizer, this method shouldn't be called.
+ */
+ @RavenwoodTestRunnerInitializing
+ public static void ravenwoodRunnerInitializing() {
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ /**
+ * Make sure ravenwoodRunnerInitializing() wasn't called.
+ */
+ @Test
+ public void testNotRavenized() {
+ sCallTracker.assertCalls(
+ "ravenwoodRunnerInitializing", 0
+ );
+ }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodSuiteTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodSuiteTest.java
new file mode 100644
index 0000000..7e396c2
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodSuiteTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.bivalenttest.ravenizer;
+
+import android.util.Log;
+
+import org.junit.AfterClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+/**
+ * Test to make sure {@link Suite} works with the ravenwood test runner.
+ */
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+ RavenwoodSuiteTest.Test1.class,
+ RavenwoodSuiteTest.Test2.class
+})
+public class RavenwoodSuiteTest {
+ public static final String TAG = "RavenwoodSuiteTest";
+
+ private static final CallTracker sCallTracker = new CallTracker();
+
+ @AfterClass
+ public static void afterClass() {
+ Log.i(TAG, "afterClass called");
+
+ sCallTracker.assertCallsOrDie(
+ "test1", 1,
+ "test2", 1
+ );
+ }
+
+ /**
+ * Workaround for the issue where tradefed won't think a class is a test class
+ * if it has a @RunWith but no @Test methods, even if it is a Suite.
+ */
+ @Test
+ public void testEmpty() {
+ }
+
+ public static class Test1 {
+ @Test
+ public void test1() {
+ sCallTracker.incrementMethodCallCount();
+ }
+ }
+
+ public static class Test2 {
+ @Test
+ public void test2() {
+ sCallTracker.incrementMethodCallCount();
+ }
+ }
+}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
index 1da93eb..f237ba9 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
@@ -57,7 +57,8 @@
*/
public static void onRunnerInitializing(Runner runner, TestClass testClass) {
// This log call also ensures the framework JNI is loaded.
- Log.i(TAG, "onRunnerInitializing: testClass=" + testClass + " runner=" + runner);
+ Log.i(TAG, "onRunnerInitializing: testClass=" + testClass.getJavaClass()
+ + " runner=" + runner);
// TODO: Move the initialization code to a better place.
diff --git a/ravenwood/junit-src/android/platform/test/annotations/NoRavenizer.java b/ravenwood/junit-src/android/platform/test/annotations/NoRavenizer.java
new file mode 100644
index 0000000..a84f16f
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/annotations/NoRavenizer.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.platform.test.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Disable the ravenizer preprocessor for a class. This should be only used for testing
+ * ravenizer itself, or to workaround issues with the preprocessor. A test class probably won't run
+ * properly if it's not preprocessed.
+ *
+ * @hide
+ */
+@Inherited
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface NoRavenizer {
+}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
index 2b55ac5..7a160955 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
@@ -21,10 +21,13 @@
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
+import android.util.Log;
+
import com.android.ravenwood.common.RavenwoodCommonUtils;
import com.android.ravenwood.common.SneakyThrow;
import org.junit.Assume;
+import org.junit.internal.builders.AllDefaultPossibilitiesBuilder;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runner.Runner;
@@ -36,8 +39,10 @@
import org.junit.runner.manipulation.Orderer;
import org.junit.runner.manipulation.Sortable;
import org.junit.runner.manipulation.Sorter;
+import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.RunnerBuilder;
import org.junit.runners.model.Statement;
import org.junit.runners.model.TestClass;
@@ -51,8 +56,6 @@
/**
* A test runner used for Ravenwood.
*
- * TODO: Handle ENABLE_PROBE_IGNORED
- *
* It will delegate to another runner specified with {@link InnerRunner}
* (default = {@link BlockJUnit4ClassRunner}) with the following features.
* - Add a {@link RavenwoodAwareTestRunnerHook#onRunnerInitializing} hook, which is called before
@@ -134,12 +137,15 @@
return runner;
}
- private final TestClass mTestClsas;
- private final Runner mRealRunner;
+ private TestClass mTestClass = null;
+ private Runner mRealRunner = null;
+ private Description mDescription = null;
+ private Throwable mExceptionInConstructor = null;
/** Simple logging method. */
private void log(String message) {
- RavenwoodCommonUtils.log(TAG, "[" + getTestClass() + " @" + this + "] " + message);
+ RavenwoodCommonUtils.log(TAG, "[" + getTestClass().getJavaClass() + " @" + this + "] "
+ + message);
}
private Error logAndFail(String message, Throwable innerException) {
@@ -149,45 +155,80 @@
}
public TestClass getTestClass() {
- return mTestClsas;
+ return mTestClass;
}
/**
* Constructor.
*/
public RavenwoodAwareTestRunner(Class<?> testClass) {
- mTestClsas = new TestClass(testClass);
-
- /*
- * If the class has @DisabledOnRavenwood, then we'll delegate to ClassSkippingTestRunner,
- * which simply skips it.
- */
- if (isOnRavenwood() && !RavenwoodAwareTestRunnerHook.shouldRunClassOnRavenwood(
- mTestClsas.getJavaClass())) {
- mRealRunner = new ClassSkippingTestRunner(mTestClsas);
- return;
- }
-
- // Find the real runner.
- final Class<? extends Runner> realRunner;
- final InnerRunner innerRunnerAnnotation = mTestClsas.getAnnotation(InnerRunner.class);
- if (innerRunnerAnnotation != null) {
- realRunner = innerRunnerAnnotation.value();
- } else {
- // Default runner.
- realRunner = BlockJUnit4ClassRunner.class;
- }
-
- onRunnerInitializing();
-
try {
- log("Initializing the inner runner: " + realRunner);
+ mTestClass = new TestClass(testClass);
- mRealRunner = realRunner.getConstructor(Class.class).newInstance(testClass);
+ /*
+ * If the class has @DisabledOnRavenwood, then we'll delegate to
+ * ClassSkippingTestRunner, which simply skips it.
+ */
+ if (isOnRavenwood() && !RavenwoodAwareTestRunnerHook.shouldRunClassOnRavenwood(
+ mTestClass.getJavaClass())) {
+ mRealRunner = new ClassSkippingTestRunner(mTestClass);
+ mDescription = mRealRunner.getDescription();
+ return;
+ }
- } catch (InstantiationException | IllegalAccessException
- | InvocationTargetException | NoSuchMethodException e) {
- throw logAndFail("Failed to instantiate " + realRunner, e);
+ // Find the real runner.
+ final Class<? extends Runner> realRunnerClass;
+ final InnerRunner innerRunnerAnnotation = mTestClass.getAnnotation(InnerRunner.class);
+ if (innerRunnerAnnotation != null) {
+ realRunnerClass = innerRunnerAnnotation.value();
+ } else {
+ // Default runner.
+ realRunnerClass = BlockJUnit4ClassRunner.class;
+ }
+
+ onRunnerInitializing();
+
+ try {
+ log("Initializing the inner runner: " + realRunnerClass);
+
+ mRealRunner = instantiateRealRunner(realRunnerClass, testClass);
+ mDescription = mRealRunner.getDescription();
+
+ } catch (InstantiationException | IllegalAccessException
+ | InvocationTargetException | NoSuchMethodException e) {
+ throw logAndFail("Failed to instantiate " + realRunnerClass, e);
+ }
+ } catch (Throwable th) {
+ // If we throw in the constructor, Tradefed may not report it and just ignore the class,
+ // so record it and throw it when the test actually started.
+ log("Fatal: Exception detected in constructor: " + th.getMessage() + "\n"
+ + Log.getStackTraceString(th));
+ if (true) {
+ // TODO(b/363094647) Remove this
+ throw th;
+ }
+ mExceptionInConstructor = new RuntimeException("Exception detected in constructor",
+ th);
+ mDescription = Description.createTestDescription(testClass, "Constructor");
+
+ // This is for testing if tradefed is fixed.
+ if ("1".equals(System.getenv("RAVENWOOD_THROW_EXCEPTION_IN_TEST_RUNNER"))) {
+ throw th;
+ }
+ }
+ }
+
+ private static Runner instantiateRealRunner(
+ Class<? extends Runner> realRunnerClass,
+ Class<?> testClass)
+ throws NoSuchMethodException, InvocationTargetException, InstantiationException,
+ IllegalAccessException {
+ try {
+ return realRunnerClass.getConstructor(Class.class).newInstance(testClass);
+ } catch (NoSuchMethodException e) {
+ var runnerBuilder = new AllDefaultPossibilitiesBuilder();
+ return realRunnerClass.getConstructor(Class.class,
+ RunnerBuilder.class).newInstance(testClass, runnerBuilder);
}
}
@@ -202,7 +243,7 @@
log("onRunnerInitializing");
- RavenwoodAwareTestRunnerHook.onRunnerInitializing(this, mTestClsas);
+ RavenwoodAwareTestRunnerHook.onRunnerInitializing(this, mTestClass);
// Hook point to allow more customization.
runAnnotatedMethodsOnRavenwood(RavenwoodTestRunnerInitializing.class, null);
@@ -230,7 +271,7 @@
@Override
public Description getDescription() {
- return mRealRunner.getDescription();
+ return mDescription;
}
@Override
@@ -241,6 +282,10 @@
return;
}
+ if (maybeReportExceptionFromConstructor(notifier)) {
+ return;
+ }
+
sCurrentRunner.set(this);
try {
runWithHooks(getDescription(), Scope.Runner, Order.First,
@@ -250,6 +295,18 @@
}
}
+ /** Throw the exception detected in the constructor, if any. */
+ private boolean maybeReportExceptionFromConstructor(RunNotifier notifier) {
+ if (mExceptionInConstructor == null) {
+ return false;
+ }
+ notifier.fireTestStarted(mDescription);
+ notifier.fireTestFailure(new Failure(mDescription, mExceptionInConstructor));
+ notifier.fireTestFinished(mDescription);
+
+ return true;
+ }
+
@Override
public void filter(Filter filter) throws NoTestsRemainException {
if (mRealRunner instanceof Filterable r) {
diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/FP16.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/FP16.java
new file mode 100644
index 0000000..478503b
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/FP16.java
@@ -0,0 +1,814 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libcore.util;
+
+/**
+ * <p>The {@code FP16} class is a wrapper and a utility class to manipulate half-precision 16-bit
+ * <a href="https://en.wikipedia.org/wiki/Half-precision_floating-point_format">IEEE 754</a>
+ * floating point data types (also called fp16 or binary16). A half-precision float can be
+ * created from or converted to single-precision floats, and is stored in a short data type.
+ *
+ * <p>The IEEE 754 standard specifies an fp16 as having the following format:</p>
+ * <ul>
+ * <li>Sign bit: 1 bit</li>
+ * <li>Exponent width: 5 bits</li>
+ * <li>Significand: 10 bits</li>
+ * </ul>
+ *
+ * <p>The format is laid out as follows:</p>
+ * <pre>
+ * 1 11111 1111111111
+ * ^ --^-- -----^----
+ * sign | |_______ significand
+ * |
+ * -- exponent
+ * </pre>
+ *
+ * <p>Half-precision floating points can be useful to save memory and/or
+ * bandwidth at the expense of range and precision when compared to single-precision
+ * floating points (fp32).</p>
+ * <p>To help you decide whether fp16 is the right storage type for you need, please
+ * refer to the table below that shows the available precision throughout the range of
+ * possible values. The <em>precision</em> column indicates the step size between two
+ * consecutive numbers in a specific part of the range.</p>
+ *
+ * <table summary="Precision of fp16 across the range">
+ * <tr><th>Range start</th><th>Precision</th></tr>
+ * <tr><td>0</td><td>1 ⁄ 16,777,216</td></tr>
+ * <tr><td>1 ⁄ 16,384</td><td>1 ⁄ 16,777,216</td></tr>
+ * <tr><td>1 ⁄ 8,192</td><td>1 ⁄ 8,388,608</td></tr>
+ * <tr><td>1 ⁄ 4,096</td><td>1 ⁄ 4,194,304</td></tr>
+ * <tr><td>1 ⁄ 2,048</td><td>1 ⁄ 2,097,152</td></tr>
+ * <tr><td>1 ⁄ 1,024</td><td>1 ⁄ 1,048,576</td></tr>
+ * <tr><td>1 ⁄ 512</td><td>1 ⁄ 524,288</td></tr>
+ * <tr><td>1 ⁄ 256</td><td>1 ⁄ 262,144</td></tr>
+ * <tr><td>1 ⁄ 128</td><td>1 ⁄ 131,072</td></tr>
+ * <tr><td>1 ⁄ 64</td><td>1 ⁄ 65,536</td></tr>
+ * <tr><td>1 ⁄ 32</td><td>1 ⁄ 32,768</td></tr>
+ * <tr><td>1 ⁄ 16</td><td>1 ⁄ 16,384</td></tr>
+ * <tr><td>1 ⁄ 8</td><td>1 ⁄ 8,192</td></tr>
+ * <tr><td>1 ⁄ 4</td><td>1 ⁄ 4,096</td></tr>
+ * <tr><td>1 ⁄ 2</td><td>1 ⁄ 2,048</td></tr>
+ * <tr><td>1</td><td>1 ⁄ 1,024</td></tr>
+ * <tr><td>2</td><td>1 ⁄ 512</td></tr>
+ * <tr><td>4</td><td>1 ⁄ 256</td></tr>
+ * <tr><td>8</td><td>1 ⁄ 128</td></tr>
+ * <tr><td>16</td><td>1 ⁄ 64</td></tr>
+ * <tr><td>32</td><td>1 ⁄ 32</td></tr>
+ * <tr><td>64</td><td>1 ⁄ 16</td></tr>
+ * <tr><td>128</td><td>1 ⁄ 8</td></tr>
+ * <tr><td>256</td><td>1 ⁄ 4</td></tr>
+ * <tr><td>512</td><td>1 ⁄ 2</td></tr>
+ * <tr><td>1,024</td><td>1</td></tr>
+ * <tr><td>2,048</td><td>2</td></tr>
+ * <tr><td>4,096</td><td>4</td></tr>
+ * <tr><td>8,192</td><td>8</td></tr>
+ * <tr><td>16,384</td><td>16</td></tr>
+ * <tr><td>32,768</td><td>32</td></tr>
+ * </table>
+ *
+ * <p>This table shows that numbers higher than 1024 lose all fractional precision.</p>
+ *
+ * @hide
+ */
+
+public final class FP16 {
+ /**
+ * The number of bits used to represent a half-precision float value.
+ *
+ * @hide
+ */
+ public static final int SIZE = 16;
+
+ /**
+ * Epsilon is the difference between 1.0 and the next value representable
+ * by a half-precision floating-point.
+ *
+ * @hide
+ */
+ public static final short EPSILON = (short) 0x1400;
+
+ /**
+ * Maximum exponent a finite half-precision float may have.
+ *
+ * @hide
+ */
+ public static final int MAX_EXPONENT = 15;
+ /**
+ * Minimum exponent a normalized half-precision float may have.
+ *
+ * @hide
+ */
+ public static final int MIN_EXPONENT = -14;
+
+ /**
+ * Smallest negative value a half-precision float may have.
+ *
+ * @hide
+ */
+ public static final short LOWEST_VALUE = (short) 0xfbff;
+ /**
+ * Maximum positive finite value a half-precision float may have.
+ *
+ * @hide
+ */
+ public static final short MAX_VALUE = (short) 0x7bff;
+ /**
+ * Smallest positive normal value a half-precision float may have.
+ *
+ * @hide
+ */
+ public static final short MIN_NORMAL = (short) 0x0400;
+ /**
+ * Smallest positive non-zero value a half-precision float may have.
+ *
+ * @hide
+ */
+ public static final short MIN_VALUE = (short) 0x0001;
+ /**
+ * A Not-a-Number representation of a half-precision float.
+ *
+ * @hide
+ */
+ public static final short NaN = (short) 0x7e00;
+ /**
+ * Negative infinity of type half-precision float.
+ *
+ * @hide
+ */
+ public static final short NEGATIVE_INFINITY = (short) 0xfc00;
+ /**
+ * Negative 0 of type half-precision float.
+ *
+ * @hide
+ */
+ public static final short NEGATIVE_ZERO = (short) 0x8000;
+ /**
+ * Positive infinity of type half-precision float.
+ *
+ * @hide
+ */
+ public static final short POSITIVE_INFINITY = (short) 0x7c00;
+ /**
+ * Positive 0 of type half-precision float.
+ *
+ * @hide
+ */
+ public static final short POSITIVE_ZERO = (short) 0x0000;
+
+ /**
+ * The offset to shift by to obtain the sign bit.
+ *
+ * @hide
+ */
+ public static final int SIGN_SHIFT = 15;
+
+ /**
+ * The offset to shift by to obtain the exponent bits.
+ *
+ * @hide
+ */
+ public static final int EXPONENT_SHIFT = 10;
+
+ /**
+ * The bitmask to AND a number with to obtain the sign bit.
+ *
+ * @hide
+ */
+ public static final int SIGN_MASK = 0x8000;
+
+ /**
+ * The bitmask to AND a number shifted by {@link #EXPONENT_SHIFT} right, to obtain exponent bits.
+ *
+ * @hide
+ */
+ public static final int SHIFTED_EXPONENT_MASK = 0x1f;
+
+ /**
+ * The bitmask to AND a number with to obtain significand bits.
+ *
+ * @hide
+ */
+ public static final int SIGNIFICAND_MASK = 0x3ff;
+
+ /**
+ * The bitmask to AND with to obtain exponent and significand bits.
+ *
+ * @hide
+ */
+ public static final int EXPONENT_SIGNIFICAND_MASK = 0x7fff;
+
+ /**
+ * The offset of the exponent from the actual value.
+ *
+ * @hide
+ */
+ public static final int EXPONENT_BIAS = 15;
+
+ private static final int FP32_SIGN_SHIFT = 31;
+ private static final int FP32_EXPONENT_SHIFT = 23;
+ private static final int FP32_SHIFTED_EXPONENT_MASK = 0xff;
+ private static final int FP32_SIGNIFICAND_MASK = 0x7fffff;
+ private static final int FP32_EXPONENT_BIAS = 127;
+ private static final int FP32_QNAN_MASK = 0x400000;
+ private static final int FP32_DENORMAL_MAGIC = 126 << 23;
+ private static final float FP32_DENORMAL_FLOAT = Float.intBitsToFloat(FP32_DENORMAL_MAGIC);
+
+ /** Hidden constructor to prevent instantiation. */
+ private FP16() {}
+
+ /**
+ * <p>Compares the two specified half-precision float values. The following
+ * conditions apply during the comparison:</p>
+ *
+ * <ul>
+ * <li>{@link #NaN} is considered by this method to be equal to itself and greater
+ * than all other half-precision float values (including {@code #POSITIVE_INFINITY})</li>
+ * <li>{@link #POSITIVE_ZERO} is considered by this method to be greater than
+ * {@link #NEGATIVE_ZERO}.</li>
+ * </ul>
+ *
+ * @param x The first half-precision float value to compare.
+ * @param y The second half-precision float value to compare
+ *
+ * @return The value {@code 0} if {@code x} is numerically equal to {@code y}, a
+ * value less than {@code 0} if {@code x} is numerically less than {@code y},
+ * and a value greater than {@code 0} if {@code x} is numerically greater
+ * than {@code y}
+ *
+ * @hide
+ */
+ public static int compare(short x, short y) {
+ if (less(x, y)) return -1;
+ if (greater(x, y)) return 1;
+
+ // Collapse NaNs, akin to halfToIntBits(), but we want to keep
+ // (signed) short value types to preserve the ordering of -0.0
+ // and +0.0
+ short xBits = isNaN(x) ? NaN : x;
+ short yBits = isNaN(y) ? NaN : y;
+
+ return (xBits == yBits ? 0 : (xBits < yBits ? -1 : 1));
+ }
+
+ /**
+ * Returns the closest integral half-precision float value to the specified
+ * half-precision float value. Special values are handled in the
+ * following ways:
+ * <ul>
+ * <li>If the specified half-precision float is NaN, the result is NaN</li>
+ * <li>If the specified half-precision float is infinity (negative or positive),
+ * the result is infinity (with the same sign)</li>
+ * <li>If the specified half-precision float is zero (negative or positive),
+ * the result is zero (with the same sign)</li>
+ * </ul>
+ *
+ * @param h A half-precision float value
+ * @return The value of the specified half-precision float rounded to the nearest
+ * half-precision float value
+ *
+ * @hide
+ */
+ public static short rint(short h) {
+ int bits = h & 0xffff;
+ int abs = bits & EXPONENT_SIGNIFICAND_MASK;
+ int result = bits;
+
+ if (abs < 0x3c00) {
+ result &= SIGN_MASK;
+ if (abs > 0x3800){
+ result |= 0x3c00;
+ }
+ } else if (abs < 0x6400) {
+ int exp = 25 - (abs >> 10);
+ int mask = (1 << exp) - 1;
+ result += ((1 << (exp - 1)) - (~(abs >> exp) & 1));
+ result &= ~mask;
+ }
+ if (isNaN((short) result)) {
+ // if result is NaN mask with qNaN
+ // (i.e. mask the most significant mantissa bit with 1)
+ // to comply with hardware implementations (ARM64, Intel, etc).
+ result |= NaN;
+ }
+
+ return (short) result;
+ }
+
+ /**
+ * Returns the smallest half-precision float value toward negative infinity
+ * greater than or equal to the specified half-precision float value.
+ * Special values are handled in the following ways:
+ * <ul>
+ * <li>If the specified half-precision float is NaN, the result is NaN</li>
+ * <li>If the specified half-precision float is infinity (negative or positive),
+ * the result is infinity (with the same sign)</li>
+ * <li>If the specified half-precision float is zero (negative or positive),
+ * the result is zero (with the same sign)</li>
+ * </ul>
+ *
+ * @param h A half-precision float value
+ * @return The smallest half-precision float value toward negative infinity
+ * greater than or equal to the specified half-precision float value
+ *
+ * @hide
+ */
+ public static short ceil(short h) {
+ int bits = h & 0xffff;
+ int abs = bits & EXPONENT_SIGNIFICAND_MASK;
+ int result = bits;
+
+ if (abs < 0x3c00) {
+ result &= SIGN_MASK;
+ result |= 0x3c00 & -(~(bits >> 15) & (abs != 0 ? 1 : 0));
+ } else if (abs < 0x6400) {
+ abs = 25 - (abs >> 10);
+ int mask = (1 << abs) - 1;
+ result += mask & ((bits >> 15) - 1);
+ result &= ~mask;
+ }
+ if (isNaN((short) result)) {
+ // if result is NaN mask with qNaN
+ // (i.e. mask the most significant mantissa bit with 1)
+ // to comply with hardware implementations (ARM64, Intel, etc).
+ result |= NaN;
+ }
+
+ return (short) result;
+ }
+
+ /**
+ * Returns the largest half-precision float value toward positive infinity
+ * less than or equal to the specified half-precision float value.
+ * Special values are handled in the following ways:
+ * <ul>
+ * <li>If the specified half-precision float is NaN, the result is NaN</li>
+ * <li>If the specified half-precision float is infinity (negative or positive),
+ * the result is infinity (with the same sign)</li>
+ * <li>If the specified half-precision float is zero (negative or positive),
+ * the result is zero (with the same sign)</li>
+ * </ul>
+ *
+ * @param h A half-precision float value
+ * @return The largest half-precision float value toward positive infinity
+ * less than or equal to the specified half-precision float value
+ *
+ * @hide
+ */
+ public static short floor(short h) {
+ int bits = h & 0xffff;
+ int abs = bits & EXPONENT_SIGNIFICAND_MASK;
+ int result = bits;
+
+ if (abs < 0x3c00) {
+ result &= SIGN_MASK;
+ result |= 0x3c00 & (bits > 0x8000 ? 0xffff : 0x0);
+ } else if (abs < 0x6400) {
+ abs = 25 - (abs >> 10);
+ int mask = (1 << abs) - 1;
+ result += mask & -(bits >> 15);
+ result &= ~mask;
+ }
+ if (isNaN((short) result)) {
+ // if result is NaN mask with qNaN
+ // i.e. (Mask the most significant mantissa bit with 1)
+ result |= NaN;
+ }
+
+ return (short) result;
+ }
+
+ /**
+ * Returns the truncated half-precision float value of the specified
+ * half-precision float value. Special values are handled in the following ways:
+ * <ul>
+ * <li>If the specified half-precision float is NaN, the result is NaN</li>
+ * <li>If the specified half-precision float is infinity (negative or positive),
+ * the result is infinity (with the same sign)</li>
+ * <li>If the specified half-precision float is zero (negative or positive),
+ * the result is zero (with the same sign)</li>
+ * </ul>
+ *
+ * @param h A half-precision float value
+ * @return The truncated half-precision float value of the specified
+ * half-precision float value
+ *
+ * @hide
+ */
+ public static short trunc(short h) {
+ int bits = h & 0xffff;
+ int abs = bits & EXPONENT_SIGNIFICAND_MASK;
+ int result = bits;
+
+ if (abs < 0x3c00) {
+ result &= SIGN_MASK;
+ } else if (abs < 0x6400) {
+ abs = 25 - (abs >> 10);
+ int mask = (1 << abs) - 1;
+ result &= ~mask;
+ }
+
+ return (short) result;
+ }
+
+ /**
+ * Returns the smaller of two half-precision float values (the value closest
+ * to negative infinity). Special values are handled in the following ways:
+ * <ul>
+ * <li>If either value is NaN, the result is NaN</li>
+ * <li>{@link #NEGATIVE_ZERO} is smaller than {@link #POSITIVE_ZERO}</li>
+ * </ul>
+ *
+ * @param x The first half-precision value
+ * @param y The second half-precision value
+ * @return The smaller of the two specified half-precision values
+ *
+ * @hide
+ */
+ public static short min(short x, short y) {
+ if (isNaN(x)) return NaN;
+ if (isNaN(y)) return NaN;
+
+ if ((x & EXPONENT_SIGNIFICAND_MASK) == 0 && (y & EXPONENT_SIGNIFICAND_MASK) == 0) {
+ return (x & SIGN_MASK) != 0 ? x : y;
+ }
+
+ return ((x & SIGN_MASK) != 0 ? 0x8000 - (x & 0xffff) : x & 0xffff) <
+ ((y & SIGN_MASK) != 0 ? 0x8000 - (y & 0xffff) : y & 0xffff) ? x : y;
+ }
+
+ /**
+ * Returns the larger of two half-precision float values (the value closest
+ * to positive infinity). Special values are handled in the following ways:
+ * <ul>
+ * <li>If either value is NaN, the result is NaN</li>
+ * <li>{@link #POSITIVE_ZERO} is greater than {@link #NEGATIVE_ZERO}</li>
+ * </ul>
+ *
+ * @param x The first half-precision value
+ * @param y The second half-precision value
+ *
+ * @return The larger of the two specified half-precision values
+ *
+ * @hide
+ */
+ public static short max(short x, short y) {
+ if (isNaN(x)) return NaN;
+ if (isNaN(y)) return NaN;
+
+ if ((x & EXPONENT_SIGNIFICAND_MASK) == 0 && (y & EXPONENT_SIGNIFICAND_MASK) == 0) {
+ return (x & SIGN_MASK) != 0 ? y : x;
+ }
+
+ return ((x & SIGN_MASK) != 0 ? 0x8000 - (x & 0xffff) : x & 0xffff) >
+ ((y & SIGN_MASK) != 0 ? 0x8000 - (y & 0xffff) : y & 0xffff) ? x : y;
+ }
+
+ /**
+ * Returns true if the first half-precision float value is less (smaller
+ * toward negative infinity) than the second half-precision float value.
+ * If either of the values is NaN, the result is false.
+ *
+ * @param x The first half-precision value
+ * @param y The second half-precision value
+ *
+ * @return True if x is less than y, false otherwise
+ *
+ * @hide
+ */
+ public static boolean less(short x, short y) {
+ if (isNaN(x)) return false;
+ if (isNaN(y)) return false;
+
+ return ((x & SIGN_MASK) != 0 ? 0x8000 - (x & 0xffff) : x & 0xffff) <
+ ((y & SIGN_MASK) != 0 ? 0x8000 - (y & 0xffff) : y & 0xffff);
+ }
+
+ /**
+ * Returns true if the first half-precision float value is less (smaller
+ * toward negative infinity) than or equal to the second half-precision
+ * float value. If either of the values is NaN, the result is false.
+ *
+ * @param x The first half-precision value
+ * @param y The second half-precision value
+ *
+ * @return True if x is less than or equal to y, false otherwise
+ *
+ * @hide
+ */
+ public static boolean lessEquals(short x, short y) {
+ if (isNaN(x)) return false;
+ if (isNaN(y)) return false;
+
+ return ((x & SIGN_MASK) != 0 ? 0x8000 - (x & 0xffff) : x & 0xffff) <=
+ ((y & SIGN_MASK) != 0 ? 0x8000 - (y & 0xffff) : y & 0xffff);
+ }
+
+ /**
+ * Returns true if the first half-precision float value is greater (larger
+ * toward positive infinity) than the second half-precision float value.
+ * If either of the values is NaN, the result is false.
+ *
+ * @param x The first half-precision value
+ * @param y The second half-precision value
+ *
+ * @return True if x is greater than y, false otherwise
+ *
+ * @hide
+ */
+ public static boolean greater(short x, short y) {
+ if (isNaN(x)) return false;
+ if (isNaN(y)) return false;
+
+ return ((x & SIGN_MASK) != 0 ? 0x8000 - (x & 0xffff) : x & 0xffff) >
+ ((y & SIGN_MASK) != 0 ? 0x8000 - (y & 0xffff) : y & 0xffff);
+ }
+
+ /**
+ * Returns true if the first half-precision float value is greater (larger
+ * toward positive infinity) than or equal to the second half-precision float
+ * value. If either of the values is NaN, the result is false.
+ *
+ * @param x The first half-precision value
+ * @param y The second half-precision value
+ *
+ * @return True if x is greater than y, false otherwise
+ *
+ * @hide
+ */
+ public static boolean greaterEquals(short x, short y) {
+ if (isNaN(x)) return false;
+ if (isNaN(y)) return false;
+
+ return ((x & SIGN_MASK) != 0 ? 0x8000 - (x & 0xffff) : x & 0xffff) >=
+ ((y & SIGN_MASK) != 0 ? 0x8000 - (y & 0xffff) : y & 0xffff);
+ }
+
+ /**
+ * Returns true if the two half-precision float values are equal.
+ * If either of the values is NaN, the result is false. {@link #POSITIVE_ZERO}
+ * and {@link #NEGATIVE_ZERO} are considered equal.
+ *
+ * @param x The first half-precision value
+ * @param y The second half-precision value
+ *
+ * @return True if x is equal to y, false otherwise
+ *
+ * @hide
+ */
+ public static boolean equals(short x, short y) {
+ if (isNaN(x)) return false;
+ if (isNaN(y)) return false;
+
+ return x == y || ((x | y) & EXPONENT_SIGNIFICAND_MASK) == 0;
+ }
+
+ /**
+ * Returns true if the specified half-precision float value represents
+ * infinity, false otherwise.
+ *
+ * @param h A half-precision float value
+ * @return True if the value is positive infinity or negative infinity,
+ * false otherwise
+ *
+ * @hide
+ */
+ public static boolean isInfinite(short h) {
+ return (h & EXPONENT_SIGNIFICAND_MASK) == POSITIVE_INFINITY;
+ }
+
+ /**
+ * Returns true if the specified half-precision float value represents
+ * a Not-a-Number, false otherwise.
+ *
+ * @param h A half-precision float value
+ * @return True if the value is a NaN, false otherwise
+ *
+ * @hide
+ */
+ public static boolean isNaN(short h) {
+ return (h & EXPONENT_SIGNIFICAND_MASK) > POSITIVE_INFINITY;
+ }
+
+ /**
+ * Returns true if the specified half-precision float value is normalized
+ * (does not have a subnormal representation). If the specified value is
+ * {@link #POSITIVE_INFINITY}, {@link #NEGATIVE_INFINITY},
+ * {@link #POSITIVE_ZERO}, {@link #NEGATIVE_ZERO}, NaN or any subnormal
+ * number, this method returns false.
+ *
+ * @param h A half-precision float value
+ * @return True if the value is normalized, false otherwise
+ *
+ * @hide
+ */
+ public static boolean isNormalized(short h) {
+ return (h & POSITIVE_INFINITY) != 0 && (h & POSITIVE_INFINITY) != POSITIVE_INFINITY;
+ }
+
+ /**
+ * <p>Converts the specified half-precision float value into a
+ * single-precision float value. The following special cases are handled:</p>
+ * <ul>
+ * <li>If the input is {@link #NaN}, the returned value is {@link Float#NaN}</li>
+ * <li>If the input is {@link #POSITIVE_INFINITY} or
+ * {@link #NEGATIVE_INFINITY}, the returned value is respectively
+ * {@link Float#POSITIVE_INFINITY} or {@link Float#NEGATIVE_INFINITY}</li>
+ * <li>If the input is 0 (positive or negative), the returned value is +/-0.0f</li>
+ * <li>Otherwise, the returned value is a normalized single-precision float value</li>
+ * </ul>
+ *
+ * @param h The half-precision float value to convert to single-precision
+ * @return A normalized single-precision float value
+ *
+ * @hide
+ */
+ public static float toFloat(short h) {
+ int bits = h & 0xffff;
+ int s = bits & SIGN_MASK;
+ int e = (bits >>> EXPONENT_SHIFT) & SHIFTED_EXPONENT_MASK;
+ int m = (bits ) & SIGNIFICAND_MASK;
+
+ int outE = 0;
+ int outM = 0;
+
+ if (e == 0) { // Denormal or 0
+ if (m != 0) {
+ // Convert denorm fp16 into normalized fp32
+ float o = Float.intBitsToFloat(FP32_DENORMAL_MAGIC + m);
+ o -= FP32_DENORMAL_FLOAT;
+ return s == 0 ? o : -o;
+ }
+ } else {
+ outM = m << 13;
+ if (e == 0x1f) { // Infinite or NaN
+ outE = 0xff;
+ if (outM != 0) { // SNaNs are quieted
+ outM |= FP32_QNAN_MASK;
+ }
+ } else {
+ outE = e - EXPONENT_BIAS + FP32_EXPONENT_BIAS;
+ }
+ }
+
+ int out = (s << 16) | (outE << FP32_EXPONENT_SHIFT) | outM;
+ return Float.intBitsToFloat(out);
+ }
+
+ /**
+ * <p>Converts the specified single-precision float value into a
+ * half-precision float value. The following special cases are handled:</p>
+ * <ul>
+ * <li>If the input is NaN (see {@link Float#isNaN(float)}), the returned
+ * value is {@link #NaN}</li>
+ * <li>If the input is {@link Float#POSITIVE_INFINITY} or
+ * {@link Float#NEGATIVE_INFINITY}, the returned value is respectively
+ * {@link #POSITIVE_INFINITY} or {@link #NEGATIVE_INFINITY}</li>
+ * <li>If the input is 0 (positive or negative), the returned value is
+ * {@link #POSITIVE_ZERO} or {@link #NEGATIVE_ZERO}</li>
+ * <li>If the input is a less than {@link #MIN_VALUE}, the returned value
+ * is flushed to {@link #POSITIVE_ZERO} or {@link #NEGATIVE_ZERO}</li>
+ * <li>If the input is a less than {@link #MIN_NORMAL}, the returned value
+ * is a denorm half-precision float</li>
+ * <li>Otherwise, the returned value is rounded to the nearest
+ * representable half-precision float value</li>
+ * </ul>
+ *
+ * @param f The single-precision float value to convert to half-precision
+ * @return A half-precision float value
+ *
+ * @hide
+ */
+ public static short toHalf(float f) {
+ int bits = Float.floatToRawIntBits(f);
+ int s = (bits >>> FP32_SIGN_SHIFT );
+ int e = (bits >>> FP32_EXPONENT_SHIFT) & FP32_SHIFTED_EXPONENT_MASK;
+ int m = (bits ) & FP32_SIGNIFICAND_MASK;
+
+ int outE = 0;
+ int outM = 0;
+
+ if (e == 0xff) { // Infinite or NaN
+ outE = 0x1f;
+ outM = m != 0 ? 0x200 : 0;
+ } else {
+ e = e - FP32_EXPONENT_BIAS + EXPONENT_BIAS;
+ if (e >= 0x1f) { // Overflow
+ outE = 0x1f;
+ } else if (e <= 0) { // Underflow
+ if (e < -10) {
+ // The absolute fp32 value is less than MIN_VALUE, flush to +/-0
+ } else {
+ // The fp32 value is a normalized float less than MIN_NORMAL,
+ // we convert to a denorm fp16
+ m = m | 0x800000;
+ int shift = 14 - e;
+ outM = m >> shift;
+
+ int lowm = m & ((1 << shift) - 1);
+ int hway = 1 << (shift - 1);
+ // if above halfway or exactly halfway and outM is odd
+ if (lowm + (outM & 1) > hway){
+ // Round to nearest even
+ // Can overflow into exponent bit, which surprisingly is OK.
+ // This increment relies on the +outM in the return statement below
+ outM++;
+ }
+ }
+ } else {
+ outE = e;
+ outM = m >> 13;
+ // if above halfway or exactly halfway and outM is odd
+ if ((m & 0x1fff) + (outM & 0x1) > 0x1000) {
+ // Round to nearest even
+ // Can overflow into exponent bit, which surprisingly is OK.
+ // This increment relies on the +outM in the return statement below
+ outM++;
+ }
+ }
+ }
+ // The outM is added here as the +1 increments for outM above can
+ // cause an overflow in the exponent bit which is OK.
+ return (short) ((s << SIGN_SHIFT) | (outE << EXPONENT_SHIFT) + outM);
+ }
+
+ /**
+ * <p>Returns a hexadecimal string representation of the specified half-precision
+ * float value. If the value is a NaN, the result is <code>"NaN"</code>,
+ * otherwise the result follows this format:</p>
+ * <ul>
+ * <li>If the sign is positive, no sign character appears in the result</li>
+ * <li>If the sign is negative, the first character is <code>'-'</code></li>
+ * <li>If the value is inifinity, the string is <code>"Infinity"</code></li>
+ * <li>If the value is 0, the string is <code>"0x0.0p0"</code></li>
+ * <li>If the value has a normalized representation, the exponent and
+ * significand are represented in the string in two fields. The significand
+ * starts with <code>"0x1."</code> followed by its lowercase hexadecimal
+ * representation. Trailing zeroes are removed unless all digits are 0, then
+ * a single zero is used. The significand representation is followed by the
+ * exponent, represented by <code>"p"</code>, itself followed by a decimal
+ * string of the unbiased exponent</li>
+ * <li>If the value has a subnormal representation, the significand starts
+ * with <code>"0x0."</code> followed by its lowercase hexadecimal
+ * representation. Trailing zeroes are removed unless all digits are 0, then
+ * a single zero is used. The significand representation is followed by the
+ * exponent, represented by <code>"p-14"</code></li>
+ * </ul>
+ *
+ * @param h A half-precision float value
+ * @return A hexadecimal string representation of the specified value
+ *
+ * @hide
+ */
+ public static String toHexString(short h) {
+ StringBuilder o = new StringBuilder();
+
+ int bits = h & 0xffff;
+ int s = (bits >>> SIGN_SHIFT );
+ int e = (bits >>> EXPONENT_SHIFT) & SHIFTED_EXPONENT_MASK;
+ int m = (bits ) & SIGNIFICAND_MASK;
+
+ if (e == 0x1f) { // Infinite or NaN
+ if (m == 0) {
+ if (s != 0) o.append('-');
+ o.append("Infinity");
+ } else {
+ o.append("NaN");
+ }
+ } else {
+ if (s == 1) o.append('-');
+ if (e == 0) {
+ if (m == 0) {
+ o.append("0x0.0p0");
+ } else {
+ o.append("0x0.");
+ String significand = Integer.toHexString(m);
+ o.append(significand.replaceFirst("0{2,}$", ""));
+ o.append("p-14");
+ }
+ } else {
+ o.append("0x1.");
+ String significand = Integer.toHexString(m);
+ o.append(significand.replaceFirst("0{2,}$", ""));
+ o.append('p');
+ o.append(Integer.toString(e - EXPONENT_BIAS));
+ }
+ }
+
+ return o.toString();
+ }
+}
diff --git a/ravenwood/scripts/list-ravenwood-tests.sh b/ravenwood/scripts/list-ravenwood-tests.sh
index fb9b823..05f3fdf 100755
--- a/ravenwood/scripts/list-ravenwood-tests.sh
+++ b/ravenwood/scripts/list-ravenwood-tests.sh
@@ -15,4 +15,4 @@
# List all the ravenwood test modules.
-jq -r 'to_entries[] | select( .value.compatibility_suites | index("ravenwood-tests") ) | .key' "$OUT/module-info.json"
+jq -r 'to_entries[] | select( .value.compatibility_suites | index("ravenwood-tests") ) | .key' "$OUT/module-info.json" | sort
diff --git a/ravenwood/scripts/remove-ravenizer-output.sh b/ravenwood/scripts/remove-ravenizer-output.sh
new file mode 100755
index 0000000..be15b71
--- /dev/null
+++ b/ravenwood/scripts/remove-ravenizer-output.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Delete all the ravenizer output jar files from Soong's intermediate directory.
+
+# `-a -prune` is needed because otherwise find would be confused if the directory disappears.
+
+find "${ANDROID_BUILD_TOP:?}/out/soong/.intermediates/" \
+ -type d \
+ -name 'ravenizer' \
+ -print \
+ -exec rm -fr \{\} \; \
+ -a -prune
diff --git a/ravenwood/scripts/update-test-mapping.sh b/ravenwood/scripts/update-test-mapping.sh
new file mode 100755
index 0000000..b6cf5b8
--- /dev/null
+++ b/ravenwood/scripts/update-test-mapping.sh
@@ -0,0 +1,82 @@
+#!/bin/bash
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Update f/b/r/TEST_MAPPING with all the ravenwood tests as presubmit.
+#
+# Note, before running it, make sure module-info.json is up-to-date by running
+# (any) build.
+
+set -e
+
+main() {
+ local script_name="${0##*/}"
+ local script_dir="${0%/*}"
+ local test_mapping="$script_dir/../TEST_MAPPING"
+ local test_mapping_bak="$script_dir/../TEST_MAPPING.bak"
+
+ local header="$(sed -ne '1,/AUTO-GENERATED-START/p' "$test_mapping")"
+ local footer="$(sed -ne '/AUTO-GENERATED-END/,$p' "$test_mapping")"
+
+ echo "Getting all tests"
+ local tests=( $("$script_dir/list-ravenwood-tests.sh") )
+
+ local num_tests="${#tests[@]}"
+
+ if (( $num_tests == 0 )) ; then
+ echo "Something went wrong. No ravenwood tests detected." 1>&2
+ return 1
+ fi
+
+ echo "Tests: ${tests[@]}"
+
+ echo "Creating backup at $test_mapping_bak"
+ cp "$test_mapping" "$test_mapping_bak"
+
+ echo "Updating $test_mapping"
+ {
+ echo "$header"
+
+ echo " // DO NOT MODIFY MANUALLY"
+ echo " // Use scripts/$script_name to update it."
+
+ local i=0
+ while (( $i < $num_tests )) ; do
+ local comma=","
+ if (( $i == ($num_tests - 1) )); then
+ comma=""
+ fi
+ echo " {"
+ echo " \"name\": \"${tests[$i]}\","
+ echo " \"host\": true"
+ echo " }$comma"
+
+ i=$(( $i + 1 ))
+ done
+
+ echo "$footer"
+ } >"$test_mapping"
+
+ if cmp "$test_mapping_bak" "$test_mapping" ; then
+ echo "No change detecetd."
+ return 0
+ fi
+ echo "Updated $test_mapping"
+
+ # `|| true` is needed because of `set -e`.
+ diff -u "$test_mapping_bak" "$test_mapping" || true
+ return 0
+}
+
+main
diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
index d8366c5..34239b8 100644
--- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
@@ -46,6 +46,7 @@
android.util.EventLog
android.util.FloatProperty
android.util.FloatMath
+android.util.Half
android.util.IndentingPrintWriter
android.util.IntArray
android.util.IntProperty
@@ -277,7 +278,11 @@
android.graphics.Insets
android.graphics.Interpolator
android.graphics.Matrix
+android.graphics.Matrix44
+android.graphics.Outline
+android.graphics.ParcelableColorSpace
android.graphics.Path
+android.graphics.PixelFormat
android.graphics.Point
android.graphics.PointF
android.graphics.Rect
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Exceptions.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Exceptions.kt
index 3a7fab3..0dcd271 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Exceptions.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Exceptions.kt
@@ -17,7 +17,14 @@
package com.android.platform.test.ravenwood.ravenizer
+import com.android.hoststubgen.UserErrorException
+
/**
* Use it for internal exception that really shouldn't happen.
*/
class RavenizerInternalException(message: String) : Exception(message)
+
+/**
+ * Thrown when an invalid test is detected in the target jar. (e.g. JUni3 tests)
+ */
+class RavenizerInvalidTestException(message: String) : Exception(message), UserErrorException
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
index e92ef72..a38512e 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
@@ -44,6 +44,9 @@
/** Time took to build [ClasNodes] */
var loadStructureTime: Double = .0,
+ /** Time took to validate the classes */
+ var validationTime: Double = .0,
+
/** Total real time spent for converting the jar file */
var totalProcessTime: Double = .0,
@@ -67,6 +70,7 @@
RavenizerStats{
totalTime=$totalTime,
loadStructureTime=$loadStructureTime,
+ validationTime=$validationTime,
totalProcessTime=$totalProcessTime,
totalConversionTime=$totalConversionTime,
totalCopyTime=$totalCopyTime,
@@ -84,16 +88,44 @@
class Ravenizer(val options: RavenizerOptions) {
fun run() {
val stats = RavenizerStats()
+
+ val fatalValidation = options.fatalValidation.get
+
stats.totalTime = log.nTime {
- process(options.inJar.get, options.outJar.get, stats)
+ process(
+ options.inJar.get,
+ options.outJar.get,
+ options.enableValidation.get,
+ fatalValidation,
+ stats,
+ )
}
log.i(stats.toString())
}
- private fun process(inJar: String, outJar: String, stats: RavenizerStats) {
+ private fun process(
+ inJar: String,
+ outJar: String,
+ enableValidation: Boolean,
+ fatalValidation: Boolean,
+ stats: RavenizerStats,
+ ) {
var allClasses = ClassNodes.loadClassStructures(inJar) {
time -> stats.loadStructureTime = time
}
+ if (enableValidation) {
+ stats.validationTime = log.iTime("Validating classes") {
+ if (!validateClasses(allClasses)) {
+ var message = "Invalid test class(es) detected." +
+ " See error log for details."
+ if (fatalValidation) {
+ throw RavenizerInvalidTestException(message)
+ } else {
+ log.w("Warning: $message")
+ }
+ }
+ }
+ }
stats.totalProcessTime = log.iTime("$executableName processing $inJar") {
ZipFile(inJar).use { inZip ->
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
index e85e3be..e8341e5 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
@@ -27,6 +27,12 @@
/** Output jar file */
var outJar: SetOnce<String> = SetOnce(""),
+
+ /** Whether to enable test validation. */
+ var enableValidation: SetOnce<Boolean> = SetOnce(true),
+
+ /** Whether the validation failure is fatal or not. */
+ var fatalValidation: SetOnce<Boolean> = SetOnce(false),
) {
companion object {
fun parseArgs(args: Array<String>): RavenizerOptions {
@@ -52,6 +58,12 @@
"--in-jar" -> ret.inJar.set(nextArg()).ensureFileExists()
"--out-jar" -> ret.outJar.set(nextArg())
+ "--enable-validation" -> ret.enableValidation.set(true)
+ "--disable-validation" -> ret.enableValidation.set(false)
+
+ "--fatal-validation" -> ret.fatalValidation.set(true)
+ "--no-fatal-validation" -> ret.fatalValidation.set(false)
+
else -> throw ArgumentsException("Unknown option: $arg")
}
} catch (e: SetOnce.SetMoreThanOnceException) {
@@ -74,6 +86,8 @@
RavenizerOptions{
inJar=$inJar,
outJar=$outJar,
+ enableValidation=$enableValidation,
+ fatalValidation=$fatalValidation,
}
""".trimIndent()
}
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
index e026e7a..1aa70c08 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
@@ -15,10 +15,12 @@
*/
package com.android.platform.test.ravenwood.ravenizer
+import android.platform.test.annotations.NoRavenizer
import android.platform.test.ravenwood.RavenwoodAwareTestRunner
import com.android.hoststubgen.asm.ClassNodes
import com.android.hoststubgen.asm.findAnyAnnotation
import com.android.hoststubgen.asm.startsWithAny
+import com.android.hoststubgen.asm.toHumanReadableClassName
import org.junit.rules.TestRule
import org.junit.runner.RunWith
import org.objectweb.asm.Type
@@ -30,6 +32,7 @@
val desc = type.descriptor
val descAsSet = setOf<String>(desc)
val internlName = type.internalName
+ val humanReadableName = type.internalName.toHumanReadableClassName()
}
val testAnotType = TypeHolder(org.junit.Test::class.java)
@@ -37,6 +40,7 @@
val classRuleAnotType = TypeHolder(org.junit.ClassRule::class.java)
val runWithAnotType = TypeHolder(RunWith::class.java)
val innerRunnerAnotType = TypeHolder(RavenwoodAwareTestRunner.InnerRunner::class.java)
+val noRavenizerAnotType = TypeHolder(NoRavenizer::class.java)
val testRuleType = TypeHolder(TestRule::class.java)
val ravenwoodTestRunnerType = TypeHolder(RavenwoodAwareTestRunner::class.java)
@@ -87,9 +91,12 @@
return this.startsWithAny(
"java/", // just in case...
"javax/",
+ "junit/",
"org/junit/",
"org/mockito/",
"kotlin/",
+ "androidx/",
+ "android/support/",
// TODO -- anything else?
)
}
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Validator.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Validator.kt
new file mode 100644
index 0000000..27092d2
--- /dev/null
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Validator.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.platform.test.ravenwood.ravenizer
+
+import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.startsWithAny
+import com.android.hoststubgen.asm.toHumanReadableClassName
+import com.android.hoststubgen.log
+import org.objectweb.asm.tree.ClassNode
+
+fun validateClasses(classes: ClassNodes): Boolean {
+ var allOk = true
+ classes.forEach { allOk = checkClass(it, classes) && allOk }
+
+ return allOk
+}
+
+/**
+ * Validate a class.
+ *
+ * - A test class shouldn't extend
+ *
+ */
+fun checkClass(cn: ClassNode, classes: ClassNodes): Boolean {
+ if (cn.name.shouldByBypassed()) {
+ // Class doesn't need to be checked.
+ return true
+ }
+ var allOk = true
+
+ // See if there's any class that extends a legacy base class.
+ // But ignore the base classes in android.test.
+ if (!cn.name.startsWithAny("android/test/")) {
+ allOk = checkSuperClass(cn, cn, classes) && allOk
+ }
+ return allOk
+}
+
+fun checkSuperClass(targetClass: ClassNode, currentClass: ClassNode, classes: ClassNodes): Boolean {
+ if (currentClass.superName == null || currentClass.superName == "java/lang/Object") {
+ return true // No parent class
+ }
+ if (currentClass.superName.isLegacyTestBaseClass()) {
+ log.e("Error: Class ${targetClass.name.toHumanReadableClassName()} extends"
+ + " a legacy test class ${currentClass.superName.toHumanReadableClassName()}.")
+ return false
+ }
+ classes.findClass(currentClass.superName)?.let {
+ return checkSuperClass(targetClass, it, classes)
+ }
+ // Super class not found.
+ // log.w("Class ${currentClass.superName} not found.")
+ return true
+}
+
+/**
+ * Check if a class internal name is a known legacy test base class.
+ */
+fun String.isLegacyTestBaseClass(): Boolean {
+ return this.startsWithAny(
+ "junit/framework/TestCase",
+
+ // In case the test doesn't statically include JUnit, we need
+ "android/test/AndroidTestCase",
+ "android/test/InstrumentationTestCase",
+ "android/test/InstrumentationTestSuite",
+ )
+}
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt
index 25cad02..eaef2cf 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt
@@ -30,6 +30,7 @@
import com.android.platform.test.ravenwood.ravenizer.classRuleAnotType
import com.android.platform.test.ravenwood.ravenizer.isTestLookingClass
import com.android.platform.test.ravenwood.ravenizer.innerRunnerAnotType
+import com.android.platform.test.ravenwood.ravenizer.noRavenizerAnotType
import com.android.platform.test.ravenwood.ravenizer.ravenwoodTestRunnerType
import com.android.platform.test.ravenwood.ravenizer.ruleAnotType
import com.android.platform.test.ravenwood.ravenizer.runWithAnotType
@@ -183,7 +184,7 @@
av.visit("value", ravenwoodTestRunnerType.type)
av.visitEnd()
}
- log.d("Processed ${classInternalName.toHumanReadableClassName()}")
+ log.i("Update the @RunWith: ${classInternalName.toHumanReadableClassName()}")
}
/*
@@ -435,7 +436,19 @@
companion object {
fun shouldProcess(classes: ClassNodes, className: String): Boolean {
- return isTestLookingClass(classes, className)
+ if (!isTestLookingClass(classes, className)) {
+ return false
+ }
+ // Don't process a class if it has a @NoRavenizer annotation.
+ classes.findClass(className)?.let { cn ->
+ if (cn.findAnyAnnotation(noRavenizerAnotType.descAsSet) != null) {
+ log.w("Class ${className.toHumanReadableClassName()} has" +
+ " @${noRavenizerAnotType.humanReadableName}. Skipping."
+ )
+ return false
+ }
+ }
+ return true
}
fun maybeApply(
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 7cbb97e..f1a8b5a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -163,6 +163,7 @@
import android.view.accessibility.IAccessibilityManager;
import android.view.accessibility.IAccessibilityManagerClient;
import android.view.accessibility.IMagnificationConnection;
+import android.view.accessibility.IUserInitializationCompleteCallback;
import android.view.inputmethod.EditorInfo;
import com.android.internal.R;
@@ -366,6 +367,11 @@
private final List<SendWindowStateChangedEventRunnable> mSendWindowStateChangedEventRunnables =
new ArrayList<>();
+ @VisibleForTesting
+ final HashSet<IUserInitializationCompleteCallback>
+ mUserInitializationCompleteCallbacks =
+ new HashSet<IUserInitializationCompleteCallback>();
+
@GuardedBy("mLock")
private @UserIdInt int mCurrentUserId = UserHandle.USER_SYSTEM;
@@ -2034,6 +2040,17 @@
obtainMessage(AccessibilityManagerService::announceNewUserIfNeeded, this),
WAIT_FOR_USER_STATE_FULLY_INITIALIZED_MILLIS);
}
+
+ for (IUserInitializationCompleteCallback callback
+ : mUserInitializationCompleteCallbacks) {
+ try {
+ callback.onUserInitializationComplete(mCurrentUserId);
+ } catch (RemoteException re) {
+ Log.e("AccessibilityManagerService",
+ "Error while dispatching userInitializationComplete callback: ",
+ re);
+ }
+ }
}
}
@@ -6251,6 +6268,24 @@
}
@Override
+ @RequiresNoPermission
+ public void registerUserInitializationCompleteCallback(
+ IUserInitializationCompleteCallback callback) {
+ synchronized (mLock) {
+ mUserInitializationCompleteCallbacks.add(callback);
+ }
+ }
+
+ @Override
+ @RequiresNoPermission
+ public void unregisterUserInitializationCompleteCallback(
+ IUserInitializationCompleteCallback callback) {
+ synchronized (mLock) {
+ mUserInitializationCompleteCallbacks.remove(callback);
+ }
+ }
+
+ @Override
@EnforcePermission(INJECT_EVENTS)
public void injectInputEventToInputFilter(InputEvent event) {
injectInputEventToInputFilter_enforcePermission();
diff --git a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManager.java b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManager.java
index 83f57b2..950246f 100644
--- a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManager.java
@@ -87,7 +87,7 @@
public Set<AndroidAccessibilityCheckerResult> maybeRunA11yChecker(
List<AccessibilityNodeInfo> nodes, @Nullable String sourceEventClassName,
ComponentName a11yServiceComponentName, @UserIdInt int userId) {
- if (!shouldRunA11yChecker()) {
+ if (!shouldRunA11yChecker() || nodes.isEmpty()) {
return Set.of();
}
@@ -95,24 +95,33 @@
String defaultBrowserName = mPackageManager.getDefaultBrowserPackageNameAsUser(userId);
try {
+ AndroidAccessibilityCheckerResult.Builder commonResultBuilder =
+ AccessibilityCheckerUtils.getCommonResultBuilder(nodes.getFirst(),
+ sourceEventClassName, mPackageManager, a11yServiceComponentName);
+ if (commonResultBuilder == null) {
+ return Set.of();
+ }
for (AccessibilityNodeInfo nodeInfo : nodes) {
- // Skip browser results because they are mostly related to web content and not the
- // browser app itself.
+ // Skip browser results because they are mostly related to web content and
+ // not the browser app itself.
if (nodeInfo.getPackageName() == null
|| nodeInfo.getPackageName().toString().equals(defaultBrowserName)) {
continue;
}
- List<AccessibilityHierarchyCheckResult> checkResults = runChecksOnNode(nodeInfo);
+ List<AccessibilityHierarchyCheckResult> checkResults = runChecksOnNode(
+ nodeInfo);
Set<AndroidAccessibilityCheckerResult> filteredResults =
AccessibilityCheckerUtils.processResults(nodeInfo, checkResults,
- sourceEventClassName, mPackageManager, a11yServiceComponentName);
+ commonResultBuilder);
allResults.addAll(filteredResults);
}
mCachedResults.addAll(allResults);
+ return allResults;
+
} catch (RuntimeException e) {
Slog.e(LOG_TAG, "An unknown error occurred while running a11y checker.", e);
+ return Set.of();
}
- return allResults;
}
private List<AccessibilityHierarchyCheckResult> runChecksOnNode(
diff --git a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java
index eb24b02..a739304 100644
--- a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java
+++ b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java
@@ -91,45 +91,55 @@
AccessibilityCheckClass.TRAVERSAL_ORDER_CHECK));
// LINT.ThenChange(/services/accessibility/java/com/android/server/accessibility/a11ychecker/proto/a11ychecker.proto)
- static Set<AndroidAccessibilityCheckerResult> processResults(
+
+ /**
+ * Returns AccessibilityCheckResultReported.Builder with the common fields for all nodes
+ * belonging in the same cache pre-filled.
+ */
+ static @Nullable AndroidAccessibilityCheckerResult.Builder getCommonResultBuilder(
AccessibilityNodeInfo nodeInfo,
- List<AccessibilityHierarchyCheckResult> checkResults,
@Nullable String activityClassName,
PackageManager packageManager,
ComponentName a11yServiceComponentName) {
- String appPackageName = nodeInfo.getPackageName().toString();
- String nodePath = AccessibilityNodePathBuilder.createNodePath(nodeInfo);
- if (nodePath == null) {
- return Set.of();
+ if (nodeInfo.getPackageName() == null) {
+ return null;
}
- AndroidAccessibilityCheckerResult.Builder commonBuilder;
+ String appPackageName = nodeInfo.getPackageName().toString();
try {
- commonBuilder = AndroidAccessibilityCheckerResult.newBuilder()
+ return AndroidAccessibilityCheckerResult.newBuilder()
.setPackageName(appPackageName)
.setAppVersionCode(getAppVersionCode(packageManager, appPackageName))
- .setUiElementPath(nodePath)
.setActivityName(
getActivityName(packageManager, appPackageName, activityClassName))
.setWindowTitle(getWindowTitle(nodeInfo))
.setSourceComponentName(a11yServiceComponentName)
- .setSourceVersionCode(
- getAppVersionCode(packageManager,
- a11yServiceComponentName.getPackageName()));
+ .setSourceVersionCode(getAppVersionCode(packageManager,
+ a11yServiceComponentName.getPackageName()));
} catch (PackageManager.NameNotFoundException e) {
Slog.e(LOG_TAG, "Unknown package name", e);
+ return null;
+ }
+ }
+
+ static Set<AndroidAccessibilityCheckerResult> processResults(
+ AccessibilityNodeInfo nodeInfo,
+ List<AccessibilityHierarchyCheckResult> checkResults,
+ AndroidAccessibilityCheckerResult.Builder resultBuilder) {
+ String nodePath = AccessibilityNodePathBuilder.createNodePath(nodeInfo);
+ if (resultBuilder == null || nodePath == null) {
return Set.of();
}
-
return checkResults.stream()
.filter(checkResult -> checkResult.getType()
== AccessibilityCheckResult.AccessibilityCheckResultType.ERROR
|| checkResult.getType()
== AccessibilityCheckResult.AccessibilityCheckResultType.WARNING)
- .map(checkResult -> new AndroidAccessibilityCheckerResult.Builder(
- commonBuilder).setResultCheckClass(
- getCheckClass(checkResult)).setResultType(
- getCheckResultType(checkResult)).setResultId(
- checkResult.getResultId()).build())
+ .map(checkResult -> new AndroidAccessibilityCheckerResult.Builder(resultBuilder)
+ .setUiElementPath(nodePath)
+ .setResultCheckClass(getCheckClass(checkResult))
+ .setResultType(getCheckResultType(checkResult))
+ .setResultId(checkResult.getResultId())
+ .build())
.collect(Collectors.toUnmodifiableSet());
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MouseEventHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/MouseEventHandler.java
index 845249e..ab94e98 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MouseEventHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MouseEventHandler.java
@@ -39,8 +39,14 @@
* @param displayId The display that is being magnified
*/
public void onEvent(MotionEvent event, int displayId) {
- if (event.getAction() == ACTION_HOVER_MOVE
- || (event.getAction() == ACTION_MOVE && event.getSource() == SOURCE_MOUSE)) {
+ // Ignore gesture events synthesized from the touchpad.
+ // TODO(b/354696546): Use synthesized pinch gestures to control scale.
+ boolean isSynthesizedFromTouchpad =
+ event.getClassification() != MotionEvent.CLASSIFICATION_NONE;
+
+ // Consume only move events from the mouse or hovers from any tool.
+ if (!isSynthesizedFromTouchpad && (event.getAction() == ACTION_HOVER_MOVE
+ || (event.getAction() == ACTION_MOVE && event.getSource() == SOURCE_MOUSE))) {
final float eventX = event.getX();
final float eventY = event.getY();
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java
index 954651d..a2d467c 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java
@@ -16,8 +16,7 @@
package com.android.server.appfunctions;
-import static android.app.appfunctions.flags.Flags.enableAppFunctionManager;
-
+import android.app.appfunctions.AppFunctionManagerConfiguration;
import android.content.Context;
import com.android.server.SystemService;
@@ -35,7 +34,7 @@
@Override
public void onStart() {
- if (enableAppFunctionManager()) {
+ if (AppFunctionManagerConfiguration.isSupported(getContext())) {
publishBinderService(Context.APP_FUNCTION_SERVICE, mServiceImpl);
}
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index 53885fc..32b8d6b 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -25,6 +25,7 @@
import android.app.appfunctions.SafeOneTimeExecuteAppFunctionCallback;
import android.content.Context;
import android.content.Intent;
+import android.os.Binder;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Slog;
@@ -94,36 +95,39 @@
targetUser = mCallerValidator.verifyTargetUserHandle(
requestInternal.getUserHandle(), validatedCallingPackage);
} catch (SecurityException exception) {
- safeExecuteAppFunctionCallback.onResult(new ExecuteAppFunctionResponse
- .Builder(ExecuteAppFunctionResponse.RESULT_DENIED,
- getExceptionMessage(exception)).build());
+ safeExecuteAppFunctionCallback.onResult(ExecuteAppFunctionResponse
+ .newFailure(ExecuteAppFunctionResponse.RESULT_DENIED,
+ exception.getMessage(),
+ /*extras=*/ null));
return;
}
// TODO(b/354956319): Add and honor the new enterprise policies.
if (mCallerValidator.isUserOrganizationManaged(targetUser)) {
- safeExecuteAppFunctionCallback.onResult(new ExecuteAppFunctionResponse.Builder(
+ safeExecuteAppFunctionCallback.onResult(ExecuteAppFunctionResponse.newFailure(
ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
- "Cannot run on a device with a device owner or from the managed profile."
- ).build());
+ "Cannot run on a device with a device owner or from the managed profile.",
+ /*extras=*/ null
+ ));
return;
}
String targetPackageName = requestInternal.getClientRequest().getTargetPackageName();
if (TextUtils.isEmpty(targetPackageName)) {
- safeExecuteAppFunctionCallback.onResult(new ExecuteAppFunctionResponse.Builder(
+ safeExecuteAppFunctionCallback.onResult(ExecuteAppFunctionResponse.newFailure(
ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT,
- "Target package name cannot be empty."
- ).build());
+ "Target package name cannot be empty.",
+ /*extras=*/ null
+ ));
return;
}
if (!mCallerValidator.verifyCallerCanExecuteAppFunction(
validatedCallingPackage, targetPackageName)) {
- safeExecuteAppFunctionCallback.onResult(new ExecuteAppFunctionResponse
- .Builder(ExecuteAppFunctionResponse.RESULT_DENIED,
- "Caller does not have permission to execute the appfunction")
- .build());
+ safeExecuteAppFunctionCallback.onResult(ExecuteAppFunctionResponse
+ .newFailure(ExecuteAppFunctionResponse.RESULT_DENIED,
+ "Caller does not have permission to execute the appfunction",
+ /*extras=*/ null));
return;
}
@@ -131,16 +135,23 @@
targetPackageName,
targetUser);
if (serviceIntent == null) {
- safeExecuteAppFunctionCallback.onResult(new ExecuteAppFunctionResponse.Builder(
+ safeExecuteAppFunctionCallback.onResult(ExecuteAppFunctionResponse.newFailure(
ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
- "Cannot find the target service."
- ).build());
+ "Cannot find the target service.",
+ /*extras=*/ null
+ ));
return;
}
- bindAppFunctionServiceUnchecked(requestInternal, serviceIntent, targetUser,
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ bindAppFunctionServiceUnchecked(requestInternal, serviceIntent, targetUser,
safeExecuteAppFunctionCallback,
/*bindFlags=*/ Context.BIND_AUTO_CREATE,
/*timeoutInMillis=*/ mServiceConfig.getExecuteAppFunctionTimeoutMillis());
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
private void bindAppFunctionServiceUnchecked(
@@ -171,9 +182,10 @@
}
);
} catch (Exception e) {
- safeExecuteAppFunctionCallback.onResult(new ExecuteAppFunctionResponse
- .Builder(ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR,
- getExceptionMessage(e)).build());
+ safeExecuteAppFunctionCallback.onResult(ExecuteAppFunctionResponse
+ .newFailure(ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR,
+ e.getMessage(),
+ /*extras=*/ null));
serviceUsageCompleteListener.onCompleted();
}
}
@@ -181,33 +193,32 @@
@Override
public void onFailedToConnect() {
Slog.e(TAG, "Failed to connect to service");
- safeExecuteAppFunctionCallback.onResult(new ExecuteAppFunctionResponse
- .Builder(ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR,
- "Failed to connect to AppFunctionService").build());
+ safeExecuteAppFunctionCallback.onResult(ExecuteAppFunctionResponse
+ .newFailure(ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR,
+ "Failed to connect to AppFunctionService",
+ /*extras=*/ null));
}
@Override
public void onTimedOut() {
Slog.e(TAG, "Timed out");
safeExecuteAppFunctionCallback.onResult(
- new ExecuteAppFunctionResponse.Builder(
+ ExecuteAppFunctionResponse.newFailure(
ExecuteAppFunctionResponse.RESULT_TIMED_OUT,
- "Binding to AppFunctionService timed out."
- ).build());
+ "Binding to AppFunctionService timed out.",
+ /*extras=*/ null
+ ));
}
}
);
if (!bindServiceResult) {
Slog.e(TAG, "Failed to bind to the AppFunctionService");
- safeExecuteAppFunctionCallback.onResult(new ExecuteAppFunctionResponse.Builder(
+ safeExecuteAppFunctionCallback.onResult(ExecuteAppFunctionResponse.newFailure(
ExecuteAppFunctionResponse.RESULT_TIMED_OUT,
- "Failed to bind the AppFunctionService."
- ).build());
+ "Failed to bind the AppFunctionService.",
+ /*extras=*/ null
+ ));
}
}
-
- private String getExceptionMessage(Exception exception) {
- return exception.getMessage() == null ? "" : exception.getMessage();
- }
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/SyncAppSearchCallHelper.java b/services/appfunctions/java/com/android/server/appfunctions/SyncAppSearchCallHelper.java
new file mode 100644
index 0000000..c01fe31
--- /dev/null
+++ b/services/appfunctions/java/com/android/server/appfunctions/SyncAppSearchCallHelper.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appfunctions;
+
+import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.WorkerThread;
+import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchManager.SearchContext;
+import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.AppSearchSession;
+import android.app.appsearch.GetSchemaResponse;
+import android.app.appsearch.SetSchemaRequest;
+import android.app.appsearch.SetSchemaResponse;
+import android.util.Slog;
+
+import com.android.internal.infra.AndroidFuture;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Helper class for interacting with a system server local appsearch session synchronously.
+ */
+@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
+public class SyncAppSearchCallHelper implements Closeable {
+ private static final String TAG = SyncAppSearchCallHelper.class.getSimpleName();
+ private final Executor mExecutor;
+ private final AppSearchManager mAppSearchManager;
+ private final AndroidFuture<AppSearchResult<AppSearchSession>> mSettableSessionFuture;
+
+ public SyncAppSearchCallHelper(@NonNull AppSearchManager appSearchManager,
+ @NonNull Executor executor,
+ @NonNull SearchContext appSearchContext) {
+ Objects.requireNonNull(appSearchManager);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(appSearchContext);
+
+ mExecutor = executor;
+ mAppSearchManager = appSearchManager;
+ mSettableSessionFuture = new AndroidFuture<>();
+ mAppSearchManager.createSearchSession(
+ appSearchContext, mExecutor, mSettableSessionFuture::complete);
+ }
+
+ /**
+ * Converts a failed app search result codes into an exception.
+ */
+ @NonNull
+ private static Exception failedResultToException(@NonNull AppSearchResult appSearchResult) {
+ return switch (appSearchResult.getResultCode()) {
+ case AppSearchResult.RESULT_INVALID_ARGUMENT -> new IllegalArgumentException(
+ appSearchResult.getErrorMessage());
+ case AppSearchResult.RESULT_IO_ERROR -> new IOException(
+ appSearchResult.getErrorMessage());
+ case AppSearchResult.RESULT_SECURITY_ERROR -> new SecurityException(
+ appSearchResult.getErrorMessage());
+ default -> new IllegalStateException(appSearchResult.getErrorMessage());
+ };
+ }
+
+ private AppSearchSession getSession() throws Exception {
+ AppSearchResult<AppSearchSession> sessionResult = mSettableSessionFuture.get();
+ if (!sessionResult.isSuccess()) {
+ throw failedResultToException(sessionResult);
+ }
+ return sessionResult.getResultValue();
+ }
+
+ /**
+ * Gets the schema for a given app search session.
+ */
+ @WorkerThread
+ public GetSchemaResponse getSchema() throws Exception {
+ AndroidFuture<AppSearchResult<GetSchemaResponse>> settableSchemaResponse =
+ new AndroidFuture<>();
+ getSession().getSchema(mExecutor, settableSchemaResponse::complete);
+ AppSearchResult<GetSchemaResponse> schemaResponse = settableSchemaResponse.get();
+ if (schemaResponse.isSuccess()) {
+ return schemaResponse.getResultValue();
+ } else {
+ throw failedResultToException(schemaResponse);
+ }
+ }
+
+ /**
+ * Sets the schema for a given app search session.
+ */
+ @WorkerThread
+ public SetSchemaResponse setSchema(
+ @NonNull SetSchemaRequest setSchemaRequest) throws Exception {
+ AndroidFuture<AppSearchResult<SetSchemaResponse>> settableSchemaResponse =
+ new AndroidFuture<>();
+ getSession().setSchema(
+ setSchemaRequest, mExecutor, mExecutor, settableSchemaResponse::complete);
+ AppSearchResult<SetSchemaResponse> schemaResponse = settableSchemaResponse.get();
+ if (schemaResponse.isSuccess()) {
+ return schemaResponse.getResultValue();
+ } else {
+ throw failedResultToException(schemaResponse);
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ try {
+ getSession().close();
+ } catch (Exception ex) {
+ Slog.e(TAG, "Failed to close app search session", ex);
+ }
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/devicepresence/ObservableUuidStore.java b/services/companion/java/com/android/server/companion/devicepresence/ObservableUuidStore.java
index 4678a16..5fd282d 100644
--- a/services/companion/java/com/android/server/companion/devicepresence/ObservableUuidStore.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/ObservableUuidStore.java
@@ -190,7 +190,7 @@
}
mCachedPerUser.set(userId, cachedObservableUuids);
}
- return cachedObservableUuids;
+ return cachedObservableUuids == null ? new ArrayList<>() : cachedObservableUuids;
}
/**
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 47203fb..fbe593f 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -20,6 +20,8 @@
import static android.content.Intent.ACTION_SHUTDOWN;
import static android.service.watchdog.ExplicitHealthCheckService.PackageConfig;
+import static com.android.server.crashrecovery.CrashRecoveryUtils.dumpCrashRecoveryEvents;
+
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.IntDef;
@@ -44,6 +46,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
+import android.util.IndentingPrintWriter;
import android.util.LongArrayQueue;
import android.util.Slog;
import android.util.Xml;
@@ -51,7 +54,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
-import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
@@ -72,6 +74,7 @@
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
+import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -1265,18 +1268,21 @@
/** Dump status of every observer in mAllObservers. */
- public void dump(IndentingPrintWriter pw) {
- pw.println("Package Watchdog status");
- pw.increaseIndent();
+ public void dump(PrintWriter pw) {
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ ipw.println("Package Watchdog status");
+ ipw.increaseIndent();
synchronized (mLock) {
for (String observerName : mAllObservers.keySet()) {
- pw.println("Observer name: " + observerName);
- pw.increaseIndent();
+ ipw.println("Observer name: " + observerName);
+ ipw.increaseIndent();
ObserverInternal observerInternal = mAllObservers.get(observerName);
- observerInternal.dump(pw);
- pw.decreaseIndent();
+ observerInternal.dump(ipw);
+ ipw.decreaseIndent();
}
}
+ ipw.decreaseIndent();
+ dumpCrashRecoveryEvents(ipw);
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index bba97fa..cadceb5 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -18,7 +18,7 @@
import static android.provider.DeviceConfig.Properties;
-import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
+import static com.android.server.crashrecovery.CrashRecoveryUtils.logCrashRecoveryEvent;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -291,13 +291,13 @@
Properties properties = new Properties.Builder(namespaceToReset).build();
try {
if (!DeviceConfig.setProperties(properties)) {
- logCriticalInfo(Log.ERROR, "Failed to clear properties under "
+ logCrashRecoveryEvent(Log.ERROR, "Failed to clear properties under "
+ namespaceToReset
+ ". Running `device_config get_sync_disabled_for_tests` will confirm"
+ " if config-bulk-update is enabled.");
}
} catch (DeviceConfig.BadConfigException exception) {
- logCriticalInfo(Log.WARN, "namespace " + namespaceToReset
+ logCrashRecoveryEvent(Log.WARN, "namespace " + namespaceToReset
+ " is already banned, skip reset.");
}
}
@@ -528,7 +528,7 @@
if (!TextUtils.isEmpty(failedPackage)) {
successMsg += " for package " + failedPackage;
}
- logCriticalInfo(Log.DEBUG, successMsg);
+ logCrashRecoveryEvent(Log.DEBUG, successMsg);
} catch (Throwable t) {
logRescueException(level, failedPackage, t);
}
@@ -687,7 +687,7 @@
if (!TextUtils.isEmpty(failedPackageName)) {
failureMsg += " for package " + failedPackageName;
}
- logCriticalInfo(Log.ERROR, failureMsg + ": " + msg);
+ logCrashRecoveryEvent(Log.ERROR, failureMsg + ": " + msg);
}
private static int mapRescueLevelToUserImpact(int rescueLevel) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index b738482..4e24cf3 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -790,8 +790,8 @@
private final BroadcastReceiver mReceiver = new AudioServiceBroadcastReceiver();
private final Executor mAudioServerLifecycleExecutor;
- private final ConcurrentLinkedQueue<Future> mScheduledPermissionTasks =
- new ConcurrentLinkedQueue();
+ private long mSysPropListenerNativeHandle;
+ private final List<Future> mScheduledPermissionTasks = new ArrayList();
private IMediaProjectionManager mProjectionService; // to validate projection token
@@ -10600,46 +10600,50 @@
// instanceof to simplify the construction requirements of AudioService for testing: no
// delayed execution during unit tests.
if (mAudioServerLifecycleExecutor instanceof ScheduledExecutorService exec) {
- // We schedule and add from a this callback thread only (serially), so the task order on
- // the serial executor matches the order on the task list. This list should almost
- // always have only two elements, except in cases of serious system contention.
- Runnable task = () -> mScheduledPermissionTasks.add(exec.schedule(() -> {
- try {
- // Clean up completed tasks before us to bound the queue length. Cancel any
- // pending permission refresh tasks, after our own, since we are about to
- // fulfill all of them. We must be the first non-completed task in the
- // queue, since the execution order matches the queue order. Note, this
- // task is the only writer on elements in the queue, and the task is
- // serialized, so
- // => no in-flight cancellation
- // => exists at least one non-completed task (ourselves)
- // => the queue is non-empty (only completed tasks removed)
- final var iter = mScheduledPermissionTasks.iterator();
- while (iter.next().isDone()) {
- iter.remove();
- }
- // iter is on the first element which is not completed (us)
- while (iter.hasNext()) {
- if (!iter.next().cancel(false)) {
- throw new AssertionError(
- "Cancel should be infallible since we" +
- "cancel from the executor");
+ // The order on the task list is an embedding on the scheduling order of the executor,
+ // since we synchronously add the scheduled task to our local queue. This list should
+ // almost always have only two elements, except in cases of serious system contention.
+ Runnable task = () -> {
+ synchronized (mScheduledPermissionTasks) {
+ mScheduledPermissionTasks.add(exec.schedule(() -> {
+ try {
+ // Our goal is to remove all tasks which don't correspond to ourselves
+ // on this queue. Either they are already done (ahead of us), or we
+ // should cancel them (behind us), since their work is redundant after
+ // we fire.
+ // We must be the first non-completed task in the queue, since the
+ // execution order matches the queue order. Note, this task is the only
+ // writer on elements in the queue, and the task is serialized, so
+ // => no in-flight cancellation
+ // => exists at least one non-completed task (ourselves)
+ // => the queue is non-empty (only completed tasks removed)
+ synchronized (mScheduledPermissionTasks) {
+ final var iter = mScheduledPermissionTasks.iterator();
+ while (iter.next().isDone()) {
+ iter.remove();
+ }
+ // iter is on the first element which is not completed (us)
+ while (iter.hasNext()) {
+ if (!iter.next().cancel(false)) {
+ throw new AssertionError(
+ "Cancel should be infallible since we" +
+ "cancel from the executor");
+ }
+ iter.remove();
+ }
}
- iter.remove();
+ mPermissionProvider.onPermissionStateChanged();
+ } catch (Exception e) {
+ // Handle executor routing exceptions to nowhere
+ Thread.getDefaultUncaughtExceptionHandler()
+ .uncaughtException(Thread.currentThread(), e);
}
- mPermissionProvider.onPermissionStateChanged();
- } catch (Exception e) {
- // Handle executor routing exceptions to nowhere
- Thread.getDefaultUncaughtExceptionHandler()
- .uncaughtException(Thread.currentThread(), e);
- }
- },
- UPDATE_DELAY_MS,
- TimeUnit.MILLISECONDS));
- mAudioSystem.listenForSystemPropertyChange(
+ }, UPDATE_DELAY_MS, TimeUnit.MILLISECONDS));
+ }
+ };
+ mSysPropListenerNativeHandle = mAudioSystem.listenForSystemPropertyChange(
PermissionManager.CACHE_KEY_PACKAGE_INFO,
task);
- task.run();
} else {
mAudioSystem.listenForSystemPropertyChange(
PermissionManager.CACHE_KEY_PACKAGE_INFO,
@@ -14710,7 +14714,12 @@
@Override
/** @see AudioManager#permissionUpdateBarrier() */
public void permissionUpdateBarrier() {
- for (var x : List.copyOf(mScheduledPermissionTasks)) {
+ mAudioSystem.triggerSystemPropertyUpdate(mSysPropListenerNativeHandle);
+ List<Future> snapshot;
+ synchronized (mScheduledPermissionTasks) {
+ snapshot = List.copyOf(mScheduledPermissionTasks);
+ }
+ for (var x : snapshot) {
try {
x.get();
} catch (CancellationException e) {
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index d083c68..5cabdde 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -748,8 +748,12 @@
return AudioSystem.setMasterMute(mute);
}
- public void listenForSystemPropertyChange(String systemPropertyName, Runnable callback) {
- AudioSystem.listenForSystemPropertyChange(systemPropertyName, callback);
+ public long listenForSystemPropertyChange(String systemPropertyName, Runnable callback) {
+ return AudioSystem.listenForSystemPropertyChange(systemPropertyName, callback);
+ }
+
+ public void triggerSystemPropertyUpdate(long handle) {
+ AudioSystem.triggerSystemPropertyUpdate(handle);
}
/**
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
index f5af5ea..bc58501 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
@@ -78,7 +78,6 @@
public void authenticate(OperationContextExt operationContext,
int statsModality, int statsAction, int statsClient, boolean isDebug, long latency,
int authState, boolean requireConfirmation, int targetUserId, float ambientLightLux) {
- Slog.d(TAG, "authenticate logging " + operationContext.getIsMandatoryBiometrics());
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_AUTHENTICATED,
statsModality,
targetUserId,
@@ -131,7 +130,6 @@
public void error(OperationContextExt operationContext,
int statsModality, int statsAction, int statsClient, boolean isDebug, long latency,
int error, int vendorCode, int targetUserId) {
- Slog.d(TAG, "error logging " + operationContext.getIsMandatoryBiometrics());
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ERROR_OCCURRED,
statsModality,
targetUserId,
diff --git a/services/core/java/com/android/server/crashrecovery/CrashRecoveryUtils.java b/services/core/java/com/android/server/crashrecovery/CrashRecoveryUtils.java
new file mode 100644
index 0000000..3eb3380
--- /dev/null
+++ b/services/core/java/com/android/server/crashrecovery/CrashRecoveryUtils.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.crashrecovery;
+
+import android.os.Environment;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+
+/**
+ * Class containing helper methods for the CrashRecoveryModule.
+ *
+ * @hide
+ */
+public class CrashRecoveryUtils {
+ private static final String TAG = "CrashRecoveryUtils";
+ private static final long MAX_CRITICAL_INFO_DUMP_SIZE = 1000 * 1000; // ~1MB
+ private static final Object sFileLock = new Object();
+
+ /** Persist recovery related events in crashrecovery events file.**/
+ public static void logCrashRecoveryEvent(int priority, String msg) {
+ Slog.println(priority, TAG, msg);
+ try {
+ File fname = getCrashRecoveryEventsFile();
+ synchronized (sFileLock) {
+ FileOutputStream out = new FileOutputStream(fname, true);
+ PrintWriter pw = new PrintWriter(out);
+ String dateString = LocalDateTime.now(ZoneId.systemDefault()).toString();
+ pw.println(dateString + ": " + msg);
+ pw.close();
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to log CrashRecoveryEvents " + e.getMessage());
+ }
+ }
+
+ /** Dump recovery related events from crashrecovery events file.**/
+ public static void dumpCrashRecoveryEvents(IndentingPrintWriter pw) {
+ pw.println("CrashRecovery Events: ");
+ pw.increaseIndent();
+ final File file = getCrashRecoveryEventsFile();
+ final long skipSize = file.length() - MAX_CRITICAL_INFO_DUMP_SIZE;
+ synchronized (sFileLock) {
+ try (BufferedReader in = new BufferedReader(new FileReader(file))) {
+ if (skipSize > 0) {
+ in.skip(skipSize);
+ }
+ String line;
+ while ((line = in.readLine()) != null) {
+ pw.println(line);
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to dump CrashRecoveryEvents " + e.getMessage());
+ }
+ }
+ pw.decreaseIndent();
+ }
+
+ private static File getCrashRecoveryEventsFile() {
+ File systemDir = new File(Environment.getDataDirectory(), "system");
+ return new File(systemDir, "crashrecovery-events.txt");
+ }
+}
diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
index dc611fc..334dda0 100644
--- a/services/core/java/com/android/server/display/DisplayBrightnessState.java
+++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java
@@ -16,6 +16,7 @@
package com.android.server.display;
+import android.hardware.display.BrightnessInfo;
import android.text.TextUtils;
import com.android.server.display.brightness.BrightnessEvent;
@@ -50,6 +51,8 @@
private final boolean mIsUserInitiatedChange;
+ private @BrightnessInfo.BrightnessMaxReason int mBrightnessMaxReason;
+
private DisplayBrightnessState(Builder builder) {
mBrightness = builder.getBrightness();
mHdrBrightness = builder.getHdrBrightness();
@@ -64,6 +67,7 @@
mBrightnessEvent = builder.getBrightnessEvent();
mBrightnessAdjustmentFlag = builder.getBrightnessAdjustmentFlag();
mIsUserInitiatedChange = builder.isUserInitiatedChange();
+ mBrightnessMaxReason = builder.getBrightnessMaxReason();
}
/**
@@ -159,6 +163,13 @@
return mIsUserInitiatedChange;
}
+ /**
+ * Gets reason for max brightness restriction
+ */
+ public @BrightnessInfo.BrightnessMaxReason int getBrightnessMaxReason() {
+ return mBrightnessMaxReason;
+ }
+
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder("DisplayBrightnessState:");
@@ -180,6 +191,8 @@
.append(Objects.toString(mBrightnessEvent, "null"));
stringBuilder.append("\n mBrightnessAdjustmentFlag:").append(mBrightnessAdjustmentFlag);
stringBuilder.append("\n mIsUserInitiatedChange:").append(mIsUserInitiatedChange);
+ stringBuilder.append("\n mBrightnessMaxReason:")
+ .append(BrightnessInfo.briMaxReasonToString(mBrightnessMaxReason));
return stringBuilder.toString();
}
@@ -212,7 +225,8 @@
== otherState.shouldUpdateScreenBrightnessSetting()
&& Objects.equals(mBrightnessEvent, otherState.getBrightnessEvent())
&& mBrightnessAdjustmentFlag == otherState.getBrightnessAdjustmentFlag()
- && mIsUserInitiatedChange == otherState.isUserInitiatedChange();
+ && mIsUserInitiatedChange == otherState.isUserInitiatedChange()
+ && mBrightnessMaxReason == otherState.getBrightnessMaxReason();
}
@Override
@@ -221,7 +235,7 @@
mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness, mMinBrightness,
mCustomAnimationRate,
mShouldUpdateScreenBrightnessSetting, mBrightnessEvent, mBrightnessAdjustmentFlag,
- mIsUserInitiatedChange);
+ mIsUserInitiatedChange, mBrightnessMaxReason);
}
/**
@@ -245,12 +259,11 @@
private float mMinBrightness;
private float mCustomAnimationRate = CUSTOM_ANIMATION_RATE_NOT_SET;
private boolean mShouldUpdateScreenBrightnessSetting;
-
private BrightnessEvent mBrightnessEvent;
-
- public int mBrightnessAdjustmentFlag = 0;
-
+ private int mBrightnessAdjustmentFlag = 0;
private boolean mIsUserInitiatedChange;
+ private @BrightnessInfo.BrightnessMaxReason int mBrightnessMaxReason =
+ BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
/**
* Create a builder starting with the values from the specified {@link
@@ -274,6 +287,7 @@
builder.setBrightnessEvent(state.getBrightnessEvent());
builder.setBrightnessAdjustmentFlag(state.getBrightnessAdjustmentFlag());
builder.setIsUserInitiatedChange(state.isUserInitiatedChange());
+ builder.setBrightnessMaxReason(state.getBrightnessMaxReason());
return builder;
}
@@ -506,5 +520,21 @@
mIsUserInitiatedChange = isUserInitiatedChange;
return this;
}
+
+ /**
+ * Gets reason for max brightness restriction
+ */
+ public @BrightnessInfo.BrightnessMaxReason int getBrightnessMaxReason() {
+ return mBrightnessMaxReason;
+ }
+
+ /**
+ * Sets reason for max brightness restriction
+ */
+ public Builder setBrightnessMaxReason(
+ @BrightnessInfo.BrightnessMaxReason int brightnessMaxReason) {
+ mBrightnessMaxReason = brightnessMaxReason;
+ return this;
+ }
}
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index a887f6d..047eb29 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1588,7 +1588,7 @@
// brightness sources (such as an app override) are not saved to the setting, but should be
// reflected in HBM calculations.
mBrightnessRangeController.onBrightnessChanged(brightnessState, unthrottledBrightnessState,
- mBrightnessClamperController.getBrightnessMaxReason());
+ clampedState.getBrightnessMaxReason());
// Animate the screen brightness when the screen is on or dozing.
// Skip the animation when the screen is off or suspended.
@@ -1804,7 +1804,7 @@
if (userSetBrightnessChanged
|| newEvent.getReason().getReason() != BrightnessReason.REASON_TEMPORARY) {
- logBrightnessEvent(newEvent, unthrottledBrightnessState);
+ logBrightnessEvent(newEvent, unthrottledBrightnessState, clampedState);
}
if (mBrightnessEventRingBuffer != null) {
mBrightnessEventRingBuffer.append(newEvent);
@@ -1997,6 +1997,9 @@
synchronized (mCachedBrightnessInfo) {
float stateMax = state != null ? state.getMaxBrightness() : PowerManager.BRIGHTNESS_MAX;
float stateMin = state != null ? state.getMinBrightness() : PowerManager.BRIGHTNESS_MAX;
+ @BrightnessInfo.BrightnessMaxReason int maxReason =
+ state != null ? state.getBrightnessMaxReason()
+ : BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
final float minBrightness = Math.max(stateMin, Math.min(
mBrightnessRangeController.getCurrentBrightnessMin(), stateMax));
final float maxBrightness = Math.min(
@@ -2023,7 +2026,7 @@
mBrightnessRangeController.getTransitionPoint());
changed |=
mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessMaxReason,
- mBrightnessClamperController.getBrightnessMaxReason());
+ maxReason);
return changed;
}
}
@@ -2926,7 +2929,8 @@
return FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__ENTIRE_REASON__REASON_UNKNOWN;
}
- private void logBrightnessEvent(BrightnessEvent event, float unmodifiedBrightness) {
+ private void logBrightnessEvent(BrightnessEvent event, float unmodifiedBrightness,
+ DisplayBrightnessState brightnessState) {
int modifier = event.getReason().getModifier();
int flags = event.getFlags();
// It's easier to check if the brightness is at maximum level using the brightness
@@ -2963,7 +2967,7 @@
event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR,
(modifier & BrightnessReason.MODIFIER_LOW_POWER) > 0,
- mBrightnessClamperController.getBrightnessMaxReason(),
+ brightnessState.getBrightnessMaxReason(),
// TODO: (flc) add brightnessMinReason here too.
(modifier & BrightnessReason.MODIFIER_DIMMED) > 0,
event.isRbcEnabled(),
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index 59fffe7..8ee7085 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -161,6 +161,7 @@
builder.setBrightness(cappedBrightness);
builder.setMaxBrightness(mBrightnessCap);
builder.setCustomAnimationRate(mCustomAnimationRate);
+ builder.setBrightnessMaxReason(getBrightnessMaxReason());
if (mClamperType != null) {
builder.getBrightnessReason().addModifier(BrightnessReason.MODIFIER_THROTTLED);
@@ -185,19 +186,8 @@
return builder.build();
}
- /**
- * See BrightnessThrottler.getBrightnessMaxReason:
- * used in:
- * 1) DPC2.CachedBrightnessInfo to determine changes
- * 2) DPC2.logBrightnessEvent
- * 3) HBMController - for logging
- * Method is called in mHandler thread (DisplayControllerHandler), in the same thread
- * recalculateBrightnessCap and DPC2.updatePowerStateInternal are called.
- * Should be moved to DisplayBrightnessState OR derived from DisplayBrightnessState
- * TODO: b/263362199
- */
@BrightnessInfo.BrightnessMaxReason
- public int getBrightnessMaxReason() {
+ private int getBrightnessMaxReason() {
if (mClamperType == null) {
return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
} else if (mClamperType == Type.THERMAL) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index bbe7b2b..3c7b9d3 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -4328,6 +4328,7 @@
HdmiCecLocalDevicePlayback playback = playback();
HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
if (playback != null) {
+ playback.dismissUiOnActiveSourceStatusRecovered();
playback.setActiveSource(playback.getDeviceInfo().getLogicalAddress(), physicalAddress,
caller);
playback.wakeUpIfActiveSource();
diff --git a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
index e3061a7..4135161 100644
--- a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
+++ b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
@@ -69,6 +69,7 @@
mUserManager = mSystemUserContext.getSystemService(UserManager.class);
NotificationChannel channel = new NotificationChannel(BUSN_CHANNEL_ID, BUSN_CHANNEL_NAME,
NotificationManager.IMPORTANCE_HIGH);
+ channel.setSound(null, null);
mNotificationManager.createNotificationChannel(channel);
setupFocusControlAudioPolicy();
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 2124ff6..ff9c3e5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -60,6 +60,7 @@
import android.app.ApplicationPackageManager;
import android.app.BroadcastOptions;
import android.app.IActivityManager;
+import android.app.admin.DevicePolicyManagerInternal;
import android.app.admin.IDevicePolicyManager;
import android.app.admin.SecurityLog;
import android.app.backup.IBackupManager;
@@ -3372,8 +3373,10 @@
// TODO(b/261957226): centralise this logic in DPM
boolean isPackageDeviceAdmin(String packageName, int userId) {
final IDevicePolicyManager dpm = getDevicePolicyManager();
+ final DevicePolicyManagerInternal dpmi =
+ mInjector.getLocalService(DevicePolicyManagerInternal.class);
try {
- if (dpm != null) {
+ if (dpm != null && dpmi != null) {
final ComponentName deviceOwnerComponentName = dpm.getDeviceOwnerComponent(
/* callingUserOnly =*/ false);
final String deviceOwnerPackageName = deviceOwnerComponentName == null ? null
@@ -3396,7 +3399,8 @@
if (dpm.packageHasActiveAdmins(packageName, users[i])) {
return true;
}
- if (isDeviceManagementRoleHolder(packageName, users[i])) {
+ if (isDeviceManagementRoleHolder(packageName, users[i])
+ && dpmi.isUserOrganizationManaged(users[i])) {
return true;
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index a683a8c..13901c1 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1194,11 +1194,11 @@
// Avoid marking pre-created users for removal.
return;
}
- if (ui.lastLoggedInTime == 0 && ui.isGuest() && Resources.getSystem().getBoolean(
- com.android.internal.R.bool.config_guestUserAutoCreated)) {
- // Avoid marking auto-created but not-yet-logged-in guest user for removal. Because a
- // new one will be created anyway, and this one doesn't have any personal data in it yet
- // due to not being logged in.
+ if (ui.lastLoggedInTime == 0) {
+ // Avoid marking a not-yet-logged-in ephemeral user for removal, since it doesn't have
+ // any personal data in it yet due to not being logged in.
+ // This will also avoid marking an auto-created not-yet-logged-in ephemeral guest user
+ // for removal, which would be recreated again later in the boot anyway.
return;
}
// Mark the user for removal.
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 95e5b84..2bc6d53 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -503,26 +503,21 @@
String restriction,
boolean isMainUser,
boolean isProfileOwnerOnOrgOwnedDevice) {
- if (android.app.admin.flags.Flags.esimManagementEnabled()) {
- if (IMMUTABLE_BY_OWNERS.contains(restriction)) {
- return false;
- }
- if (DEVICE_OWNER_ONLY_RESTRICTIONS.contains(restriction)) {
- return false;
- }
- if (!isMainUser && MAIN_USER_ONLY_RESTRICTIONS.contains(restriction)) {
- return false;
- }
- if (!isProfileOwnerOnOrgOwnedDevice
- && PROFILE_OWNER_ORGANIZATION_OWNED_PROFILE_RESTRICTIONS.contains(
- restriction)) {
- return false;
- }
- return true;
+ if (IMMUTABLE_BY_OWNERS.contains(restriction)) {
+ return false;
}
- return !IMMUTABLE_BY_OWNERS.contains(restriction)
- && !DEVICE_OWNER_ONLY_RESTRICTIONS.contains(restriction)
- && !(!isMainUser && MAIN_USER_ONLY_RESTRICTIONS.contains(restriction));
+ if (DEVICE_OWNER_ONLY_RESTRICTIONS.contains(restriction)) {
+ return false;
+ }
+ if (!isMainUser && MAIN_USER_ONLY_RESTRICTIONS.contains(restriction)) {
+ return false;
+ }
+ if (!isProfileOwnerOnOrgOwnedDevice
+ && PROFILE_OWNER_ORGANIZATION_OWNED_PROFILE_RESTRICTIONS.contains(
+ restriction)) {
+ return false;
+ }
+ return true;
}
/**
diff --git a/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java b/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java
index f518769..e9cb279 100644
--- a/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java
+++ b/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java
@@ -375,24 +375,34 @@
@Nullable String message, boolean shouldCollectMessage, boolean skiProxyOperation,
@NonNull HexFunction<Integer, AttributionSource, Boolean, String, Boolean,
Boolean, SyncNotedAppOp> superImpl) {
- if (attributionSource.getUid() == mDelegateAndOwnerUid && isDelegateOp(code)) {
- final int shellUid = UserHandle.getUid(
- UserHandle.getUserId(attributionSource.getUid()), Process.SHELL_UID);
- final long identity = Binder.clearCallingIdentity();
- try {
- return superImpl.apply(code,
- new AttributionSource(shellUid, Process.INVALID_PID, SHELL_PKG,
- attributionSource.getAttributionTag(),
- attributionSource.getToken(), /*renouncedPermissions*/ null,
- attributionSource.getDeviceId(), attributionSource.getNext()),
- shouldCollectAsyncNotedOp, message, shouldCollectMessage,
- skiProxyOperation);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
+ if (!isDelegateOp(code)) {
+ return superImpl.apply(code, attributionSource, shouldCollectAsyncNotedOp,
+ message, shouldCollectMessage, skiProxyOperation);
}
- return superImpl.apply(code, attributionSource, shouldCollectAsyncNotedOp,
- message, shouldCollectMessage, skiProxyOperation);
+
+ final int shellUid = UserHandle.getUid(
+ UserHandle.getUserId(attributionSource.getUid()), Process.SHELL_UID);
+ AttributionSource next = attributionSource.getNext();
+ if (next != null && next.getUid() == mDelegateAndOwnerUid) {
+ next = new AttributionSource(shellUid, Process.INVALID_PID, SHELL_PKG,
+ next.getAttributionTag(), next.getToken(), /*renouncedPermissions*/ null,
+ next.getDeviceId(), next.getNext());
+ attributionSource = new AttributionSource(attributionSource, next);
+ }
+ if (attributionSource.getUid() == mDelegateAndOwnerUid) {
+ attributionSource = new AttributionSource(shellUid, Process.INVALID_PID, SHELL_PKG,
+ attributionSource.getAttributionTag(),
+ attributionSource.getToken(), /*renouncedPermissions*/ null,
+ attributionSource.getDeviceId(), attributionSource.getNext());
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return superImpl.apply(code, attributionSource,
+ shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+ skiProxyOperation);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
index 7ed8972..027e69c 100644
--- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java
+++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
@@ -17,7 +17,9 @@
package com.android.server.policy;
import static com.android.server.flags.Flags.modifierShortcutManagerMultiuser;
+import static com.android.hardware.input.Flags.modifierShortcutManagerRefactor;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.role.RoleManager;
@@ -37,6 +39,7 @@
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.LongSparseArray;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.view.KeyCharacterMap;
@@ -81,8 +84,8 @@
private static final String ATTRIBUTE_SHIFT = "shift";
private static final String ATTRIBUTE_ROLE = "role";
- private final SparseArray<Intent> mIntentShortcuts = new SparseArray<>();
- private final SparseArray<Intent> mShiftShortcuts = new SparseArray<>();
+ private final SparseArray<Intent> mCategoryShortcuts = new SparseArray<>();
+ private final SparseArray<Intent> mShiftCategoryShortcuts = new SparseArray<>();
private final SparseArray<String> mRoleShortcuts = new SparseArray<String>();
private final SparseArray<String> mShiftRoleShortcuts = new SparseArray<String>();
private final Map<String, Intent> mRoleIntents = new HashMap<String, Intent>();
@@ -127,6 +130,7 @@
private boolean mSearchKeyShortcutPending = false;
private boolean mConsumeSearchKeyUp = true;
private UserHandle mCurrentUser;
+ private final Map<Pair<Character, Boolean>, Bookmark> mBookmarks = new HashMap<>();
ModifierShortcutManager(Context context, Handler handler, UserHandle currentUser) {
mContext = context;
@@ -134,7 +138,14 @@
RoleManager rm = mContext.getSystemService(RoleManager.class);
rm.addOnRoleHoldersChangedListenerAsUser(mContext.getMainExecutor(),
(String roleName, UserHandle user) -> {
- mRoleIntents.remove(roleName);
+ if (modifierShortcutManagerRefactor()) {
+ mBookmarks.values().stream().filter(b ->
+ b instanceof RoleBookmark
+ && ((RoleBookmark) b).getRole().equals(roleName))
+ .forEach(Bookmark::clearIntent);
+ } else {
+ mRoleIntents.remove(roleName);
+ }
}, UserHandle.ALL);
mCurrentUser = currentUser;
mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
@@ -146,8 +157,26 @@
// Role based shortcuts may resolve to different apps for different users
// so clear the cache.
- mRoleIntents.clear();
- mComponentIntents.clear();
+ clearRoleIntents();
+ clearComponentIntents();
+ }
+
+ void clearRoleIntents() {
+ if (modifierShortcutManagerRefactor()) {
+ mBookmarks.values().stream().filter(b ->
+ b instanceof RoleBookmark).forEach(Bookmark::clearIntent);
+ } else {
+ mRoleIntents.clear();
+ }
+ }
+
+ void clearComponentIntents() {
+ if (modifierShortcutManagerRefactor()) {
+ mBookmarks.values().stream().filter(b ->
+ b instanceof ComponentBookmark).forEach(Bookmark::clearIntent);
+ } else {
+ mComponentIntents.clear();
+ }
}
/**
@@ -176,77 +205,111 @@
Intent shortcutIntent = null;
- // If the Shift key is pressed, then search for the shift shortcuts.
- SparseArray<Intent> shortcutMap = isShiftOn ? mShiftShortcuts : mIntentShortcuts;
-
// First try the exact keycode (with modifiers).
int shortcutChar = kcm.get(keyCode, metaState);
if (shortcutChar == 0) {
return null;
}
- shortcutIntent = shortcutMap.get(shortcutChar);
- if (shortcutIntent == null) {
- // Next try the primary character on that key.
- shortcutChar = Character.toLowerCase(kcm.getDisplayLabel(keyCode));
- if (shortcutChar == 0) {
- return null;
+ if (modifierShortcutManagerRefactor()) {
+ Bookmark bookmark = mBookmarks.get(new Pair<>((char) shortcutChar, isShiftOn));
+ if (bookmark == null) {
+ // Next try the primary character on that key.
+ shortcutChar = Character.toLowerCase(kcm.getDisplayLabel(keyCode));
+ if (shortcutChar == 0) {
+ return null;
+ }
+ bookmark = mBookmarks.get(new Pair<>((char) shortcutChar, isShiftOn));
}
+
+ if (bookmark != null) {
+ Context context = modifierShortcutManagerMultiuser()
+ ? mContext.createContextAsUser(mCurrentUser, 0) : mContext;
+ shortcutIntent = bookmark.getIntent(context);
+ } else {
+ Log.d(TAG, "No bookmark found for "
+ + (isShiftOn ? "SHIFT+" : "") + (char) shortcutChar);
+ }
+ } else {
+ // If the Shift key is pressed, then search for the shift shortcuts.
+ SparseArray<Intent> shortcutMap = isShiftOn
+ ? mShiftCategoryShortcuts : mCategoryShortcuts;
shortcutIntent = shortcutMap.get(shortcutChar);
- }
- if (shortcutIntent == null) {
- // Next check for role based shortcut with primary character.
- String role = isShiftOn ? mShiftRoleShortcuts.get(shortcutChar)
- : mRoleShortcuts.get(shortcutChar);
- if (role != null) {
- shortcutIntent = getRoleLaunchIntent(role);
- }
- }
-
- if (modifierShortcutManagerMultiuser()) {
if (shortcutIntent == null) {
- // Next check component based shortcuts with primary character.
- ComponentName component = isShiftOn
- ? mShiftComponentShortcuts.get(shortcutChar)
- : mComponentShortcuts.get(shortcutChar);
- if (component != null) {
- shortcutIntent = resolveComponentNameIntent(component);
+ // Next try the primary character on that key.
+ shortcutChar = Character.toLowerCase(kcm.getDisplayLabel(keyCode));
+ if (shortcutChar == 0) {
+ return null;
+ }
+ shortcutIntent = shortcutMap.get(shortcutChar);
+ }
+
+ if (shortcutIntent == null) {
+ // Next check for role based shortcut with primary character.
+ String role = isShiftOn ? mShiftRoleShortcuts.get(shortcutChar)
+ : mRoleShortcuts.get(shortcutChar);
+ if (role != null) {
+ shortcutIntent = getRoleLaunchIntent(role);
+ }
+ }
+
+ if (modifierShortcutManagerMultiuser()) {
+ if (shortcutIntent == null) {
+ // Next check component based shortcuts with primary character.
+ ComponentName component = isShiftOn
+ ? mShiftComponentShortcuts.get(shortcutChar)
+ : mComponentShortcuts.get(shortcutChar);
+ if (component != null) {
+ shortcutIntent = resolveComponentNameIntent(component);
+ }
}
}
}
return shortcutIntent;
}
+ @Nullable
+ private static Intent getRoleLaunchIntent(Context context, String role) {
+ Intent intent = null;
+ RoleManager rm = context.getSystemService(RoleManager.class);
+ PackageManager pm = context.getPackageManager();
+ if (rm.isRoleAvailable(role)) {
+ String rolePackage = rm.getDefaultApplication(role);
+ if (rolePackage != null) {
+ intent = pm.getLaunchIntentForPackage(rolePackage);
+ if (intent != null) {
+ intent.putExtra(EXTRA_ROLE, role);
+
+ } else {
+ Log.w(TAG, "No launch intent for role " + role);
+ }
+ } else {
+ Log.w(TAG, "No default application for role "
+ + role + " user=" + context.getUser());
+ }
+ } else {
+ Log.w(TAG, "Role " + role + " is not available.");
+ }
+ return intent;
+ }
+
+ @Nullable
private Intent getRoleLaunchIntent(String role) {
Intent intent = mRoleIntents.get(role);
if (intent == null) {
Context context = modifierShortcutManagerMultiuser()
? mContext.createContextAsUser(mCurrentUser, 0) : mContext;
- RoleManager rm = context.getSystemService(RoleManager.class);
- PackageManager pm = context.getPackageManager();
- if (rm.isRoleAvailable(role)) {
- String rolePackage = rm.getDefaultApplication(role);
- if (rolePackage != null) {
- intent = pm.getLaunchIntentForPackage(rolePackage);
- if (intent != null) {
- intent.putExtra(EXTRA_ROLE, role);
- mRoleIntents.put(role, intent);
- } else {
- Log.w(TAG, "No launch intent for role " + role);
- }
- } else {
- Log.w(TAG, "No default application for role " + role);
- }
- } else {
- Log.w(TAG, "Role " + role + " is not available.");
+ intent = getRoleLaunchIntent(context, role);
+ if (intent != null) {
+ mRoleIntents.put(role, intent);
}
}
+
return intent;
}
private void loadShortcuts() {
-
try {
XmlResourceParser parser = mContext.getResources().getXml(R.xml.bookmarks);
XmlUtils.beginDocument(parser, TAG_BOOKMARKS);
@@ -276,57 +339,84 @@
continue;
}
- final int shortcutChar = shortcutName.charAt(0);
final boolean isShiftShortcut = (shiftName != null && shiftName.equals("true"));
- final Intent intent;
- if (packageName != null && className != null) {
- if (roleName != null || categoryName != null) {
- Log.w(TAG, "Cannot specify role or category when package and class"
- + " are present for bookmark packageName=" + packageName
- + " className=" + className + " shortcutChar=" + shortcutChar);
- continue;
+
+ if (modifierShortcutManagerRefactor()) {
+ final char shortcutChar = shortcutName.charAt(0);
+ Bookmark bookmark = null;
+ if (packageName != null && className != null) {
+ bookmark = new ComponentBookmark(
+ shortcutChar, isShiftShortcut, packageName, className);
+ } else if (categoryName != null) {
+ bookmark = new CategoryBookmark(
+ shortcutChar, isShiftShortcut, categoryName);
+ } else if (roleName != null) {
+ bookmark = new RoleBookmark(shortcutChar, isShiftShortcut, roleName);
}
- if (modifierShortcutManagerMultiuser()) {
- ComponentName componentName = new ComponentName(packageName, className);
- if (isShiftShortcut) {
- mShiftComponentShortcuts.put(shortcutChar, componentName);
+ if (bookmark != null) {
+ Log.d(TAG, "adding shortcut " + bookmark + "shift="
+ + isShiftShortcut + " char=" + shortcutChar);
+ mBookmarks.put(new Pair<>(shortcutChar, isShiftShortcut), bookmark);
+ }
+ } else {
+ final int shortcutChar = shortcutName.charAt(0);
+ if (packageName != null && className != null) {
+ if (roleName != null || categoryName != null) {
+ Log.w(TAG, "Cannot specify role or category when package and class"
+ + " are present for bookmark packageName=" + packageName
+ + " className=" + className + " shortcutChar=" + shortcutChar);
+ continue;
+ }
+ if (modifierShortcutManagerMultiuser()) {
+ ComponentName componentName =
+ new ComponentName(packageName, className);
+ if (isShiftShortcut) {
+ mShiftComponentShortcuts.put(shortcutChar, componentName);
+ } else {
+ mComponentShortcuts.put(shortcutChar, componentName);
+ }
} else {
- mComponentShortcuts.put(shortcutChar, componentName);
+ Intent intent = resolveComponentNameIntent(packageName, className);
+ if (isShiftShortcut) {
+ mShiftCategoryShortcuts.put(shortcutChar, intent);
+ } else {
+ mCategoryShortcuts.put(shortcutChar, intent);
+ }
+ }
+ continue;
+ } else if (categoryName != null) {
+ if (roleName != null) {
+ Log.w(TAG, "Cannot specify role bookmark when category is present for"
+ + " bookmark shortcutChar=" + shortcutChar
+ + " category= " + categoryName);
+ continue;
+ }
+ Intent intent = Intent.makeMainSelectorActivity(
+ Intent.ACTION_MAIN, categoryName);
+ if (intent == null) {
+ Log.w(TAG, "Null selector intent for " + categoryName);
+ } else {
+ if (isShiftShortcut) {
+ mShiftCategoryShortcuts.put(shortcutChar, intent);
+ } else {
+ mCategoryShortcuts.put(shortcutChar, intent);
+ }
+ }
+ continue;
+ } else if (roleName != null) {
+ // We can't resolve the role at the time of this file being parsed as the
+ // device hasn't finished booting, so we will look it up lazily.
+ if (isShiftShortcut) {
+ mShiftRoleShortcuts.put(shortcutChar, roleName);
+ } else {
+ mRoleShortcuts.put(shortcutChar, roleName);
}
continue;
} else {
- intent = resolveComponentNameIntent(packageName, className);
- }
- } else if (categoryName != null) {
- if (roleName != null) {
- Log.w(TAG, "Cannot specify role bookmark when category is present for"
- + " bookmark shortcutChar=" + shortcutChar
- + " category= " + categoryName);
+ Log.w(TAG, "Unable to add bookmark for shortcut " + shortcutName
+ + ": missing package/class, category or role attributes");
continue;
}
- intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, categoryName);
- if (intent == null) {
- Log.w(TAG, "Null selector intent for " + categoryName);
- }
- } else if (roleName != null) {
- // We can't resolve the role at the time of this file being parsed as the
- // device hasn't finished booting, so we will look it up lazily.
- if (isShiftShortcut) {
- mShiftRoleShortcuts.put(shortcutChar, roleName);
- } else {
- mRoleShortcuts.put(shortcutChar, roleName);
- }
- continue;
- } else {
- Log.w(TAG, "Unable to add bookmark for shortcut " + shortcutName
- + ": missing package/class, category or role attributes");
- continue;
- }
-
- if (isShiftShortcut) {
- mShiftShortcuts.put(shortcutChar, intent);
- } else {
- mIntentShortcuts.put(shortcutChar, intent);
}
}
} catch (XmlPullParserException | IOException e) {
@@ -336,21 +426,35 @@
@Nullable
private Intent resolveComponentNameIntent(ComponentName componentName) {
- Intent intent = mComponentIntents.get(componentName);
- if (intent == null) {
- intent = resolveComponentNameIntent(
- componentName.getPackageName(), componentName.getClassName());
- if (intent != null) {
- mComponentIntents.put(componentName, intent);
+ if (modifierShortcutManagerRefactor()) {
+ return null;
+ } else {
+ Intent intent = mComponentIntents.get(componentName);
+ if (intent == null) {
+ intent = resolveComponentNameIntent(
+ componentName.getPackageName(), componentName.getClassName());
+ if (intent != null) {
+ mComponentIntents.put(componentName, intent);
+ }
}
+ return intent;
}
- return intent;
}
@Nullable
private Intent resolveComponentNameIntent(String packageName, String className) {
- Context context = modifierShortcutManagerMultiuser()
- ? mContext.createContextAsUser(mCurrentUser, 0) : mContext;
+ if (modifierShortcutManagerRefactor()) {
+ return null;
+ } else {
+ Context context = modifierShortcutManagerMultiuser()
+ ? mContext.createContextAsUser(mCurrentUser, 0) : mContext;
+ return resolveComponentNameIntent(context, packageName, className);
+ }
+ }
+
+ @Nullable
+ private static Intent resolveComponentNameIntent(
+ Context context, String packageName, String className) {
PackageManager pm = context.getPackageManager();
int flags = PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
if (!modifierShortcutManagerMultiuser()) {
@@ -562,64 +666,81 @@
*/
public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) {
List<KeyboardShortcutInfo> shortcuts = new ArrayList();
- for (int i = 0; i < mIntentShortcuts.size(); i++) {
- KeyboardShortcutInfo info = shortcutInfoFromIntent(
- (char) (mIntentShortcuts.keyAt(i)), mIntentShortcuts.valueAt(i), false);
- if (info != null) {
- shortcuts.add(info);
- }
- }
-
- for (int i = 0; i < mShiftShortcuts.size(); i++) {
- KeyboardShortcutInfo info = shortcutInfoFromIntent(
- (char) (mShiftShortcuts.keyAt(i)), mShiftShortcuts.valueAt(i), true);
- if (info != null) {
- shortcuts.add(info);
- }
- }
-
- for (int i = 0; i < mRoleShortcuts.size(); i++) {
- String role = mRoleShortcuts.valueAt(i);
- KeyboardShortcutInfo info = shortcutInfoFromIntent(
- (char) (mRoleShortcuts.keyAt(i)), getRoleLaunchIntent(role), false);
- if (info != null) {
- shortcuts.add(info);
- }
- }
-
- for (int i = 0; i < mShiftRoleShortcuts.size(); i++) {
- String role = mShiftRoleShortcuts.valueAt(i);
- KeyboardShortcutInfo info = shortcutInfoFromIntent(
- (char) (mShiftRoleShortcuts.keyAt(i)), getRoleLaunchIntent(role), true);
- if (info != null) {
- shortcuts.add(info);
- }
- }
-
- if (modifierShortcutManagerMultiuser()) {
- for (int i = 0; i < mComponentShortcuts.size(); i++) {
- ComponentName component = mComponentShortcuts.valueAt(i);
+ if (modifierShortcutManagerRefactor()) {
+ for (Bookmark b : mBookmarks.values()) {
KeyboardShortcutInfo info = shortcutInfoFromIntent(
- (char) (mComponentShortcuts.keyAt(i)),
- resolveComponentNameIntent(component),
+ b.getShortcutChar(), b.getIntent(mContext), b.isShift());
+ if (info != null) {
+ shortcuts.add(info);
+ }
+ }
+ } else {
+ for (int i = 0; i < mCategoryShortcuts.size(); i++) {
+ KeyboardShortcutInfo info = shortcutInfoFromIntent(
+ (char) (mCategoryShortcuts.keyAt(i)),
+ mCategoryShortcuts.valueAt(i),
false);
if (info != null) {
shortcuts.add(info);
}
}
- for (int i = 0; i < mShiftComponentShortcuts.size(); i++) {
- ComponentName component = mShiftComponentShortcuts.valueAt(i);
+ for (int i = 0; i < mShiftCategoryShortcuts.size(); i++) {
KeyboardShortcutInfo info = shortcutInfoFromIntent(
- (char) (mShiftComponentShortcuts.keyAt(i)),
- resolveComponentNameIntent(component),
+ (char) (mShiftCategoryShortcuts.keyAt(i)),
+ mShiftCategoryShortcuts.valueAt(i),
true);
if (info != null) {
shortcuts.add(info);
}
}
- }
+ for (int i = 0; i < mRoleShortcuts.size(); i++) {
+ String role = mRoleShortcuts.valueAt(i);
+ KeyboardShortcutInfo info = shortcutInfoFromIntent(
+ (char) (mRoleShortcuts.keyAt(i)),
+ getRoleLaunchIntent(role),
+ false);
+ if (info != null) {
+ shortcuts.add(info);
+ }
+ }
+
+ for (int i = 0; i < mShiftRoleShortcuts.size(); i++) {
+ String role = mShiftRoleShortcuts.valueAt(i);
+ KeyboardShortcutInfo info = shortcutInfoFromIntent(
+ (char) (mShiftRoleShortcuts.keyAt(i)),
+ getRoleLaunchIntent(role),
+ true);
+ if (info != null) {
+ shortcuts.add(info);
+ }
+ }
+
+ if (modifierShortcutManagerMultiuser()) {
+ for (int i = 0; i < mComponentShortcuts.size(); i++) {
+ ComponentName component = mComponentShortcuts.valueAt(i);
+ KeyboardShortcutInfo info = shortcutInfoFromIntent(
+ (char) (mComponentShortcuts.keyAt(i)),
+ resolveComponentNameIntent(component),
+ false);
+ if (info != null) {
+ shortcuts.add(info);
+ }
+ }
+
+ for (int i = 0; i < mShiftComponentShortcuts.size(); i++) {
+ ComponentName component = mShiftComponentShortcuts.valueAt(i);
+ KeyboardShortcutInfo info = shortcutInfoFromIntent(
+ (char) (mShiftComponentShortcuts.keyAt(i)),
+ resolveComponentNameIntent(component),
+ true);
+ if (info != null) {
+ shortcuts.add(info);
+ }
+ }
+ }
+ }
return new KeyboardShortcutGroup(
mContext.getString(R.string.keyboard_shortcut_group_applications),
shortcuts);
@@ -800,57 +921,171 @@
void dump(String prefix, PrintWriter pw) {
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", prefix);
ipw.println("ModifierShortcutManager shortcuts:");
-
- ipw.increaseIndent();
- ipw.println("Roles");
- ipw.increaseIndent();
- for (int i = 0; i < mRoleShortcuts.size(); i++) {
- String role = mRoleShortcuts.valueAt(i);
- char shortcutChar = (char) mRoleShortcuts.keyAt(i);
- Intent intent = getRoleLaunchIntent(role);
- ipw.println(shortcutChar + " " + role + " " + intent);
- }
-
- for (int i = 0; i < mShiftRoleShortcuts.size(); i++) {
- String role = mShiftRoleShortcuts.valueAt(i);
- char shortcutChar = (char) mShiftRoleShortcuts.keyAt(i);
- Intent intent = getRoleLaunchIntent(role);
- ipw.println("SHIFT+" + shortcutChar + " " + role + " " + intent);
- }
-
- ipw.decreaseIndent();
- ipw.println("Selectors");
- ipw.increaseIndent();
- for (int i = 0; i < mIntentShortcuts.size(); i++) {
- char shortcutChar = (char) mIntentShortcuts.keyAt(i);
- Intent intent = mIntentShortcuts.valueAt(i);
- ipw.println(shortcutChar + " " + intent);
- }
-
- for (int i = 0; i < mShiftShortcuts.size(); i++) {
- char shortcutChar = (char) mShiftShortcuts.keyAt(i);
- Intent intent = mShiftShortcuts.valueAt(i);
- ipw.println("SHIFT+" + shortcutChar + " " + intent);
-
- }
-
- if (modifierShortcutManagerMultiuser()) {
- ipw.decreaseIndent();
- ipw.println("ComponentNames");
+ if (modifierShortcutManagerRefactor()) {
ipw.increaseIndent();
- for (int i = 0; i < mComponentShortcuts.size(); i++) {
- char shortcutChar = (char) mComponentShortcuts.keyAt(i);
- ComponentName component = mComponentShortcuts.valueAt(i);
- Intent intent = resolveComponentNameIntent(component);
- ipw.println(shortcutChar + " " + component + " " + intent);
+ for (Bookmark b : mBookmarks.values()) {
+ boolean isShift = b.isShift();
+ char shortcutChar = b.getShortcutChar();
+ Context context = modifierShortcutManagerMultiuser()
+ ? mContext.createContextAsUser(mCurrentUser, 0) : mContext;
+
+ Intent intent = b.getIntent(context);
+ ipw.print(isShift ? "SHIFT+" : "");
+ ipw.println(shortcutChar + " " + intent);
+ ipw.increaseIndent();
+ ipw.increaseIndent();
+ KeyboardShortcutInfo info = shortcutInfoFromIntent(shortcutChar, intent, isShift);
+ if (info != null) {
+ ipw.println("Resolves to: " + info.getLabel());
+ } else {
+ ipw.println("<No KeyboardShortcutInfo available for this shortcut>");
+ }
+ ipw.decreaseIndent();
+ ipw.decreaseIndent();
+ }
+ } else {
+ ipw.increaseIndent();
+ ipw.println("Roles");
+ ipw.increaseIndent();
+ for (int i = 0; i < mRoleShortcuts.size(); i++) {
+ String role = mRoleShortcuts.valueAt(i);
+ char shortcutChar = (char) mRoleShortcuts.keyAt(i);
+ Intent intent = getRoleLaunchIntent(role);
+ ipw.println(shortcutChar + " " + role + " " + intent);
}
- for (int i = 0; i < mShiftComponentShortcuts.size(); i++) {
- char shortcutChar = (char) mShiftComponentShortcuts.keyAt(i);
- ComponentName component = mShiftComponentShortcuts.valueAt(i);
- Intent intent = resolveComponentNameIntent(component);
- ipw.println("SHIFT+" + shortcutChar + " " + component + " " + intent);
+ for (int i = 0; i < mShiftRoleShortcuts.size(); i++) {
+ String role = mShiftRoleShortcuts.valueAt(i);
+ char shortcutChar = (char) mShiftRoleShortcuts.keyAt(i);
+ Intent intent = getRoleLaunchIntent(role);
+ ipw.println("SHIFT+" + shortcutChar + " " + role + " " + intent);
}
+
+ ipw.decreaseIndent();
+ ipw.println("Selectors");
+ ipw.increaseIndent();
+ for (int i = 0; i < mCategoryShortcuts.size(); i++) {
+ char shortcutChar = (char) mCategoryShortcuts.keyAt(i);
+ Intent intent = mCategoryShortcuts.valueAt(i);
+ ipw.println(shortcutChar + " " + intent);
+ }
+
+ for (int i = 0; i < mShiftCategoryShortcuts.size(); i++) {
+ char shortcutChar = (char) mShiftCategoryShortcuts.keyAt(i);
+ Intent intent = mShiftCategoryShortcuts.valueAt(i);
+ ipw.println("SHIFT+" + shortcutChar + " " + intent);
+
+ }
+
+ if (modifierShortcutManagerMultiuser()) {
+ ipw.decreaseIndent();
+ ipw.println("ComponentNames");
+ ipw.increaseIndent();
+ for (int i = 0; i < mComponentShortcuts.size(); i++) {
+ char shortcutChar = (char) mComponentShortcuts.keyAt(i);
+ ComponentName component = mComponentShortcuts.valueAt(i);
+ Intent intent = resolveComponentNameIntent(component);
+ ipw.println(shortcutChar + " " + component + " " + intent);
+ }
+
+ for (int i = 0; i < mShiftComponentShortcuts.size(); i++) {
+ char shortcutChar = (char) mShiftComponentShortcuts.keyAt(i);
+ ComponentName component = mShiftComponentShortcuts.valueAt(i);
+ Intent intent = resolveComponentNameIntent(component);
+ ipw.println("SHIFT+" + shortcutChar + " " + component + " " + intent);
+ }
+ }
+ }
+ }
+
+ private abstract static class Bookmark {
+ private final char mShortcutChar;
+ private final boolean mShift;
+ protected Intent mIntent;
+
+ Bookmark(char shortcutChar, boolean shift) {
+ mShortcutChar = shortcutChar;
+ mShift = shift;
+ }
+
+ public char getShortcutChar() {
+ return mShortcutChar;
+ }
+
+ public boolean isShift() {
+ return mShift;
+ }
+
+ public abstract Intent getIntent(Context context);
+
+ public void clearIntent() {
+ mIntent = null;
+ }
+
+ }
+
+ private static final class RoleBookmark extends Bookmark {
+ private final String mRole;
+
+ RoleBookmark(char shortcutChar, boolean shift, String role) {
+ super(shortcutChar, shift);
+ mRole = role;
+ }
+
+ public String getRole() {
+ return mRole;
+ }
+
+ @Nullable
+ @Override
+ public Intent getIntent(Context context) {
+ if (mIntent != null) {
+ return mIntent;
+ }
+ mIntent = getRoleLaunchIntent(context, mRole);
+ return mIntent;
+ }
+ }
+
+ private static final class CategoryBookmark extends Bookmark {
+ private final String mCategory;
+
+ CategoryBookmark(char shortcutChar, boolean shift, String category) {
+ super(shortcutChar, shift);
+ mCategory = category;
+ }
+
+ @NonNull
+ @Override
+ public Intent getIntent(Context context) {
+ if (mIntent != null) {
+ return mIntent;
+ }
+
+ mIntent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, mCategory);
+ return mIntent;
+ }
+ }
+
+ private static final class ComponentBookmark extends Bookmark {
+ private final String mPackageName;
+ private final String mClassName;
+
+ ComponentBookmark(
+ char shortcutChar, boolean shift, String packageName, String className) {
+ super(shortcutChar, shift);
+ mPackageName = packageName;
+ mClassName = className;
+ }
+
+ @Nullable
+ @Override
+ public Intent getIntent(Context context) {
+ if (mIntent != null) {
+ return mIntent;
+ }
+ mIntent = resolveComponentNameIntent(context, mPackageName, mClassName);
+ return mIntent;
}
}
}
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 1c786e6..68026ea 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -18,6 +18,8 @@
import static android.content.pm.Flags.provideInfoOfApkInApex;
+import static com.android.server.crashrecovery.CrashRecoveryUtils.logCrashRecoveryEvent;
+
import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -40,6 +42,7 @@
import android.os.SystemProperties;
import android.sysprop.CrashRecoveryProperties;
import android.util.ArraySet;
+import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
@@ -532,11 +535,13 @@
private void rollbackPackage(RollbackInfo rollback, VersionedPackage failedPackage,
@FailureReasons int rollbackReason) {
assertInWorkerThread();
+ String failedPackageName = (failedPackage == null ? null : failedPackage.getPackageName());
Slog.i(TAG, "Rolling back package. RollbackId: " + rollback.getRollbackId()
- + " failedPackage: "
- + (failedPackage == null ? null : failedPackage.getPackageName())
+ + " failedPackage: " + failedPackageName
+ " rollbackReason: " + rollbackReason);
+ logCrashRecoveryEvent(Log.DEBUG, String.format("Rolling back %s. Reason: %s",
+ failedPackageName, rollbackReason));
final RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
int reasonToLog = WatchdogRollbackLogger.mapFailureReasonToMetric(rollbackReason);
final String failedPackageToLog;
@@ -724,6 +729,7 @@
}
Slog.i(TAG, "Rolling back all available low impact rollbacks");
+ logCrashRecoveryEvent(Log.DEBUG, "Rolling back all available. Reason: " + rollbackReason);
// Add all rollback ids to mPendingStagedRollbackIds, so that we do not reboot before all
// pending staged rollbacks are handled.
for (RollbackInfo rollback : lowImpactRollbacks) {
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 6b3b5bd..67900f8 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -251,7 +251,7 @@
}
private void registerBroadcastReceivers() {
- PackageMonitor monitor = new PackageMonitor() {
+ PackageMonitor monitor = new PackageMonitor(/* supportsPackageRestartQuery */ true) {
private void buildTvInputList(String[] packages) {
int userId = getChangingUserId();
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index edd2fa9..6a7fc6d 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -519,7 +519,7 @@
}
private void registerBroadcastReceivers() {
- PackageMonitor monitor = new PackageMonitor() {
+ PackageMonitor monitor = new PackageMonitor(/* supportsPackageRestartQuery */ true) {
private void buildTvInteractiveAppServiceList(String[] packages) {
int userId = getChangingUserId();
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java b/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java
index 440d251..eb5361c 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java
@@ -26,6 +26,11 @@
* @hide
*/
public class CasResource {
+ /**
+ * Handle of the current resource. Should not be changed and should be aligned with the driver
+ * level implementation.
+ */
+ final int mHandle;
private final int mSystemId;
@@ -39,11 +44,16 @@
private Map<Integer, Integer> mOwnerClientIdsToSessionNum = new HashMap<>();
CasResource(Builder builder) {
+ this.mHandle = builder.mHandle;
this.mSystemId = builder.mSystemId;
this.mMaxSessionNum = builder.mMaxSessionNum;
this.mAvailableSessionNum = builder.mMaxSessionNum;
}
+ public int getHandle() {
+ return mHandle;
+ }
+
public int getSystemId() {
return mSystemId;
}
@@ -136,10 +146,12 @@
*/
public static class Builder {
+ private final int mHandle;
private int mSystemId;
protected int mMaxSessionNum;
- Builder(int systemId) {
+ Builder(int handle, int systemId) {
+ this.mHandle = handle;
this.mSystemId = systemId;
}
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/CiCamResource.java b/services/core/java/com/android/server/tv/tunerresourcemanager/CiCamResource.java
index 31149f3..5cef729 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/CiCamResource.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/CiCamResource.java
@@ -42,8 +42,8 @@
* Builder class for {@link CiCamResource}.
*/
public static class Builder extends CasResource.Builder {
- Builder(int systemId) {
- super(systemId);
+ Builder(int handle, int systemId) {
+ super(handle, systemId);
}
/**
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index 0afb049..9229f7f 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -203,13 +203,7 @@
@Override
public void unregisterClientProfile(int clientId) throws RemoteException {
enforceTrmAccessPermission("unregisterClientProfile");
- synchronized (mLock) {
- if (!checkClientExists(clientId)) {
- Slog.e(TAG, "Unregistering non exists client:" + clientId);
- return;
- }
- unregisterClientProfileInternal(clientId);
- }
+ unregisterClientProfileInternal(clientId);
}
@Override
@@ -291,20 +285,7 @@
Slog.e(TAG, "frontendHandle can't be null");
return false;
}
- synchronized (mLock) {
- if (!checkClientExists(request.clientId)) {
- Slog.e(TAG, "Request frontend from unregistered client: "
- + request.clientId);
- return false;
- }
- // If the request client is holding or sharing a frontend, throw an exception.
- if (!getClientProfile(request.clientId).getInUseFrontendHandles().isEmpty()) {
- Slog.e(TAG, "Release frontend before requesting another one. Client id: "
- + request.clientId);
- return false;
- }
- return requestFrontendInternal(request, frontendHandle);
- }
+ return requestFrontendInternal(request, frontendHandle);
}
@Override
@@ -376,13 +357,7 @@
if (demuxHandle == null) {
throw new RemoteException("demuxHandle can't be null");
}
- synchronized (mLock) {
- if (!checkClientExists(request.clientId)) {
- throw new RemoteException("Request demux from unregistered client:"
- + request.clientId);
- }
- return requestDemuxInternal(request, demuxHandle);
- }
+ return requestDemuxInternal(request, demuxHandle);
}
@Override
@@ -409,13 +384,7 @@
if (casSessionHandle == null) {
throw new RemoteException("casSessionHandle can't be null");
}
- synchronized (mLock) {
- if (!checkClientExists(request.clientId)) {
- throw new RemoteException("Request cas from unregistered client:"
- + request.clientId);
- }
- return requestCasSessionInternal(request, casSessionHandle);
- }
+ return requestCasSessionInternal(request, casSessionHandle);
}
@Override
@@ -425,13 +394,7 @@
if (ciCamHandle == null) {
throw new RemoteException("ciCamHandle can't be null");
}
- synchronized (mLock) {
- if (!checkClientExists(request.clientId)) {
- throw new RemoteException("Request ciCam from unregistered client:"
- + request.clientId);
- }
- return requestCiCamInternal(request, ciCamHandle);
- }
+ return requestCiCamInternal(request, ciCamHandle);
}
@Override
@@ -442,42 +405,14 @@
if (lnbHandle == null) {
throw new RemoteException("lnbHandle can't be null");
}
- synchronized (mLock) {
- if (!checkClientExists(request.clientId)) {
- throw new RemoteException("Request lnb from unregistered client:"
- + request.clientId);
- }
- return requestLnbInternal(request, lnbHandle);
- }
+ return requestLnbInternal(request, lnbHandle);
}
@Override
public void releaseFrontend(int frontendHandle, int clientId) throws RemoteException {
enforceTunerAccessPermission("releaseFrontend");
enforceTrmAccessPermission("releaseFrontend");
- if (!validateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND,
- frontendHandle)) {
- throw new RemoteException("frontendHandle can't be invalid");
- }
- synchronized (mLock) {
- if (!checkClientExists(clientId)) {
- throw new RemoteException("Release frontend from unregistered client:"
- + clientId);
- }
- FrontendResource fe = getFrontendResource(frontendHandle);
- if (fe == null) {
- throw new RemoteException("Releasing frontend does not exist.");
- }
- int ownerClientId = fe.getOwnerClientId();
- ClientProfile ownerProfile = getClientProfile(ownerClientId);
- if (ownerClientId != clientId
- && (ownerProfile != null
- && !ownerProfile.getShareFeClientIds().contains(clientId))) {
- throw new RemoteException(
- "Client is not the current owner of the releasing fe.");
- }
- releaseFrontendInternal(fe, clientId);
- }
+ releaseFrontendInternal(frontendHandle, clientId);
}
@Override
@@ -746,17 +681,23 @@
@VisibleForTesting
protected void unregisterClientProfileInternal(int clientId) {
- if (DEBUG) {
- Slog.d(TAG, "unregisterClientProfile(clientId=" + clientId + ")");
- }
- removeClientProfile(clientId);
- // Remove the Media Resource Manager callingPid to tvAppId mapping
- if (mMediaResourceManager != null) {
- try {
- mMediaResourceManager.overridePid(Binder.getCallingPid(), -1);
- } catch (RemoteException e) {
- Slog.e(TAG, "Could not overridePid in resourceManagerSercice when unregister,"
- + " remote exception: " + e);
+ synchronized (mLock) {
+ if (!checkClientExists(clientId)) {
+ Slog.e(TAG, "Unregistering non exists client:" + clientId);
+ return;
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "unregisterClientProfile(clientId=" + clientId + ")");
+ }
+ removeClientProfile(clientId);
+ // Remove the Media Resource Manager callingPid to tvAppId mapping
+ if (mMediaResourceManager != null) {
+ try {
+ mMediaResourceManager.overridePid(Binder.getCallingPid(), -1);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Could not overridePid in resourceManagerSercice when unregister,"
+ + " remote exception: " + e);
+ }
}
}
}
@@ -992,10 +933,14 @@
return;
}
// Add the new Cas Resource.
- cas = new CasResource.Builder(casSystemId)
+ int casSessionHandle = generateResourceHandle(
+ TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, casSystemId);
+ cas = new CasResource.Builder(casSessionHandle, casSystemId)
.maxSessionNum(maxSessionNum)
.build();
- ciCam = new CiCamResource.Builder(casSystemId)
+ int ciCamHandle = generateResourceHandle(
+ TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, casSystemId);
+ ciCam = new CiCamResource.Builder(ciCamHandle, casSystemId)
.maxSessionNum(maxSessionNum)
.build();
addCasResource(cas);
@@ -1007,86 +952,120 @@
if (DEBUG) {
Slog.d(TAG, "requestFrontend(request=" + request + ")");
}
-
- frontendHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
- ClientProfile requestClient = getClientProfile(request.clientId);
- // TODO: check if this is really needed
- if (requestClient == null) {
+ int[] reclaimOwnerId = new int[1];
+ if (!claimFrontend(request, frontendHandle, reclaimOwnerId)) {
return false;
}
- clientPriorityUpdateOnRequest(requestClient);
- int grantingFrontendHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
- int inUseLowestPriorityFrHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
- // Priority max value is 1000
- int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
- boolean isRequestFromSameProcess = false;
- // If the desired frontend id was specified, we only need to check the frontend.
- boolean hasDesiredFrontend = request.desiredId != TunerFrontendRequest.DEFAULT_DESIRED_ID;
- for (FrontendResource fr : getFrontendResources().values()) {
- int frontendId = getResourceIdFromHandle(fr.getHandle());
- if (fr.getType() == request.frontendType
- && (!hasDesiredFrontend || frontendId == request.desiredId)) {
- if (!fr.isInUse()) {
- // Unused resource cannot be acquired if the max is already reached, but
- // TRM still has to look for the reclaim candidate
- if (isFrontendMaxNumUseReached(request.frontendType)) {
- continue;
- }
- // Grant unused frontend with no exclusive group members first.
- if (fr.getExclusiveGroupMemberFeHandles().isEmpty()) {
- grantingFrontendHandle = fr.getHandle();
- break;
- } else if (grantingFrontendHandle
- == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
- // Grant the unused frontend with lower id first if all the unused
- // frontends have exclusive group members.
- grantingFrontendHandle = fr.getHandle();
- }
- } else if (grantingFrontendHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
- // Record the frontend id with the lowest client priority among all the
- // in use frontends when no available frontend has been found.
- int priority = getFrontendHighestClientPriority(fr.getOwnerClientId());
- if (currentLowestPriority > priority) {
- // we need to check the max used num if the target frontend type is not
- // currently in primary use (and simply blocked due to exclusive group)
- ClientProfile targetOwnerProfile = getClientProfile(fr.getOwnerClientId());
- int primaryFeId = targetOwnerProfile.getPrimaryFrontend();
- FrontendResource primaryFe = getFrontendResource(primaryFeId);
- if (fr.getType() != primaryFe.getType()
- && isFrontendMaxNumUseReached(fr.getType())) {
+ if (frontendHandle[0] == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
+ return false;
+ }
+ if (reclaimOwnerId[0] != INVALID_CLIENT_ID) {
+ if (!reclaimResource(reclaimOwnerId[0], TunerResourceManager
+ .TUNER_RESOURCE_TYPE_FRONTEND)) {
+ return false;
+ }
+ synchronized (mLock) {
+ if (getFrontendResource(frontendHandle[0]).isInUse()) {
+ Slog.e(TAG, "Reclaimed frontend still in use");
+ return false;
+ }
+ updateFrontendClientMappingOnNewGrant(frontendHandle[0], request.clientId);
+ }
+ }
+ return true;
+ }
+
+ protected boolean claimFrontend(
+ TunerFrontendRequest request,
+ int[] frontendHandle,
+ int[] reclaimOwnerId
+ ) {
+ frontendHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
+ reclaimOwnerId[0] = INVALID_CLIENT_ID;
+ synchronized (mLock) {
+ if (!checkClientExists(request.clientId)) {
+ Slog.e(TAG, "Request frontend from unregistered client: "
+ + request.clientId);
+ return false;
+ }
+ // If the request client is holding or sharing a frontend, throw an exception.
+ if (!getClientProfile(request.clientId).getInUseFrontendHandles().isEmpty()) {
+ Slog.e(TAG, "Release frontend before requesting another one. Client id: "
+ + request.clientId);
+ return false;
+ }
+ ClientProfile requestClient = getClientProfile(request.clientId);
+ clientPriorityUpdateOnRequest(requestClient);
+ FrontendResource grantingFrontend = null;
+ FrontendResource inUseLowestPriorityFrontend = null;
+ // Priority max value is 1000
+ int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
+ boolean isRequestFromSameProcess = false;
+ // If the desired frontend id was specified, we only need to check the frontend.
+ boolean hasDesiredFrontend = request.desiredId != TunerFrontendRequest
+ .DEFAULT_DESIRED_ID;
+ for (FrontendResource fr : getFrontendResources().values()) {
+ int frontendId = getResourceIdFromHandle(fr.getHandle());
+ if (fr.getType() == request.frontendType
+ && (!hasDesiredFrontend || frontendId == request.desiredId)) {
+ if (!fr.isInUse()) {
+ // Unused resource cannot be acquired if the max is already reached, but
+ // TRM still has to look for the reclaim candidate
+ if (isFrontendMaxNumUseReached(request.frontendType)) {
continue;
}
- // update the target frontend
- inUseLowestPriorityFrHandle = fr.getHandle();
- currentLowestPriority = priority;
- isRequestFromSameProcess = (requestClient.getProcessId()
- == (getClientProfile(fr.getOwnerClientId())).getProcessId());
+ // Grant unused frontend with no exclusive group members first.
+ if (fr.getExclusiveGroupMemberFeHandles().isEmpty()) {
+ grantingFrontend = fr;
+ break;
+ } else if (grantingFrontend == null) {
+ // Grant the unused frontend with lower id first if all the unused
+ // frontends have exclusive group members.
+ grantingFrontend = fr;
+ }
+ } else if (grantingFrontend == null) {
+ // Record the frontend id with the lowest client priority among all the
+ // in use frontends when no available frontend has been found.
+ int priority = getFrontendHighestClientPriority(fr.getOwnerClientId());
+ if (currentLowestPriority > priority) {
+ // we need to check the max used num if the target frontend type is not
+ // currently in primary use (and simply blocked due to exclusive group)
+ ClientProfile targetOwnerProfile =
+ getClientProfile(fr.getOwnerClientId());
+ int primaryFeId = targetOwnerProfile.getPrimaryFrontend();
+ FrontendResource primaryFe = getFrontendResource(primaryFeId);
+ if (fr.getType() != primaryFe.getType()
+ && isFrontendMaxNumUseReached(fr.getType())) {
+ continue;
+ }
+ // update the target frontend
+ inUseLowestPriorityFrontend = fr;
+ currentLowestPriority = priority;
+ isRequestFromSameProcess = (requestClient.getProcessId()
+ == (getClientProfile(fr.getOwnerClientId())).getProcessId());
+ }
}
}
}
- }
- // Grant frontend when there is unused resource.
- if (grantingFrontendHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE) {
- frontendHandle[0] = grantingFrontendHandle;
- updateFrontendClientMappingOnNewGrant(grantingFrontendHandle, request.clientId);
- return true;
- }
-
- // When all the resources are occupied, grant the lowest priority resource if the
- // request client has higher priority.
- if (inUseLowestPriorityFrHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE
- && ((requestClient.getPriority() > currentLowestPriority) || (
- (requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess))) {
- if (!reclaimResource(
- getFrontendResource(inUseLowestPriorityFrHandle).getOwnerClientId(),
- TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
- return false;
+ // Grant frontend when there is unused resource.
+ if (grantingFrontend != null) {
+ updateFrontendClientMappingOnNewGrant(grantingFrontend.getHandle(),
+ request.clientId);
+ frontendHandle[0] = grantingFrontend.getHandle();
+ return true;
}
- frontendHandle[0] = inUseLowestPriorityFrHandle;
- updateFrontendClientMappingOnNewGrant(
- inUseLowestPriorityFrHandle, request.clientId);
- return true;
+
+ // When all the resources are occupied, grant the lowest priority resource if the
+ // request client has higher priority.
+ if (inUseLowestPriorityFrontend != null
+ && ((requestClient.getPriority() > currentLowestPriority)
+ || ((requestClient.getPriority() == currentLowestPriority)
+ && isRequestFromSameProcess))) {
+ frontendHandle[0] = inUseLowestPriorityFrontend.getHandle();
+ reclaimOwnerId[0] = inUseLowestPriorityFrontend.getOwnerClientId();
+ return true;
+ }
}
return false;
@@ -1192,165 +1171,257 @@
}
@VisibleForTesting
- protected boolean requestLnbInternal(TunerLnbRequest request, int[] lnbHandle) {
+ protected boolean requestLnbInternal(TunerLnbRequest request, int[] lnbHandle)
+ throws RemoteException {
if (DEBUG) {
Slog.d(TAG, "requestLnb(request=" + request + ")");
}
-
- lnbHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
- ClientProfile requestClient = getClientProfile(request.clientId);
- clientPriorityUpdateOnRequest(requestClient);
- int grantingLnbHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
- int inUseLowestPriorityLnbHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
- // Priority max value is 1000
- int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
- boolean isRequestFromSameProcess = false;
- for (LnbResource lnb : getLnbResources().values()) {
- if (!lnb.isInUse()) {
- // Grant the unused lnb with lower handle first
- grantingLnbHandle = lnb.getHandle();
- break;
- } else {
- // Record the lnb id with the lowest client priority among all the
- // in use lnb when no available lnb has been found.
- int priority = updateAndGetOwnerClientPriority(lnb.getOwnerClientId());
- if (currentLowestPriority > priority) {
- inUseLowestPriorityLnbHandle = lnb.getHandle();
- currentLowestPriority = priority;
- isRequestFromSameProcess = (requestClient.getProcessId()
- == (getClientProfile(lnb.getOwnerClientId())).getProcessId());
- }
- }
+ int[] reclaimOwnerId = new int[1];
+ if (!claimLnb(request, lnbHandle, reclaimOwnerId)) {
+ return false;
}
-
- // Grant Lnb when there is unused resource.
- if (grantingLnbHandle > -1) {
- lnbHandle[0] = grantingLnbHandle;
- updateLnbClientMappingOnNewGrant(grantingLnbHandle, request.clientId);
- return true;
+ if (lnbHandle[0] == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
+ return false;
}
-
- // When all the resources are occupied, grant the lowest priority resource if the
- // request client has higher priority.
- if (inUseLowestPriorityLnbHandle > TunerResourceManager.INVALID_RESOURCE_HANDLE
- && ((requestClient.getPriority() > currentLowestPriority) || (
- (requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess))) {
- if (!reclaimResource(getLnbResource(inUseLowestPriorityLnbHandle).getOwnerClientId(),
+ if (reclaimOwnerId[0] != INVALID_CLIENT_ID) {
+ if (!reclaimResource(reclaimOwnerId[0],
TunerResourceManager.TUNER_RESOURCE_TYPE_LNB)) {
return false;
}
- lnbHandle[0] = inUseLowestPriorityLnbHandle;
- updateLnbClientMappingOnNewGrant(inUseLowestPriorityLnbHandle, request.clientId);
- return true;
+ synchronized (mLock) {
+ if (getLnbResource(lnbHandle[0]).isInUse()) {
+ Slog.e(TAG, "Reclaimed lnb still in use");
+ return false;
+ }
+ updateLnbClientMappingOnNewGrant(lnbHandle[0], request.clientId);
+ }
+ }
+ return true;
+ }
+
+ protected boolean claimLnb(TunerLnbRequest request, int[] lnbHandle, int[] reclaimOwnerId)
+ throws RemoteException {
+ lnbHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
+ reclaimOwnerId[0] = INVALID_CLIENT_ID;
+ synchronized (mLock) {
+ if (!checkClientExists(request.clientId)) {
+ throw new RemoteException("Request lnb from unregistered client:"
+ + request.clientId);
+ }
+ ClientProfile requestClient = getClientProfile(request.clientId);
+ clientPriorityUpdateOnRequest(requestClient);
+ LnbResource grantingLnb = null;
+ LnbResource inUseLowestPriorityLnb = null;
+ // Priority max value is 1000
+ int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
+ boolean isRequestFromSameProcess = false;
+ for (LnbResource lnb : getLnbResources().values()) {
+ if (!lnb.isInUse()) {
+ // Grant the unused lnb with lower handle first
+ grantingLnb = lnb;
+ break;
+ } else {
+ // Record the lnb id with the lowest client priority among all the
+ // in use lnb when no available lnb has been found.
+ int priority = updateAndGetOwnerClientPriority(lnb.getOwnerClientId());
+ if (currentLowestPriority > priority) {
+ inUseLowestPriorityLnb = lnb;
+ currentLowestPriority = priority;
+ isRequestFromSameProcess = (requestClient.getProcessId()
+ == (getClientProfile(lnb.getOwnerClientId())).getProcessId());
+ }
+ }
+ }
+
+ // Grant Lnb when there is unused resource.
+ if (grantingLnb != null) {
+ updateLnbClientMappingOnNewGrant(grantingLnb.getHandle(), request.clientId);
+ lnbHandle[0] = grantingLnb.getHandle();
+ return true;
+ }
+
+ // When all the resources are occupied, grant the lowest priority resource if the
+ // request client has higher priority.
+ if (inUseLowestPriorityLnb != null
+ && ((requestClient.getPriority() > currentLowestPriority) || (
+ (requestClient.getPriority() == currentLowestPriority)
+ && isRequestFromSameProcess))) {
+ lnbHandle[0] = inUseLowestPriorityLnb.getHandle();
+ reclaimOwnerId[0] = inUseLowestPriorityLnb.getOwnerClientId();
+ return true;
+ }
}
return false;
}
@VisibleForTesting
- protected boolean requestCasSessionInternal(CasSessionRequest request, int[] casSessionHandle) {
+ protected boolean requestCasSessionInternal(CasSessionRequest request, int[] casSessionHandle)
+ throws RemoteException {
if (DEBUG) {
Slog.d(TAG, "requestCasSession(request=" + request + ")");
}
- CasResource cas = getCasResource(request.casSystemId);
- // Unregistered Cas System is treated as having unlimited sessions.
- if (cas == null) {
- cas = new CasResource.Builder(request.casSystemId)
- .maxSessionNum(Integer.MAX_VALUE)
- .build();
- addCasResource(cas);
+ int[] reclaimOwnerId = new int[1];
+ if (!claimCasSession(request, casSessionHandle, reclaimOwnerId)) {
+ return false;
}
- casSessionHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
- ClientProfile requestClient = getClientProfile(request.clientId);
- clientPriorityUpdateOnRequest(requestClient);
- int lowestPriorityOwnerId = -1;
- // Priority max value is 1000
- int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
- boolean isRequestFromSameProcess = false;
- if (!cas.isFullyUsed()) {
- casSessionHandle[0] = generateResourceHandle(
- TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, cas.getSystemId());
- updateCasClientMappingOnNewGrant(request.casSystemId, request.clientId);
- return true;
+ if (casSessionHandle[0] == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
+ return false;
}
- for (int ownerId : cas.getOwnerClientIds()) {
- // Record the client id with lowest priority that is using the current Cas system.
- int priority = updateAndGetOwnerClientPriority(ownerId);
- if (currentLowestPriority > priority) {
- lowestPriorityOwnerId = ownerId;
- currentLowestPriority = priority;
- isRequestFromSameProcess = (requestClient.getProcessId()
- == (getClientProfile(ownerId)).getProcessId());
- }
- }
-
- // When all the Cas sessions are occupied, reclaim the lowest priority client if the
- // request client has higher priority.
- if (lowestPriorityOwnerId > -1 && ((requestClient.getPriority() > currentLowestPriority)
- || ((requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess))) {
- if (!reclaimResource(lowestPriorityOwnerId,
+ if (reclaimOwnerId[0] != INVALID_CLIENT_ID) {
+ if (!reclaimResource(reclaimOwnerId[0],
TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION)) {
return false;
}
- casSessionHandle[0] = generateResourceHandle(
- TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, cas.getSystemId());
- updateCasClientMappingOnNewGrant(request.casSystemId, request.clientId);
- return true;
+ synchronized (mLock) {
+ if (getCasResource(request.casSystemId).isFullyUsed()) {
+ Slog.e(TAG, "Reclaimed cas still fully used");
+ return false;
+ }
+ updateCasClientMappingOnNewGrant(request.casSystemId, request.clientId);
+ }
}
+ return true;
+ }
+
+ protected boolean claimCasSession(CasSessionRequest request, int[] casSessionHandle,
+ int[] reclaimOwnerId) throws RemoteException {
+ casSessionHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
+ reclaimOwnerId[0] = INVALID_CLIENT_ID;
+ synchronized (mLock) {
+ if (!checkClientExists(request.clientId)) {
+ throw new RemoteException("Request cas from unregistered client:"
+ + request.clientId);
+ }
+ CasResource cas = getCasResource(request.casSystemId);
+ // Unregistered Cas System is treated as having unlimited sessions.
+ if (cas == null) {
+ int resourceHandle = generateResourceHandle(
+ TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, request.clientId);
+ cas = new CasResource.Builder(resourceHandle, request.casSystemId)
+ .maxSessionNum(Integer.MAX_VALUE)
+ .build();
+ addCasResource(cas);
+ }
+ ClientProfile requestClient = getClientProfile(request.clientId);
+ clientPriorityUpdateOnRequest(requestClient);
+ int lowestPriorityOwnerId = INVALID_CLIENT_ID;
+ // Priority max value is 1000
+ int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
+ boolean isRequestFromSameProcess = false;
+ if (!cas.isFullyUsed()) {
+ updateCasClientMappingOnNewGrant(request.casSystemId, request.clientId);
+ casSessionHandle[0] = cas.getHandle();
+ return true;
+ }
+ for (int ownerId : cas.getOwnerClientIds()) {
+ // Record the client id with lowest priority that is using the current Cas system.
+ int priority = updateAndGetOwnerClientPriority(ownerId);
+ if (currentLowestPriority > priority) {
+ lowestPriorityOwnerId = ownerId;
+ currentLowestPriority = priority;
+ isRequestFromSameProcess = (requestClient.getProcessId()
+ == (getClientProfile(ownerId)).getProcessId());
+ }
+ }
+
+ // When all the Cas sessions are occupied, reclaim the lowest priority client if the
+ // request client has higher priority.
+ if (lowestPriorityOwnerId != INVALID_CLIENT_ID
+ && ((requestClient.getPriority() > currentLowestPriority)
+ || ((requestClient.getPriority() == currentLowestPriority)
+ && isRequestFromSameProcess))) {
+ casSessionHandle[0] = cas.getHandle();
+ reclaimOwnerId[0] = lowestPriorityOwnerId;
+ return true;
+ }
+ }
+
return false;
}
@VisibleForTesting
- protected boolean requestCiCamInternal(TunerCiCamRequest request, int[] ciCamHandle) {
+ protected boolean requestCiCamInternal(TunerCiCamRequest request, int[] ciCamHandle)
+ throws RemoteException {
if (DEBUG) {
Slog.d(TAG, "requestCiCamInternal(TunerCiCamRequest=" + request + ")");
}
- CiCamResource ciCam = getCiCamResource(request.ciCamId);
- // Unregistered Cas System is treated as having unlimited sessions.
- if (ciCam == null) {
- ciCam = new CiCamResource.Builder(request.ciCamId)
- .maxSessionNum(Integer.MAX_VALUE)
- .build();
- addCiCamResource(ciCam);
+ int[] reclaimOwnerId = new int[1];
+ if (!claimCiCam(request, ciCamHandle, reclaimOwnerId)) {
+ return false;
}
- ciCamHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
- ClientProfile requestClient = getClientProfile(request.clientId);
- clientPriorityUpdateOnRequest(requestClient);
- int lowestPriorityOwnerId = -1;
- // Priority max value is 1000
- int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
- boolean isRequestFromSameProcess = false;
- if (!ciCam.isFullyUsed()) {
- ciCamHandle[0] = generateResourceHandle(
- TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, ciCam.getCiCamId());
- updateCiCamClientMappingOnNewGrant(request.ciCamId, request.clientId);
- return true;
+ if (ciCamHandle[0] == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
+ return false;
}
- for (int ownerId : ciCam.getOwnerClientIds()) {
- // Record the client id with lowest priority that is using the current Cas system.
- int priority = updateAndGetOwnerClientPriority(ownerId);
- if (currentLowestPriority > priority) {
- lowestPriorityOwnerId = ownerId;
- currentLowestPriority = priority;
- isRequestFromSameProcess = (requestClient.getProcessId()
- == (getClientProfile(ownerId)).getProcessId());
- }
- }
-
- // When all the CiCam sessions are occupied, reclaim the lowest priority client if the
- // request client has higher priority.
- if (lowestPriorityOwnerId > -1 && ((requestClient.getPriority() > currentLowestPriority)
- || ((requestClient.getPriority() == currentLowestPriority)
- && isRequestFromSameProcess))) {
- if (!reclaimResource(lowestPriorityOwnerId,
+ if (reclaimOwnerId[0] != INVALID_CLIENT_ID) {
+ if (!reclaimResource(reclaimOwnerId[0],
TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM)) {
return false;
}
- ciCamHandle[0] = generateResourceHandle(
- TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, ciCam.getCiCamId());
- updateCiCamClientMappingOnNewGrant(request.ciCamId, request.clientId);
- return true;
+ synchronized (mLock) {
+ if (getCiCamResource(request.ciCamId).isFullyUsed()) {
+ Slog.e(TAG, "Reclaimed ciCam still fully used");
+ return false;
+ }
+ updateCiCamClientMappingOnNewGrant(request.ciCamId, request.clientId);
+ }
}
+ return true;
+ }
+
+ protected boolean claimCiCam(TunerCiCamRequest request, int[] ciCamHandle,
+ int[] reclaimOwnerId) throws RemoteException {
+ ciCamHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
+ reclaimOwnerId[0] = INVALID_CLIENT_ID;
+ synchronized (mLock) {
+ if (!checkClientExists(request.clientId)) {
+ throw new RemoteException("Request ciCam from unregistered client:"
+ + request.clientId);
+ }
+ CiCamResource ciCam = getCiCamResource(request.ciCamId);
+ // Unregistered CiCam is treated as having unlimited sessions.
+ if (ciCam == null) {
+ int resourceHandle = generateResourceHandle(
+ TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, request.ciCamId);
+ ciCam = new CiCamResource.Builder(resourceHandle, request.ciCamId)
+ .maxSessionNum(Integer.MAX_VALUE)
+ .build();
+ addCiCamResource(ciCam);
+ }
+ ClientProfile requestClient = getClientProfile(request.clientId);
+ clientPriorityUpdateOnRequest(requestClient);
+ int lowestPriorityOwnerId = INVALID_CLIENT_ID;
+ // Priority max value is 1000
+ int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
+ boolean isRequestFromSameProcess = false;
+ if (!ciCam.isFullyUsed()) {
+ updateCiCamClientMappingOnNewGrant(request.ciCamId, request.clientId);
+ ciCamHandle[0] = ciCam.getHandle();
+ return true;
+ }
+ for (int ownerId : ciCam.getOwnerClientIds()) {
+ // Record the client id with lowest priority that is using the current CiCam.
+ int priority = updateAndGetOwnerClientPriority(ownerId);
+ if (currentLowestPriority > priority) {
+ lowestPriorityOwnerId = ownerId;
+ currentLowestPriority = priority;
+ isRequestFromSameProcess = (requestClient.getProcessId()
+ == (getClientProfile(ownerId)).getProcessId());
+ }
+ }
+
+ // When all the CiCam sessions are occupied, reclaim the lowest priority client if the
+ // request client has higher priority.
+ if (lowestPriorityOwnerId != INVALID_CLIENT_ID
+ && ((requestClient.getPriority() > currentLowestPriority)
+ || ((requestClient.getPriority() == currentLowestPriority)
+ && isRequestFromSameProcess))) {
+ ciCamHandle[0] = ciCam.getHandle();
+ reclaimOwnerId[0] = lowestPriorityOwnerId;
+ return true;
+ }
+ }
+
return false;
}
@@ -1383,20 +1454,49 @@
}
@VisibleForTesting
- protected void releaseFrontendInternal(FrontendResource fe, int clientId) {
+ protected void releaseFrontendInternal(int frontendHandle, int clientId)
+ throws RemoteException {
if (DEBUG) {
- Slog.d(TAG, "releaseFrontend(id=" + fe.getHandle() + ", clientId=" + clientId + " )");
+ Slog.d(TAG, "releaseFrontend(id=" + frontendHandle + ", clientId=" + clientId + " )");
}
- if (clientId == fe.getOwnerClientId()) {
- ClientProfile ownerClient = getClientProfile(fe.getOwnerClientId());
- if (ownerClient != null) {
- for (int shareOwnerId : ownerClient.getShareFeClientIds()) {
- reclaimResource(shareOwnerId,
- TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND);
+ if (!validateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND,
+ frontendHandle)) {
+ throw new RemoteException("frontendHandle can't be invalid");
+ }
+ Set<Integer> reclaimedResourceOwnerIds = unclaimFrontend(frontendHandle, clientId);
+ if (reclaimedResourceOwnerIds != null) {
+ for (int shareOwnerId : reclaimedResourceOwnerIds) {
+ reclaimResource(shareOwnerId,
+ TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND);
+ }
+ }
+ synchronized (mLock) {
+ clearFrontendAndClientMapping(getClientProfile(clientId));
+ }
+ }
+
+ private Set<Integer> unclaimFrontend(int frontendHandle, int clientId) throws RemoteException {
+ Set<Integer> reclaimedResourceOwnerIds = null;
+ synchronized (mLock) {
+ if (!checkClientExists(clientId)) {
+ throw new RemoteException("Release frontend from unregistered client:"
+ + clientId);
+ }
+ FrontendResource fe = getFrontendResource(frontendHandle);
+ if (fe == null) {
+ throw new RemoteException("Releasing frontend does not exist.");
+ }
+ int ownerClientId = fe.getOwnerClientId();
+ ClientProfile ownerProfile = getClientProfile(ownerClientId);
+ if (ownerClientId == clientId) {
+ reclaimedResourceOwnerIds = ownerProfile.getShareFeClientIds();
+ } else {
+ if (!ownerProfile.getShareFeClientIds().contains(clientId)) {
+ throw new RemoteException("Client is not a sharee of the releasing fe.");
}
}
}
- clearFrontendAndClientMapping(getClientProfile(clientId));
+ return reclaimedResourceOwnerIds;
}
@VisibleForTesting
@@ -1432,103 +1532,129 @@
}
@VisibleForTesting
- protected boolean requestDemuxInternal(TunerDemuxRequest request, int[] demuxHandle) {
+ public boolean requestDemuxInternal(@NonNull TunerDemuxRequest request,
+ @NonNull int[] demuxHandle) throws RemoteException {
if (DEBUG) {
Slog.d(TAG, "requestDemux(request=" + request + ")");
}
-
- // For Tuner 2.0 and below or any HW constraint devices that are unable to support
- // ITuner.openDemuxById(), demux resources are not really managed under TRM and
- // mDemuxResources.size() will be zero
- if (mDemuxResources.size() == 0) {
- // There are enough Demux resources, so we don't manage Demux in R.
- demuxHandle[0] =
- generateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, 0);
- return true;
- }
-
- demuxHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
- ClientProfile requestClient = getClientProfile(request.clientId);
-
- if (requestClient == null) {
+ int[] reclaimOwnerId = new int[1];
+ if (!claimDemux(request, demuxHandle, reclaimOwnerId)) {
return false;
}
+ if (demuxHandle[0] == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
+ return false;
+ }
+ if (reclaimOwnerId[0] != INVALID_CLIENT_ID) {
+ if (!reclaimResource(reclaimOwnerId[0],
+ TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
+ return false;
+ }
+ synchronized (mLock) {
+ if (getDemuxResource(demuxHandle[0]).isInUse()) {
+ Slog.e(TAG, "Reclaimed demux still in use");
+ return false;
+ }
+ updateDemuxClientMappingOnNewGrant(demuxHandle[0], request.clientId);
+ }
+ }
+ return true;
+ }
- clientPriorityUpdateOnRequest(requestClient);
- int grantingDemuxHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
- int inUseLowestPriorityDrHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
- // Priority max value is 1000
- int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
- boolean isRequestFromSameProcess = false;
- // If the desired demux id was specified, we only need to check the demux.
- boolean hasDesiredDemuxCap = request.desiredFilterTypes
- != DemuxFilterMainType.UNDEFINED;
- int smallestNumOfSupportedCaps = Integer.SIZE + 1;
- int smallestNumOfSupportedCapsInUse = Integer.SIZE + 1;
- for (DemuxResource dr : getDemuxResources().values()) {
- if (!hasDesiredDemuxCap || dr.hasSufficientCaps(request.desiredFilterTypes)) {
- if (!dr.isInUse()) {
- int numOfSupportedCaps = dr.getNumOfCaps();
+ protected boolean claimDemux(TunerDemuxRequest request, int[] demuxHandle, int[] reclaimOwnerId)
+ throws RemoteException {
+ demuxHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
+ reclaimOwnerId[0] = INVALID_CLIENT_ID;
+ synchronized (mLock) {
+ if (!checkClientExists(request.clientId)) {
+ throw new RemoteException("Request demux from unregistered client:"
+ + request.clientId);
+ }
- // look for the demux with minimum caps supporting the desired caps
- if (smallestNumOfSupportedCaps > numOfSupportedCaps) {
- smallestNumOfSupportedCaps = numOfSupportedCaps;
- grantingDemuxHandle = dr.getHandle();
- }
- } else if (grantingDemuxHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
- // Record the demux id with the lowest client priority among all the
- // in use demuxes when no availabledemux has been found.
- int priority = updateAndGetOwnerClientPriority(dr.getOwnerClientId());
- if (currentLowestPriority >= priority) {
+ // For Tuner 2.0 and below or any HW constraint devices that are unable to support
+ // ITuner.openDemuxById(), demux resources are not really managed under TRM and
+ // mDemuxResources.size() will be zero
+ if (mDemuxResources.size() == 0) {
+ // There are enough Demux resources, so we don't manage Demux in R.
+ demuxHandle[0] =
+ generateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, 0);
+ return true;
+ }
+
+ ClientProfile requestClient = getClientProfile(request.clientId);
+ if (requestClient == null) {
+ return false;
+ }
+ clientPriorityUpdateOnRequest(requestClient);
+ DemuxResource grantingDemux = null;
+ DemuxResource inUseLowestPriorityDemux = null;
+ // Priority max value is 1000
+ int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
+ boolean isRequestFromSameProcess = false;
+ // If the desired demux id was specified, we only need to check the demux.
+ boolean hasDesiredDemuxCap = request.desiredFilterTypes
+ != DemuxFilterMainType.UNDEFINED;
+ int smallestNumOfSupportedCaps = Integer.SIZE + 1;
+ int smallestNumOfSupportedCapsInUse = Integer.SIZE + 1;
+ for (DemuxResource dr : getDemuxResources().values()) {
+ if (!hasDesiredDemuxCap || dr.hasSufficientCaps(request.desiredFilterTypes)) {
+ if (!dr.isInUse()) {
int numOfSupportedCaps = dr.getNumOfCaps();
- boolean shouldUpdate = false;
- // update lowest priority
- if (currentLowestPriority > priority) {
- currentLowestPriority = priority;
- isRequestFromSameProcess = (requestClient.getProcessId()
- == (getClientProfile(dr.getOwnerClientId())).getProcessId());
- // reset the smallest caps when lower priority resource is found
- smallestNumOfSupportedCapsInUse = numOfSupportedCaps;
-
- shouldUpdate = true;
- } else {
- // This is the case when the priority is the same as previously found
- // one. Update smallest caps when priority.
- if (smallestNumOfSupportedCapsInUse > numOfSupportedCaps) {
- smallestNumOfSupportedCapsInUse = numOfSupportedCaps;
- shouldUpdate = true;
- }
+ // look for the demux with minimum caps supporting the desired caps
+ if (smallestNumOfSupportedCaps > numOfSupportedCaps) {
+ smallestNumOfSupportedCaps = numOfSupportedCaps;
+ grantingDemux = dr;
}
- if (shouldUpdate) {
- inUseLowestPriorityDrHandle = dr.getHandle();
+ } else if (grantingDemux == null) {
+ // Record the demux id with the lowest client priority among all the
+ // in use demuxes when no availabledemux has been found.
+ int priority = updateAndGetOwnerClientPriority(dr.getOwnerClientId());
+ if (currentLowestPriority >= priority) {
+ int numOfSupportedCaps = dr.getNumOfCaps();
+ boolean shouldUpdate = false;
+ // update lowest priority
+ if (currentLowestPriority > priority) {
+ currentLowestPriority = priority;
+ isRequestFromSameProcess = (requestClient.getProcessId()
+ == (getClientProfile(dr.getOwnerClientId())).getProcessId());
+
+ // reset the smallest caps when lower priority resource is found
+ smallestNumOfSupportedCapsInUse = numOfSupportedCaps;
+
+ shouldUpdate = true;
+ } else {
+ // This is the case when the priority is the same as previously
+ // found one. Update smallest caps when priority.
+ if (smallestNumOfSupportedCapsInUse > numOfSupportedCaps) {
+ smallestNumOfSupportedCapsInUse = numOfSupportedCaps;
+ shouldUpdate = true;
+ }
+ }
+ if (shouldUpdate) {
+ inUseLowestPriorityDemux = dr;
+ }
}
}
}
}
- }
- // Grant demux when there is unused resource.
- if (grantingDemuxHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE) {
- demuxHandle[0] = grantingDemuxHandle;
- updateDemuxClientMappingOnNewGrant(grantingDemuxHandle, request.clientId);
- return true;
- }
-
- // When all the resources are occupied, grant the lowest priority resource if the
- // request client has higher priority.
- if (inUseLowestPriorityDrHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE
- && ((requestClient.getPriority() > currentLowestPriority) || (
- (requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess))) {
- if (!reclaimResource(
- getDemuxResource(inUseLowestPriorityDrHandle).getOwnerClientId(),
- TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
- return false;
+ // Grant demux when there is unused resource.
+ if (grantingDemux != null) {
+ updateDemuxClientMappingOnNewGrant(grantingDemux.getHandle(), request.clientId);
+ demuxHandle[0] = grantingDemux.getHandle();
+ return true;
}
- demuxHandle[0] = inUseLowestPriorityDrHandle;
- updateDemuxClientMappingOnNewGrant(
- inUseLowestPriorityDrHandle, request.clientId);
- return true;
+
+ // When all the resources are occupied, grant the lowest priority resource if the
+ // request client has higher priority.
+ if (inUseLowestPriorityDemux != null
+ && ((requestClient.getPriority() > currentLowestPriority) || (
+ (requestClient.getPriority() == currentLowestPriority)
+ && isRequestFromSameProcess))) {
+ demuxHandle[0] = inUseLowestPriorityDemux.getHandle();
+ reclaimOwnerId[0] = inUseLowestPriorityDemux.getOwnerClientId();
+ return true;
+ }
}
return false;
@@ -1792,7 +1918,9 @@
return;
}
- mListeners.put(clientId, record);
+ synchronized (mLock) {
+ mListeners.put(clientId, record);
+ }
}
@VisibleForTesting
@@ -1808,33 +1936,44 @@
// Reclaim all the resources of the share owners of the frontend that is used by the current
// resource reclaimed client.
- ClientProfile profile = getClientProfile(reclaimingClientId);
- // TODO: check if this check is really needed.
- if (profile == null) {
- return true;
- }
- Set<Integer> shareFeClientIds = profile.getShareFeClientIds();
- for (int clientId : shareFeClientIds) {
- try {
- mListeners.get(clientId).getListener().onReclaimResources();
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to reclaim resources on client " + clientId, e);
- return false;
+ Set<Integer> shareFeClientIds;
+ synchronized (mLock) {
+ ClientProfile profile = getClientProfile(reclaimingClientId);
+ if (profile == null) {
+ return true;
}
- clearAllResourcesAndClientMapping(getClientProfile(clientId));
+ shareFeClientIds = profile.getShareFeClientIds();
+ }
+ ResourcesReclaimListenerRecord listenerRecord = null;
+ for (int clientId : shareFeClientIds) {
+ synchronized (mLock) {
+ listenerRecord = mListeners.get(clientId);
+ }
+ if (listenerRecord != null) {
+ try {
+ listenerRecord.getListener().onReclaimResources();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to reclaim resources on client " + clientId, e);
+ }
+ }
}
if (DEBUG) {
Slog.d(TAG, "Reclaiming resources because higher priority client request resource type "
+ resourceType + ", clientId:" + reclaimingClientId);
}
- try {
- mListeners.get(reclaimingClientId).getListener().onReclaimResources();
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to reclaim resources on client " + reclaimingClientId, e);
- return false;
+
+ synchronized (mLock) {
+ listenerRecord = mListeners.get(reclaimingClientId);
}
- clearAllResourcesAndClientMapping(profile);
+ if (listenerRecord != null) {
+ try {
+ listenerRecord.getListener().onReclaimResources();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to reclaim resources on client " + reclaimingClientId, e);
+ }
+ }
+
return true;
}
@@ -2258,6 +2397,7 @@
addResourcesReclaimListener(clientId, listener);
}
+ @SuppressWarnings("GuardedBy") // Lock is held on mListeners
private void removeClientProfile(int clientId) {
for (int shareOwnerId : getClientProfile(clientId).getShareFeClientIds()) {
clearFrontendAndClientMapping(getClientProfile(shareOwnerId));
@@ -2265,12 +2405,9 @@
clearAllResourcesAndClientMapping(getClientProfile(clientId));
mClientProfiles.remove(clientId);
- // it may be called by unregisterClientProfileInternal under test
- synchronized (mLock) {
- ResourcesReclaimListenerRecord record = mListeners.remove(clientId);
- if (record != null) {
- record.getListener().asBinder().unlinkToDeath(record, 0);
- }
+ ResourcesReclaimListenerRecord record = mListeners.remove(clientId);
+ if (record != null) {
+ record.getListener().asBinder().unlinkToDeath(record, 0);
}
}
@@ -2304,7 +2441,8 @@
profile.releaseFrontend();
}
- private void clearAllResourcesAndClientMapping(ClientProfile profile) {
+ @VisibleForTesting
+ protected void clearAllResourcesAndClientMapping(ClientProfile profile) {
// TODO: check if this check is really needed. Maybe needed for reclaimResource path.
if (profile == null) {
return;
diff --git a/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java b/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java
new file mode 100644
index 0000000..b4d3862
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.ExternalVibration;
+import android.os.ExternalVibrationScale;
+import android.os.IBinder;
+import android.os.VibrationAttributes;
+import android.os.vibrator.Flags;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+/**
+ * A vibration session holding a single {@link ExternalVibration} request.
+ */
+final class ExternalVibrationSession extends Vibration
+ implements VibrationSession, IBinder.DeathRecipient {
+
+ private final ExternalVibration mExternalVibration;
+ private final ExternalVibrationScale mScale = new ExternalVibrationScale();
+
+ @Nullable
+ private Runnable mBinderDeathCallback;
+
+ ExternalVibrationSession(ExternalVibration externalVibration) {
+ super(externalVibration.getToken(), new CallerInfo(
+ externalVibration.getVibrationAttributes(), externalVibration.getUid(),
+ // TODO(b/249785241): Find a way to link ExternalVibration to a VirtualDevice
+ // instead of using DEVICE_ID_INVALID here and relying on the UID checks.
+ Context.DEVICE_ID_INVALID, externalVibration.getPackage(), null));
+ mExternalVibration = externalVibration;
+ }
+
+ public ExternalVibrationScale getScale() {
+ return mScale;
+ }
+
+ @Override
+ public CallerInfo getCallerInfo() {
+ return callerInfo;
+ }
+
+ @Override
+ public VibrationSession.DebugInfo getDebugInfo() {
+ return new Vibration.DebugInfoImpl(getStatus(), stats, /* playedEffect= */ null,
+ /* originalEffect= */ null, mScale.scaleLevel, mScale.adaptiveHapticsScale,
+ callerInfo);
+ }
+
+ @Override
+ public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
+ return new VibrationStats.StatsInfo(
+ mExternalVibration.getUid(),
+ FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__EXTERNAL,
+ mExternalVibration.getVibrationAttributes().getUsage(), getStatus(), stats,
+ completionUptimeMillis);
+ }
+
+ @Override
+ public boolean isRepeating() {
+ // We don't currently know if the external vibration is repeating, so we just use a
+ // heuristic based on the usage. Ideally this would be propagated in the ExternalVibration.
+ int usage = mExternalVibration.getVibrationAttributes().getUsage();
+ return usage == VibrationAttributes.USAGE_RINGTONE
+ || usage == VibrationAttributes.USAGE_ALARM;
+ }
+
+ @Override
+ public void linkToDeath(Runnable callback) {
+ synchronized (this) {
+ mBinderDeathCallback = callback;
+ }
+ mExternalVibration.linkToDeath(this);
+ }
+
+ @Override
+ public void unlinkToDeath() {
+ mExternalVibration.unlinkToDeath(this);
+ synchronized (this) {
+ mBinderDeathCallback = null;
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ synchronized (this) {
+ if (mBinderDeathCallback != null) {
+ mBinderDeathCallback.run();
+ }
+ }
+ }
+
+ @Override
+ void end(EndInfo endInfo) {
+ super.end(endInfo);
+ if (stats.hasStarted()) {
+ // External vibration doesn't have feedback from total time the vibrator was playing
+ // with non-zero amplitude, so we use the duration between start and end times of
+ // the vibration as the time the vibrator was ON, since the haptic channels are
+ // open for this duration and can receive vibration waveform data.
+ stats.reportVibratorOn(stats.getEndUptimeMillis() - stats.getStartUptimeMillis());
+ }
+ }
+
+ @Override
+ public void notifyEnded() {
+ // Notify external client that this vibration should stop sending data to the vibrator.
+ mExternalVibration.mute();
+ }
+
+ boolean isHoldingSameVibration(ExternalVibration vib) {
+ return mExternalVibration.equals(vib);
+ }
+
+ void muteScale() {
+ mScale.scaleLevel = ExternalVibrationScale.ScaleLevel.SCALE_MUTE;
+ if (Flags.hapticsScaleV2Enabled()) {
+ mScale.scaleFactor = 0;
+ }
+ }
+
+ void scale(VibrationScaler scaler, int usage) {
+ mScale.scaleLevel = scaler.getScaleLevel(usage);
+ if (Flags.hapticsScaleV2Enabled()) {
+ mScale.scaleFactor = scaler.getScaleFactor(usage);
+ }
+ mScale.adaptiveHapticsScale = scaler.getAdaptiveHapticsScale(usage);
+ stats.reportAdaptiveScale(mScale.adaptiveHapticsScale);
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/HalVibration.java b/services/core/java/com/android/server/vibrator/HalVibration.java
index fe0cf59..ea4bd01 100644
--- a/services/core/java/com/android/server/vibrator/HalVibration.java
+++ b/services/core/java/com/android/server/vibrator/HalVibration.java
@@ -51,37 +51,22 @@
@NonNull
private volatile CombinedVibration mEffectToPlay;
- /** Vibration status. */
- private Vibration.Status mStatus;
-
/** Reported scale values applied to the vibration effects. */
private int mScaleLevel;
private float mAdaptiveScale;
HalVibration(@NonNull IBinder token, @NonNull CombinedVibration effect,
- @NonNull CallerInfo callerInfo) {
+ @NonNull VibrationSession.CallerInfo callerInfo) {
super(token, callerInfo);
mOriginalEffect = effect;
mEffectToPlay = effect;
- mStatus = Vibration.Status.RUNNING;
mScaleLevel = VibrationScaler.SCALE_NONE;
mAdaptiveScale = VibrationScaler.ADAPTIVE_SCALE_NONE;
}
- /**
- * Set the {@link Status} of this vibration and reports the current system time as this
- * vibration end time, for debugging purposes.
- *
- * <p>This method will only accept given value if the current status is {@link
- * Status#RUNNING}.
- */
- public void end(EndInfo info) {
- if (hasEnded()) {
- // Vibration already ended, keep first ending status set and ignore this one.
- return;
- }
- mStatus = info.status;
- stats.reportEnded(info.endedBy);
+ @Override
+ public void end(EndInfo endInfo) {
+ super.end(endInfo);
mCompletionLatch.countDown();
}
@@ -144,11 +129,6 @@
// No need to update fallback effects, they are already configured per device.
}
- /** Return true is current status is different from {@link Status#RUNNING}. */
- public boolean hasEnded() {
- return mStatus != Status.RUNNING;
- }
-
@Override
public boolean isRepeating() {
return mOriginalEffect.getDuration() == Long.MAX_VALUE;
@@ -159,16 +139,16 @@
return mEffectToPlay;
}
- /** Return {@link Vibration.DebugInfo} with read-only debug information about this vibration. */
- public Vibration.DebugInfo getDebugInfo() {
+ @Override
+ public VibrationSession.DebugInfo getDebugInfo() {
// Clear the original effect if it's the same as the effect that was played, for simplicity
CombinedVibration originalEffect =
Objects.equals(mOriginalEffect, mEffectToPlay) ? null : mOriginalEffect;
- return new Vibration.DebugInfo(mStatus, stats, mEffectToPlay, originalEffect,
+ return new Vibration.DebugInfoImpl(getStatus(), stats, mEffectToPlay, originalEffect,
mScaleLevel, mAdaptiveScale, callerInfo);
}
- /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */
+ @Override
public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
int vibrationType = mEffectToPlay.hasVendorEffects()
? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__VENDOR
@@ -176,7 +156,7 @@
? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED
: FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
return new VibrationStats.StatsInfo(
- callerInfo.uid, vibrationType, callerInfo.attrs.getUsage(), mStatus,
+ callerInfo.uid, vibrationType, callerInfo.attrs.getUsage(), getStatus(),
stats, completionUptimeMillis);
}
diff --git a/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java b/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java
index 4e58b9a..83e05f4 100644
--- a/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java
+++ b/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java
@@ -26,6 +26,7 @@
import android.view.InputDevice;
import com.android.internal.annotations.GuardedBy;
+import com.android.server.vibrator.VibrationSession.CallerInfo;
/** Delegates vibrations to all connected {@link InputDevice} with one or more vibrators. */
final class InputDeviceDelegate implements InputManager.InputDeviceListener {
@@ -93,7 +94,7 @@
*
* @return {@link #isAvailable()}
*/
- public boolean vibrateIfAvailable(Vibration.CallerInfo callerInfo, CombinedVibration effect) {
+ public boolean vibrateIfAvailable(CallerInfo callerInfo, CombinedVibration effect) {
synchronized (mLock) {
for (int i = 0; i < mInputDeviceVibrators.size(); i++) {
mInputDeviceVibrators.valueAt(i).vibrate(callerInfo.uid, callerInfo.opPkg, effect,
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index aa4b9f3..9a04793 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -31,7 +31,6 @@
import android.util.IndentingPrintWriter;
import android.util.proto.ProtoOutputStream;
-import java.io.PrintWriter;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
@@ -52,131 +51,69 @@
private static final AtomicInteger sNextVibrationId = new AtomicInteger(1); // 0 = no callback
public final long id;
- public final CallerInfo callerInfo;
+ public final VibrationSession.CallerInfo callerInfo;
public final VibrationStats stats = new VibrationStats();
public final IBinder callerToken;
- /** Vibration status with reference to values from vibratormanagerservice.proto for logging. */
- enum Status {
- UNKNOWN(VibrationProto.UNKNOWN),
- RUNNING(VibrationProto.RUNNING),
- FINISHED(VibrationProto.FINISHED),
- FINISHED_UNEXPECTED(VibrationProto.FINISHED_UNEXPECTED),
- FORWARDED_TO_INPUT_DEVICES(VibrationProto.FORWARDED_TO_INPUT_DEVICES),
- CANCELLED_BINDER_DIED(VibrationProto.CANCELLED_BINDER_DIED),
- CANCELLED_BY_SCREEN_OFF(VibrationProto.CANCELLED_BY_SCREEN_OFF),
- CANCELLED_BY_SETTINGS_UPDATE(VibrationProto.CANCELLED_BY_SETTINGS_UPDATE),
- CANCELLED_BY_USER(VibrationProto.CANCELLED_BY_USER),
- CANCELLED_BY_FOREGROUND_USER(VibrationProto.CANCELLED_BY_FOREGROUND_USER),
- CANCELLED_BY_UNKNOWN_REASON(VibrationProto.CANCELLED_BY_UNKNOWN_REASON),
- CANCELLED_SUPERSEDED(VibrationProto.CANCELLED_SUPERSEDED),
- CANCELLED_BY_APP_OPS(VibrationProto.CANCELLED_BY_APP_OPS),
- IGNORED_ERROR_APP_OPS(VibrationProto.IGNORED_ERROR_APP_OPS),
- IGNORED_ERROR_CANCELLING(VibrationProto.IGNORED_ERROR_CANCELLING),
- IGNORED_ERROR_SCHEDULING(VibrationProto.IGNORED_ERROR_SCHEDULING),
- IGNORED_ERROR_TOKEN(VibrationProto.IGNORED_ERROR_TOKEN),
- IGNORED_APP_OPS(VibrationProto.IGNORED_APP_OPS),
- IGNORED_BACKGROUND(VibrationProto.IGNORED_BACKGROUND),
- IGNORED_MISSING_PERMISSION(VibrationProto.IGNORED_MISSING_PERMISSION),
- IGNORED_UNSUPPORTED(VibrationProto.IGNORED_UNSUPPORTED),
- IGNORED_FOR_EXTERNAL(VibrationProto.IGNORED_FOR_EXTERNAL),
- IGNORED_FOR_HIGHER_IMPORTANCE(VibrationProto.IGNORED_FOR_HIGHER_IMPORTANCE),
- IGNORED_FOR_ONGOING(VibrationProto.IGNORED_FOR_ONGOING),
- IGNORED_FOR_POWER(VibrationProto.IGNORED_FOR_POWER),
- IGNORED_FOR_RINGER_MODE(VibrationProto.IGNORED_FOR_RINGER_MODE),
- IGNORED_FOR_SETTINGS(VibrationProto.IGNORED_FOR_SETTINGS),
- IGNORED_SUPERSEDED(VibrationProto.IGNORED_SUPERSEDED),
- IGNORED_FROM_VIRTUAL_DEVICE(VibrationProto.IGNORED_FROM_VIRTUAL_DEVICE),
- IGNORED_ON_WIRELESS_CHARGER(VibrationProto.IGNORED_ON_WIRELESS_CHARGER);
+ private VibrationSession.Status mStatus;
- private final int mProtoEnumValue;
-
- Status(int value) {
- mProtoEnumValue = value;
- }
-
- public int getProtoEnumValue() {
- return mProtoEnumValue;
- }
- }
-
- Vibration(@NonNull IBinder token, @NonNull CallerInfo callerInfo) {
+ Vibration(@NonNull IBinder token, @NonNull VibrationSession.CallerInfo callerInfo) {
Objects.requireNonNull(token);
Objects.requireNonNull(callerInfo);
+ mStatus = VibrationSession.Status.RUNNING;
this.id = sNextVibrationId.getAndIncrement();
this.callerToken = token;
this.callerInfo = callerInfo;
}
+ VibrationSession.Status getStatus() {
+ return mStatus;
+ }
+
+ /** Return true is current status is different from {@link VibrationSession.Status#RUNNING}. */
+ boolean hasEnded() {
+ return mStatus != VibrationSession.Status.RUNNING;
+ }
+
+ /**
+ * Set the {@link VibrationSession} of this vibration and reports the current system time as
+ * this vibration end time, for debugging purposes.
+ *
+ * <p>This method will only accept given value if the current status is {@link
+ * VibrationSession.Status#RUNNING}.
+ */
+ void end(Vibration.EndInfo endInfo) {
+ if (hasEnded()) {
+ // Vibration already ended, keep first ending status set and ignore this one.
+ return;
+ }
+ mStatus = endInfo.status;
+ stats.reportEnded(endInfo.endedBy);
+ }
+
/** Return true if vibration is a repeating vibration. */
abstract boolean isRepeating();
- /**
- * Holds lightweight immutable info on the process that triggered the vibration. This data
- * could potentially be kept in memory for a long time for bugreport dumpsys operations.
- *
- * Since CallerInfo can be kept in memory for a long time, it shouldn't hold any references to
- * potentially expensive or resource-linked objects, such as {@link IBinder}.
- */
- static final class CallerInfo {
- public final VibrationAttributes attrs;
- public final int uid;
- public final int deviceId;
- public final String opPkg;
- public final String reason;
+ /** Return {@link VibrationSession.DebugInfo} with read-only debug data about this vibration. */
+ abstract VibrationSession.DebugInfo getDebugInfo();
- CallerInfo(@NonNull VibrationAttributes attrs, int uid, int deviceId, String opPkg,
- String reason) {
- Objects.requireNonNull(attrs);
- this.attrs = attrs;
- this.uid = uid;
- this.deviceId = deviceId;
- this.opPkg = opPkg;
- this.reason = reason;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof CallerInfo)) return false;
- CallerInfo that = (CallerInfo) o;
- return Objects.equals(attrs, that.attrs)
- && uid == that.uid
- && deviceId == that.deviceId
- && Objects.equals(opPkg, that.opPkg)
- && Objects.equals(reason, that.reason);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(attrs, uid, deviceId, opPkg, reason);
- }
-
- @Override
- public String toString() {
- return "CallerInfo{"
- + " uid=" + uid
- + ", opPkg=" + opPkg
- + ", deviceId=" + deviceId
- + ", attrs=" + attrs
- + ", reason=" + reason
- + '}';
- }
- }
+ /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */
+ abstract VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis);
/** Immutable info passed as a signal to end a vibration. */
static final class EndInfo {
- /** The {@link Status} to be set to the vibration when it ends with this info. */
+ /** The vibration status to be set when it ends with this info. */
@NonNull
- public final Status status;
+ public final VibrationSession.Status status;
/** Info about the process that ended the vibration. */
- public final CallerInfo endedBy;
+ public final VibrationSession.CallerInfo endedBy;
- EndInfo(@NonNull Vibration.Status status) {
+ EndInfo(@NonNull VibrationSession.Status status) {
this(status, null);
}
- EndInfo(@NonNull Vibration.Status status, @Nullable CallerInfo endedBy) {
+ EndInfo(@NonNull VibrationSession.Status status,
+ @Nullable VibrationSession.CallerInfo endedBy) {
this.status = status;
this.endedBy = endedBy;
}
@@ -211,10 +148,10 @@
* Since DebugInfo can be kept in memory for a long time, it shouldn't hold any references to
* potentially expensive or resource-linked objects, such as {@link IBinder}.
*/
- static final class DebugInfo {
- final Status mStatus;
+ static final class DebugInfoImpl implements VibrationSession.DebugInfo {
+ final VibrationSession.Status mStatus;
final long mCreateTime;
- final CallerInfo mCallerInfo;
+ final VibrationSession.CallerInfo mCallerInfo;
@Nullable
final CombinedVibration mPlayedEffect;
@@ -226,9 +163,10 @@
private final int mScaleLevel;
private final float mAdaptiveScale;
- DebugInfo(Status status, VibrationStats stats, @Nullable CombinedVibration playedEffect,
+ DebugInfoImpl(VibrationSession.Status status, VibrationStats stats,
+ @Nullable CombinedVibration playedEffect,
@Nullable CombinedVibration originalEffect, int scaleLevel,
- float adaptiveScale, @NonNull CallerInfo callerInfo) {
+ float adaptiveScale, @NonNull VibrationSession.CallerInfo callerInfo) {
Objects.requireNonNull(callerInfo);
mCreateTime = stats.getCreateTimeDebug();
mStartTime = stats.getStartTimeDebug();
@@ -243,6 +181,27 @@
}
@Override
+ public VibrationSession.Status getStatus() {
+ return mStatus;
+ }
+
+ @Override
+ public long getCreateUptimeMillis() {
+ return mCreateTime;
+ }
+
+ @Override
+ public VibrationSession.CallerInfo getCallerInfo() {
+ return mCallerInfo;
+ }
+
+ @Nullable
+ @Override
+ public Object getDumpAggregationKey() {
+ return mPlayedEffect;
+ }
+
+ @Override
public String toString() {
return "createTime: " + formatTime(mCreateTime, /*includeDate=*/ true)
+ ", startTime: " + formatTime(mStartTime, /*includeDate=*/ true)
@@ -257,17 +216,13 @@
+ ", callerInfo: " + mCallerInfo;
}
- void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
+ @Override
+ public void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
statsLogger.logVibrationAdaptiveHapticScale(mCallerInfo.uid, mAdaptiveScale);
}
- /**
- * Write this info in a compact way into given {@link PrintWriter}.
- *
- * <p>This is used by dumpsys to log multiple vibration records in single lines that are
- * easy to skim through by the sorted created time.
- */
- void dumpCompact(IndentingPrintWriter pw) {
+ @Override
+ public void dumpCompact(IndentingPrintWriter pw) {
boolean isExternalVibration = mPlayedEffect == null;
String timingsStr = String.format(Locale.ROOT,
"%s | %8s | %20s | duration: %5dms | start: %12s | end: %12s",
@@ -299,8 +254,8 @@
pw.println(timingsStr + paramStr + audioUsageStr + callerStr + effectStr);
}
- /** Write this info into given {@link PrintWriter}. */
- void dump(IndentingPrintWriter pw) {
+ @Override
+ public void dump(IndentingPrintWriter pw) {
pw.println("Vibration:");
pw.increaseIndent();
pw.println("status = " + mStatus.name().toLowerCase(Locale.ROOT));
@@ -317,8 +272,8 @@
pw.decreaseIndent();
}
- /** Write this info into given {@code fieldId} on {@link ProtoOutputStream}. */
- void dump(ProtoOutputStream proto, long fieldId) {
+ @Override
+ public void dump(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
proto.write(VibrationProto.START_TIME, mStartTime);
proto.write(VibrationProto.END_TIME, mEndTime);
diff --git a/services/core/java/com/android/server/vibrator/VibrationSession.java b/services/core/java/com/android/server/vibrator/VibrationSession.java
new file mode 100644
index 0000000..5640b49
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/VibrationSession.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.CombinedVibration;
+import android.os.IBinder;
+import android.os.VibrationAttributes;
+import android.util.IndentingPrintWriter;
+import android.util.proto.ProtoOutputStream;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+/**
+ * Represents a generic vibration session that plays one or more vibration requests.
+ *
+ * <p>This might represent:
+ *
+ * <ol>
+ * <li>A single {@link CombinedVibration} playback.
+ * <li>An {@link android.os.ExternalVibration} playback.
+ * </ol>
+ */
+interface VibrationSession {
+
+ /** Returns data about the client app that triggered this vibration session. */
+ CallerInfo getCallerInfo();
+
+ /** Returns debug data for logging and metric reports. */
+ DebugInfo getDebugInfo();
+
+ /**
+ * Links this session to the app process death with given callback to handle it.
+ *
+ * <p>This can be used by the service to end the vibration session when the app process dies.
+ */
+ void linkToDeath(Runnable callback);
+
+ /** Removes link to the app process death. */
+ void unlinkToDeath();
+
+ /** Notify the session end was requested, which might be acted upon asynchronously. */
+ void notifyEnded();
+
+ /**
+ * Session status with reference to values from vibratormanagerservice.proto for logging.
+ */
+ enum Status {
+ UNKNOWN(VibrationProto.UNKNOWN),
+ RUNNING(VibrationProto.RUNNING),
+ FINISHED(VibrationProto.FINISHED),
+ FINISHED_UNEXPECTED(VibrationProto.FINISHED_UNEXPECTED),
+ FORWARDED_TO_INPUT_DEVICES(VibrationProto.FORWARDED_TO_INPUT_DEVICES),
+ CANCELLED_BINDER_DIED(VibrationProto.CANCELLED_BINDER_DIED),
+ CANCELLED_BY_SCREEN_OFF(VibrationProto.CANCELLED_BY_SCREEN_OFF),
+ CANCELLED_BY_SETTINGS_UPDATE(VibrationProto.CANCELLED_BY_SETTINGS_UPDATE),
+ CANCELLED_BY_USER(VibrationProto.CANCELLED_BY_USER),
+ CANCELLED_BY_FOREGROUND_USER(VibrationProto.CANCELLED_BY_FOREGROUND_USER),
+ CANCELLED_BY_UNKNOWN_REASON(VibrationProto.CANCELLED_BY_UNKNOWN_REASON),
+ CANCELLED_SUPERSEDED(VibrationProto.CANCELLED_SUPERSEDED),
+ CANCELLED_BY_APP_OPS(VibrationProto.CANCELLED_BY_APP_OPS),
+ IGNORED_ERROR_APP_OPS(VibrationProto.IGNORED_ERROR_APP_OPS),
+ IGNORED_ERROR_CANCELLING(VibrationProto.IGNORED_ERROR_CANCELLING),
+ IGNORED_ERROR_SCHEDULING(VibrationProto.IGNORED_ERROR_SCHEDULING),
+ IGNORED_ERROR_TOKEN(VibrationProto.IGNORED_ERROR_TOKEN),
+ IGNORED_APP_OPS(VibrationProto.IGNORED_APP_OPS),
+ IGNORED_BACKGROUND(VibrationProto.IGNORED_BACKGROUND),
+ IGNORED_MISSING_PERMISSION(VibrationProto.IGNORED_MISSING_PERMISSION),
+ IGNORED_UNSUPPORTED(VibrationProto.IGNORED_UNSUPPORTED),
+ IGNORED_FOR_EXTERNAL(VibrationProto.IGNORED_FOR_EXTERNAL),
+ IGNORED_FOR_HIGHER_IMPORTANCE(VibrationProto.IGNORED_FOR_HIGHER_IMPORTANCE),
+ IGNORED_FOR_ONGOING(VibrationProto.IGNORED_FOR_ONGOING),
+ IGNORED_FOR_POWER(VibrationProto.IGNORED_FOR_POWER),
+ IGNORED_FOR_RINGER_MODE(VibrationProto.IGNORED_FOR_RINGER_MODE),
+ IGNORED_FOR_SETTINGS(VibrationProto.IGNORED_FOR_SETTINGS),
+ IGNORED_SUPERSEDED(VibrationProto.IGNORED_SUPERSEDED),
+ IGNORED_FROM_VIRTUAL_DEVICE(VibrationProto.IGNORED_FROM_VIRTUAL_DEVICE),
+ IGNORED_ON_WIRELESS_CHARGER(VibrationProto.IGNORED_ON_WIRELESS_CHARGER);
+
+ private final int mProtoEnumValue;
+
+ Status(int value) {
+ mProtoEnumValue = value;
+ }
+
+ public int getProtoEnumValue() {
+ return mProtoEnumValue;
+ }
+ }
+
+ /**
+ * Holds lightweight immutable info on the process that triggered the vibration session.
+ *
+ * <p>This data could potentially be kept in memory for a long time for bugreport dumpsys
+ * operations. It shouldn't hold any references to potentially expensive or resource-linked
+ * objects, such as {@link IBinder}.
+ */
+ final class CallerInfo {
+ public final VibrationAttributes attrs;
+ public final int uid;
+ public final int deviceId;
+ public final String opPkg;
+ public final String reason;
+
+ CallerInfo(@NonNull VibrationAttributes attrs, int uid, int deviceId, String opPkg,
+ String reason) {
+ Objects.requireNonNull(attrs);
+ this.attrs = attrs;
+ this.uid = uid;
+ this.deviceId = deviceId;
+ this.opPkg = opPkg;
+ this.reason = reason;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof CallerInfo)) return false;
+ CallerInfo that = (CallerInfo) o;
+ return Objects.equals(attrs, that.attrs)
+ && uid == that.uid
+ && deviceId == that.deviceId
+ && Objects.equals(opPkg, that.opPkg)
+ && Objects.equals(reason, that.reason);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(attrs, uid, deviceId, opPkg, reason);
+ }
+
+ @Override
+ public String toString() {
+ return "CallerInfo{"
+ + " uid=" + uid
+ + ", opPkg=" + opPkg
+ + ", deviceId=" + deviceId
+ + ", attrs=" + attrs
+ + ", reason=" + reason
+ + '}';
+ }
+ }
+
+ /**
+ * Interface for lightweight debug information about the vibration session for debugging.
+ *
+ * <p>This data could potentially be kept in memory for a long time for bugreport dumpsys
+ * operations. It shouldn't hold any references to potentially expensive or resource-linked
+ * objects, such as {@link IBinder}.
+ */
+ interface DebugInfo {
+
+ /** Return the vibration session status. */
+ Status getStatus();
+
+ /** Returns the session creation time from {@link android.os.SystemClock#uptimeMillis()}. */
+ long getCreateUptimeMillis();
+
+ /** Returns information about the process that created the session. */
+ CallerInfo getCallerInfo();
+
+ /**
+ * Returns the aggregation key for log records.
+ *
+ * <p>This is used to aggregate similar vibration sessions triggered in quick succession
+ * (e.g. multiple keyboard vibrations when the user is typing).
+ *
+ * <p>This does not need to include data from {@link CallerInfo} or {@link Status}.
+ *
+ * @see GroupedAggregatedLogRecords
+ */
+ @Nullable
+ Object getDumpAggregationKey();
+
+ /** Logs vibration session fields for metric reports. */
+ void logMetrics(VibratorFrameworkStatsLogger statsLogger);
+
+ /** Write this info into given {@code fieldId} on {@link ProtoOutputStream}. */
+ void dump(ProtoOutputStream proto, long fieldId);
+
+ /** Write this info into given {@link PrintWriter}. */
+ void dump(IndentingPrintWriter pw);
+
+ /**
+ * Write this info in a compact way into given {@link PrintWriter}.
+ *
+ * <p>This is used by dumpsys to log multiple records in single lines that are easy to skim
+ * through by the sorted created time.
+ */
+ void dumpCompact(IndentingPrintWriter pw);
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 0d6778c..69cdcf4 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -66,6 +66,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
+import com.android.server.vibrator.VibrationSession.CallerInfo;
+import com.android.server.vibrator.VibrationSession.Status;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -416,46 +418,46 @@
/**
* Check if given vibration should be ignored by the service.
*
- * @return One of Vibration.Status.IGNORED_* values if the vibration should be ignored,
+ * @return One of VibrationSession.Status.IGNORED_* values if the vibration should be ignored,
* null otherwise.
*/
@Nullable
- public Vibration.Status shouldIgnoreVibration(@NonNull Vibration.CallerInfo callerInfo) {
+ public Status shouldIgnoreVibration(@NonNull CallerInfo callerInfo) {
final int usage = callerInfo.attrs.getUsage();
synchronized (mLock) {
if (!mUidObserver.isUidForeground(callerInfo.uid)
&& !BACKGROUND_PROCESS_USAGE_ALLOWLIST.contains(usage)) {
- return Vibration.Status.IGNORED_BACKGROUND;
+ return Status.IGNORED_BACKGROUND;
}
if (callerInfo.deviceId != Context.DEVICE_ID_DEFAULT
&& callerInfo.deviceId != Context.DEVICE_ID_INVALID) {
- return Vibration.Status.IGNORED_FROM_VIRTUAL_DEVICE;
+ return Status.IGNORED_FROM_VIRTUAL_DEVICE;
}
if (callerInfo.deviceId == Context.DEVICE_ID_INVALID
&& isAppRunningOnAnyVirtualDevice(callerInfo.uid)) {
- return Vibration.Status.IGNORED_FROM_VIRTUAL_DEVICE;
+ return Status.IGNORED_FROM_VIRTUAL_DEVICE;
}
if (mBatterySaverMode && !BATTERY_SAVER_USAGE_ALLOWLIST.contains(usage)) {
- return Vibration.Status.IGNORED_FOR_POWER;
+ return Status.IGNORED_FOR_POWER;
}
if (!callerInfo.attrs.isFlagSet(
VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)
&& !shouldVibrateForUserSetting(callerInfo)) {
- return Vibration.Status.IGNORED_FOR_SETTINGS;
+ return Status.IGNORED_FOR_SETTINGS;
}
if (!callerInfo.attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)) {
if (!shouldVibrateForRingerModeLocked(usage)) {
- return Vibration.Status.IGNORED_FOR_RINGER_MODE;
+ return Status.IGNORED_FOR_RINGER_MODE;
}
}
if (mVibrationConfig.ignoreVibrationsOnWirelessCharger() && mOnWirelessCharger) {
- return Vibration.Status.IGNORED_ON_WIRELESS_CHARGER;
+ return Status.IGNORED_ON_WIRELESS_CHARGER;
}
}
return null;
@@ -471,7 +473,7 @@
*
* @return true if the vibration should be cancelled when the screen goes off, false otherwise.
*/
- public boolean shouldCancelVibrationOnScreenOff(@NonNull Vibration.CallerInfo callerInfo,
+ public boolean shouldCancelVibrationOnScreenOff(@NonNull CallerInfo callerInfo,
long vibrationStartUptimeMillis) {
PowerManagerInternal pm;
synchronized (mLock) {
@@ -483,8 +485,8 @@
// ignored here and not cancel a vibration, and those are usually triggered by timeout
// or inactivity, so it's unlikely that it will override a more active goToSleep reason.
PowerManager.SleepData sleepData = pm.getLastGoToSleep();
- if ((sleepData.goToSleepUptimeMillis < vibrationStartUptimeMillis)
- || POWER_MANAGER_SLEEP_REASON_ALLOWLIST.contains(sleepData.goToSleepReason)) {
+ if (sleepData != null && (sleepData.goToSleepUptimeMillis < vibrationStartUptimeMillis
+ || POWER_MANAGER_SLEEP_REASON_ALLOWLIST.contains(sleepData.goToSleepReason))) {
// Ignore screen off events triggered before the vibration started, and all
// automatic "go to sleep" events from allowlist.
Slog.d(TAG, "Ignoring screen off event triggered at uptime "
@@ -522,7 +524,7 @@
* {@code false} to ignore the vibration.
*/
@GuardedBy("mLock")
- private boolean shouldVibrateForUserSetting(Vibration.CallerInfo callerInfo) {
+ private boolean shouldVibrateForUserSetting(CallerInfo callerInfo) {
final int usage = callerInfo.attrs.getUsage();
if (!mVibrateOn && (VIBRATE_ON_DISABLED_USAGE_ALLOWED != usage)) {
// Main setting disabled.
diff --git a/services/core/java/com/android/server/vibrator/VibrationStats.java b/services/core/java/com/android/server/vibrator/VibrationStats.java
index 8179d6a..fc0c6e7 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStats.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStats.java
@@ -166,7 +166,7 @@
* @return true if the status was accepted. This method will only accept given values if
* the end timestamp was never set.
*/
- boolean reportEnded(@Nullable Vibration.CallerInfo endedBy) {
+ boolean reportEnded(@Nullable VibrationSession.CallerInfo endedBy) {
if (hasEnded()) {
// Vibration already ended, keep first ending stats set and ignore this one.
return false;
@@ -187,7 +187,7 @@
* <p>This method will only accept the first value as the one that was interrupted by this
* vibration, and will ignore all successive calls.
*/
- void reportInterruptedAnotherVibration(@NonNull Vibration.CallerInfo callerInfo) {
+ void reportInterruptedAnotherVibration(@NonNull VibrationSession.CallerInfo callerInfo) {
if (mInterruptedUsage < 0) {
mInterruptedUsage = callerInfo.attrs.getUsage();
}
@@ -330,7 +330,7 @@
public final int[] halUnsupportedEffectsUsed;
private boolean mIsWritten;
- StatsInfo(int uid, int vibrationType, int usage, Vibration.Status status,
+ StatsInfo(int uid, int vibrationType, int usage, VibrationSession.Status status,
VibrationStats stats, long completionUptimeMillis) {
this.uid = uid;
this.vibrationType = vibrationType;
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index 7152844..5137d19 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -32,6 +32,7 @@
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.server.vibrator.VibrationSession.Status;
import java.util.ArrayList;
import java.util.Iterator;
@@ -217,7 +218,7 @@
}
/**
- * Calculate the {@link Vibration.Status} based on the current queue state and the expected
+ * Calculate the {@link Vibration.EndInfo} based on the current queue state and the expected
* number of {@link StartSequentialEffectStep} to be played.
*/
@Nullable
@@ -235,10 +236,10 @@
}
// No pending steps, and something happened.
if (mSuccessfulVibratorOnSteps > 0) {
- return new Vibration.EndInfo(Vibration.Status.FINISHED);
+ return new Vibration.EndInfo(Status.FINISHED);
}
// If no step was able to turn the vibrator ON successfully.
- return new Vibration.EndInfo(Vibration.Status.IGNORED_UNSUPPORTED);
+ return new Vibration.EndInfo(Status.IGNORED_UNSUPPORTED);
}
/**
@@ -352,7 +353,7 @@
if (DEBUG) {
Slog.d(TAG, "Binder died, cancelling vibration...");
}
- notifyCancelled(new Vibration.EndInfo(Vibration.Status.CANCELLED_BINDER_DIED),
+ notifyCancelled(new Vibration.EndInfo(Status.CANCELLED_BINDER_DIED),
/* immediate= */ false);
}
@@ -377,7 +378,7 @@
if ((cancelInfo == null) || !cancelInfo.status.name().startsWith("CANCEL")) {
Slog.w(TAG, "Vibration cancel requested with bad signal=" + cancelInfo
+ ", using CANCELLED_UNKNOWN_REASON to ensure cancellation.");
- cancelInfo = new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_UNKNOWN_REASON);
+ cancelInfo = new Vibration.EndInfo(Status.CANCELLED_BY_UNKNOWN_REASON);
}
synchronized (mLock) {
if ((immediate && mSignalCancelImmediate) || (mSignalCancel != null)) {
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index cfb4c74..ab4a4d8 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -29,6 +29,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.vibrator.VibrationSession.Status;
import java.util.NoSuchElementException;
import java.util.Objects;
@@ -240,7 +241,7 @@
runCurrentVibrationWithWakeLockAndDeathLink();
} finally {
clientVibrationCompleteIfNotAlready(
- new Vibration.EndInfo(Vibration.Status.FINISHED_UNEXPECTED));
+ new Vibration.EndInfo(Status.FINISHED_UNEXPECTED));
}
} finally {
mWakeLock.release();
@@ -259,7 +260,7 @@
} catch (RemoteException e) {
Slog.e(TAG, "Error linking vibration to token death", e);
clientVibrationCompleteIfNotAlready(
- new Vibration.EndInfo(Vibration.Status.IGNORED_ERROR_TOKEN));
+ new Vibration.EndInfo(Status.IGNORED_ERROR_TOKEN));
return;
}
// Ensure that the unlink always occurs now.
diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java
index de5e662..3a814cd 100644
--- a/services/core/java/com/android/server/vibrator/VibratorControlService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java
@@ -559,8 +559,8 @@
}
/**
- * Record for a single {@link Vibration.DebugInfo}, that can be grouped by usage and aggregated
- * by UID, {@link VibrationAttributes} and {@link VibrationEffect}.
+ * Record for a single {@link VibrationSession.DebugInfo}, that can be grouped by usage and
+ * aggregated by UID, {@link VibrationAttributes} and {@link VibrationEffect}.
*/
private static final class VibrationScaleParamRecord
implements GroupedAggregatedLogRecords.SingleLogRecord {
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index c143beb..799934a 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -73,9 +73,11 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
import com.android.internal.util.DumpUtils;
-import com.android.internal.util.FrameworkStatsLog;
import com.android.server.SystemService;
import com.android.server.pm.BackgroundUserSoundNotifier;
+import com.android.server.vibrator.VibrationSession.CallerInfo;
+import com.android.server.vibrator.VibrationSession.DebugInfo;
+import com.android.server.vibrator.VibrationSession.Status;
import libcore.util.NativeAllocationRegistry;
@@ -160,11 +162,12 @@
@GuardedBy("mLock")
private VibrationStepConductor mNextVibration;
@GuardedBy("mLock")
- private ExternalVibrationHolder mCurrentExternalVibration;
+ private ExternalVibrationSession mCurrentExternalVibration;
@GuardedBy("mLock")
private boolean mServiceReady;
- private final VibrationSettings mVibrationSettings;
+ @VisibleForTesting
+ final VibrationSettings mVibrationSettings;
private final VibrationScaler mVibrationScaler;
private final VibratorControlService mVibratorControlService;
private final InputDeviceDelegate mInputDeviceDelegate;
@@ -184,13 +187,12 @@
// When the system is entering a non-interactive state, we want to cancel
// vibrations in case a misbehaving app has abandoned them.
if (shouldCancelOnScreenOffLocked(mNextVibration)) {
- clearNextVibrationLocked(
- new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF));
+ clearNextVibrationLocked(new Vibration.EndInfo(
+ Status.CANCELLED_BY_SCREEN_OFF));
}
if (shouldCancelOnScreenOffLocked(mCurrentVibration)) {
- mCurrentVibration.notifyCancelled(
- new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
- /* immediate= */ false);
+ mCurrentVibration.notifyCancelled(new Vibration.EndInfo(
+ Status.CANCELLED_BY_SCREEN_OFF), /* immediate= */ false);
}
}
} else if (android.multiuser.Flags.addUiForSoundsFromBackgroundUsers()
@@ -198,12 +200,11 @@
synchronized (mLock) {
if (shouldCancelOnFgUserRequest(mNextVibration)) {
clearNextVibrationLocked(new Vibration.EndInfo(
- Vibration.Status.CANCELLED_BY_FOREGROUND_USER));
+ Status.CANCELLED_BY_FOREGROUND_USER));
}
if (shouldCancelOnFgUserRequest(mCurrentVibration)) {
mCurrentVibration.notifyCancelled(new Vibration.EndInfo(
- Vibration.Status.CANCELLED_BY_FOREGROUND_USER),
- /* immediate= */ false);
+ Status.CANCELLED_BY_FOREGROUND_USER), /* immediate= */ false);
}
}
}
@@ -220,12 +221,12 @@
}
synchronized (mLock) {
if (shouldCancelAppOpModeChangedLocked(mNextVibration)) {
- clearNextVibrationLocked(
- new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_APP_OPS));
+ clearNextVibrationLocked(new Vibration.EndInfo(
+ Status.CANCELLED_BY_APP_OPS));
}
if (shouldCancelAppOpModeChangedLocked(mCurrentVibration)) {
mCurrentVibration.notifyCancelled(new Vibration.EndInfo(
- Vibration.Status.CANCELLED_BY_APP_OPS), /* immediate= */ false);
+ Status.CANCELLED_BY_APP_OPS), /* immediate= */ false);
}
}
}
@@ -441,8 +442,8 @@
return false;
}
AlwaysOnVibration alwaysOnVibration = new AlwaysOnVibration(alwaysOnId,
- new Vibration.CallerInfo(attrs, uid, Context.DEVICE_ID_DEFAULT, opPkg,
- null), effects);
+ new CallerInfo(attrs, uid, Context.DEVICE_ID_DEFAULT, opPkg, null),
+ effects);
mAlwaysOnEffects.put(alwaysOnId, alwaysOnVibration);
updateAlwaysOnLocked(alwaysOnVibration);
}
@@ -490,8 +491,7 @@
// Make sure we report the constant id in the requested haptic feedback reason.
reason = "performHapticFeedback(constant=" + constant + "): " + reason;
HapticFeedbackVibrationProvider hapticVibrationProvider = getHapticVibrationProvider();
- Vibration.Status ignoreStatus = shouldIgnoreHapticFeedback(constant, reason,
- hapticVibrationProvider);
+ Status ignoreStatus = shouldIgnoreHapticFeedback(constant, reason, hapticVibrationProvider);
if (ignoreStatus != null) {
logAndRecordPerformHapticFeedbackAttempt(uid, deviceId, opPkg, reason, ignoreStatus);
return null;
@@ -516,8 +516,7 @@
reason = "performHapticFeedbackForInputDevice(constant=" + constant + ", inputDeviceId="
+ inputDeviceId + ", inputSource=" + inputSource + "): " + reason;
HapticFeedbackVibrationProvider hapticVibrationProvider = getHapticVibrationProvider();
- Vibration.Status ignoreStatus = shouldIgnoreHapticFeedback(constant, reason,
- hapticVibrationProvider);
+ Status ignoreStatus = shouldIgnoreHapticFeedback(constant, reason, hapticVibrationProvider);
if (ignoreStatus != null) {
logAndRecordPerformHapticFeedbackAttempt(uid, deviceId, opPkg, reason, ignoreStatus);
return null;
@@ -533,7 +532,7 @@
VibrationAttributes attrs) {
if (effect == null) {
logAndRecordPerformHapticFeedbackAttempt(uid, deviceId, opPkg, reason,
- Vibration.Status.IGNORED_UNSUPPORTED);
+ Status.IGNORED_UNSUPPORTED);
Slog.w(TAG,
"performHapticFeedbackWithEffect; vibration absent for constant " + constant);
return null;
@@ -578,23 +577,21 @@
private HalVibration vibrateInternal(int uid, int deviceId, String opPkg,
@NonNull CombinedVibration effect, @NonNull VibrationAttributes attrs,
String reason, IBinder token) {
- Vibration.CallerInfo callerInfo =
- new Vibration.CallerInfo(attrs, uid, deviceId, opPkg, reason);
+ CallerInfo callerInfo = new CallerInfo(attrs, uid, deviceId, opPkg, reason);
if (token == null) {
Slog.e(TAG, "token must not be null");
- logAndRecordVibrationAttempt(effect, callerInfo, Vibration.Status.IGNORED_ERROR_TOKEN);
+ logAndRecordVibrationAttempt(effect, callerInfo, Status.IGNORED_ERROR_TOKEN);
return null;
}
if (effect.hasVendorEffects()
&& !hasPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS)) {
Slog.e(TAG, "vibrate; no permission for vendor effects");
- logAndRecordVibrationAttempt(effect, callerInfo,
- Vibration.Status.IGNORED_MISSING_PERMISSION);
+ logAndRecordVibrationAttempt(effect, callerInfo, Status.IGNORED_MISSING_PERMISSION);
return null;
}
enforceUpdateAppOpsStatsPermission(uid);
if (!isEffectValid(effect)) {
- logAndRecordVibrationAttempt(effect, callerInfo, Vibration.Status.IGNORED_UNSUPPORTED);
+ logAndRecordVibrationAttempt(effect, callerInfo, Status.IGNORED_UNSUPPORTED);
return null;
}
// Create Vibration.Stats as close to the received request as possible, for tracking.
@@ -625,11 +622,11 @@
final long ident = Binder.clearCallingIdentity();
try {
if (mCurrentExternalVibration != null) {
- mCurrentExternalVibration.mute();
+ mCurrentExternalVibration.notifyEnded();
vib.stats.reportInterruptedAnotherVibration(
mCurrentExternalVibration.callerInfo);
endExternalVibrateLocked(
- new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED,
+ new Vibration.EndInfo(Status.CANCELLED_SUPERSEDED,
vib.callerInfo),
/* continueExternalControl= */ false);
} else if (mCurrentVibration != null) {
@@ -645,7 +642,7 @@
vib.stats.reportInterruptedAnotherVibration(
mCurrentVibration.getVibration().callerInfo);
mCurrentVibration.notifyCancelled(
- new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED,
+ new Vibration.EndInfo(Status.CANCELLED_SUPERSEDED,
vib.callerInfo),
/* immediate= */ false);
}
@@ -677,7 +674,7 @@
Slog.d(TAG, "Canceling vibration");
}
Vibration.EndInfo cancelledByUserInfo =
- new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER);
+ new Vibration.EndInfo(Status.CANCELLED_BY_USER);
final long ident = Binder.clearCallingIdentity();
try {
if (mNextVibration != null
@@ -693,9 +690,9 @@
}
if (mCurrentExternalVibration != null
&& shouldCancelVibration(
- mCurrentExternalVibration.externalVibration.getVibrationAttributes(),
+ mCurrentExternalVibration.getCallerInfo().attrs,
usageFilter)) {
- mCurrentExternalVibration.mute();
+ mCurrentExternalVibration.notifyEnded();
endExternalVibrateLocked(
cancelledByUserInfo, /* continueExternalControl= */ false);
}
@@ -860,7 +857,7 @@
: vibrationEndInfo.status));
}
mCurrentVibration.notifyCancelled(
- new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE),
+ new Vibration.EndInfo(Status.CANCELLED_BY_SETTINGS_UPDATE),
/* immediate= */ false);
}
}
@@ -911,7 +908,7 @@
// Note that we don't consider pipelining here, because new pipelined ones should
// replace pending non-executing pipelined ones anyway.
clearNextVibrationLocked(
- new Vibration.EndInfo(Vibration.Status.IGNORED_SUPERSEDED, vib.callerInfo));
+ new Vibration.EndInfo(Status.IGNORED_SUPERSEDED, vib.callerInfo));
mNextVibration = conductor;
return null;
} finally {
@@ -934,15 +931,15 @@
if (!mVibrationThread.runVibrationOnVibrationThread(mCurrentVibration)) {
// Shouldn't happen. The method call already logs a wtf.
mCurrentVibration = null; // Aborted.
- return new Vibration.EndInfo(Vibration.Status.IGNORED_ERROR_SCHEDULING);
+ return new Vibration.EndInfo(Status.IGNORED_ERROR_SCHEDULING);
}
return null;
case AppOpsManager.MODE_ERRORED:
Slog.w(TAG, "Start AppOpsManager operation errored for uid "
+ vib.callerInfo.uid);
- return new Vibration.EndInfo(Vibration.Status.IGNORED_ERROR_APP_OPS);
+ return new Vibration.EndInfo(Status.IGNORED_ERROR_APP_OPS);
default:
- return new Vibration.EndInfo(Vibration.Status.IGNORED_APP_OPS);
+ return new Vibration.EndInfo(Status.IGNORED_APP_OPS);
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
@@ -950,7 +947,7 @@
}
@GuardedBy("mLock")
- private void endVibrationLocked(HalVibration vib, Vibration.EndInfo vibrationEndInfo,
+ private void endVibrationLocked(Vibration vib, Vibration.EndInfo vibrationEndInfo,
boolean shouldWriteStats) {
vib.end(vibrationEndInfo);
logAndRecordVibration(vib.getDebugInfo());
@@ -960,15 +957,6 @@
}
}
- @GuardedBy("mLock")
- private void endVibrationAndWriteStatsLocked(ExternalVibrationHolder vib,
- Vibration.EndInfo vibrationEndInfo) {
- vib.end(vibrationEndInfo);
- logAndRecordVibration(vib.getDebugInfo());
- mFrameworkStatsLogger.writeVibrationReportedAsync(
- vib.getStatsInfo(/* completionUptimeMillis= */ SystemClock.uptimeMillis()));
- }
-
private VibrationStepConductor createVibrationStepConductor(HalVibration vib) {
CompletableFuture<Void> requestVibrationParamsFuture = null;
@@ -990,33 +978,32 @@
vib.scaleEffects(mVibrationScaler);
mInputDeviceDelegate.vibrateIfAvailable(vib.callerInfo, vib.getEffectToPlay());
- return new Vibration.EndInfo(Vibration.Status.FORWARDED_TO_INPUT_DEVICES);
+ return new Vibration.EndInfo(Status.FORWARDED_TO_INPUT_DEVICES);
}
private void logAndRecordPerformHapticFeedbackAttempt(int uid, int deviceId, String opPkg,
- String reason, Vibration.Status status) {
- Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(
+ String reason, Status status) {
+ CallerInfo callerInfo = new CallerInfo(
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_UNKNOWN),
uid, deviceId, opPkg, reason);
logAndRecordVibrationAttempt(/* effect= */ null, callerInfo, status);
}
private void logAndRecordVibrationAttempt(@Nullable CombinedVibration effect,
- Vibration.CallerInfo callerInfo, Vibration.Status status) {
+ CallerInfo callerInfo, Status status) {
logAndRecordVibration(
- new Vibration.DebugInfo(status, new VibrationStats(),
+ new Vibration.DebugInfoImpl(status, new VibrationStats(),
effect, /* originalEffect= */ null, VibrationScaler.SCALE_NONE,
VibrationScaler.ADAPTIVE_SCALE_NONE, callerInfo));
}
- private void logAndRecordVibration(Vibration.DebugInfo info) {
+ private void logAndRecordVibration(DebugInfo info) {
info.logMetrics(mFrameworkStatsLogger);
- logVibrationStatus(info.mCallerInfo.uid, info.mCallerInfo.attrs, info.mStatus);
+ logVibrationStatus(info.getCallerInfo().uid, info.getCallerInfo().attrs, info.getStatus());
mVibratorManagerRecords.record(info);
}
- private void logVibrationStatus(int uid, VibrationAttributes attrs,
- Vibration.Status status) {
+ private void logVibrationStatus(int uid, VibrationAttributes attrs, Status status) {
switch (status) {
case IGNORED_BACKGROUND:
Slog.e(TAG, "Ignoring incoming vibration as process with"
@@ -1161,15 +1148,14 @@
if (ongoingVibrationImportance > newVibrationImportance) {
// Existing vibration has higher importance and should not be cancelled.
- return new Vibration.EndInfo(Vibration.Status.IGNORED_FOR_HIGHER_IMPORTANCE,
+ return new Vibration.EndInfo(Status.IGNORED_FOR_HIGHER_IMPORTANCE,
ongoingVibration.callerInfo);
}
// Same importance, use repeating as a tiebreaker.
if (ongoingVibration.isRepeating() && !newVibration.isRepeating()) {
// Ongoing vibration is repeating and new one is not, give priority to ongoing
- return new Vibration.EndInfo(Vibration.Status.IGNORED_FOR_ONGOING,
- ongoingVibration.callerInfo);
+ return new Vibration.EndInfo(Status.IGNORED_FOR_ONGOING, ongoingVibration.callerInfo);
}
// New vibration is repeating or this is a complete tie between them,
// give priority to new vibration.
@@ -1220,8 +1206,8 @@
*/
@GuardedBy("mLock")
@Nullable
- private Vibration.EndInfo shouldIgnoreVibrationLocked(Vibration.CallerInfo callerInfo) {
- Vibration.Status statusFromSettings = mVibrationSettings.shouldIgnoreVibration(callerInfo);
+ private Vibration.EndInfo shouldIgnoreVibrationLocked(CallerInfo callerInfo) {
+ Status statusFromSettings = mVibrationSettings.shouldIgnoreVibration(callerInfo);
if (statusFromSettings != null) {
return new Vibration.EndInfo(statusFromSettings);
}
@@ -1231,9 +1217,9 @@
if (mode == AppOpsManager.MODE_ERRORED) {
// We might be getting calls from within system_server, so we don't actually
// want to throw a SecurityException here.
- return new Vibration.EndInfo(Vibration.Status.IGNORED_ERROR_APP_OPS);
+ return new Vibration.EndInfo(Status.IGNORED_ERROR_APP_OPS);
} else {
- return new Vibration.EndInfo(Vibration.Status.IGNORED_APP_OPS);
+ return new Vibration.EndInfo(Status.IGNORED_APP_OPS);
}
}
@@ -1241,16 +1227,16 @@
}
@Nullable
- private Vibration.Status shouldIgnoreHapticFeedback(int constant, String reason,
+ private Status shouldIgnoreHapticFeedback(int constant, String reason,
HapticFeedbackVibrationProvider hapticVibrationProvider) {
if (hapticVibrationProvider == null) {
Slog.e(TAG, reason + "; haptic vibration provider not ready.");
- return Vibration.Status.IGNORED_ERROR_SCHEDULING;
+ return Status.IGNORED_ERROR_SCHEDULING;
}
if (hapticVibrationProvider.isRestrictedHapticFeedback(constant)
&& !hasPermission(android.Manifest.permission.VIBRATE_SYSTEM_CONSTANTS)) {
Slog.w(TAG, reason + "; no permission for system constant " + constant);
- return Vibration.Status.IGNORED_MISSING_PERMISSION;
+ return Status.IGNORED_MISSING_PERMISSION;
}
return null;
}
@@ -1291,7 +1277,7 @@
* {@code attrs}. This will return one of the AppOpsManager.MODE_*.
*/
@GuardedBy("mLock")
- private int checkAppOpModeLocked(Vibration.CallerInfo callerInfo) {
+ private int checkAppOpModeLocked(CallerInfo callerInfo) {
int mode = mAppOps.checkAudioOpNoThrow(AppOpsManager.OP_VIBRATE,
callerInfo.attrs.getAudioUsage(), callerInfo.uid, callerInfo.opPkg);
int fixedMode = fixupAppOpModeLocked(mode, callerInfo.attrs);
@@ -1306,7 +1292,7 @@
/** Start an operation in {@link AppOpsManager}, if allowed. */
@GuardedBy("mLock")
- private int startAppOpModeLocked(Vibration.CallerInfo callerInfo) {
+ private int startAppOpModeLocked(CallerInfo callerInfo) {
return fixupAppOpModeLocked(
mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, callerInfo.uid, callerInfo.opPkg),
callerInfo.attrs);
@@ -1317,7 +1303,7 @@
* operation with same uid was previously started.
*/
@GuardedBy("mLock")
- private void finishAppOpModeLocked(Vibration.CallerInfo callerInfo) {
+ private void finishAppOpModeLocked(CallerInfo callerInfo) {
mAppOps.finishOp(AppOpsManager.OP_VIBRATE, callerInfo.uid, callerInfo.opPkg);
}
@@ -1735,10 +1721,10 @@
*/
private static final class AlwaysOnVibration {
public final int alwaysOnId;
- public final Vibration.CallerInfo callerInfo;
+ public final CallerInfo callerInfo;
public final SparseArray<PrebakedSegment> effects;
- AlwaysOnVibration(int alwaysOnId, Vibration.CallerInfo callerInfo,
+ AlwaysOnVibration(int alwaysOnId, CallerInfo callerInfo,
SparseArray<PrebakedSegment> effects) {
this.alwaysOnId = alwaysOnId;
this.callerInfo = callerInfo;
@@ -1746,113 +1732,6 @@
}
}
- /** Holder for a {@link ExternalVibration}. */
- private final class ExternalVibrationHolder extends Vibration implements
- IBinder.DeathRecipient {
-
- public final ExternalVibration externalVibration;
- public final ExternalVibrationScale scale = new ExternalVibrationScale();
-
- private Vibration.Status mStatus;
-
- private ExternalVibrationHolder(ExternalVibration externalVibration) {
- super(externalVibration.getToken(), new Vibration.CallerInfo(
- externalVibration.getVibrationAttributes(), externalVibration.getUid(),
- // TODO(b/249785241): Find a way to link ExternalVibration to a VirtualDevice
- // instead of using DEVICE_ID_INVALID here and relying on the UID checks.
- Context.DEVICE_ID_INVALID, externalVibration.getPackage(), null));
- this.externalVibration = externalVibration;
- mStatus = Vibration.Status.RUNNING;
- }
-
- public void muteScale() {
- scale.scaleLevel = ExternalVibrationScale.ScaleLevel.SCALE_MUTE;
- if (Flags.hapticsScaleV2Enabled()) {
- scale.scaleFactor = 0;
- }
- }
-
- public void scale(VibrationScaler scaler, int usage) {
- scale.scaleLevel = scaler.getScaleLevel(usage);
- if (Flags.hapticsScaleV2Enabled()) {
- scale.scaleFactor = scaler.getScaleFactor(usage);
- }
- scale.adaptiveHapticsScale = scaler.getAdaptiveHapticsScale(usage);
- stats.reportAdaptiveScale(scale.adaptiveHapticsScale);
- }
-
- public void mute() {
- externalVibration.mute();
- }
-
- public void linkToDeath() {
- externalVibration.linkToDeath(this);
- }
-
- public void unlinkToDeath() {
- externalVibration.unlinkToDeath(this);
- }
-
- public boolean isHoldingSameVibration(ExternalVibration externalVibration) {
- return this.externalVibration.equals(externalVibration);
- }
-
- public void end(Vibration.EndInfo info) {
- if (mStatus != Vibration.Status.RUNNING) {
- // Already ended, ignore this call
- return;
- }
- mStatus = info.status;
- stats.reportEnded(info.endedBy);
-
- if (stats.hasStarted()) {
- // External vibration doesn't have feedback from total time the vibrator was playing
- // with non-zero amplitude, so we use the duration between start and end times of
- // the vibration as the time the vibrator was ON, since the haptic channels are
- // open for this duration and can receive vibration waveform data.
- stats.reportVibratorOn(
- stats.getEndUptimeMillis() - stats.getStartUptimeMillis());
- }
- }
-
- public void binderDied() {
- synchronized (mLock) {
- if (mCurrentExternalVibration != null) {
- if (DEBUG) {
- Slog.d(TAG, "External vibration finished because binder died");
- }
- endExternalVibrateLocked(
- new Vibration.EndInfo(Vibration.Status.CANCELLED_BINDER_DIED),
- /* continueExternalControl= */ false);
- }
- }
- }
-
- public Vibration.DebugInfo getDebugInfo() {
- return new Vibration.DebugInfo(mStatus, stats, /* playedEffect= */ null,
- /* originalEffect= */ null, scale.scaleLevel, scale.adaptiveHapticsScale,
- callerInfo);
- }
-
- public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
- return new VibrationStats.StatsInfo(
- externalVibration.getUid(),
- FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__EXTERNAL,
- externalVibration.getVibrationAttributes().getUsage(), mStatus, stats,
- completionUptimeMillis);
- }
-
- @Override
- boolean isRepeating() {
- // We don't currently know if the external vibration is repeating, so we just use a
- // heuristic based on the usage. Ideally this would be propagated in the
- // ExternalVibration.
- int usage = externalVibration.getVibrationAttributes().getUsage();
- return usage == VibrationAttributes.USAGE_RINGTONE
- || usage == VibrationAttributes.USAGE_ALARM;
- }
- }
-
/** Wrapper around the static-native methods of {@link VibratorManagerService} for tests. */
@VisibleForTesting
public static class NativeWrapper {
@@ -1912,7 +1791,7 @@
new VibrationRecords(recentVibrationSizeLimit, /* aggregationTimeLimit= */ 0);
}
- synchronized void record(Vibration.DebugInfo info) {
+ synchronized void record(DebugInfo info) {
GroupedAggregatedLogRecords.AggregatedLogRecord<VibrationRecord> droppedRecord =
mRecentVibrations.add(new VibrationRecord(info));
if (droppedRecord != null) {
@@ -1969,25 +1848,25 @@
}
/**
- * Record for a single {@link Vibration.DebugInfo}, that can be grouped by usage and aggregated
- * by UID, {@link VibrationAttributes} and {@link VibrationEffect}.
+ * Record for a single {@link DebugInfo}, that can be grouped by usage and aggregated by UID,
+ * {@link VibrationAttributes} and {@link CombinedVibration}.
*/
private static final class VibrationRecord
implements GroupedAggregatedLogRecords.SingleLogRecord {
- private final Vibration.DebugInfo mInfo;
+ private final DebugInfo mInfo;
- VibrationRecord(Vibration.DebugInfo info) {
+ VibrationRecord(DebugInfo info) {
mInfo = info;
}
@Override
public int getGroupKey() {
- return mInfo.mCallerInfo.attrs.getUsage();
+ return mInfo.getCallerInfo().attrs.getUsage();
}
@Override
public long getCreateUptimeMs() {
- return mInfo.mCreateTime;
+ return mInfo.getCreateUptimeMillis();
}
@Override
@@ -1995,10 +1874,10 @@
if (!(record instanceof VibrationRecord)) {
return false;
}
- Vibration.DebugInfo info = ((VibrationRecord) record).mInfo;
- return mInfo.mCallerInfo.uid == info.mCallerInfo.uid
- && Objects.equals(mInfo.mCallerInfo.attrs, info.mCallerInfo.attrs)
- && Objects.equals(mInfo.mPlayedEffect, info.mPlayedEffect);
+ DebugInfo info = ((VibrationRecord) record).mInfo;
+ return mInfo.getCallerInfo().uid == info.getCallerInfo().uid
+ && Objects.equals(mInfo.getCallerInfo().attrs, info.getCallerInfo().attrs)
+ && Objects.equals(mInfo.getDumpAggregationKey(), info.getDumpAggregationKey());
}
@Override
@@ -2048,7 +1927,8 @@
setExternalControl(false, mCurrentExternalVibration.stats);
}
// The external control was turned off, end it and report metrics right away.
- endVibrationAndWriteStatsLocked(mCurrentExternalVibration, vibrationEndInfo);
+ endVibrationLocked(mCurrentExternalVibration, vibrationEndInfo,
+ /* shouldWriteStats= */ true);
mCurrentExternalVibration = null;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
@@ -2108,17 +1988,18 @@
@Override
public ExternalVibrationScale onExternalVibrationStart(ExternalVibration vib) {
// Create Vibration.Stats as close to the received request as possible, for tracking.
- ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib);
+ ExternalVibrationSession externalVibration = new ExternalVibrationSession(vib);
// Mute the request until we run all the checks and accept the vibration.
- vibHolder.muteScale();
+ externalVibration.muteScale();
boolean alreadyUnderExternalControl = false;
boolean waitForCompletion = false;
synchronized (mLock) {
if (!hasExternalControlCapability()) {
- endVibrationAndWriteStatsLocked(vibHolder,
- new Vibration.EndInfo(Vibration.Status.IGNORED_UNSUPPORTED));
- return vibHolder.scale;
+ endVibrationLocked(externalVibration,
+ new Vibration.EndInfo(Status.IGNORED_UNSUPPORTED),
+ /* shouldWriteStats= */ true);
+ return externalVibration.getScale();
}
if (ActivityManager.checkComponentPermission(android.Manifest.permission.VIBRATE,
@@ -2127,44 +2008,46 @@
Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid()
+ " tried to play externally controlled vibration"
+ " without VIBRATE permission, ignoring.");
- endVibrationAndWriteStatsLocked(vibHolder,
- new Vibration.EndInfo(Vibration.Status.IGNORED_MISSING_PERMISSION));
- return vibHolder.scale;
+ endVibrationLocked(externalVibration,
+ new Vibration.EndInfo(Status.IGNORED_MISSING_PERMISSION),
+ /* shouldWriteStats= */ true);
+ return externalVibration.getScale();
}
Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(
- vibHolder.callerInfo);
+ externalVibration.callerInfo);
if (vibrationEndInfo == null
&& mCurrentExternalVibration != null
&& mCurrentExternalVibration.isHoldingSameVibration(vib)) {
// We are already playing this external vibration, so we can return the same
// scale calculated in the previous call to this method.
- return mCurrentExternalVibration.scale;
+ return mCurrentExternalVibration.getScale();
}
if (vibrationEndInfo == null) {
// Check if ongoing vibration is more important than this vibration.
- vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(vibHolder);
+ vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(externalVibration);
}
if (vibrationEndInfo != null) {
- endVibrationAndWriteStatsLocked(vibHolder, vibrationEndInfo);
- return vibHolder.scale;
+ endVibrationLocked(externalVibration, vibrationEndInfo,
+ /* shouldWriteStats= */ true);
+ return externalVibration.getScale();
}
if (mCurrentExternalVibration == null) {
// If we're not under external control right now, then cancel any normal
// vibration that may be playing and ready the vibrator for external control.
if (mCurrentVibration != null) {
- vibHolder.stats.reportInterruptedAnotherVibration(
+ externalVibration.stats.reportInterruptedAnotherVibration(
mCurrentVibration.getVibration().callerInfo);
clearNextVibrationLocked(
- new Vibration.EndInfo(Vibration.Status.IGNORED_FOR_EXTERNAL,
- vibHolder.callerInfo));
+ new Vibration.EndInfo(Status.IGNORED_FOR_EXTERNAL,
+ externalVibration.callerInfo));
mCurrentVibration.notifyCancelled(
- new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED,
- vibHolder.callerInfo),
+ new Vibration.EndInfo(Status.CANCELLED_SUPERSEDED,
+ externalVibration.callerInfo),
/* immediate= */ true);
waitForCompletion = true;
}
@@ -2178,12 +2061,12 @@
// Note that this doesn't support multiple concurrent external controls, as we
// would need to mute the old one still if it came from a different controller.
alreadyUnderExternalControl = true;
- mCurrentExternalVibration.mute();
- vibHolder.stats.reportInterruptedAnotherVibration(
+ mCurrentExternalVibration.notifyEnded();
+ externalVibration.stats.reportInterruptedAnotherVibration(
mCurrentExternalVibration.callerInfo);
endExternalVibrateLocked(
- new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED,
- vibHolder.callerInfo),
+ new Vibration.EndInfo(Status.CANCELLED_SUPERSEDED,
+ externalVibration.callerInfo),
/* continueExternalControl= */ true);
}
@@ -2195,9 +2078,9 @@
mVibrationSettings.update();
}
- mCurrentExternalVibration = vibHolder;
- vibHolder.linkToDeath();
- vibHolder.scale(mVibrationScaler, attrs.getUsage());
+ mCurrentExternalVibration = externalVibration;
+ externalVibration.linkToDeath(this::onExternalVibrationBinderDied);
+ externalVibration.scale(mVibrationScaler, attrs.getUsage());
}
if (waitForCompletion) {
@@ -2206,27 +2089,27 @@
synchronized (mLock) {
// Trigger endExternalVibrateLocked to unlink to death recipient.
endExternalVibrateLocked(
- new Vibration.EndInfo(Vibration.Status.IGNORED_ERROR_CANCELLING),
+ new Vibration.EndInfo(Status.IGNORED_ERROR_CANCELLING),
/* continueExternalControl= */ false);
// Mute the request, vibration will be ignored.
- vibHolder.muteScale();
+ externalVibration.muteScale();
}
- return vibHolder.scale;
+ return externalVibration.getScale();
}
}
if (!alreadyUnderExternalControl) {
if (DEBUG) {
Slog.d(TAG, "Vibrator going under external control.");
}
- setExternalControl(true, vibHolder.stats);
+ setExternalControl(true, externalVibration.stats);
}
if (DEBUG) {
Slog.d(TAG, "Playing external vibration: " + vib);
}
// Vibrator will start receiving data from external channels after this point.
// Report current time as the vibration start time, for debugging.
- vibHolder.stats.reportStarted();
- return vibHolder.scale;
+ externalVibration.stats.reportStarted();
+ return externalVibration.getScale();
}
@Override
@@ -2238,7 +2121,7 @@
Slog.d(TAG, "Stopping external vibration: " + vib);
}
endExternalVibrateLocked(
- new Vibration.EndInfo(Vibration.Status.FINISHED),
+ new Vibration.EndInfo(Status.FINISHED),
/* continueExternalControl= */ false);
}
}
@@ -2252,6 +2135,19 @@
}
return false;
}
+
+ private void onExternalVibrationBinderDied() {
+ synchronized (mLock) {
+ if (mCurrentExternalVibration != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "External vibration finished because binder died");
+ }
+ endExternalVibrateLocked(
+ new Vibration.EndInfo(Status.CANCELLED_BINDER_DIED),
+ /* continueExternalControl= */ false);
+ }
+ }
+ }
}
/** Provide limited functionality from {@link VibratorManagerService} as shell commands. */
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index ba2594a..f53dda6 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -42,6 +42,7 @@
import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_LOCK_ORIG;
import static com.android.server.wallpaper.WallpaperUtils.getWallpaperDir;
import static com.android.server.wallpaper.WallpaperUtils.makeWallpaperIdLocked;
+import static com.android.window.flags.Flags.avoidRebindingIntentionallyDisconnectedWallpaper;
import static com.android.window.flags.Flags.multiCrop;
import static com.android.window.flags.Flags.offloadColorExtraction;
@@ -897,6 +898,12 @@
return;
}
+ if (avoidRebindingIntentionallyDisconnectedWallpaper()
+ && mWallpaper.connection == null) {
+ Slog.w(TAG, "Trying to reset an intentionally disconnected wallpaper!");
+ return;
+ }
+
if (!mWallpaper.wallpaperUpdating && mWallpaper.userId == mCurrentUserId) {
Slog.w(TAG, "Wallpaper reconnect timed out for " + mWallpaper.wallpaperComponent
+ ", reverting to built-in wallpaper!");
@@ -1066,6 +1073,13 @@
if (mWallpaper.wallpaperUpdating) {
return;
}
+
+ if (avoidRebindingIntentionallyDisconnectedWallpaper()
+ && mWallpaper.connection == null) {
+ Slog.w(TAG, "Trying to rebind an intentionally disconnected wallpaper!");
+ return;
+ }
+
final ComponentName wpService = mWallpaper.wallpaperComponent;
// The broadcast of package update could be delayed after service disconnected. Try
// to re-bind the service for 10 seconds.
diff --git a/services/core/java/com/android/server/webkit/SystemImpl.java b/services/core/java/com/android/server/webkit/SystemImpl.java
index c4d601d..5e35925 100644
--- a/services/core/java/com/android/server/webkit/SystemImpl.java
+++ b/services/core/java/com/android/server/webkit/SystemImpl.java
@@ -19,14 +19,12 @@
import static android.webkit.Flags.updateServiceV2;
import android.app.ActivityManager;
-import android.app.AppGlobals;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.UserInfo;
import android.content.res.XmlResourceParser;
import android.os.Build;
import android.os.RemoteException;
@@ -79,7 +77,7 @@
XmlResourceParser parser = null;
List<WebViewProviderInfo> webViewProviders = new ArrayList<WebViewProviderInfo>();
try {
- parser = AppGlobals.getInitialApplication().getResources().getXml(
+ parser = mContext.getResources().getXml(
com.android.internal.R.xml.config_webview_packages);
XmlUtils.beginDocument(parser, TAG_START);
while(true) {
@@ -148,7 +146,7 @@
}
public long getFactoryPackageVersion(String packageName) throws NameNotFoundException {
- PackageManager pm = AppGlobals.getInitialApplication().getPackageManager();
+ PackageManager pm = mContext.getPackageManager();
return pm.getPackageInfo(packageName, PackageManager.MATCH_FACTORY_ONLY)
.getLongVersionCode();
}
@@ -203,47 +201,48 @@
@Override
public void enablePackageForAllUsers(String packageName, boolean enable) {
UserManager userManager = mContext.getSystemService(UserManager.class);
- for(UserInfo userInfo : userManager.getUsers()) {
- enablePackageForUser(packageName, enable, userInfo.id);
+ for (UserHandle user : userManager.getUserHandles(false)) {
+ enablePackageForUser(packageName, enable, user);
}
}
- private void enablePackageForUser(String packageName, boolean enable, int userId) {
+ private void enablePackageForUser(String packageName, boolean enable, UserHandle user) {
+ Context contextAsUser = mContext.createContextAsUser(user, 0);
+ PackageManager pm = contextAsUser.getPackageManager();
try {
- AppGlobals.getPackageManager().setApplicationEnabledSetting(
+ pm.setApplicationEnabledSetting(
packageName,
enable ? PackageManager.COMPONENT_ENABLED_STATE_DEFAULT :
- PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER, 0,
- userId, null);
- } catch (RemoteException | IllegalArgumentException e) {
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER, 0);
+ } catch (IllegalArgumentException e) {
Log.w(TAG, "Tried to " + (enable ? "enable " : "disable ") + packageName
- + " for user " + userId + ": " + e);
+ + " for user " + user + ": " + e);
}
}
@Override
public void installExistingPackageForAllUsers(String packageName) {
UserManager userManager = mContext.getSystemService(UserManager.class);
- for (UserInfo userInfo : userManager.getUsers()) {
- installPackageForUser(packageName, userInfo.id);
+ for (UserHandle user : userManager.getUserHandles(false)) {
+ installPackageForUser(packageName, user);
}
}
- private void installPackageForUser(String packageName, int userId) {
- final Context contextAsUser = mContext.createContextAsUser(UserHandle.of(userId), 0);
- final PackageInstaller installer = contextAsUser.getPackageManager().getPackageInstaller();
+ private void installPackageForUser(String packageName, UserHandle user) {
+ Context contextAsUser = mContext.createContextAsUser(user, 0);
+ PackageInstaller installer = contextAsUser.getPackageManager().getPackageInstaller();
installer.installExistingPackage(packageName, PackageManager.INSTALL_REASON_UNKNOWN, null);
}
@Override
public boolean systemIsDebuggable() {
- return Build.IS_DEBUGGABLE;
+ return Build.isDebuggable();
}
@Override
public PackageInfo getPackageInfoForProvider(WebViewProviderInfo configInfo)
throws NameNotFoundException {
- PackageManager pm = AppGlobals.getInitialApplication().getPackageManager();
+ PackageManager pm = mContext.getPackageManager();
return pm.getPackageInfo(configInfo.packageName, PACKAGE_FLAGS);
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 235a211..ebdf52c 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -813,6 +813,8 @@
/** The last set {@link DropInputMode} for this activity surface. */
@DropInputMode
private int mLastDropInputMode = DropInputMode.NONE;
+ /** Whether the input to this activity will be dropped during the current playing animation. */
+ private boolean mIsInputDroppedForAnimation;
/**
* Whether the application has desk mode resources. Calculated and cached when
@@ -1647,6 +1649,15 @@
}
}
+ /** Sets if all input will be dropped as a protection during the client-driven animation. */
+ void setDropInputForAnimation(boolean isInputDroppedForAnimation) {
+ if (mIsInputDroppedForAnimation == isInputDroppedForAnimation) {
+ return;
+ }
+ mIsInputDroppedForAnimation = isInputDroppedForAnimation;
+ updateUntrustedEmbeddingInputProtection();
+ }
+
/**
* Sets to drop input when obscured to activity if it is embedded in untrusted mode.
*
@@ -1659,7 +1670,10 @@
if (getSurfaceControl() == null) {
return;
}
- if (isEmbeddedInUntrustedMode()) {
+ if (mIsInputDroppedForAnimation) {
+ // Disable all input during the animation.
+ setDropInputMode(DropInputMode.ALL);
+ } else if (isEmbeddedInUntrustedMode()) {
// Set drop input to OBSCURED when untrusted embedded.
setDropInputMode(DropInputMode.OBSCURED);
} else {
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 197bd5a..ab02d49 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -97,6 +97,7 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayDeque;
import java.util.ArrayList;
+import java.util.function.Consumer;
import java.util.function.Predicate;
/**
@@ -253,10 +254,12 @@
getTopApp(mDisplayContent.mChangingContainers, false /* ignoreHidden */);
final WindowManager.LayoutParams animLp = getAnimLp(animLpActivity);
- // No AE remote animation with Shell transition.
- // Unfreeze the windows that were previously frozen for TaskFragment animation.
- unfreezeEmbeddedChangingWindows();
- overrideWithRemoteAnimationIfSet(animLpActivity, transit, activityTypes);
+ // Check if there is any override
+ if (!overrideWithTaskFragmentRemoteAnimation(transit, activityTypes)) {
+ // Unfreeze the windows that were previously frozen for TaskFragment animation.
+ unfreezeEmbeddedChangingWindows();
+ overrideWithRemoteAnimationIfSet(animLpActivity, transit, activityTypes);
+ }
final boolean voiceInteraction = containsVoiceInteraction(mDisplayContent.mClosingApps)
|| containsVoiceInteraction(mDisplayContent.mOpeningApps);
@@ -687,6 +690,64 @@
}
/**
+ * Overrides the pending transition with the remote animation defined by the
+ * {@link ITaskFragmentOrganizer} if all windows in the transition are children of
+ * {@link TaskFragment} that are organized by the same organizer.
+ *
+ * @return {@code true} if the transition is overridden.
+ */
+ private boolean overrideWithTaskFragmentRemoteAnimation(@TransitionOldType int transit,
+ ArraySet<Integer> activityTypes) {
+ if (transitionMayContainNonAppWindows(transit)) {
+ return false;
+ }
+ if (!transitionContainsTaskFragmentWithBoundsOverride()) {
+ // No need to play TaskFragment remote animation if all embedded TaskFragment in the
+ // transition fill the Task.
+ return false;
+ }
+
+ final Task task = findParentTaskForAllEmbeddedWindows();
+ final ITaskFragmentOrganizer organizer = findTaskFragmentOrganizer(task);
+ final RemoteAnimationDefinition definition = organizer != null
+ ? mDisplayContent.mAtmService.mTaskFragmentOrganizerController
+ .getRemoteAnimationDefinition(organizer)
+ : null;
+ final RemoteAnimationAdapter adapter = definition != null
+ ? definition.getAdapter(transit, activityTypes)
+ : null;
+ if (adapter == null) {
+ return false;
+ }
+ mDisplayContent.mAppTransition.overridePendingAppTransitionRemote(
+ adapter, false /* sync */, true /*isActivityEmbedding*/);
+ ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
+ "Override with TaskFragment remote animation for transit=%s",
+ AppTransition.appTransitionOldToString(transit));
+
+ final int organizerUid = mDisplayContent.mAtmService.mTaskFragmentOrganizerController
+ .getTaskFragmentOrganizerUid(organizer);
+ final boolean shouldDisableInputForRemoteAnimation = !task.isFullyTrustedEmbedding(
+ organizerUid);
+ final RemoteAnimationController remoteAnimationController =
+ mDisplayContent.mAppTransition.getRemoteAnimationController();
+ if (shouldDisableInputForRemoteAnimation && remoteAnimationController != null) {
+ // We are going to use client-driven animation, Disable all input on activity windows
+ // during the animation (unless it is fully trusted) to ensure it is safe to allow
+ // client to animate the surfaces.
+ // This is needed for all activity windows in the animation Task.
+ remoteAnimationController.setOnRemoteAnimationReady(() -> {
+ final Consumer<ActivityRecord> updateActivities =
+ activity -> activity.setDropInputForAnimation(true);
+ task.forAllActivities(updateActivities);
+ });
+ ProtoLog.d(WM_DEBUG_APP_TRANSITIONS, "Task=%d contains embedded TaskFragment."
+ + " Disabled all input during TaskFragment remote animation.", task.mTaskId);
+ }
+ return true;
+ }
+
+ /**
* Overrides the pending transition with the remote animation defined for the transition in the
* set of defined remote animations in the app window token.
*/
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 3710f7f..28dbc3a 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -882,6 +882,7 @@
} else {
if (mAnimationHandler.mPrepareCloseTransition != null) {
Slog.e(TAG, "Gesture animation is applied on another transition?");
+ return;
}
mAnimationHandler.mPrepareCloseTransition = transition;
if (!migratePredictToTransition) {
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 20c5f02..a5cea34 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -1685,6 +1685,21 @@
(state.mOriginatingPendingIntent != null));
}
+ if (finalVerdict.getRawCode() == BAL_ALLOW_GRACE_PERIOD) {
+ if (state.realCallerExplicitOptInOrAutoOptIn()
+ && state.mResultForRealCaller.allows()
+ && state.mResultForRealCaller.getRawCode() != BAL_ALLOW_GRACE_PERIOD) {
+ // real caller could allow with a different exemption
+ } else if (state.callerExplicitOptInOrAutoOptIn() && state.mResultForCaller.allows()
+ && state.mResultForCaller.getRawCode() != BAL_ALLOW_GRACE_PERIOD) {
+ // caller could allow with a different exemption
+ } else {
+ // log to determine grace period length distribution
+ Slog.wtf(TAG, "Activity start ONLY allowed by BAL_ALLOW_GRACE_PERIOD "
+ + finalVerdict.mMessage + ": " + state);
+ }
+ }
+
if (balImprovedMetrics()) {
if (shouldLogStats(finalVerdict, state)) {
String activityName;
diff --git a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
index 4a870a3..5f5365d 100644
--- a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
+++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
@@ -17,7 +17,6 @@
package com.android.server.wm;
import static com.android.internal.util.Preconditions.checkArgument;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ACTIVITY_STARTS;
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.ActivityTaskManagerService.ACTIVITY_BG_START_GRACE_PERIOD_MS;
@@ -48,7 +47,6 @@
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.IntArray;
-import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.server.wm.BackgroundActivityStartController.BalVerdict;
@@ -138,22 +136,16 @@
if (appSwitchState == APP_SWITCH_ALLOW) {
// Allow if any activity in the caller has either started or finished very recently, and
// it must be started or finished after last stop app switches time.
- final long now = SystemClock.uptimeMillis();
- if (now - lastActivityLaunchTime < ACTIVITY_BG_START_GRACE_PERIOD_MS
- || now - lastActivityFinishTime < ACTIVITY_BG_START_GRACE_PERIOD_MS) {
- // If activity is started and finished before stop app switch time, we should not
- // let app to be able to start background activity even it's in grace period.
- if (lastActivityLaunchTime > lastStopAppSwitchesTime
- || lastActivityFinishTime > lastStopAppSwitchesTime) {
+ if (lastActivityLaunchTime > lastStopAppSwitchesTime
+ || lastActivityFinishTime > lastStopAppSwitchesTime) {
+ final long now = SystemClock.uptimeMillis();
+ long timeSinceLastStartOrFinish = now - Math.max(lastActivityLaunchTime,
+ lastActivityFinishTime);
+ if (timeSinceLastStartOrFinish < ACTIVITY_BG_START_GRACE_PERIOD_MS) {
return new BalVerdict(BAL_ALLOW_GRACE_PERIOD, /*background*/ true,
- "within " + ACTIVITY_BG_START_GRACE_PERIOD_MS + "ms grace period");
+ "within " + ACTIVITY_BG_START_GRACE_PERIOD_MS + "ms grace period ("
+ + timeSinceLastStartOrFinish + "ms)");
}
- if (DEBUG_ACTIVITY_STARTS) {
- Slog.d(TAG, "[Process(" + pid + ")] Activity start within "
- + ACTIVITY_BG_START_GRACE_PERIOD_MS
- + "ms grace period but also within stop app switch window");
- }
-
}
}
return BalVerdict.BLOCK;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 0597ed7..34bbe6a 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1835,7 +1835,7 @@
if (mTransitionController.useShellTransitionsRotation()) {
return ROTATION_UNDEFINED;
}
- final int activityOrientation = r.getOverrideOrientation();
+ int activityOrientation = r.getOverrideOrientation();
if (!WindowManagerService.ENABLE_FIXED_ROTATION_TRANSFORM
|| shouldIgnoreOrientationRequest(activityOrientation)) {
return ROTATION_UNDEFINED;
@@ -1846,14 +1846,15 @@
r /* boundary */, false /* includeBoundary */, true /* traverseTopToBottom */);
if (nextCandidate != null) {
r = nextCandidate;
+ activityOrientation = r.getOverrideOrientation();
}
}
- if (r.inMultiWindowMode() || r.getRequestedConfigurationOrientation(true /* forDisplay */)
- == getConfiguration().orientation) {
+ if (r.inMultiWindowMode() || r.getRequestedConfigurationOrientation(true /* forDisplay */,
+ activityOrientation) == getConfiguration().orientation) {
return ROTATION_UNDEFINED;
}
final int currentRotation = getRotation();
- final int rotation = mDisplayRotation.rotationForOrientation(r.getRequestedOrientation(),
+ final int rotation = mDisplayRotation.rotationForOrientation(activityOrientation,
currentRotation);
if (rotation == currentRotation) {
return ROTATION_UNDEFINED;
diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS
index 781023c..5d6d8bc 100644
--- a/services/core/java/com/android/server/wm/OWNERS
+++ b/services/core/java/com/android/server/wm/OWNERS
@@ -24,6 +24,7 @@
per-file Background*Start* = set noparent
per-file Background*Start* = file:/BAL_OWNERS
per-file Background*Start* = ogunwale@google.com, louischang@google.com
+per-file BackgroundLaunchProcessController.java = file:/BAL_OWNERS
# File related to activity callers
per-file ActivityCallerState.java = file:/core/java/android/app/COMPONENT_CALLER_OWNERS
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index f8665c7..432089f 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -53,6 +53,7 @@
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
+import java.util.function.Consumer;
/**
* Helper class to run app animations in a remote process.
@@ -348,6 +349,10 @@
} finally {
mIsFinishing = false;
}
+ // Reset input for all activities when the remote animation is finished.
+ final Consumer<ActivityRecord> updateActivities =
+ activity -> activity.setDropInputForAnimation(false);
+ mDisplayContent.forAllActivities(updateActivities);
}
setRunningRemoteAnimation(false);
ProtoLog.i(WM_DEBUG_REMOTE_ANIMATIONS, "Finishing remote animation");
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index e4a3176..5aa34d2 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -46,6 +46,7 @@
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
+import android.view.RemoteAnimationDefinition;
import android.view.WindowManager;
import android.window.ITaskFragmentOrganizer;
import android.window.ITaskFragmentOrganizerController;
@@ -156,6 +157,13 @@
private final boolean mIsSystemOrganizer;
/**
+ * {@link RemoteAnimationDefinition} for embedded activities transition animation that is
+ * organized by this organizer.
+ */
+ @Nullable
+ private RemoteAnimationDefinition mRemoteAnimationDefinition;
+
+ /**
* Map from {@link TaskFragmentTransaction#getTransactionToken()} to the
* {@link Transition#getSyncId()} that has been deferred. {@link TransitionController} will
* wait until the organizer finished handling the {@link TaskFragmentTransaction}.
@@ -592,6 +600,50 @@
}
@Override
+ public void registerRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer,
+ @NonNull RemoteAnimationDefinition definition) {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ synchronized (mGlobalLock) {
+ ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
+ "Register remote animations for organizer=%s uid=%d pid=%d",
+ organizer.asBinder(), uid, pid);
+ final TaskFragmentOrganizerState organizerState =
+ mTaskFragmentOrganizerState.get(organizer.asBinder());
+ if (organizerState == null) {
+ throw new IllegalStateException("The organizer hasn't been registered.");
+ }
+ if (organizerState.mRemoteAnimationDefinition != null) {
+ throw new IllegalStateException(
+ "The organizer has already registered remote animations="
+ + organizerState.mRemoteAnimationDefinition);
+ }
+
+ definition.setCallingPidUid(pid, uid);
+ organizerState.mRemoteAnimationDefinition = definition;
+ }
+ }
+
+ @Override
+ public void unregisterRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer) {
+ final int pid = Binder.getCallingPid();
+ final long uid = Binder.getCallingUid();
+ synchronized (mGlobalLock) {
+ ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
+ "Unregister remote animations for organizer=%s uid=%d pid=%d",
+ organizer.asBinder(), uid, pid);
+ final TaskFragmentOrganizerState organizerState =
+ mTaskFragmentOrganizerState.get(organizer.asBinder());
+ if (organizerState == null) {
+ Slog.e(TAG, "The organizer hasn't been registered.");
+ return;
+ }
+
+ organizerState.mRemoteAnimationDefinition = null;
+ }
+ }
+
+ @Override
public void setSavedState(@NonNull ITaskFragmentOrganizer organizer, @Nullable Bundle state) {
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
@@ -649,6 +701,25 @@
}
}
+ /**
+ * Gets the {@link RemoteAnimationDefinition} set on the given organizer if exists. Returns
+ * {@code null} if it doesn't.
+ */
+ @Nullable
+ public RemoteAnimationDefinition getRemoteAnimationDefinition(
+ @NonNull ITaskFragmentOrganizer organizer) {
+ synchronized (mGlobalLock) {
+ final TaskFragmentOrganizerState organizerState =
+ mTaskFragmentOrganizerState.get(organizer.asBinder());
+ if (organizerState == null) {
+ Slog.e(TAG, "TaskFragmentOrganizer has been unregistered or died when trying"
+ + " to play animation on its organized windows.");
+ return null;
+ }
+ return organizerState.mRemoteAnimationDefinition;
+ }
+ }
+
int getTaskFragmentOrganizerUid(@NonNull ITaskFragmentOrganizer organizer) {
final TaskFragmentOrganizerState state = validateAndGetState(organizer);
return state.mOrganizerUid;
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 6bfa32a..7f6dc84 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1057,9 +1057,8 @@
* needs to be passed/applied in shell because until finish is called, shell owns the surfaces.
* Additionally, this gives shell the ability to better deal with merged transitions.
*/
- private void buildFinishTransaction(SurfaceControl.Transaction t, TransitionInfo info) {
- // usually only size 1
- final ArraySet<DisplayContent> displays = new ArraySet<>();
+ private void buildFinishTransaction(SurfaceControl.Transaction t, TransitionInfo info,
+ DisplayContent[] participantDisplays) {
for (int i = mTargets.size() - 1; i >= 0; --i) {
final WindowContainer<?> target = mTargets.get(i).mContainer;
if (target.getParent() == null) continue;
@@ -1071,7 +1070,6 @@
t.setCornerRadius(targetLeash, 0);
t.setShadowRadius(targetLeash, 0);
t.setAlpha(targetLeash, 1);
- displays.add(target.getDisplayContent());
// For config-at-end, the end-transform will be reset after the config is actually
// applied in the client (since the transform depends on config). The other properties
// remain here because shell might want to persistently override them.
@@ -1085,9 +1083,8 @@
}
// Need to update layers on involved displays since they were all paused while
// the animation played. This puts the layers back into the correct order.
- for (int i = displays.size() - 1; i >= 0; --i) {
- if (displays.valueAt(i) == null) continue;
- assignLayers(displays.valueAt(i), t);
+ for (int i = participantDisplays.length - 1; i >= 0; --i) {
+ assignLayers(participantDisplays[i], t);
}
for (int i = 0; i < info.getRootCount(); ++i) {
@@ -1800,6 +1797,8 @@
mController.moveToPlaying(this);
// Repopulate the displays based on the resolved targets.
+ final DisplayContent[] participantDisplays = mTargetDisplays.toArray(
+ new DisplayContent[mTargetDisplays.size()]);
mTargetDisplays.clear();
for (int i = 0; i < info.getRootCount(); ++i) {
final DisplayContent dc = mController.mAtm.mRootWindowContainer.getDisplayContent(
@@ -1893,7 +1892,9 @@
controller.setupStartTransaction(transaction);
}
}
- buildFinishTransaction(mFinishTransaction, info);
+ // Use participant displays here (rather than just targets) because it's possible for
+ // there to be order changes between non-top tasks in an otherwise no-op transition.
+ buildFinishTransaction(mFinishTransaction, info, participantDisplays);
mCleanupTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get();
buildCleanupTransaction(mCleanupTransaction, info);
if (mController.getTransitionPlayer() != null && mIsPlayerEnabled) {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 6995027..790ca1b 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1731,13 +1731,13 @@
* last time {@link #getOrientation(int) was called.
*/
@Nullable
- WindowContainer getLastOrientationSource() {
- final WindowContainer source = mLastOrientationSource;
- if (source != null && source != this) {
- final WindowContainer nextSource = source.getLastOrientationSource();
- if (nextSource != null) {
- return nextSource;
- }
+ final WindowContainer<?> getLastOrientationSource() {
+ if (mLastOrientationSource == null) {
+ return null;
+ }
+ WindowContainer<?> source = this;
+ while (source != source.mLastOrientationSource && source.mLastOrientationSource != null) {
+ source = source.mLastOrientationSource;
}
return source;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 979b3a5..29ab4dd 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7920,7 +7920,7 @@
}
boolean allWindowsDrawn = false;
synchronized (mGlobalLock) {
- if (displayId == DEFAULT_DISPLAY
+ if (displayId == INVALID_DISPLAY
&& mRoot.getDefaultDisplay().mDisplayUpdater.waitForTransition(message)) {
// Use the ready-to-play of transition as the signal.
return;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index af4a48d..886ae7a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -871,6 +871,16 @@
EXEMPT_FROM_POWER_RESTRICTIONS, OPSTR_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS);
}
+ private static final Set<String> METERED_DATA_RESTRICTION_EXEMPT_ROLES =
+ new ArraySet<>();
+ static {
+ // TODO(b/362545319): reference role name from role manager once it's exposed.
+ final String roleDeviceLockController =
+ "android.app.role.SYSTEM_FINANCED_DEVICE_CONTROLLER";
+ METERED_DATA_RESTRICTION_EXEMPT_ROLES.add(roleDeviceLockController);
+ METERED_DATA_RESTRICTION_EXEMPT_ROLES.add(RoleManager.ROLE_FINANCED_DEVICE_KIOSK);
+ }
+
/**
* Admin apps targeting Android S+ may not use
* {@link android.app.admin.DevicePolicyManager#setPasswordQuality} to set password quality
@@ -1959,6 +1969,10 @@
return UserManager.isHeadlessSystemUserMode();
}
+ List<String> roleManagerGetRoleHoldersAsUser(String role, UserHandle userHandle) {
+ return getRoleManager().getRoleHoldersAsUser(role, userHandle);
+ }
+
@SuppressWarnings("AndroidFrameworkPendingIntentMutability")
PendingIntent pendingIntentGetActivityAsUser(Context context, int requestCode,
@NonNull Intent intent, int flags, Bundle options, UserHandle user) {
@@ -13972,11 +13986,9 @@
UserManager.DISALLOW_THREAD_NETWORK,
new String[]{MANAGE_DEVICE_POLICY_THREAD_NETWORK});
}
- if (Flags.assistContentUserRestrictionEnabled()) {
- USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_ASSIST_CONTENT,
- new String[]{MANAGE_DEVICE_POLICY_ASSIST_CONTENT});
- }
+ USER_RESTRICTION_PERMISSIONS.put(
+ UserManager.DISALLOW_ASSIST_CONTENT,
+ new String[]{MANAGE_DEVICE_POLICY_ASSIST_CONTENT});
USER_RESTRICTION_PERMISSIONS.put(
UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO, new String[]{MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION});
USER_RESTRICTION_PERMISSIONS.put(
@@ -17902,15 +17914,28 @@
});
}
+ private Set<String> getMeteredDataRestrictionExemptPackages(int userId) {
+ final Set<String> exemptPkgs = new ArraySet<>();
+ for (String role: METERED_DATA_RESTRICTION_EXEMPT_ROLES) {
+ String pkg = getRoleHolderPackageNameOnUser(role, userId);
+ if (pkg != null) {
+ exemptPkgs.add(pkg);
+ }
+ }
+
+ return exemptPkgs;
+ }
+
private List<String> removeInvalidPkgsForMeteredDataRestriction(
int userId, List<String> pkgNames) {
+ final Set<String> exemptRolePkgs = getMeteredDataRestrictionExemptPackages(userId);
synchronized (getLockObject()) {
final Set<String> activeAdmins = getActiveAdminPackagesLocked(userId);
final List<String> excludedPkgs = new ArrayList<>();
for (int i = pkgNames.size() - 1; i >= 0; --i) {
final String pkgName = pkgNames.get(i);
- // If the package is an active admin, don't restrict it.
- if (activeAdmins.contains(pkgName)) {
+ // If the package is an active admin or exempt role, don't restrict it.
+ if (activeAdmins.contains(pkgName) || exemptRolePkgs.contains(pkgName)) {
excludedPkgs.add(pkgName);
continue;
}
@@ -19755,16 +19780,14 @@
}
private void transferSubscriptionOwnership(ComponentName admin, ComponentName target) {
- if (Flags.esimManagementEnabled()) {
- SubscriptionManager subscriptionManager = mContext.getSystemService(
- SubscriptionManager.class);
- for (int subId : getSubscriptionIdsInternal(admin.getPackageName()).toArray()) {
- try {
- subscriptionManager.setGroupOwner(subId, target.getPackageName());
- } catch (Exception e) {
- // Shouldn't happen.
- Slogf.e(LOG_TAG, e, "Error setting group owner for subId: " + subId);
- }
+ SubscriptionManager subscriptionManager = mContext.getSystemService(
+ SubscriptionManager.class);
+ for (int subId : getSubscriptionIdsInternal(admin.getPackageName()).toArray()) {
+ try {
+ subscriptionManager.setGroupOwner(subId, target.getPackageName());
+ } catch (Exception e) {
+ // Shouldn't happen.
+ Slogf.e(LOG_TAG, e, "Error setting group owner for subId: " + subId);
}
}
}
@@ -21753,8 +21776,6 @@
*/
@Nullable
private String getRoleHolderPackageNameOnUser(String role, int userId) {
- RoleManager roleManager = mContext.getSystemService(RoleManager.class);
-
// Clear calling identity as the RoleManager APIs require privileged permissions.
return mInjector.binderWithCleanCallingIdentity(() -> {
List<UserInfo> users;
@@ -21766,7 +21787,7 @@
}
for (UserInfo user : users) {
List<String> roleHolders =
- roleManager.getRoleHoldersAsUser(role, user.getUserHandle());
+ mInjector.roleManagerGetRoleHoldersAsUser(role, user.getUserHandle());
if (!roleHolders.isEmpty()) {
return roleHolders.get(0);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 19a942c..24ee46f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -536,7 +536,6 @@
USER_RESTRICTION_FLAGS.put(
UserManager.DISALLOW_THREAD_NETWORK, POLICY_FLAG_GLOBAL_ONLY_POLICY);
}
- USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_ASSIST_CONTENT, /* flags= */ 0);
for (String key : USER_RESTRICTION_FLAGS.keySet()) {
createAndAddUserRestrictionPolicyDefinition(key, USER_RESTRICTION_FLAGS.get(key));
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index c5c371f..13c436d 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -16,7 +16,6 @@
package com.android.server;
-import static android.app.appfunctions.flags.Flags.enableAppFunctionManager;
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH;
@@ -38,6 +37,7 @@
import android.app.INotificationManager;
import android.app.SystemServiceRegistry;
import android.app.admin.DevicePolicySafetyChecker;
+import android.app.appfunctions.AppFunctionManagerConfiguration;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -1743,12 +1743,11 @@
mSystemServiceManager.startService(LogcatManagerService.class);
t.traceEnd();
- t.traceBegin("StartAppFunctionManager");
- if (enableAppFunctionManager()) {
+ if (AppFunctionManagerConfiguration.isSupported(context)) {
+ t.traceBegin("StartAppFunctionManager");
mSystemServiceManager.startService(AppFunctionManagerService.class);
+ t.traceEnd();
}
- t.traceEnd();
-
} catch (Throwable e) {
Slog.e("System", "******************************************");
Slog.e("System", "************ Failure starting core service");
diff --git a/services/supervision/OWNERS b/services/supervision/OWNERS
new file mode 100644
index 0000000..e5f4147
--- /dev/null
+++ b/services/supervision/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/app/supervision/OWNERS
diff --git a/services/supervision/java/com/android/server/supervision/SupervisionService.java b/services/supervision/java/com/android/server/supervision/SupervisionService.java
index a4ef629..7ffd0ec 100644
--- a/services/supervision/java/com/android/server/supervision/SupervisionService.java
+++ b/services/supervision/java/com/android/server/supervision/SupervisionService.java
@@ -20,7 +20,9 @@
import android.annotation.Nullable;
import android.app.supervision.ISupervisionManager;
import android.content.Context;
-
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
import com.android.internal.util.DumpUtils;
import com.android.server.SystemService;
@@ -28,7 +30,9 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
-/** Service for handling system supervision. */
+/**
+ * Service for handling system supervision.
+ */
public class SupervisionService extends ISupervisionManager.Stub {
private static final String LOG_TAG = "SupervisionService";
@@ -44,8 +48,20 @@
}
@Override
- protected void dump(@NonNull FileDescriptor fd,
- @NonNull PrintWriter fout, @Nullable String[] args) {
+ public void onShellCommand(
+ @Nullable FileDescriptor in,
+ @Nullable FileDescriptor out,
+ @Nullable FileDescriptor err,
+ @NonNull String[] args,
+ @Nullable ShellCallback callback,
+ @NonNull ResultReceiver resultReceiver) throws RemoteException {
+ new SupervisionServiceShellCommand(this)
+ .exec(this, in, out, err, args, callback, resultReceiver);
+ }
+
+ @Override
+ protected void dump(
+ @NonNull FileDescriptor fd, @NonNull PrintWriter fout, @Nullable String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, fout)) return;
fout.println("Supervision enabled: " + isSupervisionEnabled());
diff --git a/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java b/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java
new file mode 100644
index 0000000..3aba24a
--- /dev/null
+++ b/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.supervision;
+
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+public class SupervisionServiceShellCommand extends ShellCommand {
+ private final SupervisionService mService;
+
+ public SupervisionServiceShellCommand(SupervisionService mService) {
+ this.mService = mService;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(null);
+ }
+ final PrintWriter pw = getOutPrintWriter();
+ switch (cmd) {
+ case "help": return help(pw);
+ case "is-enabled": return isEnabled(pw);
+ default: return handleDefaultCommands(cmd);
+ }
+ }
+
+ private int help(PrintWriter pw) {
+ pw.println("Supervision service commands:");
+ pw.println(" help");
+ pw.println(" Prints this help text");
+ pw.println(" is-enabled");
+ pw.println(" Is supervision enabled");
+ return 0;
+ }
+
+ private int isEnabled(PrintWriter pw) {
+ pw.println(mService.isSupervisionEnabled());
+ return 0;
+ }
+
+ @Override
+ public void onHelp() {
+ help(getOutPrintWriter());
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
index f690b1b..2d4a29b 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
@@ -18,6 +18,8 @@
import static org.junit.Assert.assertEquals;
+import android.hardware.display.BrightnessInfo;
+
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -112,7 +114,10 @@
.append("\n mBrightnessAdjustmentFlag:")
.append(displayBrightnessState.getBrightnessAdjustmentFlag())
.append("\n mIsUserInitiatedChange:")
- .append(displayBrightnessState.isUserInitiatedChange());
+ .append(displayBrightnessState.isUserInitiatedChange())
+ .append("\n mBrightnessMaxReason:")
+ .append(BrightnessInfo.briMaxReasonToString(
+ displayBrightnessState.getBrightnessMaxReason()));
return sb.toString();
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
index 0ce9233..0a03702 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
@@ -31,7 +31,6 @@
import android.content.Context;
import android.hardware.SensorManager;
-import android.hardware.display.BrightnessInfo;
import android.hardware.display.DisplayManagerInternal;
import android.os.Handler;
import android.os.PowerManager;
@@ -161,12 +160,6 @@
}
@Test
- public void testMaxReasonIsNoneOnInit() {
- assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE,
- mClamperController.getBrightnessMaxReason());
- }
-
- @Test
public void testOnDisplayChanged_DelegatesToClamper() {
mClamperController.onDisplayChanged(mMockDisplayDeviceData);
diff --git a/services/tests/mockingservicestests/src/com/android/server/crashrecovery/CrashRecoveryUtilsTest.java b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/CrashRecoveryUtilsTest.java
new file mode 100644
index 0000000..6f38fca
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/CrashRecoveryUtilsTest.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.crashrecovery;
+
+
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.quality.Strictness.LENIENT;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.os.Environment;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoSession;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+
+
+/**
+ * Test CrashRecovery Utils.
+ */
+@RunWith(AndroidJUnit4.class)
+public class CrashRecoveryUtilsTest {
+
+ private MockitoSession mStaticMockSession;
+ private final String mLogMsg = "Logging from test";
+ private final String mCrashrecoveryEventTag = "CrashRecovery Events: ";
+ private File mCacheDir;
+
+ @Before
+ public void setup() throws IOException {
+ Context context = ApplicationProvider.getApplicationContext();
+ mCacheDir = context.getCacheDir();
+ mStaticMockSession = ExtendedMockito.mockitoSession()
+ .spyStatic(Environment.class)
+ .strictness(LENIENT)
+ .startMocking();
+ ExtendedMockito.doReturn(mCacheDir).when(() -> Environment.getDataDirectory());
+
+ createCrashRecoveryEventsTempDir();
+ }
+
+ @After
+ public void tearDown() throws IOException {
+ mStaticMockSession.finishMocking();
+ deleteCrashRecoveryEventsTempFile();
+ }
+
+ @Test
+ public void testCrashRecoveryUtils() {
+ testLogCrashRecoveryEvent();
+ testDumpCrashRecoveryEvents();
+ }
+
+ @Test
+ public void testDumpCrashRecoveryEventsWithoutAnyLogs() {
+ assertThat(getCrashRecoveryEventsTempFile().exists()).isFalse();
+ StringWriter sw = new StringWriter();
+ IndentingPrintWriter ipw = new IndentingPrintWriter(sw, " ");
+ CrashRecoveryUtils.dumpCrashRecoveryEvents(ipw);
+ ipw.close();
+
+ String dump = sw.getBuffer().toString();
+ assertThat(dump).contains(mCrashrecoveryEventTag);
+ assertThat(dump).doesNotContain(mLogMsg);
+ }
+
+ private void testLogCrashRecoveryEvent() {
+ assertThat(getCrashRecoveryEventsTempFile().exists()).isFalse();
+ CrashRecoveryUtils.logCrashRecoveryEvent(Log.WARN, mLogMsg);
+
+ assertThat(getCrashRecoveryEventsTempFile().exists()).isTrue();
+ String fileContent = null;
+ try {
+ File file = getCrashRecoveryEventsTempFile();
+ FileInputStream fis = new FileInputStream(file);
+ byte[] data = new byte[(int) file.length()];
+ fis.read(data);
+ fis.close();
+ fileContent = new String(data, StandardCharsets.UTF_8);
+ } catch (Exception e) {
+ fail("Unable to read the events file");
+ }
+ assertThat(fileContent).contains(mLogMsg);
+ }
+
+ private void testDumpCrashRecoveryEvents() {
+ StringWriter sw = new StringWriter();
+ IndentingPrintWriter ipw = new IndentingPrintWriter(sw, " ");
+ CrashRecoveryUtils.dumpCrashRecoveryEvents(ipw);
+ ipw.close();
+
+ String dump = sw.getBuffer().toString();
+ assertThat(dump).contains(mCrashrecoveryEventTag);
+ assertThat(dump).contains(mLogMsg);
+ }
+
+ private void createCrashRecoveryEventsTempDir() throws IOException {
+ Files.deleteIfExists(getCrashRecoveryEventsTempFile().toPath());
+ File mMockDirectory = new File(mCacheDir, "system");
+ if (!mMockDirectory.exists()) {
+ assertThat(mMockDirectory.mkdir()).isTrue();
+ }
+ }
+
+ private void deleteCrashRecoveryEventsTempFile() throws IOException {
+ Files.deleteIfExists(getCrashRecoveryEventsTempFile().toPath());
+ }
+
+ private File getCrashRecoveryEventsTempFile() {
+ File systemTempDir = new File(mCacheDir, "system");
+ return new File(systemTempDir, "crashrecovery-events.txt");
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index c8cbbb5..2e6c93c 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -102,6 +102,7 @@
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityWindowAttributes;
import android.view.accessibility.IAccessibilityManager;
+import android.view.accessibility.IUserInitializationCompleteCallback;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
@@ -137,6 +138,7 @@
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
import org.mockito.internal.util.reflection.FieldReader;
import org.mockito.internal.util.reflection.FieldSetter;
import org.mockito.stubbing.Answer;
@@ -209,6 +211,7 @@
@Mock private FullScreenMagnificationController mMockFullScreenMagnificationController;
@Mock private ProxyManager mProxyManager;
@Mock private StatusBarManagerInternal mStatusBarManagerInternal;
+ @Spy private IUserInitializationCompleteCallback mUserInitializationCompleteCallback;
@Captor private ArgumentCaptor<Intent> mIntentArgumentCaptor;
private IAccessibilityManager mA11yManagerServiceOnDevice;
private AccessibilityServiceConnection mAccessibilityServiceConnection;
@@ -2042,6 +2045,36 @@
.isEqualTo(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
}
+ @Test
+ public void registerUserInitializationCompleteCallback_isRegistered() {
+ mA11yms.mUserInitializationCompleteCallbacks.clear();
+
+ mA11yms.registerUserInitializationCompleteCallback(mUserInitializationCompleteCallback);
+
+ assertThat(mA11yms.mUserInitializationCompleteCallbacks).containsExactly(
+ mUserInitializationCompleteCallback);
+ }
+
+ @Test
+ public void unregisterUserInitializationCompleteCallback_isUnregistered() {
+ mA11yms.mUserInitializationCompleteCallbacks.clear();
+ mA11yms.mUserInitializationCompleteCallbacks.add(mUserInitializationCompleteCallback);
+
+ mA11yms.unregisterUserInitializationCompleteCallback(mUserInitializationCompleteCallback);
+
+ assertThat(mA11yms.mUserInitializationCompleteCallbacks).isEmpty();
+ }
+
+ @Test
+ public void switchUser_callsUserInitializationCompleteCallback() throws RemoteException {
+ mA11yms.mUserInitializationCompleteCallbacks.add(mUserInitializationCompleteCallback);
+
+ mA11yms.switchUser(UserHandle.MIN_SECONDARY_USER_ID);
+
+ verify(mUserInitializationCompleteCallback).onUserInitializationComplete(
+ UserHandle.MIN_SECONDARY_USER_ID);
+ }
+
private Set<String> readStringsFromSetting(String setting) {
final Set<String> result = new ArraySet<>();
mA11yms.readColonDelimitedSettingToSet(
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java
index 4ec2fb9..cdaeade 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java
@@ -89,14 +89,15 @@
AccessibilityCheckResult.AccessibilityCheckResultType.NOT_RUN, null, 5,
null);
- Set<AndroidAccessibilityCheckerResult> results =
- AccessibilityCheckerUtils.processResults(
- mockNodeInfo,
- List.of(result1, result2, result3, result4),
- null,
+
+ AndroidAccessibilityCheckerResult.Builder resultBuilder =
+ AccessibilityCheckerUtils.getCommonResultBuilder(mockNodeInfo, null,
mMockPackageManager,
new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME,
TEST_A11Y_SERVICE_CLASS_NAME));
+ Set<AndroidAccessibilityCheckerResult> results =
+ AccessibilityCheckerUtils.processResults(mockNodeInfo,
+ List.of(result1, result2, result3, result4), resultBuilder);
assertThat(results).containsExactly(
createResult("TargetNode", "",
@@ -128,14 +129,14 @@
TouchTargetSizeCheck.class,
AccessibilityCheckResult.AccessibilityCheckResultType.ERROR, null, 2, null);
- Set<AndroidAccessibilityCheckerResult> results =
- AccessibilityCheckerUtils.processResults(
- mockNodeInfo,
- List.of(result1, result2),
- null,
+ AndroidAccessibilityCheckerResult.Builder resultBuilder =
+ AccessibilityCheckerUtils.getCommonResultBuilder(mockNodeInfo, null,
mMockPackageManager,
new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME,
TEST_A11Y_SERVICE_CLASS_NAME));
+ Set<AndroidAccessibilityCheckerResult> results =
+ AccessibilityCheckerUtils.processResults(mockNodeInfo,
+ List.of(result1, result2), resultBuilder);
assertThat(results).isEmpty();
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 957ee06..598d3a3 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -23,6 +23,8 @@
import static android.view.MotionEvent.ACTION_POINTER_INDEX_SHIFT;
import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;
+import static android.view.MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE;
+import static android.view.MotionEvent.TOOL_TYPE_FINGER;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -1414,6 +1416,49 @@
}
@Test
+ public void testSynthesizedGestureEventsDoNotMoveMagnifierViewport() {
+ final EventCaptor eventCaptor = new EventCaptor();
+ mMgh.setNext(eventCaptor);
+
+ float centerX =
+ (INITIAL_MAGNIFICATION_BOUNDS.left + INITIAL_MAGNIFICATION_BOUNDS.width()) / 2.0f;
+ float centerY =
+ (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
+ float scale = 5.6f; // value is unimportant but unique among tests to increase coverage.
+ mFullScreenMagnificationController.setScaleAndCenter(
+ DISPLAY_0, centerX, centerY, scale, /* animate= */ false, 1);
+ centerX = mFullScreenMagnificationController.getCenterX(DISPLAY_0);
+ centerY = mFullScreenMagnificationController.getCenterY(DISPLAY_0);
+
+ // Second finger down on trackpad starts a synthesized two-finger swipe with source
+ // mouse.
+ MotionEvent downEvent = motionEvent(centerX, centerY, ACTION_DOWN,
+ TOOL_TYPE_FINGER, CLASSIFICATION_TWO_FINGER_SWIPE);
+ send(downEvent, InputDevice.SOURCE_MOUSE);
+ fastForward(20);
+
+ // Two-finger swipe creates a synthesized move event, and shouldn't impact magnifier
+ // viewport.
+ MotionEvent moveEvent = motionEvent(centerX - 42, centerY - 42, ACTION_MOVE,
+ TOOL_TYPE_FINGER, CLASSIFICATION_TWO_FINGER_SWIPE);
+ send(moveEvent, InputDevice.SOURCE_MOUSE);
+ fastForward(20);
+
+ assertThat(mFullScreenMagnificationController.getCenterX(DISPLAY_0)).isEqualTo(centerX);
+ assertThat(mFullScreenMagnificationController.getCenterY(DISPLAY_0)).isEqualTo(centerY);
+
+ // The events were not consumed by magnifier.
+ assertThat(eventCaptor.mEvents.size()).isEqualTo(2);
+ assertThat(eventCaptor.mEvents.get(0).getSource()).isEqualTo(InputDevice.SOURCE_MOUSE);
+ assertThat(eventCaptor.mEvents.get(1).getSource()).isEqualTo(InputDevice.SOURCE_MOUSE);
+
+ final List<Integer> expectedActions = new ArrayList();
+ expectedActions.add(Integer.valueOf(ACTION_DOWN));
+ expectedActions.add(Integer.valueOf(ACTION_MOVE));
+ assertActionsInOrder(eventCaptor.mEvents, expectedActions);
+ }
+
+ @Test
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
public void testMouseHoverMoveEventsDoNotMoveMagnifierViewport() {
runHoverMoveEventsDoNotMoveMagnifierViewport(InputDevice.SOURCE_MOUSE);
@@ -2130,6 +2175,30 @@
return MotionEvent.obtain(mLastDownTime, mClock.now(), action, x, y, 0);
}
+ private MotionEvent motionEvent(float x, float y, int action, int toolType,
+ int classification) {
+ // Create a generic motion event to populate the parameters.
+ MotionEvent event = motionEvent(x, y, action);
+ int pointerCount = event.getPointerCount();
+ MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[pointerCount];
+ MotionEvent.PointerProperties[] properties =
+ new MotionEvent.PointerProperties[pointerCount];
+ for (int i = 0; i < pointerCount; i++) {
+ properties[i] = new MotionEvent.PointerProperties();
+ event.getPointerProperties(i, properties[i]);
+ properties[i].toolType = toolType;
+ coords[i] = new MotionEvent.PointerCoords();
+ event.getPointerCoords(i, coords[i]);
+ }
+ // Apply the custom classification.
+ return MotionEvent.obtain(event.getDownTime(), event.getEventTime(), action,
+ /*pointerCount=*/1, properties, coords,
+ event.getMetaState(), event.getButtonState(),
+ event.getXPrecision(), event.getYPrecision(), event.getDeviceId(),
+ event.getEdgeFlags(), event.getSource(), event.getDisplayId(), event.getFlags(),
+ classification);
+ }
+
private MotionEvent mouseEvent(float x, float y, int action) {
return fromMouse(motionEvent(x, y, action));
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index b4cc343..698bda3 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -60,6 +60,7 @@
import java.io.File;
import java.io.IOException;
+import java.util.List;
import java.util.Map;
/**
@@ -325,6 +326,11 @@
}
@Override
+ List<String> roleManagerGetRoleHoldersAsUser(String role, UserHandle userHandle) {
+ return services.roleManagerForMock.getRoleHoldersAsUser(role, userHandle);
+ }
+
+ @Override
PendingIntent pendingIntentGetActivityAsUser(Context context, int requestCode,
Intent intent, int flags, Bundle options, UserHandle user) {
return null;
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index b7483d6..cb4269a 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -109,6 +109,7 @@
import android.app.admin.PreferentialNetworkServiceConfig;
import android.app.admin.SystemUpdatePolicy;
import android.app.admin.WifiSsidPolicy;
+import android.app.role.RoleManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Intent;
@@ -2889,6 +2890,52 @@
}
@Test
+ public void testSetMeteredDataDisabledPackagesExemptRoles() throws Exception {
+ // TODO(b/362545319): reference role name from role manager once it's exposed.
+ final String controllerRole = "android.app.role.SYSTEM_FINANCED_DEVICE_CONTROLLER";
+
+ setAsProfileOwner(admin1);
+
+ assertThat(dpm.getMeteredDataDisabledPackages(admin1)).isEmpty();
+
+ // Setup
+ final ArrayList<String> pkgsToRestrict = new ArrayList<>();
+ final ArrayList<String> pkgsExpectedAsNotRestricted = new ArrayList<>();
+ final String packageWithControllerRole = "com.example.controller";
+ final String packageWithKioskRole = "com.example.kiosk";
+ final String packageWithNotExemptRole = "com.example.notexempt";
+
+ pkgsToRestrict.add(packageWithControllerRole);
+ pkgsToRestrict.add(packageWithKioskRole);
+ pkgsToRestrict.add(packageWithNotExemptRole);
+
+ pkgsExpectedAsNotRestricted.add(packageWithControllerRole);
+ pkgsExpectedAsNotRestricted.add(packageWithKioskRole);
+
+ setupPackageInPackageManager(packageWithControllerRole, CALLER_USER_HANDLE, 123, 0);
+ setupPackageInPackageManager(packageWithKioskRole, CALLER_USER_HANDLE, 456, 0);
+ setupPackageInPackageManager(packageWithNotExemptRole, CALLER_USER_HANDLE, 789, 0);
+
+ when(getServices().roleManagerForMock.getRoleHoldersAsUser(controllerRole,
+ UserHandle.of(CALLER_USER_HANDLE)))
+ .thenReturn(new ArrayList<>(Arrays.asList(packageWithControllerRole)));
+ when(getServices().roleManagerForMock.getRoleHoldersAsUser(
+ RoleManager.ROLE_FINANCED_DEVICE_KIOSK,
+ UserHandle.of(CALLER_USER_HANDLE)))
+ .thenReturn(new ArrayList<>(Arrays.asList(packageWithKioskRole)));
+
+ List<String> excludedPkgs = dpm.setMeteredDataDisabledPackages(admin1, pkgsToRestrict);
+
+ // Verify
+ assertThat(excludedPkgs).containsExactlyElementsIn(pkgsExpectedAsNotRestricted);
+ assertThat(dpm.getMeteredDataDisabledPackages(admin1))
+ .isEqualTo(Arrays.asList(packageWithNotExemptRole));
+ verify(getServices().networkPolicyManagerInternal).setMeteredRestrictedPackages(
+ MockUtils.checkApps(packageWithNotExemptRole),
+ eq(CALLER_USER_HANDLE));
+ }
+
+ @Test
public void testSetGetMeteredDataDisabledPackages_deviceAdmin() {
mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
dpm.setActiveAdmin(admin1, true);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 76aa40c..2e200a9 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -142,6 +142,7 @@
public final DevicePolicyManager devicePolicyManager;
public final LocationManager locationManager;
public final RoleManager roleManager;
+ public final RoleManagerForMock roleManagerForMock;
public final SubscriptionManager subscriptionManager;
/** Note this is a partial mock, not a real mock. */
public final PackageManager packageManager;
@@ -200,6 +201,7 @@
devicePolicyManager = mock(DevicePolicyManager.class);
locationManager = mock(LocationManager.class);
roleManager = realContext.getSystemService(RoleManager.class);
+ roleManagerForMock = mock(RoleManagerForMock.class);
subscriptionManager = mock(SubscriptionManager.class);
// Package manager is huge, so we use a partial mock instead.
@@ -495,6 +497,12 @@
}
}
+ public static class RoleManagerForMock {
+ public List<String> getRoleHoldersAsUser(String role, UserHandle userHandle) {
+ return new ArrayList<>();
+ }
+ }
+
public static class SettingsForMock {
public int settingsSecureGetIntForUser(String name, int def, int userHandle) {
return 0;
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
index 55c48e0..f0a5f75 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
@@ -36,7 +36,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -55,11 +54,6 @@
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
- @Before
- public void setUp() {
- mSetFlagsRule.enableFlags(android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
- }
-
@Test
public void testNonNull() {
Bundle out = UserRestrictionsUtils.nonNull(null);
@@ -144,7 +138,6 @@
@Test
public void testCanProfileOwnerChange_restrictionRequiresOrgOwnedDevice_orgOwned() {
- mSetFlagsRule.enableFlags(android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
assertTrue(UserRestrictionsUtils.canProfileOwnerChange(
UserManager.DISALLOW_SIM_GLOBALLY,
false,
@@ -157,7 +150,6 @@
@Test
public void testCanProfileOwnerChange_restrictionRequiresOrgOwnedDevice_notOrgOwned() {
- mSetFlagsRule.enableFlags(android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
assertFalse(UserRestrictionsUtils.canProfileOwnerChange(
UserManager.DISALLOW_SIM_GLOBALLY,
false,
@@ -169,22 +161,7 @@
}
@Test
- public void
- testCanProfileOwnerChange_disabled_restrictionRequiresOrgOwnedDevice_notOrgOwned() {
- mSetFlagsRule.disableFlags(android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
- assertTrue(UserRestrictionsUtils.canProfileOwnerChange(
- UserManager.DISALLOW_SIM_GLOBALLY,
- false,
- false));
- assertTrue(UserRestrictionsUtils.canProfileOwnerChange(
- UserManager.DISALLOW_SIM_GLOBALLY,
- true,
- false));
- }
-
- @Test
public void testCanProfileOwnerChange_restrictionNotRequiresOrgOwnedDevice_orgOwned() {
- mSetFlagsRule.enableFlags(android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
assertTrue(UserRestrictionsUtils.canProfileOwnerChange(
UserManager.DISALLOW_ADJUST_VOLUME,
false,
@@ -197,7 +174,6 @@
@Test
public void testCanProfileOwnerChange_restrictionNotRequiresOrgOwnedDevice_notOrgOwned() {
- mSetFlagsRule.enableFlags(android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
assertTrue(UserRestrictionsUtils.canProfileOwnerChange(
UserManager.DISALLOW_ADJUST_VOLUME,
false,
diff --git a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
index 963b27e..bf58443 100644
--- a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
@@ -38,6 +38,7 @@
import android.media.tv.tunerresourcemanager.TunerFrontendRequest;
import android.media.tv.tunerresourcemanager.TunerLnbRequest;
import android.media.tv.tunerresourcemanager.TunerResourceManager;
+import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
@@ -69,6 +70,61 @@
private TunerResourceManagerService mTunerResourceManagerService;
private boolean mIsForeground;
+ private final class TunerClient extends IResourcesReclaimListener.Stub {
+ int[] mClientId;
+ ClientProfile mProfile;
+ boolean mReclaimed;
+
+ TunerClient() {
+ mClientId = new int[1];
+ mClientId[0] = TunerResourceManagerService.INVALID_CLIENT_ID;
+ }
+
+ public void register(String sessionId, int useCase) {
+ ResourceClientProfile profile = new ResourceClientProfile();
+ profile.tvInputSessionId = sessionId;
+ profile.useCase = useCase;
+ mTunerResourceManagerService.registerClientProfileInternal(
+ profile, this, mClientId);
+ assertThat(mClientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+ mProfile = mTunerResourceManagerService.getClientProfile(mClientId[0]);
+ }
+
+ public void register(String sessionId, int useCase, int priority, int niceValue) {
+ register(sessionId, useCase);
+ mTunerResourceManagerService.updateClientPriorityInternal(
+ mClientId[0], priority, niceValue);
+ }
+
+ public void register(String sessionId, int useCase, int priority) {
+ register(sessionId, useCase, priority, 0);
+ }
+
+ public void unregister() {
+ mTunerResourceManagerService.unregisterClientProfileInternal(mClientId[0]);
+ mClientId[0] = TunerResourceManagerService.INVALID_CLIENT_ID;
+ mReclaimed = false;
+ }
+
+ public int getId() {
+ return mClientId[0];
+ }
+
+ public ClientProfile getProfile() {
+ return mProfile;
+ }
+
+ @Override
+ public void onReclaimResources() {
+ mTunerResourceManagerService.clearAllResourcesAndClientMapping(mProfile);
+ mReclaimed = true;
+ }
+
+ public boolean isReclaimed() {
+ return mReclaimed;
+ }
+ }
+
private static final class TestResourcesReclaimListener extends IResourcesReclaimListener.Stub {
boolean mReclaimed;
@@ -247,13 +303,11 @@
}
@Test
- public void requestFrontendTest_NoFrontendWithGiveTypeAvailable() {
- ResourceClientProfile profile = resourceClientProfile("0" /*sessionId*/,
- TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
- int[] clientId = new int[1];
- mTunerResourceManagerService.registerClientProfileInternal(
- profile, null /*listener*/, clientId);
- assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+ public void requestFrontendTest_NoFrontendWithGiveTypeAvailable() throws RemoteException {
+ // Register clients
+ TunerClient client0 = new TunerClient();
+ client0.register("0" /*sessionId*/,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
// Init frontend resources.
TunerFrontendInfo[] infos = new TunerFrontendInfo[1];
@@ -262,21 +316,20 @@
mTunerResourceManagerService.setFrontendInfoListInternal(infos);
TunerFrontendRequest request =
- tunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
+ tunerFrontendRequest(client0.getId() /*clientId*/, FrontendSettings.TYPE_DVBT);
int[] frontendHandle = new int[1];
assertThat(mTunerResourceManagerService
.requestFrontendInternal(request, frontendHandle)).isFalse();
assertThat(frontendHandle[0]).isEqualTo(TunerResourceManager.INVALID_RESOURCE_HANDLE);
+ client0.unregister();
}
@Test
- public void requestFrontendTest_FrontendWithNoExclusiveGroupAvailable() {
- ResourceClientProfile profile = resourceClientProfile("0" /*sessionId*/,
- TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
- int[] clientId = new int[1];
- mTunerResourceManagerService.registerClientProfileInternal(
- profile, null /*listener*/, clientId);
- assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+ public void requestFrontendTest_FrontendWithNoExclusiveGroupAvailable() throws RemoteException {
+ // Register clients
+ TunerClient client0 = new TunerClient();
+ client0.register("0" /*sessionId*/,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
// Init frontend resources.
TunerFrontendInfo[] infos = new TunerFrontendInfo[3];
@@ -295,27 +348,23 @@
mTunerResourceManagerService.setFrontendInfoListInternal(infos);
TunerFrontendRequest request =
- tunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
+ tunerFrontendRequest(client0.getId() /*clientId*/, FrontendSettings.TYPE_DVBT);
int[] frontendHandle = new int[1];
assertThat(mTunerResourceManagerService
.requestFrontendInternal(request, frontendHandle)).isTrue();
assertThat(frontendHandle[0]).isEqualTo(0);
+ client0.unregister();
}
@Test
- public void requestFrontendTest_FrontendWithExclusiveGroupAvailable() {
- ResourceClientProfile profile0 = resourceClientProfile("0" /*sessionId*/,
- TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
- ResourceClientProfile profile1 = resourceClientProfile("1" /*sessionId*/,
- TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
- int[] clientId0 = new int[1];
- int[] clientId1 = new int[1];
- mTunerResourceManagerService.registerClientProfileInternal(
- profile0, null /*listener*/, clientId0);
- mTunerResourceManagerService.registerClientProfileInternal(
- profile1, null /*listener*/, clientId1);
- assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
- assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+ public void requestFrontendTest_FrontendWithExclusiveGroupAvailable() throws RemoteException {
+ // Register clients
+ TunerClient client0 = new TunerClient();
+ TunerClient client1 = new TunerClient();
+ client0.register("0" /*sessionId*/,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
+ client1.register("1" /*sessionId*/,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
// Init frontend resources.
TunerFrontendInfo[] infos = new TunerFrontendInfo[3];
@@ -335,13 +384,13 @@
int[] frontendHandle = new int[1];
TunerFrontendRequest request =
- tunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
+ tunerFrontendRequest(client1.getId() /*clientId*/, FrontendSettings.TYPE_DVBT);
assertThat(mTunerResourceManagerService
.requestFrontendInternal(request, frontendHandle)).isTrue();
assertThat(frontendHandle[0]).isEqualTo(infos[0].handle);
request =
- tunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
+ tunerFrontendRequest(client0.getId() /*clientId*/, FrontendSettings.TYPE_DVBT);
assertThat(mTunerResourceManagerService
.requestFrontendInternal(request, frontendHandle)).isTrue();
assertThat(frontendHandle[0]).isEqualTo(infos[1].handle);
@@ -349,31 +398,20 @@
.isTrue();
assertThat(mTunerResourceManagerService.getFrontendResource(infos[2].handle).isInUse())
.isTrue();
+ client0.unregister();
+ client1.unregister();
}
@Test
- public void requestFrontendTest_NoFrontendAvailable_RequestWithLowerPriority() {
+ public void requestFrontendTest_NoFrontendAvailable_RequestWithLowerPriority()
+ throws RemoteException {
// Register clients
- ResourceClientProfile[] profiles = new ResourceClientProfile[2];
- profiles[0] = resourceClientProfile("0" /*sessionId*/,
- TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
- profiles[1] = resourceClientProfile("1" /*sessionId*/,
- TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
- int[] clientPriorities = {100, 50};
- int[] clientId0 = new int[1];
- int[] clientId1 = new int[1];
- TestResourcesReclaimListener listener = new TestResourcesReclaimListener();
-
- mTunerResourceManagerService.registerClientProfileInternal(
- profiles[0], listener, clientId0);
- assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
- mTunerResourceManagerService.updateClientPriorityInternal(
- clientId0[0], clientPriorities[0], 0/*niceValue*/);
- mTunerResourceManagerService.registerClientProfileInternal(
- profiles[1], new TestResourcesReclaimListener(), clientId1);
- assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
- mTunerResourceManagerService.updateClientPriorityInternal(
- clientId1[0], clientPriorities[1], 0/*niceValue*/);
+ TunerClient client0 = new TunerClient();
+ TunerClient client1 = new TunerClient();
+ client0.register("0" /*sessionId*/,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 100);
+ client1.register("1" /*sessionId*/,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 50);
// Init frontend resources.
TunerFrontendInfo[] infos = new TunerFrontendInfo[2];
@@ -384,46 +422,36 @@
mTunerResourceManagerService.setFrontendInfoListInternal(infos);
TunerFrontendRequest request =
- tunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
+ tunerFrontendRequest(client0.getId() /*clientId*/, FrontendSettings.TYPE_DVBT);
int[] frontendHandle = new int[1];
assertThat(mTunerResourceManagerService
.requestFrontendInternal(request, frontendHandle)).isTrue();
request =
- tunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
+ tunerFrontendRequest(client1.getId() /*clientId*/, FrontendSettings.TYPE_DVBT);
assertThat(mTunerResourceManagerService
.requestFrontendInternal(request, frontendHandle)).isFalse();
- assertThat(listener.isReclaimed()).isFalse();
+ assertThat(client0.isReclaimed()).isFalse();
request =
- tunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBS);
+ tunerFrontendRequest(client1.getId() /*clientId*/, FrontendSettings.TYPE_DVBS);
assertThat(mTunerResourceManagerService
.requestFrontendInternal(request, frontendHandle)).isFalse();
- assertThat(listener.isReclaimed()).isFalse();
+ assertThat(client0.isReclaimed()).isFalse();
+ client0.unregister();
+ client1.unregister();
}
@Test
- public void requestFrontendTest_NoFrontendAvailable_RequestWithHigherPriority() {
+ public void requestFrontendTest_NoFrontendAvailable_RequestWithHigherPriority()
+ throws RemoteException {
// Register clients
- ResourceClientProfile[] profiles = new ResourceClientProfile[2];
- profiles[0] = resourceClientProfile("0" /*sessionId*/,
- TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
- profiles[1] = resourceClientProfile("1" /*sessionId*/,
- TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
- int[] clientPriorities = {100, 500};
- int[] clientId0 = new int[1];
- int[] clientId1 = new int[1];
- TestResourcesReclaimListener listener = new TestResourcesReclaimListener();
- mTunerResourceManagerService.registerClientProfileInternal(
- profiles[0], listener, clientId0);
- assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
- mTunerResourceManagerService.updateClientPriorityInternal(
- clientId0[0], clientPriorities[0], 0/*niceValue*/);
- mTunerResourceManagerService.registerClientProfileInternal(
- profiles[1], new TestResourcesReclaimListener(), clientId1);
- assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
- mTunerResourceManagerService.updateClientPriorityInternal(
- clientId1[0], clientPriorities[1], 0/*niceValue*/);
+ TunerClient client0 = new TunerClient();
+ TunerClient client1 = new TunerClient();
+ client0.register("0" /*sessionId*/,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 100);
+ client1.register("1" /*sessionId*/,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 500);
// Init frontend resources.
TunerFrontendInfo[] infos = new TunerFrontendInfo[2];
@@ -434,17 +462,16 @@
mTunerResourceManagerService.setFrontendInfoListInternal(infos);
TunerFrontendRequest request =
- tunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
+ tunerFrontendRequest(client0.getId() /*clientId*/, FrontendSettings.TYPE_DVBT);
int[] frontendHandle = new int[1];
assertThat(mTunerResourceManagerService
.requestFrontendInternal(request, frontendHandle)).isTrue();
assertThat(frontendHandle[0]).isEqualTo(infos[0].handle);
- assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0])
- .getInUseFrontendHandles()).isEqualTo(new HashSet<Integer>(Arrays.asList(
- infos[0].handle, infos[1].handle)));
+ assertThat(client0.getProfile().getInUseFrontendHandles())
+ .isEqualTo(new HashSet<Integer>(Arrays.asList(infos[0].handle, infos[1].handle)));
request =
- tunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBS);
+ tunerFrontendRequest(client1.getId() /*clientId*/, FrontendSettings.TYPE_DVBS);
assertThat(mTunerResourceManagerService
.requestFrontendInternal(request, frontendHandle)).isTrue();
assertThat(frontendHandle[0]).isEqualTo(infos[1].handle);
@@ -453,22 +480,20 @@
assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].handle)
.isInUse()).isTrue();
assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].handle)
- .getOwnerClientId()).isEqualTo(clientId1[0]);
+ .getOwnerClientId()).isEqualTo(client1.getId());
assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].handle)
- .getOwnerClientId()).isEqualTo(clientId1[0]);
- assertThat(listener.isReclaimed()).isTrue();
+ .getOwnerClientId()).isEqualTo(client1.getId());
+ assertThat(client0.isReclaimed()).isTrue();
+ client0.unregister();
+ client1.unregister();
}
@Test
- public void releaseFrontendTest_UnderTheSameExclusiveGroup() {
+ public void releaseFrontendTest_UnderTheSameExclusiveGroup() throws RemoteException {
// Register clients
- ResourceClientProfile[] profiles = new ResourceClientProfile[1];
- profiles[0] = resourceClientProfile("0" /*sessionId*/,
- TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
- int[] clientId = new int[1];
- TestResourcesReclaimListener listener = new TestResourcesReclaimListener();
- mTunerResourceManagerService.registerClientProfileInternal(profiles[0], listener, clientId);
- assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+ TunerClient client0 = new TunerClient();
+ client0.register("0" /*sessionId*/,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
// Init frontend resources.
TunerFrontendInfo[] infos = new TunerFrontendInfo[2];
@@ -479,7 +504,7 @@
mTunerResourceManagerService.setFrontendInfoListInternal(infos);
TunerFrontendRequest request =
- tunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
+ tunerFrontendRequest(client0.getId() /*clientId*/, FrontendSettings.TYPE_DVBT);
int[] frontendHandle = new int[1];
assertThat(mTunerResourceManagerService
.requestFrontendInternal(request, frontendHandle)).isTrue();
@@ -488,43 +513,29 @@
.getFrontendResource(infos[1].handle).isInUse()).isTrue();
// Release frontend
- mTunerResourceManagerService.releaseFrontendInternal(mTunerResourceManagerService
- .getFrontendResource(frontendHandle[0]), clientId[0]);
+ mTunerResourceManagerService.releaseFrontendInternal(frontendHandle[0], client0.getId());
assertThat(mTunerResourceManagerService
.getFrontendResource(frontendHandle[0]).isInUse()).isFalse();
assertThat(mTunerResourceManagerService
.getFrontendResource(infos[1].handle).isInUse()).isFalse();
- assertThat(mTunerResourceManagerService
- .getClientProfile(clientId[0]).getInUseFrontendHandles().size()).isEqualTo(0);
+ assertThat(client0.getProfile().getInUseFrontendHandles().size()).isEqualTo(0);
+ client0.unregister();
}
@Test
- public void requestCasTest_NoCasAvailable_RequestWithHigherPriority() {
+ public void requestCasTest_NoCasAvailable_RequestWithHigherPriority() throws RemoteException {
// Register clients
- ResourceClientProfile[] profiles = new ResourceClientProfile[2];
- profiles[0] = resourceClientProfile("0" /*sessionId*/,
- TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
- profiles[1] = resourceClientProfile("1" /*sessionId*/,
- TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
- int[] clientPriorities = {100, 500};
- int[] clientId0 = new int[1];
- int[] clientId1 = new int[1];
- TestResourcesReclaimListener listener = new TestResourcesReclaimListener();
- mTunerResourceManagerService.registerClientProfileInternal(
- profiles[0], listener, clientId0);
- assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
- mTunerResourceManagerService.updateClientPriorityInternal(
- clientId0[0], clientPriorities[0], 0/*niceValue*/);
- mTunerResourceManagerService.registerClientProfileInternal(
- profiles[1], new TestResourcesReclaimListener(), clientId1);
- assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
- mTunerResourceManagerService.updateClientPriorityInternal(
- clientId1[0], clientPriorities[1], 0/*niceValue*/);
+ TunerClient client0 = new TunerClient();
+ TunerClient client1 = new TunerClient();
+ client0.register("0" /*sessionId*/,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 100);
+ client1.register("1" /*sessionId*/,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 500);
// Init cas resources.
mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/);
- CasSessionRequest request = casSessionRequest(clientId0[0], 1 /*casSystemId*/);
+ CasSessionRequest request = casSessionRequest(client0.getId(), 1 /*casSystemId*/);
int[] casSessionHandle = new int[1];
// Request for 2 cas sessions.
assertThat(mTunerResourceManagerService
@@ -533,54 +544,45 @@
.requestCasSessionInternal(request, casSessionHandle)).isTrue();
assertThat(mTunerResourceManagerService.getResourceIdFromHandle(casSessionHandle[0]))
.isEqualTo(1);
- assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0])
- .getInUseCasSystemId()).isEqualTo(1);
+ assertThat(client0.getProfile().getInUseCasSystemId())
+ .isEqualTo(1);
assertThat(mTunerResourceManagerService.getCasResource(1)
- .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId0[0])));
+ .getOwnerClientIds()).isEqualTo(
+ new HashSet<Integer>(Arrays.asList(client0.getId())));
assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isTrue();
- request = casSessionRequest(clientId1[0], 1);
+ request = casSessionRequest(client1.getId(), 1);
assertThat(mTunerResourceManagerService
.requestCasSessionInternal(request, casSessionHandle)).isTrue();
assertThat(mTunerResourceManagerService.getResourceIdFromHandle(casSessionHandle[0]))
.isEqualTo(1);
- assertThat(mTunerResourceManagerService.getClientProfile(clientId1[0])
- .getInUseCasSystemId()).isEqualTo(1);
- assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0])
- .getInUseCasSystemId()).isEqualTo(ClientProfile.INVALID_RESOURCE_ID);
+ assertThat(client1.getProfile().getInUseCasSystemId()).isEqualTo(1);
+ assertThat(client0.getProfile().getInUseCasSystemId())
+ .isEqualTo(ClientProfile.INVALID_RESOURCE_ID);
assertThat(mTunerResourceManagerService.getCasResource(1)
- .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId1[0])));
+ .getOwnerClientIds()).isEqualTo(
+ new HashSet<Integer>(Arrays.asList(client1.getId())));
assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isFalse();
- assertThat(listener.isReclaimed()).isTrue();
+ assertThat(client0.isReclaimed()).isTrue();
+ client0.unregister();
+ client1.unregister();
}
@Test
- public void requestCiCamTest_NoCiCamAvailable_RequestWithHigherPriority() {
+ public void requestCiCamTest_NoCiCamAvailable_RequestWithHigherPriority()
+ throws RemoteException {
// Register clients
- ResourceClientProfile[] profiles = new ResourceClientProfile[2];
- profiles[0] = resourceClientProfile("0" /*sessionId*/,
- TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
- profiles[1] = resourceClientProfile("1" /*sessionId*/,
- TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
- int[] clientPriorities = {100, 500};
- int[] clientId0 = new int[1];
- int[] clientId1 = new int[1];
- TestResourcesReclaimListener listener = new TestResourcesReclaimListener();
- mTunerResourceManagerService.registerClientProfileInternal(
- profiles[0], listener, clientId0);
- assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
- mTunerResourceManagerService.updateClientPriorityInternal(
- clientId0[0], clientPriorities[0], 0/*niceValue*/);
- mTunerResourceManagerService.registerClientProfileInternal(
- profiles[1], new TestResourcesReclaimListener(), clientId1);
- assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
- mTunerResourceManagerService.updateClientPriorityInternal(
- clientId1[0], clientPriorities[1], 0/*niceValue*/);
+ TunerClient client0 = new TunerClient();
+ TunerClient client1 = new TunerClient();
+ client0.register("0" /*sessionId*/,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 100);
+ client1.register("1" /*sessionId*/,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 500);
// Init cicam/cas resources.
mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/);
- TunerCiCamRequest request = tunerCiCamRequest(clientId0[0], 1 /*ciCamId*/);
+ TunerCiCamRequest request = tunerCiCamRequest(client0.getId(), 1 /*ciCamId*/);
int[] ciCamHandle = new int[1];
// Request for 2 ciCam sessions.
assertThat(mTunerResourceManagerService
@@ -589,139 +591,125 @@
.requestCiCamInternal(request, ciCamHandle)).isTrue();
assertThat(mTunerResourceManagerService.getResourceIdFromHandle(ciCamHandle[0]))
.isEqualTo(1);
- assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0])
- .getInUseCiCamId()).isEqualTo(1);
+ assertThat(client0.getProfile().getInUseCiCamId()).isEqualTo(1);
assertThat(mTunerResourceManagerService.getCiCamResource(1)
- .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId0[0])));
+ .getOwnerClientIds()).isEqualTo(
+ new HashSet<Integer>(Arrays.asList(client0.getId())));
assertThat(mTunerResourceManagerService.getCiCamResource(1).isFullyUsed()).isTrue();
- request = tunerCiCamRequest(clientId1[0], 1);
+ request = tunerCiCamRequest(client1.getId(), 1);
assertThat(mTunerResourceManagerService
.requestCiCamInternal(request, ciCamHandle)).isTrue();
assertThat(mTunerResourceManagerService.getResourceIdFromHandle(ciCamHandle[0]))
.isEqualTo(1);
- assertThat(mTunerResourceManagerService.getClientProfile(clientId1[0])
- .getInUseCiCamId()).isEqualTo(1);
- assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0])
- .getInUseCiCamId()).isEqualTo(ClientProfile.INVALID_RESOURCE_ID);
+ assertThat(client1.getProfile().getInUseCiCamId()).isEqualTo(1);
+ assertThat(client0.getProfile().getInUseCiCamId())
+ .isEqualTo(ClientProfile.INVALID_RESOURCE_ID);
assertThat(mTunerResourceManagerService.getCiCamResource(1)
- .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId1[0])));
- assertThat(mTunerResourceManagerService.getCiCamResource(1).isFullyUsed()).isFalse();
- assertThat(listener.isReclaimed()).isTrue();
+ .getOwnerClientIds()).isEqualTo(
+ new HashSet<Integer>(Arrays.asList(client1.getId())));
+ assertThat(mTunerResourceManagerService
+ .getCiCamResource(1).isFullyUsed()).isFalse();
+ assertThat(client0.isReclaimed()).isTrue();
+ client0.unregister();
+ client1.unregister();
}
@Test
- public void releaseCasTest() {
+ public void releaseCasTest() throws RemoteException {
// Register clients
- ResourceClientProfile[] profiles = new ResourceClientProfile[1];
- profiles[0] = resourceClientProfile("0" /*sessionId*/,
- TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
- int[] clientId = new int[1];
- TestResourcesReclaimListener listener = new TestResourcesReclaimListener();
- mTunerResourceManagerService.registerClientProfileInternal(profiles[0], listener, clientId);
- assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+ TunerClient client0 = new TunerClient();
+ client0.register("0" /*sessionId*/,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
// Init cas resources.
mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/);
- CasSessionRequest request = casSessionRequest(clientId[0], 1 /*casSystemId*/);
+ CasSessionRequest request = casSessionRequest(client0.getId(), 1 /*casSystemId*/);
int[] casSessionHandle = new int[1];
// Request for 1 cas sessions.
assertThat(mTunerResourceManagerService
.requestCasSessionInternal(request, casSessionHandle)).isTrue();
assertThat(mTunerResourceManagerService.getResourceIdFromHandle(casSessionHandle[0]))
.isEqualTo(1);
- assertThat(mTunerResourceManagerService.getClientProfile(clientId[0])
- .getInUseCasSystemId()).isEqualTo(1);
+ assertThat(client0.getProfile().getInUseCasSystemId()).isEqualTo(1);
assertThat(mTunerResourceManagerService.getCasResource(1)
- .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId[0])));
+ .getOwnerClientIds()).isEqualTo(
+ new HashSet<Integer>(Arrays.asList(client0.getId())));
assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isFalse();
// Release cas
mTunerResourceManagerService.releaseCasSessionInternal(mTunerResourceManagerService
- .getCasResource(1), clientId[0]);
- assertThat(mTunerResourceManagerService.getClientProfile(clientId[0])
- .getInUseCasSystemId()).isEqualTo(ClientProfile.INVALID_RESOURCE_ID);
+ .getCasResource(1), client0.getId());
+ assertThat(client0.getProfile().getInUseCasSystemId())
+ .isEqualTo(ClientProfile.INVALID_RESOURCE_ID);
assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isFalse();
assertThat(mTunerResourceManagerService.getCasResource(1)
.getOwnerClientIds()).isEmpty();
+ client0.unregister();
}
@Test
- public void releaseCiCamTest() {
+ public void releaseCiCamTest() throws RemoteException {
// Register clients
- ResourceClientProfile[] profiles = new ResourceClientProfile[1];
- profiles[0] = resourceClientProfile("0" /*sessionId*/,
- TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
- int[] clientId = new int[1];
- TestResourcesReclaimListener listener = new TestResourcesReclaimListener();
- mTunerResourceManagerService.registerClientProfileInternal(profiles[0], listener, clientId);
- assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+ TunerClient client0 = new TunerClient();
+ client0.register("0" /*sessionId*/,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
// Init cas resources.
mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/);
- TunerCiCamRequest request = tunerCiCamRequest(clientId[0], 1 /*ciCamId*/);
+ TunerCiCamRequest request = tunerCiCamRequest(client0.getId(), 1 /*ciCamId*/);
int[] ciCamHandle = new int[1];
// Request for 1 ciCam sessions.
assertThat(mTunerResourceManagerService
.requestCiCamInternal(request, ciCamHandle)).isTrue();
assertThat(mTunerResourceManagerService.getResourceIdFromHandle(ciCamHandle[0]))
.isEqualTo(1);
- assertThat(mTunerResourceManagerService.getClientProfile(clientId[0])
- .getInUseCiCamId()).isEqualTo(1);
+ assertThat(client0.getProfile().getInUseCiCamId()).isEqualTo(1);
assertThat(mTunerResourceManagerService.getCiCamResource(1)
- .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId[0])));
- assertThat(mTunerResourceManagerService.getCiCamResource(1).isFullyUsed()).isFalse();
+ .getOwnerClientIds()).isEqualTo(
+ new HashSet<Integer>(Arrays.asList(client0.getId())));
+ assertThat(mTunerResourceManagerService
+ .getCiCamResource(1).isFullyUsed()).isFalse();
// Release ciCam
mTunerResourceManagerService.releaseCiCamInternal(mTunerResourceManagerService
- .getCiCamResource(1), clientId[0]);
- assertThat(mTunerResourceManagerService.getClientProfile(clientId[0])
- .getInUseCiCamId()).isEqualTo(ClientProfile.INVALID_RESOURCE_ID);
- assertThat(mTunerResourceManagerService.getCiCamResource(1).isFullyUsed()).isFalse();
+ .getCiCamResource(1), client0.getId());
+ assertThat(client0.getProfile().getInUseCiCamId())
+ .isEqualTo(ClientProfile.INVALID_RESOURCE_ID);
+ assertThat(mTunerResourceManagerService
+ .getCiCamResource(1).isFullyUsed()).isFalse();
assertThat(mTunerResourceManagerService.getCiCamResource(1)
.getOwnerClientIds()).isEmpty();
+ client0.unregister();
}
@Test
- public void requestLnbTest_NoLnbAvailable_RequestWithHigherPriority() {
+ public void requestLnbTest_NoLnbAvailable_RequestWithHigherPriority() throws RemoteException {
// Register clients
- ResourceClientProfile[] profiles = new ResourceClientProfile[2];
- profiles[0] = resourceClientProfile("0" /*sessionId*/,
- TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
- profiles[1] = resourceClientProfile("1" /*sessionId*/,
- TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
- int[] clientPriorities = {100, 500};
- int[] clientId0 = new int[1];
- int[] clientId1 = new int[1];
- TestResourcesReclaimListener listener = new TestResourcesReclaimListener();
- mTunerResourceManagerService.registerClientProfileInternal(
- profiles[0], listener, clientId0);
- assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
- mTunerResourceManagerService.updateClientPriorityInternal(
- clientId0[0], clientPriorities[0], 0/*niceValue*/);
- mTunerResourceManagerService.registerClientProfileInternal(
- profiles[1], new TestResourcesReclaimListener(), clientId1);
- assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
- mTunerResourceManagerService.updateClientPriorityInternal(
- clientId1[0], clientPriorities[1], 0/*niceValue*/);
+ TunerClient client0 = new TunerClient();
+ TunerClient client1 = new TunerClient();
+ client0.register("0" /*sessionId*/,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 100);
+ client1.register("1" /*sessionId*/,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 500);
// Init lnb resources.
int[] lnbHandles = {1};
mTunerResourceManagerService.setLnbInfoListInternal(lnbHandles);
TunerLnbRequest request = new TunerLnbRequest();
- request.clientId = clientId0[0];
+ request.clientId = client0.getId();
int[] lnbHandle = new int[1];
assertThat(mTunerResourceManagerService
.requestLnbInternal(request, lnbHandle)).isTrue();
assertThat(lnbHandle[0]).isEqualTo(lnbHandles[0]);
- assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0]).getInUseLnbHandles())
+ assertThat(client0.getProfile().getInUseLnbHandles())
.isEqualTo(new HashSet<Integer>(Arrays.asList(lnbHandles[0])));
request = new TunerLnbRequest();
- request.clientId = clientId1[0];
+ request.clientId = client1.getId();
assertThat(mTunerResourceManagerService
.requestLnbInternal(request, lnbHandle)).isTrue();
@@ -729,29 +717,26 @@
assertThat(mTunerResourceManagerService.getLnbResource(lnbHandles[0])
.isInUse()).isTrue();
assertThat(mTunerResourceManagerService.getLnbResource(lnbHandles[0])
- .getOwnerClientId()).isEqualTo(clientId1[0]);
- assertThat(listener.isReclaimed()).isTrue();
- assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0])
- .getInUseLnbHandles().size()).isEqualTo(0);
+ .getOwnerClientId()).isEqualTo(client1.getId());
+ assertThat(client0.isReclaimed()).isTrue();
+ assertThat(client0.getProfile().getInUseLnbHandles().size()).isEqualTo(0);
+ client0.unregister();
+ client1.unregister();
}
@Test
- public void releaseLnbTest() {
+ public void releaseLnbTest() throws RemoteException {
// Register clients
- ResourceClientProfile[] profiles = new ResourceClientProfile[1];
- profiles[0] = resourceClientProfile("0" /*sessionId*/,
- TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
- int[] clientId = new int[1];
- TestResourcesReclaimListener listener = new TestResourcesReclaimListener();
- mTunerResourceManagerService.registerClientProfileInternal(profiles[0], listener, clientId);
- assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+ TunerClient client0 = new TunerClient();
+ client0.register("0" /*sessionId*/,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
// Init lnb resources.
int[] lnbHandles = {0};
mTunerResourceManagerService.setLnbInfoListInternal(lnbHandles);
TunerLnbRequest request = new TunerLnbRequest();
- request.clientId = clientId[0];
+ request.clientId = client0.getId();
int[] lnbHandle = new int[1];
assertThat(mTunerResourceManagerService
.requestLnbInternal(request, lnbHandle)).isTrue();
@@ -762,19 +747,16 @@
.getLnbResource(lnbHandle[0]));
assertThat(mTunerResourceManagerService
.getLnbResource(lnbHandle[0]).isInUse()).isFalse();
- assertThat(mTunerResourceManagerService
- .getClientProfile(clientId[0]).getInUseLnbHandles().size()).isEqualTo(0);
+ assertThat(client0.getProfile().getInUseLnbHandles().size()).isEqualTo(0);
+ client0.unregister();
}
@Test
- public void unregisterClientTest_usingFrontend() {
- // Register client
- ResourceClientProfile profile = resourceClientProfile("0" /*sessionId*/,
- TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
- int[] clientId = new int[1];
- mTunerResourceManagerService.registerClientProfileInternal(
- profile, null /*listener*/, clientId);
- assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+ public void unregisterClientTest_usingFrontend() throws RemoteException {
+ // Register clients
+ TunerClient client0 = new TunerClient();
+ client0.register("0" /*sessionId*/,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
// Init frontend resources.
TunerFrontendInfo[] infos = new TunerFrontendInfo[2];
@@ -785,7 +767,7 @@
mTunerResourceManagerService.setFrontendInfoListInternal(infos);
TunerFrontendRequest request =
- tunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
+ tunerFrontendRequest(client0.getId() /*clientId*/, FrontendSettings.TYPE_DVBT);
int[] frontendHandle = new int[1];
assertThat(mTunerResourceManagerService
.requestFrontendInternal(request, frontendHandle)).isTrue();
@@ -796,26 +778,20 @@
.isInUse()).isTrue();
// Unregister client when using frontend
- mTunerResourceManagerService.unregisterClientProfileInternal(clientId[0]);
+ client0.unregister();
assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].handle)
.isInUse()).isFalse();
assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].handle)
.isInUse()).isFalse();
- assertThat(mTunerResourceManagerService.checkClientExists(clientId[0])).isFalse();
-
+ assertThat(mTunerResourceManagerService.checkClientExists(client0.getId())).isFalse();
}
@Test
- public void requestDemuxTest() {
- // Register client
- ResourceClientProfile profile0 = resourceClientProfile("0" /*sessionId*/,
- TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
- ResourceClientProfile profile1 = resourceClientProfile("1" /*sessionId*/,
- TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
- int[] clientId0 = new int[1];
- mTunerResourceManagerService.registerClientProfileInternal(
- profile0, null /*listener*/, clientId0);
- assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+ public void requestDemuxTest() throws RemoteException {
+ // Register clients
+ TunerClient client0 = new TunerClient();
+ client0.register("0" /*sessionId*/,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
TunerDemuxInfo[] infos = new TunerDemuxInfo[3];
infos[0] = tunerDemuxInfo(0 /* handle */, Filter.TYPE_TS | Filter.TYPE_IP);
@@ -825,7 +801,7 @@
int[] demuxHandle0 = new int[1];
// first with undefined type (should be the first one with least # of caps)
- TunerDemuxRequest request = tunerDemuxRequest(clientId0[0], Filter.TYPE_UNDEFINED);
+ TunerDemuxRequest request = tunerDemuxRequest(client0.getId(), Filter.TYPE_UNDEFINED);
assertThat(mTunerResourceManagerService.requestDemuxInternal(request, demuxHandle0))
.isTrue();
assertThat(demuxHandle0[0]).isEqualTo(1);
@@ -846,16 +822,16 @@
assertThat(demuxHandle0[0]).isEqualTo(2);
// request for another TS
- int[] clientId1 = new int[1];
- mTunerResourceManagerService.registerClientProfileInternal(
- profile1, null /*listener*/, clientId1);
- assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+ TunerClient client1 = new TunerClient();
+ client1.register("1" /*sessionId*/,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
+
int[] demuxHandle1 = new int[1];
- TunerDemuxRequest request1 = tunerDemuxRequest(clientId1[0], Filter.TYPE_TS);
+ TunerDemuxRequest request1 = tunerDemuxRequest(client1.getId(), Filter.TYPE_TS);
assertThat(mTunerResourceManagerService.requestDemuxInternal(request1, demuxHandle1))
.isTrue();
assertThat(demuxHandle1[0]).isEqualTo(0);
- assertThat(mTunerResourceManagerService.getResourceIdFromHandle(demuxHandle1[0]))
+ assertThat(mTunerResourceManagerService.getResourceIdFromHandle(client1.getId()))
.isEqualTo(0);
// release demuxes
@@ -863,33 +839,23 @@
mTunerResourceManagerService.releaseDemuxInternal(dr);
dr = mTunerResourceManagerService.getDemuxResource(demuxHandle1[0]);
mTunerResourceManagerService.releaseDemuxInternal(dr);
+
+ client0.unregister();
+ client1.unregister();
}
@Test
- public void requestDemuxTest_ResourceReclaim() {
+ public void requestDemuxTest_ResourceReclaim() throws RemoteException {
// Register clients
- ResourceClientProfile profile0 = resourceClientProfile("0" /*sessionId*/,
- TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
- ResourceClientProfile profile1 = resourceClientProfile("1" /*sessionId*/,
- TvInputService.PRIORITY_HINT_USE_CASE_TYPE_SCAN);
- ResourceClientProfile profile2 = resourceClientProfile("2" /*sessionId*/,
- TvInputService.PRIORITY_HINT_USE_CASE_TYPE_SCAN);
- int[] clientId0 = new int[1];
- int[] clientId1 = new int[1];
- int[] clientId2 = new int[1];
- TestResourcesReclaimListener listener0 = new TestResourcesReclaimListener();
- TestResourcesReclaimListener listener1 = new TestResourcesReclaimListener();
- TestResourcesReclaimListener listener2 = new TestResourcesReclaimListener();
-
- mTunerResourceManagerService.registerClientProfileInternal(
- profile0, listener0, clientId0);
- assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
- mTunerResourceManagerService.registerClientProfileInternal(
- profile1, listener1, clientId1);
- assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
- mTunerResourceManagerService.registerClientProfileInternal(
- profile2, listener2, clientId1);
- assertThat(clientId2[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+ TunerClient client0 = new TunerClient();
+ TunerClient client1 = new TunerClient();
+ TunerClient client2 = new TunerClient();
+ client0.register("0" /*sessionId*/,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
+ client1.register("1" /*sessionId*/,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_SCAN);
+ client2.register("2" /*sessionId*/,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_SCAN);
// Init demux resources.
TunerDemuxInfo[] infos = new TunerDemuxInfo[2];
@@ -897,66 +863,67 @@
infos[1] = tunerDemuxInfo(1 /*handle*/, Filter.TYPE_TS);
mTunerResourceManagerService.setDemuxInfoListInternal(infos);
- // let clientId0(prio:100) request for IP - should succeed
- TunerDemuxRequest request0 = tunerDemuxRequest(clientId0[0], Filter.TYPE_IP);
+ // let client0(prio:100) request for IP - should succeed
+ TunerDemuxRequest request0 = tunerDemuxRequest(client0.getId(), Filter.TYPE_IP);
int[] demuxHandle0 = new int[1];
assertThat(mTunerResourceManagerService
.requestDemuxInternal(request0, demuxHandle0)).isTrue();
assertThat(demuxHandle0[0]).isEqualTo(0);
- // let clientId1(prio:50) request for IP - should fail
- TunerDemuxRequest request1 = tunerDemuxRequest(clientId1[0], Filter.TYPE_IP);
+ // let client1(prio:50) request for IP - should fail
+ TunerDemuxRequest request1 = tunerDemuxRequest(client1.getId(), Filter.TYPE_IP);
int[] demuxHandle1 = new int[1];
demuxHandle1[0] = -1;
assertThat(mTunerResourceManagerService
.requestDemuxInternal(request1, demuxHandle1)).isFalse();
- assertThat(listener0.isReclaimed()).isFalse();
+ assertThat(client0.isReclaimed()).isFalse();
assertThat(demuxHandle1[0]).isEqualTo(-1);
- // let clientId1(prio:50) request for TS - should succeed
+ // let client1(prio:50) request for TS - should succeed
request1.desiredFilterTypes = Filter.TYPE_TS;
assertThat(mTunerResourceManagerService
.requestDemuxInternal(request1, demuxHandle1)).isTrue();
assertThat(demuxHandle1[0]).isEqualTo(1);
- assertThat(listener0.isReclaimed()).isFalse();
+ assertThat(client0.isReclaimed()).isFalse();
- // now release demux for the clientId0 (higher priority) and request demux
+ // now release demux for the client0 (higher priority) and request demux
DemuxResource dr = mTunerResourceManagerService.getDemuxResource(demuxHandle0[0]);
mTunerResourceManagerService.releaseDemuxInternal(dr);
- // let clientId2(prio:50) request for TS - should succeed
- TunerDemuxRequest request2 = tunerDemuxRequest(clientId2[0], Filter.TYPE_TS);
+ // let client2(prio:50) request for TS - should succeed
+ TunerDemuxRequest request2 = tunerDemuxRequest(client2.getId(), Filter.TYPE_TS);
int[] demuxHandle2 = new int[1];
assertThat(mTunerResourceManagerService
.requestDemuxInternal(request2, demuxHandle2)).isTrue();
assertThat(demuxHandle2[0]).isEqualTo(0);
- assertThat(listener1.isReclaimed()).isFalse();
+ assertThat(client1.isReclaimed()).isFalse();
- // let clientId0(prio:100) request for TS - should reclaim from clientId2
+ // let client0(prio:100) request for TS - should reclaim from client1
// , who has the smaller caps
request0.desiredFilterTypes = Filter.TYPE_TS;
assertThat(mTunerResourceManagerService
.requestDemuxInternal(request0, demuxHandle0)).isTrue();
- assertThat(listener1.isReclaimed()).isFalse();
- assertThat(listener2.isReclaimed()).isTrue();
+ assertThat(client1.isReclaimed()).isTrue();
+ assertThat(client2.isReclaimed()).isFalse();
+ client0.unregister();
+ client1.unregister();
+ client2.unregister();
}
@Test
public void requestDescramblerTest() {
- // Register client
- ResourceClientProfile profile = resourceClientProfile("0" /*sessionId*/,
- TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
- int[] clientId = new int[1];
- mTunerResourceManagerService.registerClientProfileInternal(
- profile, null /*listener*/, clientId);
- assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+ // Register clients
+ TunerClient client0 = new TunerClient();
+ client0.register("0" /*sessionId*/,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
int[] desHandle = new int[1];
TunerDescramblerRequest request = new TunerDescramblerRequest();
- request.clientId = clientId[0];
+ request.clientId = client0.getId();
assertThat(mTunerResourceManagerService.requestDescramblerInternal(request, desHandle))
.isTrue();
assertThat(mTunerResourceManagerService.getResourceIdFromHandle(desHandle[0])).isEqualTo(0);
+ client0.unregister();
}
@Test
@@ -978,74 +945,26 @@
}
@Test
- public void shareFrontendTest_FrontendWithExclusiveGroupReadyToShare() {
+ public void shareFrontendTest_FrontendWithExclusiveGroupReadyToShare() throws RemoteException {
/**** Register Clients and Set Priority ****/
-
- // Int array to save the returned client ids
- int[] ownerClientId0 = new int[1];
- int[] ownerClientId1 = new int[1];
- int[] shareClientId0 = new int[1];
- int[] shareClientId1 = new int[1];
-
- // Predefined client profiles
- ResourceClientProfile[] ownerProfiles = new ResourceClientProfile[2];
- ResourceClientProfile[] shareProfiles = new ResourceClientProfile[2];
- ownerProfiles[0] = resourceClientProfile(
- "0" /*sessionId*/,
- TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE);
- ownerProfiles[1] = resourceClientProfile(
- "1" /*sessionId*/,
- TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE);
- shareProfiles[0] = resourceClientProfile(
- "2" /*sessionId*/,
- TvInputService.PRIORITY_HINT_USE_CASE_TYPE_RECORD);
- shareProfiles[1] = resourceClientProfile(
- "3" /*sessionId*/,
- TvInputService.PRIORITY_HINT_USE_CASE_TYPE_RECORD);
-
- // Predefined client reclaim listeners
- TestResourcesReclaimListener ownerListener0 = new TestResourcesReclaimListener();
- TestResourcesReclaimListener shareListener0 = new TestResourcesReclaimListener();
- TestResourcesReclaimListener ownerListener1 = new TestResourcesReclaimListener();
- TestResourcesReclaimListener shareListener1 = new TestResourcesReclaimListener();
- // Register clients and validate the returned client ids
- mTunerResourceManagerService
- .registerClientProfileInternal(ownerProfiles[0], ownerListener0, ownerClientId0);
- mTunerResourceManagerService
- .registerClientProfileInternal(shareProfiles[0], shareListener0, shareClientId0);
- mTunerResourceManagerService
- .registerClientProfileInternal(ownerProfiles[1], ownerListener1, ownerClientId1);
- mTunerResourceManagerService
- .registerClientProfileInternal(shareProfiles[1], shareListener1, shareClientId1);
- assertThat(ownerClientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
- assertThat(shareClientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
- assertThat(ownerClientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
- assertThat(shareClientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+ TunerClient ownerClient0 = new TunerClient();
+ TunerClient ownerClient1 = new TunerClient();
+ TunerClient shareClient0 = new TunerClient();
+ TunerClient shareClient1 = new TunerClient();
+ ownerClient0.register("0" /*sessionId*/,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE, 100);
+ ownerClient1.register("1" /*sessionId*/,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE, 300);
+ shareClient0.register("2" /*sessionId*/,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_RECORD, 200);
+ shareClient1.register("3" /*sessionId*/,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_RECORD, 400);
mTunerResourceManagerService.updateClientPriorityInternal(
- ownerClientId0[0],
- 100/*priority*/,
- 0/*niceValue*/);
- mTunerResourceManagerService.updateClientPriorityInternal(
- shareClientId0[0],
- 200/*priority*/,
- 0/*niceValue*/);
- mTunerResourceManagerService.updateClientPriorityInternal(
- ownerClientId1[0],
- 300/*priority*/,
- 0/*niceValue*/);
- mTunerResourceManagerService.updateClientPriorityInternal(
- shareClientId1[0],
- 400/*priority*/,
- 0/*niceValue*/);
- mTunerResourceManagerService.updateClientPriorityInternal(
- shareClientId1[0],
+ shareClient1.getId(),
-1/*invalid priority*/,
0/*niceValue*/);
- assertThat(mTunerResourceManagerService
- .getClientProfile(shareClientId1[0])
- .getPriority())
- .isEqualTo(400);
+ assertThat(shareClient1.getProfile().getPriority()).isEqualTo(400);
/**** Init Frontend Resources ****/
@@ -1072,7 +991,7 @@
// Predefined frontend request and array to save returned frontend handle
int[] frontendHandle = new int[1];
TunerFrontendRequest request = tunerFrontendRequest(
- ownerClientId0[0] /*clientId*/,
+ ownerClient0.getId() /*clientId*/,
FrontendSettings.TYPE_DVBT);
// Request call and validate granted resource and internal mapping
@@ -1080,9 +999,7 @@
.requestFrontendInternal(request, frontendHandle))
.isTrue();
assertThat(frontendHandle[0]).isEqualTo(infos[0].handle);
- assertThat(mTunerResourceManagerService
- .getClientProfile(ownerClientId0[0])
- .getInUseFrontendHandles())
+ assertThat(ownerClient0.getProfile().getInUseFrontendHandles())
.isEqualTo(new HashSet<Integer>(Arrays.asList(
infos[0].handle,
infos[1].handle)));
@@ -1091,11 +1008,11 @@
// Share frontend call and validate the internal mapping
mTunerResourceManagerService.shareFrontendInternal(
- shareClientId0[0]/*selfClientId*/,
- ownerClientId0[0]/*targetClientId*/);
+ shareClient0.getId()/*selfClientId*/,
+ ownerClient0.getId()/*targetClientId*/);
mTunerResourceManagerService.shareFrontendInternal(
- shareClientId1[0]/*selfClientId*/,
- ownerClientId0[0]/*targetClientId*/);
+ shareClient1.getId()/*selfClientId*/,
+ ownerClient0.getId()/*targetClientId*/);
// Verify fe in use status
assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].handle)
.isInUse()).isTrue();
@@ -1103,31 +1020,24 @@
.isInUse()).isTrue();
// Verify fe owner status
assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].handle)
- .getOwnerClientId()).isEqualTo(ownerClientId0[0]);
+ .getOwnerClientId()).isEqualTo(ownerClient0.getId());
assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].handle)
- .getOwnerClientId()).isEqualTo(ownerClientId0[0]);
+ .getOwnerClientId()).isEqualTo(ownerClient0.getId());
// Verify share fe client status in the primary owner client
- assertThat(mTunerResourceManagerService.getClientProfile(ownerClientId0[0])
- .getShareFeClientIds())
+ assertThat(ownerClient0.getProfile().getShareFeClientIds())
.isEqualTo(new HashSet<Integer>(Arrays.asList(
- shareClientId0[0],
- shareClientId1[0])));
+ shareClient0.getId(),
+ shareClient1.getId())));
// Verify in use frontend list in all the primary owner and share owner clients
- assertThat(mTunerResourceManagerService
- .getClientProfile(ownerClientId0[0])
- .getInUseFrontendHandles())
+ assertThat(ownerClient0.getProfile().getInUseFrontendHandles())
.isEqualTo(new HashSet<Integer>(Arrays.asList(
infos[0].handle,
infos[1].handle)));
- assertThat(mTunerResourceManagerService
- .getClientProfile(shareClientId0[0])
- .getInUseFrontendHandles())
+ assertThat(shareClient0.getProfile().getInUseFrontendHandles())
.isEqualTo(new HashSet<Integer>(Arrays.asList(
infos[0].handle,
infos[1].handle)));
- assertThat(mTunerResourceManagerService
- .getClientProfile(shareClientId1[0])
- .getInUseFrontendHandles())
+ assertThat(shareClient1.getProfile().getInUseFrontendHandles())
.isEqualTo(new HashSet<Integer>(Arrays.asList(
infos[0].handle,
infos[1].handle)));
@@ -1135,21 +1045,17 @@
/**** Remove Frontend Share Owner ****/
// Unregister the second share fe client
- mTunerResourceManagerService.unregisterClientProfileInternal(shareClientId1[0]);
+ shareClient1.unregister();
// Validate the internal mapping
- assertThat(mTunerResourceManagerService.getClientProfile(ownerClientId0[0])
- .getShareFeClientIds())
+ assertThat(ownerClient0.getProfile().getShareFeClientIds())
.isEqualTo(new HashSet<Integer>(Arrays.asList(
- shareClientId0[0])));
- assertThat(mTunerResourceManagerService
- .getClientProfile(ownerClientId0[0])
- .getInUseFrontendHandles())
+ shareClient0.getId())));
+ assertThat(ownerClient0.getProfile().getInUseFrontendHandles())
.isEqualTo(new HashSet<Integer>(Arrays.asList(
infos[0].handle,
infos[1].handle)));
- assertThat(mTunerResourceManagerService
- .getClientProfile(shareClientId0[0])
+ assertThat(shareClient0.getProfile()
.getInUseFrontendHandles())
.isEqualTo(new HashSet<Integer>(Arrays.asList(
infos[0].handle,
@@ -1159,7 +1065,7 @@
// Predefined second frontend request
request = tunerFrontendRequest(
- ownerClientId1[0] /*clientId*/,
+ ownerClient1.getId() /*clientId*/,
FrontendSettings.TYPE_DVBT);
// Second request call
@@ -1170,43 +1076,35 @@
// Validate granted resource and internal mapping
assertThat(frontendHandle[0]).isEqualTo(infos[0].handle);
assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].handle)
- .getOwnerClientId()).isEqualTo(ownerClientId1[0]);
+ .getOwnerClientId()).isEqualTo(ownerClient1.getId());
assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].handle)
- .getOwnerClientId()).isEqualTo(ownerClientId1[0]);
- assertThat(mTunerResourceManagerService
- .getClientProfile(ownerClientId1[0])
- .getInUseFrontendHandles())
+ .getOwnerClientId()).isEqualTo(ownerClient1.getId());
+ assertThat(ownerClient1.getProfile().getInUseFrontendHandles())
.isEqualTo(new HashSet<Integer>(Arrays.asList(
infos[0].handle,
infos[1].handle)));
- assertThat(mTunerResourceManagerService
- .getClientProfile(ownerClientId0[0])
- .getInUseFrontendHandles()
+ assertThat(ownerClient0.getProfile().getInUseFrontendHandles()
.isEmpty())
.isTrue();
- assertThat(mTunerResourceManagerService
- .getClientProfile(shareClientId0[0])
- .getInUseFrontendHandles()
+ assertThat(shareClient0.getProfile().getInUseFrontendHandles()
.isEmpty())
.isTrue();
- assertThat(mTunerResourceManagerService
- .getClientProfile(ownerClientId0[0])
- .getShareFeClientIds()
+ assertThat(ownerClient0.getProfile().getShareFeClientIds()
.isEmpty())
.isTrue();
- assertThat(ownerListener0.isReclaimed()).isTrue();
- assertThat(shareListener0.isReclaimed()).isTrue();
+ assertThat(ownerClient0.isReclaimed()).isTrue();
+ assertThat(shareClient0.isReclaimed()).isTrue();
/**** Release Frontend Resource From Primary Owner ****/
// Reshare the frontend
mTunerResourceManagerService.shareFrontendInternal(
- shareClientId0[0]/*selfClientId*/,
- ownerClientId1[0]/*targetClientId*/);
+ shareClient0.getId()/*selfClientId*/,
+ ownerClient1.getId()/*targetClientId*/);
// Release the frontend resource from the primary owner
- mTunerResourceManagerService.releaseFrontendInternal(mTunerResourceManagerService
- .getFrontendResource(infos[0].handle), ownerClientId1[0]);
+ mTunerResourceManagerService.releaseFrontendInternal(infos[0].handle,
+ ownerClient1.getId());
// Validate the internal mapping
assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].handle)
@@ -1214,19 +1112,13 @@
assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].handle)
.isInUse()).isFalse();
// Verify client status
- assertThat(mTunerResourceManagerService
- .getClientProfile(ownerClientId1[0])
- .getInUseFrontendHandles()
+ assertThat(ownerClient1.getProfile().getInUseFrontendHandles()
.isEmpty())
.isTrue();
- assertThat(mTunerResourceManagerService
- .getClientProfile(shareClientId0[0])
- .getInUseFrontendHandles()
+ assertThat(shareClient0.getProfile().getInUseFrontendHandles()
.isEmpty())
.isTrue();
- assertThat(mTunerResourceManagerService
- .getClientProfile(ownerClientId1[0])
- .getShareFeClientIds()
+ assertThat(ownerClient1.getProfile().getShareFeClientIds()
.isEmpty())
.isTrue();
@@ -1234,7 +1126,7 @@
// Predefined Lnb request and handle array
TunerLnbRequest requestLnb = new TunerLnbRequest();
- requestLnb.clientId = shareClientId0[0];
+ requestLnb.clientId = shareClient0.getId();
int[] lnbHandle = new int[1];
// Request for an Lnb
@@ -1247,11 +1139,11 @@
.requestFrontendInternal(request, frontendHandle))
.isTrue();
mTunerResourceManagerService.shareFrontendInternal(
- shareClientId0[0]/*selfClientId*/,
- ownerClientId1[0]/*targetClientId*/);
+ shareClient0.getId()/*selfClientId*/,
+ ownerClient1.getId()/*targetClientId*/);
// Unregister the primary owner of the shared frontend
- mTunerResourceManagerService.unregisterClientProfileInternal(ownerClientId1[0]);
+ ownerClient1.unregister();
// Validate the internal mapping
assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].handle)
@@ -1259,16 +1151,15 @@
assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].handle)
.isInUse()).isFalse();
// Verify client status
- assertThat(mTunerResourceManagerService
- .getClientProfile(shareClientId0[0])
- .getInUseFrontendHandles()
+ assertThat(shareClient0.getProfile().getInUseFrontendHandles()
.isEmpty())
.isTrue();
- assertThat(mTunerResourceManagerService
- .getClientProfile(shareClientId0[0])
- .getInUseLnbHandles())
+ assertThat(shareClient0.getProfile().getInUseLnbHandles())
.isEqualTo(new HashSet<Integer>(Arrays.asList(
lnbHandles[0])));
+
+ ownerClient0.unregister();
+ shareClient0.unregister();
}
private TunerFrontendInfo tunerFrontendInfo(
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/InputDeviceDelegateTest.java b/services/tests/vibrator/src/com/android/server/vibrator/InputDeviceDelegateTest.java
index f3ecfcc..66788b6 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/InputDeviceDelegateTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/InputDeviceDelegateTest.java
@@ -45,6 +45,8 @@
import androidx.test.InstrumentationRegistry;
+import com.android.server.vibrator.VibrationSession.CallerInfo;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -262,7 +264,7 @@
public void vibrateIfAvailable_withNoInputDevice_returnsFalse() {
assertFalse(mInputDeviceDelegate.isAvailable());
assertFalse(mInputDeviceDelegate.vibrateIfAvailable(
- new Vibration.CallerInfo(VIBRATION_ATTRIBUTES, UID, -1, PACKAGE_NAME, REASON),
+ new CallerInfo(VIBRATION_ATTRIBUTES, UID, -1, PACKAGE_NAME, REASON),
SYNCED_EFFECT));
}
@@ -277,7 +279,7 @@
mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
assertTrue(mInputDeviceDelegate.vibrateIfAvailable(
- new Vibration.CallerInfo(VIBRATION_ATTRIBUTES, UID, -1, PACKAGE_NAME, REASON),
+ new CallerInfo(VIBRATION_ATTRIBUTES, UID, -1, PACKAGE_NAME, REASON),
SYNCED_EFFECT));
verify(mIInputManagerMock).vibrateCombined(eq(1), same(SYNCED_EFFECT), any());
verify(mIInputManagerMock).vibrateCombined(eq(2), same(SYNCED_EFFECT), any());
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSessionTest.java
similarity index 80%
rename from services/tests/vibrator/src/com/android/server/vibrator/VibrationTest.java
rename to services/tests/vibrator/src/com/android/server/vibrator/VibrationSessionTest.java
index 84f8412..f69d1c4 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSessionTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,13 +24,13 @@
import java.util.Arrays;
-public class VibrationTest {
+public class VibrationSessionTest {
@Test
public void status_hasUniqueProtoEnumValues() {
assertThat(
- Arrays.stream(Vibration.Status.values())
- .map(Vibration.Status::getProtoEnumValue)
+ Arrays.stream(VibrationSession.Status.values())
+ .map(VibrationSession.Status::getProtoEnumValue)
.collect(toList()))
.containsNoDuplicates();
}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
index 38cd49d..c7a136a 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
@@ -81,6 +81,8 @@
import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.LocalServices;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
+import com.android.server.vibrator.VibrationSession.CallerInfo;
+import com.android.server.vibrator.VibrationSession.Status;
import org.junit.After;
import org.junit.Before;
@@ -292,7 +294,7 @@
if (expectedAllowedVibrations.contains(usage)) {
assertVibrationNotIgnoredForUsage(usage);
} else {
- assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_BACKGROUND);
+ assertVibrationIgnoredForUsage(usage, Status.IGNORED_BACKGROUND);
}
}
}
@@ -350,7 +352,7 @@
createSystemReadyVibrationSettings();
for (int usage : ALL_USAGES) {
- assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_ON_WIRELESS_CHARGER);
+ assertVibrationIgnoredForUsage(usage, Status.IGNORED_ON_WIRELESS_CHARGER);
}
}
@@ -365,7 +367,7 @@
mRegisteredBatteryBroadcastReceiver.onReceive(mContextSpy, wirelessChargingIntent);
for (int usage : ALL_USAGES) {
- assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_ON_WIRELESS_CHARGER);
+ assertVibrationIgnoredForUsage(usage, Status.IGNORED_ON_WIRELESS_CHARGER);
}
}
@@ -377,7 +379,7 @@
createSystemReadyVibrationSettings();
// Check that initially, all usages are ignored due to the wireless charging.
for (int usage : ALL_USAGES) {
- assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_ON_WIRELESS_CHARGER);
+ assertVibrationIgnoredForUsage(usage, Status.IGNORED_ON_WIRELESS_CHARGER);
}
Intent nonWirelessChargingIntent = getBatteryChangedIntent(BATTERY_PLUGGED_USB);
@@ -404,7 +406,7 @@
if (expectedAllowedVibrations.contains(usage)) {
assertVibrationNotIgnoredForUsage(usage);
} else {
- assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_POWER);
+ assertVibrationIgnoredForUsage(usage, Status.IGNORED_FOR_POWER);
}
}
}
@@ -426,7 +428,7 @@
for (int usage : ALL_USAGES) {
if (usage == USAGE_RINGTONE || usage == USAGE_NOTIFICATION) {
- assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_RINGER_MODE);
+ assertVibrationIgnoredForUsage(usage, Status.IGNORED_FOR_RINGER_MODE);
} else {
assertVibrationNotIgnoredForUsage(usage);
}
@@ -470,7 +472,7 @@
if (usage == USAGE_ACCESSIBILITY) {
assertVibrationNotIgnoredForUsage(usage);
} else {
- assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS);
+ assertVibrationIgnoredForUsage(usage, Status.IGNORED_FOR_SETTINGS);
}
assertVibrationNotIgnoredForUsageAndFlags(usage,
VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF);
@@ -512,7 +514,7 @@
for (int usage : ALL_USAGES) {
if (usage == USAGE_TOUCH) {
- assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS);
+ assertVibrationIgnoredForUsage(usage, Status.IGNORED_FOR_SETTINGS);
} else {
assertVibrationNotIgnoredForUsage(usage);
}
@@ -527,7 +529,7 @@
for (int usage : ALL_USAGES) {
if (usage == USAGE_TOUCH || usage == USAGE_IME_FEEDBACK) {
- assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS);
+ assertVibrationIgnoredForUsage(usage, Status.IGNORED_FOR_SETTINGS);
} else {
assertVibrationNotIgnoredForUsage(usage);
}
@@ -542,7 +544,7 @@
for (int usage : ALL_USAGES) {
if (usage == USAGE_HARDWARE_FEEDBACK || usage == USAGE_PHYSICAL_EMULATION) {
- assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS);
+ assertVibrationIgnoredForUsage(usage, Status.IGNORED_FOR_SETTINGS);
} else {
assertVibrationNotIgnoredForUsage(usage);
}
@@ -557,7 +559,7 @@
for (int usage : ALL_USAGES) {
if (usage == USAGE_NOTIFICATION) {
- assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS);
+ assertVibrationIgnoredForUsage(usage, Status.IGNORED_FOR_SETTINGS);
} else {
assertVibrationNotIgnoredForUsage(usage);
}
@@ -574,7 +576,7 @@
for (int usage : ALL_USAGES) {
if (usage == USAGE_RINGTONE) {
- assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS);
+ assertVibrationIgnoredForUsage(usage, Status.IGNORED_FOR_SETTINGS);
} else {
assertVibrationNotIgnoredForUsage(usage);
}
@@ -597,7 +599,7 @@
mVibrationSettings.mSettingChangeReceiver.onReceive(mContextSpy,
new Intent(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION));
- assertVibrationIgnoredForUsage(USAGE_RINGTONE, Vibration.Status.IGNORED_FOR_RINGER_MODE);
+ assertVibrationIgnoredForUsage(USAGE_RINGTONE, Status.IGNORED_FOR_RINGER_MODE);
}
@Test
@@ -611,7 +613,7 @@
new VibrationAttributes.Builder()
.setUsage(USAGE_IME_FEEDBACK)
.build(),
- Vibration.Status.IGNORED_FOR_SETTINGS);
+ Status.IGNORED_FOR_SETTINGS);
// General touch and keyboard touch with bypass flag not ignored.
assertVibrationNotIgnoredForUsage(USAGE_TOUCH);
@@ -629,7 +631,7 @@
setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 1 /* ON */);
// General touch ignored.
- assertVibrationIgnoredForUsage(USAGE_TOUCH, Vibration.Status.IGNORED_FOR_SETTINGS);
+ assertVibrationIgnoredForUsage(USAGE_TOUCH, Status.IGNORED_FOR_SETTINGS);
// Keyboard touch not ignored.
assertVibrationNotIgnoredForAttributes(
@@ -645,14 +647,14 @@
setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 1 /* ON */);
// General touch ignored.
- assertVibrationIgnoredForUsage(USAGE_TOUCH, Vibration.Status.IGNORED_FOR_SETTINGS);
+ assertVibrationIgnoredForUsage(USAGE_TOUCH, Status.IGNORED_FOR_SETTINGS);
// Keyboard touch ignored.
assertVibrationIgnoredForAttributes(
new VibrationAttributes.Builder()
.setUsage(USAGE_IME_FEEDBACK)
.build(),
- Vibration.Status.IGNORED_FOR_SETTINGS);
+ Status.IGNORED_FOR_SETTINGS);
}
@Test
@@ -668,7 +670,7 @@
// Ignore the vibration when the coming device id represents a virtual device.
for (int usage : ALL_USAGES) {
assertVibrationIgnoredForUsageAndDevice(usage, VIRTUAL_DEVICE_ID,
- Vibration.Status.IGNORED_FROM_VIRTUAL_DEVICE);
+ Status.IGNORED_FROM_VIRTUAL_DEVICE);
}
}
@@ -911,22 +913,21 @@
}
private void assertVibrationIgnoredForUsage(@VibrationAttributes.Usage int usage,
- Vibration.Status expectedStatus) {
+ Status expectedStatus) {
assertVibrationIgnoredForUsageAndDevice(usage, Context.DEVICE_ID_DEFAULT, expectedStatus);
}
private void assertVibrationIgnoredForUsageAndDevice(@VibrationAttributes.Usage int usage,
- int deviceId, Vibration.Status expectedStatus) {
- Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(
+ int deviceId, Status expectedStatus) {
+ CallerInfo callerInfo = new CallerInfo(
VibrationAttributes.createForUsage(usage), UID, deviceId, null, null);
assertEquals(errorMessageForUsage(usage), expectedStatus,
mVibrationSettings.shouldIgnoreVibration(callerInfo));
}
private void assertVibrationIgnoredForAttributes(VibrationAttributes attrs,
- Vibration.Status expectedStatus) {
- Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(attrs, UID,
- Context.DEVICE_ID_DEFAULT, null, null);
+ Status expectedStatus) {
+ CallerInfo callerInfo = new CallerInfo(attrs, UID, Context.DEVICE_ID_DEFAULT, null, null);
assertEquals(errorMessageForAttributes(attrs), expectedStatus,
mVibrationSettings.shouldIgnoreVibration(callerInfo));
}
@@ -948,7 +949,7 @@
private void assertVibrationNotIgnoredForUsageAndFlagsAndDevice(
@VibrationAttributes.Usage int usage, int deviceId,
@VibrationAttributes.Flag int flags) {
- Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(
+ CallerInfo callerInfo = new CallerInfo(
new VibrationAttributes.Builder().setUsage(usage).setFlags(flags).build(), UID,
deviceId, null, null);
assertNull(errorMessageForUsage(usage),
@@ -956,7 +957,7 @@
}
private void assertVibrationNotIgnoredForAttributes(VibrationAttributes attrs) {
- Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(attrs, UID,
+ CallerInfo callerInfo = new CallerInfo(attrs, UID,
Context.DEVICE_ID_DEFAULT, null, null);
assertNull(errorMessageForAttributes(attrs),
mVibrationSettings.shouldIgnoreVibration(callerInfo));
@@ -1017,10 +1018,10 @@
new PowerManager.SleepData(sleepTime, reason));
}
- private Vibration.CallerInfo createCallerInfo(int uid, String opPkg,
+ private CallerInfo createCallerInfo(int uid, String opPkg,
@VibrationAttributes.Usage int usage) {
VibrationAttributes attrs = VibrationAttributes.createForUsage(usage);
- return new Vibration.CallerInfo(attrs, uid, VIRTUAL_DEVICE_ID, opPkg, null);
+ return new CallerInfo(attrs, uid, VIRTUAL_DEVICE_ID, opPkg, null);
}
private void setBatteryReceiverRegistrationResult(Intent result) {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index bfdaa78..31cc50f1 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -78,6 +78,8 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.LocalServices;
+import com.android.server.vibrator.VibrationSession.CallerInfo;
+import com.android.server.vibrator.VibrationSession.Status;
import org.junit.After;
import org.junit.Before;
@@ -189,7 +191,7 @@
waitForCompletion();
verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
+ verifyCallbacksTriggered(vibrationId, Status.IGNORED_UNSUPPORTED);
}
@Test
@@ -202,7 +204,7 @@
waitForCompletion();
verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
+ verifyCallbacksTriggered(vibrationId, Status.IGNORED_UNSUPPORTED);
}
@Test
@@ -216,7 +218,7 @@
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+ verifyCallbacksTriggered(vibrationId, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedOneShot(10)),
@@ -233,7 +235,7 @@
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+ verifyCallbacksTriggered(vibrationId, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedOneShot(10)),
@@ -253,7 +255,7 @@
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(15L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+ verifyCallbacksTriggered(vibrationId, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedOneShot(15)),
@@ -326,13 +328,10 @@
assertTrue(mThread.isRunningVibrationId(vibrationId));
assertTrue(mControllers.get(VIBRATOR_ID).isVibrating());
- Vibration.EndInfo cancelVibrationInfo = new Vibration.EndInfo(
- Vibration.Status.CANCELLED_SUPERSEDED, new Vibration.CallerInfo(
- VibrationAttributes.createForUsage(VibrationAttributes.USAGE_ALARM), /* uid= */
- 1, /* deviceId= */ -1, /* opPkg= */ null, /* reason= */ null));
- mVibrationConductor.notifyCancelled(
- cancelVibrationInfo,
- /* immediate= */ false);
+ Vibration.EndInfo cancelVibrationInfo = new Vibration.EndInfo(Status.CANCELLED_SUPERSEDED,
+ new CallerInfo(VibrationAttributes.createForUsage(VibrationAttributes.USAGE_ALARM),
+ /* uid= */ 1, /* deviceId= */ -1, /* opPkg= */ null, /* reason= */ null));
+ mVibrationConductor.notifyCancelled(cancelVibrationInfo, /* immediate= */ false);
waitForCompletion();
assertFalse(mThread.isRunningVibrationId(vibrationId));
@@ -363,11 +362,10 @@
assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS));
mVibrationConductor.notifyCancelled(
- new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
- /* immediate= */ false);
+ new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false);
waitForCompletion();
- verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
+ verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_USER);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedOneShot(5000)),
fakeVibrator.getEffectSegments(vibrationId));
@@ -385,7 +383,7 @@
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(300L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+ verifyCallbacksTriggered(vibrationId, Status.FINISHED);
assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId))
@@ -406,7 +404,7 @@
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(350L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+ verifyCallbacksTriggered(vibrationId, Status.FINISHED);
assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId))
@@ -433,11 +431,10 @@
assertTrue(waitUntil(() -> fakeVibrator.getEffectSegments(vibrationId).size() >= 5,
5000L + TEST_TIMEOUT_MILLIS));
mVibrationConductor.notifyCancelled(
- new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
- /* immediate= */ false);
+ new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false);
waitForCompletion();
- verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
+ verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_USER);
assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId).subList(0, 5))
@@ -466,12 +463,11 @@
assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(),
TEST_TIMEOUT_MILLIS));
mVibrationConductor.notifyCancelled(
- new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
- /* immediate= */ false);
+ new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false);
waitForCompletion();
// PWLE size max was used to generate a single vibrate call with 10 segments.
- verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
+ verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_USER);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(10, fakeVibrator.getEffectSegments(vibrationId).size());
}
@@ -496,12 +492,11 @@
assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(),
TEST_TIMEOUT_MILLIS));
mVibrationConductor.notifyCancelled(
- new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
- /* immediate= */ false);
+ new Vibration.EndInfo(Status.CANCELLED_BY_SCREEN_OFF), /* immediate= */ false);
waitForCompletion();
// Composition size max was used to generate a single vibrate call with 10 primitives.
- verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
+ verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_SCREEN_OFF);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(10, fakeVibrator.getEffectSegments(vibrationId).size());
}
@@ -519,11 +514,10 @@
assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS));
mVibrationConductor.notifyCancelled(
- new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
- /* immediate= */ false);
+ new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false);
waitForCompletion();
- verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
+ verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_USER);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedOneShot(5550)),
fakeVibrator.getEffectSegments(vibrationId));
@@ -545,11 +539,10 @@
assertTrue(waitUntil(() -> fakeVibrator.getEffectSegments(vibrationId).size() > 1,
expectedOnDuration + TEST_TIMEOUT_MILLIS));
mVibrationConductor.notifyCancelled(
- new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
- /* immediate= */ false);
+ new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false);
waitForCompletion();
- verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
+ verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_USER);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
List<VibrationEffectSegment> effectSegments = fakeVibrator.getEffectSegments(vibrationId);
// First time, turn vibrator ON for the expected fixed duration.
@@ -584,14 +577,14 @@
// fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
Thread cancellingThread =
new Thread(() -> mVibrationConductor.notifyCancelled(
- new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE),
+ new Vibration.EndInfo(Status.CANCELLED_BY_SETTINGS_UPDATE),
/* immediate= */ false));
cancellingThread.start();
waitForCompletion(/* timeout= */ 50);
cancellingThread.join();
- verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE);
+ verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_SETTINGS_UPDATE);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
}
@@ -614,14 +607,14 @@
// fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
Thread cancellingThread =
new Thread(() -> mVibrationConductor.notifyCancelled(
- new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE),
+ new Vibration.EndInfo(Status.CANCELLED_BY_SETTINGS_UPDATE),
/* immediate= */ false));
cancellingThread.start();
waitForCompletion(/* timeout= */ 50);
cancellingThread.join();
- verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE);
+ verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_SETTINGS_UPDATE);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
}
@@ -641,14 +634,14 @@
// fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
Thread cancellingThread =
new Thread(() -> mVibrationConductor.notifyCancelled(
- new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+ new Vibration.EndInfo(Status.CANCELLED_BY_SCREEN_OFF),
/* immediate= */ false));
cancellingThread.start();
waitForCompletion(/* timeout= */ 50);
cancellingThread.join();
- verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
+ verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_SCREEN_OFF);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
}
@@ -663,7 +656,7 @@
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+ verifyCallbacksTriggered(vibrationId, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_THUD)),
@@ -684,7 +677,7 @@
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+ verifyCallbacksTriggered(vibrationId, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedOneShot(10)),
@@ -701,7 +694,7 @@
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L));
verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
+ verifyCallbacksTriggered(vibrationId, Status.IGNORED_UNSUPPORTED);
assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId).isEmpty());
}
@@ -718,7 +711,7 @@
eq(PerformVendorEffectVibratorStep.VENDOR_EFFECT_MAX_DURATION_MS));
verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+ verifyCallbacksTriggered(vibrationId, Status.FINISHED);
assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
assertThat(mVibratorProviders.get(VIBRATOR_ID).getVendorEffects(vibrationId))
@@ -743,7 +736,7 @@
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(40L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+ verifyCallbacksTriggered(vibrationId, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(
expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0),
@@ -762,7 +755,7 @@
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L));
verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
+ verifyCallbacksTriggered(vibrationId, Status.IGNORED_UNSUPPORTED);
assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId).isEmpty());
}
@@ -784,7 +777,7 @@
long vibrationId = startThreadAndDispatcher(effect);
waitForCompletion();
- verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+ verifyCallbacksTriggered(vibrationId, Status.FINISHED);
// Vibrator compose called twice.
verify(mControllerCallbacks, times(2)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
assertEquals(3, fakeVibrator.getEffectSegments(vibrationId).size());
@@ -824,7 +817,7 @@
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks, times(5)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+ verifyCallbacksTriggered(vibrationId, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(
expectedOneShot(10),
@@ -865,7 +858,7 @@
verify(mManagerHooks).noteVibratorOn(eq(UID), anyLong());
verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks, times(4)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+ verifyCallbacksTriggered(vibrationId, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
List<VibrationEffectSegment> segments =
@@ -904,7 +897,7 @@
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(100L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+ verifyCallbacksTriggered(vibrationId, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(
expectedRamp(/* amplitude= */ 1, /* frequencyHz= */ 150, /* duration= */ 10),
@@ -942,7 +935,7 @@
long vibrationId = startThreadAndDispatcher(effect);
waitForCompletion();
- verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+ verifyCallbacksTriggered(vibrationId, Status.FINISHED);
// Vibrator compose called 3 times with 2 segments instead of 2 times with 3 segments.
// Using best split points instead of max-packing PWLEs.
@@ -967,7 +960,7 @@
waitForCompletion();
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
- verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BINDER_DIED);
+ verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BINDER_DIED);
}
@Test
@@ -977,7 +970,7 @@
long vibrationId = startThreadAndDispatcher(VibrationEffect.createOneShot(10, 100));
waitForCompletion();
- verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+ verifyCallbacksTriggered(vibrationId, Status.FINISHED);
verify(mManagerHooks, never()).prepareSyncedVibration(anyLong(), any());
verify(mManagerHooks, never()).triggerSyncedVibration(anyLong());
verify(mManagerHooks, never()).cancelSyncedVibration();
@@ -998,7 +991,7 @@
verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verify(mControllerCallbacks, never()).onComplete(eq(2), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+ verifyCallbacksTriggered(vibrationId, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_TICK)),
@@ -1022,7 +1015,7 @@
verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+ verifyCallbacksTriggered(vibrationId, Status.FINISHED);
assertFalse(mControllers.get(1).isVibrating());
assertFalse(mControllers.get(2).isVibrating());
assertFalse(mControllers.get(3).isVibrating());
@@ -1065,7 +1058,7 @@
verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
verify(mControllerCallbacks).onComplete(eq(4), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+ verifyCallbacksTriggered(vibrationId, Status.FINISHED);
assertFalse(mControllers.get(1).isVibrating());
assertFalse(mControllers.get(2).isVibrating());
assertFalse(mControllers.get(3).isVibrating());
@@ -1117,7 +1110,7 @@
batteryVerifier.verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
batteryVerifier.verify(mManagerHooks).noteVibratorOff(eq(UID));
- verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+ verifyCallbacksTriggered(vibrationId, Status.FINISHED);
assertFalse(mControllers.get(1).isVibrating());
assertFalse(mControllers.get(2).isVibrating());
assertFalse(mControllers.get(3).isVibrating());
@@ -1166,7 +1159,7 @@
verify(mManagerHooks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
verify(mManagerHooks).triggerSyncedVibration(eq(vibrationId));
verify(mManagerHooks, never()).cancelSyncedVibration();
- verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+ verifyCallbacksTriggered(vibrationId, Status.FINISHED);
VibrationEffectSegment expected = expectedPrimitive(
VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100);
@@ -1213,7 +1206,7 @@
verify(mManagerHooks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
verify(mManagerHooks).triggerSyncedVibration(eq(vibrationId));
verify(mManagerHooks, never()).cancelSyncedVibration();
- verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+ verifyCallbacksTriggered(vibrationId, Status.FINISHED);
}
@Test
@@ -1305,7 +1298,7 @@
verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+ verifyCallbacksTriggered(vibrationId, Status.FINISHED);
assertFalse(mControllers.get(1).isVibrating());
assertFalse(mControllers.get(2).isVibrating());
assertFalse(mControllers.get(3).isVibrating());
@@ -1478,8 +1471,7 @@
// fail at waitForCompletion(cancellingThread).
Thread cancellingThread = new Thread(
() -> mVibrationConductor.notifyCancelled(
- new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
- /* immediate= */ false));
+ new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false));
cancellingThread.start();
// Cancelling the vibration should be fast and return right away, even if the thread is
@@ -1488,7 +1480,7 @@
// After the vibrator call ends the vibration is cancelled and the vibrator is turned off.
waitForCompletion(/* timeout= */ latency + TEST_TIMEOUT_MILLIS);
- verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
+ verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_USER);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
}
@@ -1517,14 +1509,14 @@
// fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
Thread cancellingThread = new Thread(
() -> mVibrationConductor.notifyCancelled(
- new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+ new Vibration.EndInfo(Status.CANCELLED_BY_SCREEN_OFF),
/* immediate= */ false));
cancellingThread.start();
waitForCompletion(/* timeout= */ 50);
cancellingThread.join();
- verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
+ verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_SCREEN_OFF);
assertFalse(mControllers.get(1).isVibrating());
assertFalse(mControllers.get(2).isVibrating());
}
@@ -1551,14 +1543,14 @@
// fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
Thread cancellingThread = new Thread(
() -> mVibrationConductor.notifyCancelled(
- new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+ new Vibration.EndInfo(Status.CANCELLED_BY_SCREEN_OFF),
/* immediate= */ false));
cancellingThread.start();
waitForCompletion(/* timeout= */ 50);
cancellingThread.join();
- verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
+ verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_SCREEN_OFF);
assertFalse(mControllers.get(1).isVibrating());
assertFalse(mControllers.get(2).isVibrating());
}
@@ -1585,15 +1577,14 @@
// fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
Thread cancellingThread = new Thread(
() -> mVibrationConductor.notifyCancelled(
- new Vibration.EndInfo(
- Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+ new Vibration.EndInfo(Status.CANCELLED_BY_SCREEN_OFF),
/* immediate= */ false));
cancellingThread.start();
waitForCompletion(/* timeout= */ 50);
cancellingThread.join();
- verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
+ verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_SCREEN_OFF);
assertFalse(mControllers.get(1).isVibrating());
assertFalse(mControllers.get(2).isVibrating());
}
@@ -1612,7 +1603,7 @@
verify(mVibrationToken).linkToDeath(same(mVibrationConductor), eq(0));
verify(mVibrationToken).unlinkToDeath(same(mVibrationConductor), eq(0));
- verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BINDER_DIED);
+ verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BINDER_DIED);
assertFalse(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId).isEmpty());
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
}
@@ -1628,7 +1619,7 @@
waitForCompletion();
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+ verifyCallbacksTriggered(vibrationId, Status.FINISHED);
// Duration extended for 5 + 5 + 5 + 15.
assertEquals(Arrays.asList(expectedOneShot(30)),
@@ -1651,7 +1642,7 @@
// Vibration completed but vibrator not yet released.
verify(mManagerHooks, timeout(TEST_TIMEOUT_MILLIS)).onVibrationCompleted(eq(vibrationId),
- eq(new Vibration.EndInfo(Vibration.Status.FINISHED)));
+ eq(new Vibration.EndInfo(Status.FINISHED)));
verify(mManagerHooks, never()).onVibrationThreadReleased(anyLong());
// Thread still running ramp down.
@@ -1663,13 +1654,12 @@
// Will stop the ramp down right away.
mVibrationConductor.notifyCancelled(
- new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE),
- /* immediate= */ true);
+ new Vibration.EndInfo(Status.CANCELLED_BY_SETTINGS_UPDATE), /* immediate= */ true);
waitForCompletion();
// Does not cancel already finished vibration, but releases vibrator.
verify(mManagerHooks, never()).onVibrationCompleted(eq(vibrationId),
- eq(new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE)));
+ eq(new Vibration.EndInfo(Status.CANCELLED_BY_SETTINGS_UPDATE)));
verify(mManagerHooks).onVibrationThreadReleased(vibrationId);
}
@@ -1684,11 +1674,10 @@
assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
TEST_TIMEOUT_MILLIS));
mVibrationConductor.notifyCancelled(
- new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
- /* immediate= */ false);
+ new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false);
waitForCompletion();
- verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
+ verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_USER);
// Duration extended for 10000 + 15.
assertEquals(Arrays.asList(expectedOneShot(10_015)),
@@ -1711,7 +1700,7 @@
waitForCompletion();
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+ verifyCallbacksTriggered(vibrationId, Status.FINISHED);
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
@@ -1729,7 +1718,7 @@
waitForCompletion();
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+ verifyCallbacksTriggered(vibrationId, Status.FINISHED);
assertThat(mVibratorProviders.get(VIBRATOR_ID).getVendorEffects(vibrationId))
.containsExactly(effect)
@@ -1752,7 +1741,7 @@
waitForCompletion();
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+ verifyCallbacksTriggered(vibrationId, Status.FINISHED);
assertEquals(
Arrays.asList(expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
@@ -1779,7 +1768,7 @@
waitForCompletion();
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+ verifyCallbacksTriggered(vibrationId, Status.FINISHED);
assertEquals(Arrays.asList(expectedRamp(0, 1, 150, 150, 1)),
fakeVibrator.getEffectSegments(vibrationId));
@@ -1810,14 +1799,13 @@
long vibrationId1 = startThreadAndDispatcher(effect1);
waitForCompletion();
verify(mControllerCallbacks).onComplete(VIBRATOR_ID, vibrationId1);
- verifyCallbacksTriggered(vibrationId1, Vibration.Status.FINISHED);
+ verifyCallbacksTriggered(vibrationId1, Status.FINISHED);
long vibrationId2 = startThreadAndDispatcher(effect2);
// Effect2 won't complete on its own. Cancel it after a couple of repeats.
Thread.sleep(150); // More than two TICKs.
mVibrationConductor.notifyCancelled(
- new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
- /* immediate= */ false);
+ new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false);
waitForCompletion();
long vibrationId3 = startThreadAndDispatcher(effect3);
@@ -1827,8 +1815,7 @@
long start4 = System.currentTimeMillis();
long vibrationId4 = startThreadAndDispatcher(effect4);
mVibrationConductor.notifyCancelled(
- new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
- /* immediate= */ true);
+ new Vibration.EndInfo(Status.CANCELLED_BY_SCREEN_OFF), /* immediate= */ true);
waitForCompletion();
long duration4 = System.currentTimeMillis() - start4;
@@ -1841,14 +1828,14 @@
// Effect1
verify(mControllerCallbacks).onComplete(VIBRATOR_ID, vibrationId1);
- verifyCallbacksTriggered(vibrationId1, Vibration.Status.FINISHED);
+ verifyCallbacksTriggered(vibrationId1, Status.FINISHED);
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
fakeVibrator.getEffectSegments(vibrationId1));
// Effect2: repeating, cancelled.
verify(mControllerCallbacks, atLeast(2)).onComplete(VIBRATOR_ID, vibrationId2);
- verifyCallbacksTriggered(vibrationId2, Vibration.Status.CANCELLED_BY_USER);
+ verifyCallbacksTriggered(vibrationId2, Status.CANCELLED_BY_USER);
// The exact count of segments might vary, so just check that there's more than 2 and
// all elements are the same segment.
@@ -1860,13 +1847,13 @@
// Effect3
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId3));
- verifyCallbacksTriggered(vibrationId3, Vibration.Status.FINISHED);
+ verifyCallbacksTriggered(vibrationId3, Status.FINISHED);
assertEquals(Arrays.asList(
expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
fakeVibrator.getEffectSegments(vibrationId3));
// Effect4: cancelled quickly.
- verifyCallbacksTriggered(vibrationId4, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
+ verifyCallbacksTriggered(vibrationId4, Status.CANCELLED_BY_SCREEN_OFF);
assertTrue("Tested duration=" + duration4, duration4 < 2000);
// Effect5: played normally after effect4, which may or may not have played.
@@ -1907,7 +1894,7 @@
.build();
HalVibration vib = new HalVibration(mVibrationToken,
CombinedVibration.createParallel(effect),
- new Vibration.CallerInfo(attrs, UID, DEVICE_ID, PACKAGE_NAME, "reason"));
+ new CallerInfo(attrs, UID, DEVICE_ID, PACKAGE_NAME, "reason"));
return startThreadAndDispatcher(vib, requestVibrationParamsFuture);
}
@@ -1944,7 +1931,7 @@
private HalVibration createVibration(CombinedVibration effect) {
return new HalVibration(mVibrationToken, effect,
- new Vibration.CallerInfo(ATTRS, UID, DEVICE_ID, PACKAGE_NAME, "reason"));
+ new CallerInfo(ATTRS, UID, DEVICE_ID, PACKAGE_NAME, "reason"));
}
private SparseArray<VibratorController> createVibratorControllers() {
@@ -2007,7 +1994,7 @@
.collect(Collectors.toList());
}
- private void verifyCallbacksTriggered(long vibrationId, Vibration.Status expectedStatus) {
+ private void verifyCallbacksTriggered(long vibrationId, Status expectedStatus) {
verifyCallbacksTriggered(vibrationId, new Vibration.EndInfo(expectedStatus));
}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
index 3466bbb..cd057b6 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
@@ -22,6 +22,8 @@
import android.os.Handler;
import android.os.test.TestLooper;
+import com.android.server.vibrator.VibrationSession.Status;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -113,7 +115,6 @@
}
private static VibrationStats.StatsInfo newEmptyStatsInfo() {
- return new VibrationStats.StatsInfo(
- 0, 0, 0, Vibration.Status.FINISHED, new VibrationStats(), 0L);
+ return new VibrationStats.StatsInfo(0, 0, 0, Status.FINISHED, new VibrationStats(), 0L);
}
}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 4afb562..4012575 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -110,6 +110,7 @@
import com.android.server.LocalServices;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.pm.BackgroundUserSoundNotifier;
+import com.android.server.vibrator.VibrationSession.Status;
import org.junit.After;
import org.junit.Before;
@@ -803,8 +804,8 @@
mockVibrators(1);
VibratorManagerService service = createSystemReadyService();
- var vib = vibrate(service,
- VibrationEffect.createWaveform(new long[]{100, 100, 100, 100}, 0), RINGTONE_ATTRS);
+ HalVibration vib = vibrate(service, VibrationEffect.createWaveform(
+ new long[]{0, TEST_TIMEOUT_MILLIS, TEST_TIMEOUT_MILLIS}, 0), RINGTONE_ATTRS);
assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
@@ -815,14 +816,80 @@
service.mAppOpsChangeListener.onOpChanged(AppOpsManager.OP_VIBRATE, null);
assertTrue(waitUntil(s -> vib.hasEnded(), service, TEST_TIMEOUT_MILLIS));
+ assertThat(vib.getStatus()).isEqualTo(Status.CANCELLED_BY_APP_OPS);
+ }
- var statsInfoCaptor = ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
- verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
- .writeVibrationReportedAsync(statsInfoCaptor.capture());
+ @Test
+ public void vibrate_thenPowerModeChanges_getsCancelled() throws Exception {
+ mockVibrators(1, 2);
+ VibratorManagerService service = createSystemReadyService();
- VibrationStats.StatsInfo touchMetrics = statsInfoCaptor.getAllValues().get(0);
- assertEquals(Vibration.Status.CANCELLED_BY_APP_OPS.getProtoEnumValue(),
- touchMetrics.status);
+ HalVibration vib = vibrate(service,
+ CombinedVibration.startParallel()
+ .addVibrator(1, VibrationEffect.createOneShot(2 * TEST_TIMEOUT_MILLIS, 100))
+ .combine(),
+ HAPTIC_FEEDBACK_ATTRS);
+
+ // VibrationThread will start this vibration async, so wait until vibration is triggered.
+ assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+ mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
+
+ assertTrue(waitUntil(s -> vib.hasEnded(), service, TEST_TIMEOUT_MILLIS));
+ assertThat(vib.getStatus()).isEqualTo(Status.CANCELLED_BY_SETTINGS_UPDATE);
+ }
+
+ @Test
+ public void vibrate_thenSettingsRefreshedWithoutChange_doNotCancelVibration() throws Exception {
+ mockVibrators(1);
+ VibratorManagerService service = createSystemReadyService();
+
+ vibrate(service, VibrationEffect.createOneShot(2 * TEST_TIMEOUT_MILLIS, 100),
+ HAPTIC_FEEDBACK_ATTRS);
+
+ // VibrationThread will start this vibration async, so wait until vibration is triggered.
+ assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+ service.updateServiceState();
+
+ // Vibration is not stopped nearly after updating service.
+ assertFalse(waitUntil(s -> !s.isVibrating(1), service, 50));
+ }
+
+ @Test
+ public void vibrate_thenSettingsChange_getsCancelled() throws Exception {
+ mockVibrators(1);
+ VibratorManagerService service = createSystemReadyService();
+
+ HalVibration vib = vibrate(service,
+ VibrationEffect.createOneShot(2 * TEST_TIMEOUT_MILLIS, 100),
+ HAPTIC_FEEDBACK_ATTRS);
+
+ // VibrationThread will start this vibration async, so wait until vibration is triggered.
+ assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+ setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, Vibrator.VIBRATION_INTENSITY_OFF);
+ service.mVibrationSettings.mSettingObserver.onChange(false);
+ service.updateServiceState();
+
+ assertTrue(waitUntil(s -> vib.hasEnded(), service, TEST_TIMEOUT_MILLIS));
+ assertThat(vib.getStatus()).isEqualTo(Status.CANCELLED_BY_SETTINGS_UPDATE);
+ }
+
+ @Test
+ public void vibrate_thenScreenTurnsOff_getsCancelled() throws Throwable {
+ mockVibrators(1);
+ VibratorManagerService service = createSystemReadyService();
+
+ HalVibration vib = vibrate(service, VibrationEffect.createWaveform(
+ new long[]{0, TEST_TIMEOUT_MILLIS, TEST_TIMEOUT_MILLIS}, 0), ALARM_ATTRS);
+
+ assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+ service.mIntentReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_SCREEN_OFF));
+
+ assertTrue(waitUntil(s -> vib.hasEnded(), service, TEST_TIMEOUT_MILLIS));
+ assertThat(vib.getStatus()).isEqualTo(Status.CANCELLED_BY_SCREEN_OFF);
}
@Test
@@ -831,8 +898,8 @@
mockVibrators(1);
VibratorManagerService service = createSystemReadyService();
- var vib = vibrate(service,
- VibrationEffect.createWaveform(new long[]{100, 100, 100, 100}, 0), ALARM_ATTRS);
+ HalVibration vib = vibrate(service, VibrationEffect.createWaveform(
+ new long[]{0, TEST_TIMEOUT_MILLIS, TEST_TIMEOUT_MILLIS}, 0), ALARM_ATTRS);
assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
@@ -841,14 +908,7 @@
BackgroundUserSoundNotifier.ACTION_MUTE_SOUND));
assertTrue(waitUntil(s -> vib.hasEnded(), service, TEST_TIMEOUT_MILLIS));
-
- var statsInfoCaptor = ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
- verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
- .writeVibrationReportedAsync(statsInfoCaptor.capture());
-
- VibrationStats.StatsInfo touchMetrics = statsInfoCaptor.getAllValues().get(0);
- assertEquals(Vibration.Status.CANCELLED_BY_FOREGROUND_USER.getProtoEnumValue(),
- touchMetrics.status);
+ assertThat(vib.getStatus()).isEqualTo(Status.CANCELLED_BY_FOREGROUND_USER);
}
@Test
@@ -1314,7 +1374,7 @@
}
@Test
- public void vibrate_withriggerCallback_finishesVibration() throws Exception {
+ public void vibrate_withTriggerCallback_finishesVibration() throws Exception {
mockCapabilities(IVibratorManager.CAP_SYNC, IVibratorManager.CAP_PREPARE_COMPOSE);
mockVibrators(1, 2);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
@@ -1947,41 +2007,6 @@
}
@Test
- public void vibrate_withPowerModeChange_cancelVibrationIfNotAllowed() throws Exception {
- mockVibrators(1, 2);
- VibratorManagerService service = createSystemReadyService();
- vibrate(service,
- CombinedVibration.startParallel()
- .addVibrator(1, VibrationEffect.createOneShot(1000, 100))
- .combine(),
- HAPTIC_FEEDBACK_ATTRS);
-
- // VibrationThread will start this vibration async, so wait until vibration is triggered.
- assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
-
- mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
-
- // Haptic feedback cancelled on low power mode.
- assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
- }
-
- @Test
- public void vibrate_withSettingsChange_doNotCancelVibration() throws Exception {
- mockVibrators(1);
- VibratorManagerService service = createSystemReadyService();
-
- vibrate(service, VibrationEffect.createOneShot(1000, 100), HAPTIC_FEEDBACK_ATTRS);
-
- // VibrationThread will start this vibration async, so wait until vibration is triggered.
- assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
-
- service.updateServiceState();
-
- // Vibration is not stopped nearly after updating service.
- assertFalse(waitUntil(s -> !s.isVibrating(1), service, 50));
- }
-
- @Test
public void vibrate_ignoreVibrationFromVirtualDevice() throws Exception {
mockVibrators(1);
VibratorManagerService service = createSystemReadyService();
@@ -2475,6 +2500,113 @@
}
@Test
+ public void onExternalVibration_thenDeniedAppOps_doNotCancelVibration() throws Throwable {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+ VibratorManagerService service = createSystemReadyService();
+
+ IExternalVibrationController externalVibrationControllerMock =
+ mock(IExternalVibrationController.class);
+ ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+ AUDIO_ALARM_ATTRS, externalVibrationControllerMock, mock(IBinder.class));
+ ExternalVibrationScale scale = mExternalVibratorService.onExternalVibrationStart(
+ externalVibration);
+
+ assertThat(scale.scaleLevel).isNotEqualTo(ExternalVibrationScale.ScaleLevel.SCALE_MUTE);
+
+ when(mAppOpsManagerMock.checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+ eq(AudioAttributes.USAGE_ALARM), anyInt(), anyString()))
+ .thenReturn(AppOpsManager.MODE_IGNORED);
+ service.mAppOpsChangeListener.onOpChanged(AppOpsManager.OP_VIBRATE, null);
+
+ verify(externalVibrationControllerMock, never()).mute();
+ }
+
+ @Test
+ public void onExternalVibration_thenPowerModeChanges_doNotCancelVibration() throws Exception {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+ createSystemReadyService();
+
+ IExternalVibrationController externalVibrationControllerMock =
+ mock(IExternalVibrationController.class);
+ ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+ AUDIO_ALARM_ATTRS, externalVibrationControllerMock, mock(IBinder.class));
+ ExternalVibrationScale scale = mExternalVibratorService.onExternalVibrationStart(
+ externalVibration);
+
+ assertThat(scale.scaleLevel).isNotEqualTo(ExternalVibrationScale.ScaleLevel.SCALE_MUTE);
+
+ mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
+
+ verify(externalVibrationControllerMock, never()).mute();
+ }
+
+ @Test
+ public void onExternalVibration_thenSettingsChange_doNotCancelVibration() throws Exception {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+ VibratorManagerService service = createSystemReadyService();
+
+ IExternalVibrationController externalVibrationControllerMock =
+ mock(IExternalVibrationController.class);
+ ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+ AUDIO_ALARM_ATTRS, externalVibrationControllerMock, mock(IBinder.class));
+ ExternalVibrationScale scale = mExternalVibratorService.onExternalVibrationStart(
+ externalVibration);
+
+ assertThat(scale.scaleLevel).isNotEqualTo(ExternalVibrationScale.ScaleLevel.SCALE_MUTE);
+
+ setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_OFF);
+ service.mVibrationSettings.mSettingObserver.onChange(false);
+ service.updateServiceState();
+
+ verify(externalVibrationControllerMock, never()).mute();
+ }
+
+ @Test
+ public void onExternalVibration_thenScreenTurnsOff_doNotCancelVibration() throws Throwable {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+ VibratorManagerService service = createSystemReadyService();
+
+ IExternalVibrationController externalVibrationControllerMock =
+ mock(IExternalVibrationController.class);
+ ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+ AUDIO_ALARM_ATTRS, externalVibrationControllerMock, mock(IBinder.class));
+ ExternalVibrationScale scale = mExternalVibratorService.onExternalVibrationStart(
+ externalVibration);
+
+ assertThat(scale.scaleLevel).isNotEqualTo(ExternalVibrationScale.ScaleLevel.SCALE_MUTE);
+
+ service.mIntentReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_SCREEN_OFF));
+
+ verify(externalVibrationControllerMock, never()).mute();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.multiuser.Flags.FLAG_ADD_UI_FOR_SOUNDS_FROM_BACKGROUND_USERS)
+ public void onExternalVibration_thenFgUserRequestsMute_doNotCancelVibration() throws Throwable {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+ VibratorManagerService service = createSystemReadyService();
+
+ IExternalVibrationController externalVibrationControllerMock =
+ mock(IExternalVibrationController.class);
+ ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+ AUDIO_ALARM_ATTRS, externalVibrationControllerMock, mock(IBinder.class));
+ ExternalVibrationScale scale = mExternalVibratorService.onExternalVibrationStart(
+ externalVibration);
+
+ assertThat(scale.scaleLevel).isNotEqualTo(ExternalVibrationScale.ScaleLevel.SCALE_MUTE);
+
+ service.mIntentReceiver.onReceive(mContextSpy, new Intent(
+ BackgroundUserSoundNotifier.ACTION_MUTE_SOUND));
+
+ verify(externalVibrationControllerMock, never()).mute();
+ }
+
+ @Test
public void frameworkStats_externalVibration_reportsAllMetrics() throws Exception {
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
@@ -2501,7 +2633,7 @@
assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__EXTERNAL,
statsInfo.vibrationType);
assertEquals(VibrationAttributes.USAGE_ALARM, statsInfo.usage);
- assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), statsInfo.status);
+ assertEquals(Status.FINISHED.getProtoEnumValue(), statsInfo.status);
assertTrue(statsInfo.totalDurationMillis > 0);
assertTrue(
"Expected vibrator ON for at least 10ms, got " + statsInfo.vibratorOnMillis + "ms",
@@ -2533,7 +2665,7 @@
assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE,
metrics.vibrationType);
assertEquals(VibrationAttributes.USAGE_RINGTONE, metrics.usage);
- assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), metrics.status);
+ assertEquals(Status.FINISHED.getProtoEnumValue(), metrics.status);
assertTrue("Total duration was too low, " + metrics.totalDurationMillis + "ms",
metrics.totalDurationMillis >= 20);
assertTrue("Vibrator ON duration was too low, " + metrics.vibratorOnMillis + "ms",
@@ -2586,7 +2718,7 @@
assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED,
metrics.vibrationType);
assertEquals(VibrationAttributes.USAGE_RINGTONE, metrics.usage);
- assertEquals(Vibration.Status.CANCELLED_BY_USER.getProtoEnumValue(), metrics.status);
+ assertEquals(Status.CANCELLED_BY_USER.getProtoEnumValue(), metrics.status);
assertTrue("Total duration was too low, " + metrics.totalDurationMillis + "ms",
metrics.totalDurationMillis >= 100);
assertTrue("Vibrator ON duration was too low, " + metrics.vibratorOnMillis + "ms",
@@ -2647,7 +2779,7 @@
assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE,
metrics.vibrationType);
assertEquals(VibrationAttributes.USAGE_ALARM, metrics.usage);
- assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), metrics.status);
+ assertEquals(Status.FINISHED.getProtoEnumValue(), metrics.status);
// At least 4 effect/primitive played, 20ms each, plus configured fallback.
assertTrue("Total duration was too low, " + metrics.totalDurationMillis + "ms",
@@ -2703,7 +2835,7 @@
VibrationStats.StatsInfo touchMetrics = argumentCaptor.getAllValues().get(0);
assertEquals(UID, touchMetrics.uid);
assertEquals(VibrationAttributes.USAGE_TOUCH, touchMetrics.usage);
- assertEquals(Vibration.Status.CANCELLED_SUPERSEDED.getProtoEnumValue(),
+ assertEquals(Status.CANCELLED_SUPERSEDED.getProtoEnumValue(),
touchMetrics.status);
assertTrue(touchMetrics.endedBySameUid);
assertEquals(VibrationAttributes.USAGE_ALARM, touchMetrics.endedByUsage);
@@ -2712,7 +2844,7 @@
VibrationStats.StatsInfo alarmMetrics = argumentCaptor.getAllValues().get(1);
assertEquals(UID, alarmMetrics.uid);
assertEquals(VibrationAttributes.USAGE_ALARM, alarmMetrics.usage);
- assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), alarmMetrics.status);
+ assertEquals(Status.FINISHED.getProtoEnumValue(), alarmMetrics.status);
assertFalse(alarmMetrics.endedBySameUid);
assertEquals(-1, alarmMetrics.endedByUsage);
assertEquals(VibrationAttributes.USAGE_TOUCH, alarmMetrics.interruptedUsage);
@@ -2742,12 +2874,12 @@
VibrationStats.StatsInfo touchMetrics = argumentCaptor.getAllValues().get(0);
assertEquals(UID, touchMetrics.uid);
assertEquals(VibrationAttributes.USAGE_TOUCH, touchMetrics.usage);
- assertEquals(Vibration.Status.IGNORED_FOR_POWER.getProtoEnumValue(), touchMetrics.status);
+ assertEquals(Status.IGNORED_FOR_POWER.getProtoEnumValue(), touchMetrics.status);
VibrationStats.StatsInfo ringtoneMetrics = argumentCaptor.getAllValues().get(1);
assertEquals(UID, ringtoneMetrics.uid);
assertEquals(VibrationAttributes.USAGE_RINGTONE, ringtoneMetrics.usage);
- assertEquals(Vibration.Status.IGNORED_FOR_SETTINGS.getProtoEnumValue(),
+ assertEquals(Status.IGNORED_FOR_SETTINGS.getProtoEnumValue(),
ringtoneMetrics.status);
for (VibrationStats.StatsInfo metrics : argumentCaptor.getAllValues()) {
@@ -2814,7 +2946,7 @@
assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE,
metrics.vibrationType);
assertEquals(VibrationAttributes.USAGE_NOTIFICATION, metrics.usage);
- assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), metrics.status);
+ assertEquals(Status.FINISHED.getProtoEnumValue(), metrics.status);
assertTrue(metrics.totalDurationMillis >= 20);
// vibratorOnMillis accumulates both vibrators, it's 20 for each constant.
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
index d147325..0575d98 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
@@ -41,6 +41,8 @@
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.view.KeyEvent;
import android.view.KeyboardShortcutGroup;
import android.view.KeyboardShortcutInfo;
@@ -50,6 +52,8 @@
import com.android.internal.R;
import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
import org.junit.Test;
import java.util.Collections;
@@ -62,7 +66,13 @@
*/
@SmallTest
+@EnableFlags(com.android.hardware.input.Flags.FLAG_MODIFIER_SHORTCUT_MANAGER_REFACTOR)
public class ModifierShortcutManagerTests {
+
+ @ClassRule public static final SetFlagsRule.ClassRule SET_FLAGS_CLASS_RULE =
+ new SetFlagsRule.ClassRule();
+ @Rule public final SetFlagsRule mSetFlagsRule = SET_FLAGS_CLASS_RULE.createSetFlagsRule();
+
private ModifierShortcutManager mModifierShortcutManager;
private Handler mHandler;
private Context mContext;
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
index 71f90a2..43171f8 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
@@ -44,26 +44,21 @@
import android.content.ComponentName;
import android.content.Intent;
import android.os.RemoteException;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
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.util.SparseArray;
import androidx.test.filters.SmallTest;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
@Presubmit
@SmallTest
+@EnableFlags(com.android.hardware.input.Flags.FLAG_MODIFIER_SHORTCUT_MANAGER_REFACTOR)
public class ModifierShortcutTests extends ShortcutKeyTestBase {
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
private static final SparseArray<String> INTENT_SHORTCUTS = new SparseArray<>();
private static final SparseArray<String> ROLE_SHORTCUTS = new SparseArray<>();
static {
@@ -258,7 +253,7 @@
* META+CTRL+BACKSPACE for taking a bugreport when the flag is enabled.
*/
@Test
- @RequiresFlagsEnabled(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT)
+ @EnableFlags(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT)
public void testTakeBugReport_flagEnabled() throws RemoteException {
sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_CTRL_LEFT, KEYCODE_DEL}, 0);
mPhoneWindowManager.assertTakeBugreport(true);
@@ -268,7 +263,7 @@
* META+CTRL+BACKSPACE for taking a bugreport does nothing when the flag is disabledd.
*/
@Test
- @RequiresFlagsDisabled(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT)
+ @DisableFlags(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT)
public void testTakeBugReport_flagDisabled() throws RemoteException {
sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_CTRL_LEFT, KEYCODE_DEL}, 0);
mPhoneWindowManager.assertTakeBugreport(false);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index af4394a..0c1fbf3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -25,11 +25,15 @@
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_OPEN;
import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -37,6 +41,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -46,11 +51,14 @@
import static org.junit.Assume.assumeFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.gui.DropInputMode;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
@@ -833,6 +841,353 @@
}
@Test
+ public void testOverrideTaskFragmentAdapter_overrideWithEmbeddedActivity() {
+ final Task task = createTask(mDisplayContent);
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+
+ // Create a TaskFragment with embedded activity.
+ final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final ActivityRecord activity = taskFragment.getTopMostActivity();
+ prepareActivityForAppTransition(activity);
+ spyOn(mDisplayContent.mAppTransition);
+
+ // Prepare and start transition.
+ prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+
+ // Animation run by the remote handler.
+ assertTrue(remoteAnimationRunner.isAnimationStarted());
+ }
+
+ @Test
+ public void testOverrideTaskFragmentAdapter_noOverrideWithOnlyTaskFragmentFillingTask() {
+ final Task task = createTask(mDisplayContent);
+ final ActivityRecord closingActivity = createActivityRecord(task);
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+
+ // Create a TaskFragment with embedded activity.
+ final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+
+ // Make sure the TaskFragment is not embedded.
+ assertFalse(taskFragment.isEmbeddedWithBoundsOverride());
+ final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
+ prepareActivityForAppTransition(closingActivity);
+ prepareActivityForAppTransition(openingActivity);
+ final int uid = 12345;
+ closingActivity.info.applicationInfo.uid = uid;
+ openingActivity.info.applicationInfo.uid = uid;
+ task.effectiveUid = uid;
+ spyOn(mDisplayContent.mAppTransition);
+
+ // Prepare and start transition.
+ prepareAndTriggerAppTransition(openingActivity, closingActivity,
+ null /* changingTaskFragment */);
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+
+ // Animation is not run by the remote handler because the activity is filling the Task.
+ assertFalse(remoteAnimationRunner.isAnimationStarted());
+ }
+
+ @Test
+ public void testOverrideTaskFragmentAdapter_overrideWithTaskFragmentNotFillingTask() {
+ final Task task = createTask(mDisplayContent);
+ final ActivityRecord closingActivity = createActivityRecord(task);
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+
+ // Create a TaskFragment with embedded activity.
+ final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+
+ // Make sure the TaskFragment is embedded.
+ taskFragment.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ final Rect embeddedBounds = new Rect(task.getBounds());
+ embeddedBounds.right = embeddedBounds.left + embeddedBounds.width() / 2;
+ taskFragment.setBounds(embeddedBounds);
+ assertTrue(taskFragment.isEmbeddedWithBoundsOverride());
+ final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
+ prepareActivityForAppTransition(closingActivity);
+ prepareActivityForAppTransition(openingActivity);
+ final int uid = 12345;
+ closingActivity.info.applicationInfo.uid = uid;
+ openingActivity.info.applicationInfo.uid = uid;
+ task.effectiveUid = uid;
+ spyOn(mDisplayContent.mAppTransition);
+
+ // Prepare and start transition.
+ prepareAndTriggerAppTransition(openingActivity, closingActivity,
+ null /* changingTaskFragment */);
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+
+ // Animation run by the remote handler.
+ assertTrue(remoteAnimationRunner.isAnimationStarted());
+ }
+
+ @Test
+ public void testOverrideTaskFragmentAdapter_overrideWithNonEmbeddedActivity() {
+ final Task task = createTask(mDisplayContent);
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+
+ // Closing non-embedded activity.
+ final ActivityRecord closingActivity = createActivityRecord(task);
+ prepareActivityForAppTransition(closingActivity);
+ // Opening TaskFragment with embedded activity.
+ final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
+ prepareActivityForAppTransition(openingActivity);
+ task.effectiveUid = openingActivity.getUid();
+ spyOn(mDisplayContent.mAppTransition);
+
+ // Prepare and start transition.
+ prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment);
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+
+ // Animation run by the remote handler.
+ assertTrue(remoteAnimationRunner.isAnimationStarted());
+ }
+
+ @Test
+ public void testOverrideTaskFragmentAdapter_overrideEmbeddedActivityWithDiffUid() {
+ final Task task = createTask(mDisplayContent);
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+
+ // Closing TaskFragment with embedded activity.
+ final TaskFragment taskFragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final ActivityRecord closingActivity = taskFragment1.getTopMostActivity();
+ prepareActivityForAppTransition(closingActivity);
+ closingActivity.info.applicationInfo.uid = 12345;
+ // Opening TaskFragment with embedded activity with different UID.
+ final TaskFragment taskFragment2 = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final ActivityRecord openingActivity = taskFragment2.getTopMostActivity();
+ prepareActivityForAppTransition(openingActivity);
+ openingActivity.info.applicationInfo.uid = 54321;
+ spyOn(mDisplayContent.mAppTransition);
+
+ // Prepare and start transition.
+ prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment1);
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+
+ // Animation run by the remote handler.
+ assertTrue(remoteAnimationRunner.isAnimationStarted());
+ }
+
+ @Test
+ public void testOverrideTaskFragmentAdapter_noOverrideWithTwoApps() {
+ final Task task = createTask(mDisplayContent);
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+
+ // Closing activity in Task1.
+ final ActivityRecord closingActivity = createActivityRecord(mDisplayContent);
+ prepareActivityForAppTransition(closingActivity);
+ // Opening TaskFragment with embedded activity in Task2.
+ final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
+ prepareActivityForAppTransition(openingActivity);
+ spyOn(mDisplayContent.mAppTransition);
+
+ // Prepare and start transition.
+ prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment);
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+
+ // Animation not run by the remote handler.
+ assertFalse(remoteAnimationRunner.isAnimationStarted());
+ }
+
+ @Test
+ public void testOverrideTaskFragmentAdapter_noOverrideNonEmbeddedActivityWithDiffUid() {
+ final Task task = createTask(mDisplayContent);
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+
+ // Closing TaskFragment with embedded activity.
+ final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final ActivityRecord closingActivity = taskFragment.getTopMostActivity();
+ prepareActivityForAppTransition(closingActivity);
+ closingActivity.info.applicationInfo.uid = 12345;
+ task.effectiveUid = closingActivity.getUid();
+ // Opening non-embedded activity with different UID.
+ final ActivityRecord openingActivity = createActivityRecord(task);
+ prepareActivityForAppTransition(openingActivity);
+ openingActivity.info.applicationInfo.uid = 54321;
+ spyOn(mDisplayContent.mAppTransition);
+
+ // Prepare and start transition.
+ prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment);
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+
+ // Animation should not run by the remote handler when there are non-embedded activities of
+ // different UID.
+ assertFalse(remoteAnimationRunner.isAnimationStarted());
+ }
+
+ @Test
+ public void testOverrideTaskFragmentAdapter_noOverrideWithWallpaper() {
+ final Task task = createTask(mDisplayContent);
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+
+ // Create a TaskFragment with embedded activity.
+ final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final ActivityRecord activity = taskFragment.getTopMostActivity();
+ prepareActivityForAppTransition(activity);
+ // Set wallpaper as visible.
+ final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
+ mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */);
+ spyOn(mDisplayContent.mWallpaperController);
+ doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
+ spyOn(mDisplayContent.mAppTransition);
+
+ // Prepare and start transition.
+ prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+
+ // Animation should not run by the remote handler when there is wallpaper in the transition.
+ assertFalse(remoteAnimationRunner.isAnimationStarted());
+ }
+
+ @Test
+ public void testOverrideTaskFragmentAdapter_inputProtectedForUntrustedAnimation() {
+ final Task task = createTask(mDisplayContent);
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+
+ // Create a TaskFragment with embedded activities, one is trusted embedded, and the other
+ // one is untrusted embedded.
+ final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .createActivityCount(2)
+ .setOrganizer(organizer)
+ .build();
+ final ActivityRecord activity0 = taskFragment.getChildAt(0).asActivityRecord();
+ final ActivityRecord activity1 = taskFragment.getChildAt(1).asActivityRecord();
+ // Also create a non-embedded activity in the Task.
+ final ActivityRecord activity2 = new ActivityBuilder(mAtm).build();
+ task.addChild(activity2, POSITION_BOTTOM);
+ prepareActivityForAppTransition(activity0);
+ prepareActivityForAppTransition(activity1);
+ prepareActivityForAppTransition(activity2);
+ doReturn(false).when(taskFragment).isAllowedToEmbedActivityInTrustedMode(activity0);
+ doReturn(true).when(taskFragment).isAllowedToEmbedActivityInTrustedMode(activity1);
+ spyOn(mDisplayContent.mAppTransition);
+
+ // Prepare and start transition.
+ prepareAndTriggerAppTransition(activity1, null /* closingActivity */, taskFragment);
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+
+ // The animation will be animated remotely by client and all activities are input disabled
+ // for untrusted animation.
+ assertTrue(remoteAnimationRunner.isAnimationStarted());
+ verify(activity0).setDropInputForAnimation(true);
+ verify(activity1).setDropInputForAnimation(true);
+ verify(activity2).setDropInputForAnimation(true);
+ verify(activity0).setDropInputMode(DropInputMode.ALL);
+ verify(activity1).setDropInputMode(DropInputMode.ALL);
+ verify(activity2).setDropInputMode(DropInputMode.ALL);
+
+ // Reset input after animation is finished.
+ clearInvocations(activity0);
+ clearInvocations(activity1);
+ clearInvocations(activity2);
+ remoteAnimationRunner.finishAnimation();
+
+ verify(activity0).setDropInputForAnimation(false);
+ verify(activity1).setDropInputForAnimation(false);
+ verify(activity2).setDropInputForAnimation(false);
+ verify(activity0).setDropInputMode(DropInputMode.OBSCURED);
+ verify(activity1).setDropInputMode(DropInputMode.NONE);
+ verify(activity2).setDropInputMode(DropInputMode.NONE);
+ }
+
+ /**
+ * Since we don't have any use case to rely on handling input during animation, disable it even
+ * if it is trusted embedding so that it could cover some edge-cases when a previously trusted
+ * host starts doing something bad.
+ */
+ @Test
+ public void testOverrideTaskFragmentAdapter_inputProtectedForTrustedAnimation() {
+ final Task task = createTask(mDisplayContent);
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+
+ // Create a TaskFragment with only trusted embedded activity
+ final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .createActivityCount(1)
+ .setOrganizer(organizer)
+ .build();
+ final ActivityRecord activity = taskFragment.getChildAt(0).asActivityRecord();
+ prepareActivityForAppTransition(activity);
+ doReturn(true).when(taskFragment).isAllowedToEmbedActivityInTrustedMode(activity);
+ spyOn(mDisplayContent.mAppTransition);
+
+ // Prepare and start transition.
+ prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+
+ // The animation will be animated remotely by client and all activities are input disabled
+ // for untrusted animation.
+ assertTrue(remoteAnimationRunner.isAnimationStarted());
+ verify(activity).setDropInputForAnimation(true);
+ verify(activity).setDropInputMode(DropInputMode.ALL);
+
+ // Reset input after animation is finished.
+ clearInvocations(activity);
+ remoteAnimationRunner.finishAnimation();
+
+ verify(activity).setDropInputForAnimation(false);
+ verify(activity).setDropInputMode(DropInputMode.NONE);
+ }
+
+ /**
+ * We don't need to drop input for fully trusted embedding (system app, and embedding in the
+ * same app). This will allow users to do fast tapping.
+ */
+ @Test
+ public void testOverrideTaskFragmentAdapter_noInputProtectedForFullyTrustedAnimation() {
+ final Task task = createTask(mDisplayContent);
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+
+ // Create a TaskFragment with only trusted embedded activity
+ final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .createActivityCount(1)
+ .setOrganizer(organizer)
+ .build();
+ final ActivityRecord activity = taskFragment.getChildAt(0).asActivityRecord();
+ prepareActivityForAppTransition(activity);
+ final int uid = mAtm.mTaskFragmentOrganizerController.getTaskFragmentOrganizerUid(
+ getITaskFragmentOrganizer(organizer));
+ doReturn(true).when(task).isFullyTrustedEmbedding(uid);
+ spyOn(mDisplayContent.mAppTransition);
+
+ // Prepare and start transition.
+ prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+
+ // The animation will be animated remotely by client, but input should not be dropped for
+ // fully trusted.
+ assertTrue(remoteAnimationRunner.isAnimationStarted());
+ verify(activity, never()).setDropInputForAnimation(true);
+ verify(activity, never()).setDropInputMode(DropInputMode.ALL);
+ }
+
+ @Test
public void testTransitionGoodToGoForTaskFragments() {
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final Task task = createTask(mDisplayContent);
@@ -898,6 +1253,22 @@
verify(mDisplayContent.mAppTransition).goodToGo(anyInt(), any());
}
+ /** Registers remote animation for the organizer. */
+ private void setupTaskFragmentRemoteAnimation(TaskFragmentOrganizer organizer,
+ TestRemoteAnimationRunner remoteAnimationRunner) {
+ final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
+ remoteAnimationRunner, 10, 1);
+ final ITaskFragmentOrganizer iOrganizer = getITaskFragmentOrganizer(organizer);
+ final RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
+ definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, adapter);
+ definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, adapter);
+ definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, adapter);
+ definition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, adapter);
+ definition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_CLOSE, adapter);
+ registerTaskFragmentOrganizer(iOrganizer);
+ mAtm.mTaskFragmentOrganizerController.registerRemoteAnimations(iOrganizer, definition);
+ }
+
private static ITaskFragmentOrganizer getITaskFragmentOrganizer(
TaskFragmentOrganizer organizer) {
return ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder());
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
index affaad6..14276ae 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
@@ -16,7 +16,7 @@
package com.android.server.wm;
-import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
@@ -277,7 +277,7 @@
mDisplayContent.mDisplayUpdater.onDisplaySwitching(/* switching= */ true);
mWmInternal.waitForAllWindowsDrawn(mScreenUnblocker,
- /* timeout= */ Integer.MAX_VALUE, DEFAULT_DISPLAY);
+ /* timeout= */ Integer.MAX_VALUE, INVALID_DISPLAY);
mWmInternal.waitForAllWindowsDrawn(mSecondaryScreenUnblocker,
/* timeout= */ Integer.MAX_VALUE, mSecondaryDisplayContent.getDisplayId());
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index f2ea1c9..eca4d21 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1144,6 +1144,7 @@
@Test
public void testOrientationBehind() {
+ assertNull(mDisplayContent.getLastOrientationSource());
final ActivityRecord prev = new ActivityBuilder(mAtm).setCreateTask(true)
.setScreenOrientation(getRotatedOrientation(mDisplayContent)).build();
prev.setVisibleRequested(false);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index d013053..a71b81e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -90,6 +90,7 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.view.RemoteAnimationDefinition;
import android.view.SurfaceControl;
import android.window.IRemoteTransition;
import android.window.ITaskFragmentOrganizer;
@@ -139,6 +140,7 @@
private IBinder mFragmentToken;
private WindowContainerTransaction mTransaction;
private WindowContainerToken mFragmentWindowToken;
+ private RemoteAnimationDefinition mDefinition;
private IBinder mErrorToken;
private Rect mTaskFragBounds;
@@ -167,6 +169,7 @@
mTransaction = new WindowContainerTransaction();
mTransaction.setTaskFragmentOrganizer(mIOrganizer);
mFragmentWindowToken = mTaskFragment.mRemoteToken.toWindowContainerToken();
+ mDefinition = new RemoteAnimationDefinition();
mErrorToken = new Binder();
final Rect displayBounds = mDisplayContent.getBounds();
mTaskFragBounds = new Rect(displayBounds.left, displayBounds.top, displayBounds.centerX(),
@@ -576,6 +579,17 @@
}
@Test
+ public void testRegisterRemoteAnimations() {
+ mController.registerRemoteAnimations(mIOrganizer, mDefinition);
+
+ assertEquals(mDefinition, mController.getRemoteAnimationDefinition(mIOrganizer));
+
+ mController.unregisterRemoteAnimations(mIOrganizer);
+
+ assertNull(mController.getRemoteAnimationDefinition(mIOrganizer));
+ }
+
+ @Test
public void testApplyTransaction_disallowRemoteTransitionForNonSystemOrganizer() {
mTransaction.setRelativeBounds(mFragmentWindowToken, new Rect(0, 0, 100, 100));
mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
diff --git a/telecomm/java/android/telecom/CallControl.java b/telecomm/java/android/telecom/CallControl.java
index 808a575..1e1abf2 100644
--- a/telecomm/java/android/telecom/CallControl.java
+++ b/telecomm/java/android/telecom/CallControl.java
@@ -39,7 +39,10 @@
/**
* CallControl provides client side control of a call. Each Call will get an individual CallControl
- * instance in which the client can alter the state of the associated call.
+ * instance in which the client can alter the state of the associated call. Outgoing and incoming
+ * calls should move to active (via {@link CallControl#setActive(Executor, OutcomeReceiver)} or
+ * answered (via {@link CallControl#answer(int, Executor, OutcomeReceiver)} before 60 seconds. If
+ * the new call is not moved to active or answered before 60 seconds, the call will be disconnected.
*
* <p>
* Each method is Transactional meaning that it can succeed or fail. If a transaction succeeds,
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 1df6cf7..ad7d987 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -2621,7 +2621,9 @@
}
/**
- * Sets state to ringing (e.g., an inbound ringing connection).
+ * Sets state to ringing (e.g., an inbound ringing connection). The Connection should not be
+ * in STATE_RINGING for more than 60 seconds. After 60 seconds, the Connection will
+ * be disconnected.
*/
public final void setRinging() {
checkImmutable();
@@ -2645,7 +2647,9 @@
}
/**
- * Sets state to dialing (e.g., dialing an outbound connection).
+ * Sets state to dialing (e.g., dialing an outbound connection). The Connection should not be
+ * in STATE_DIALING for more than 60 seconds. After 60 seconds, the Connection will
+ * be disconnected.
*/
public final void setDialing() {
checkImmutable();
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index cba2eea..13bd5eb 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -10014,6 +10014,19 @@
public static final String KEY_SATELLITE_NIDD_APN_NAME_STRING =
"satellite_nidd_apn_name_string";
+ /**
+ * Default value {@code true}, meaning when an emergency call request comes in, if the device is
+ * in emergency satellite mode but hasn't sent the first satellite datagram, then exits
+ * satellite mode to allow the emergency call to go through.
+ *
+ * If {@code false}, the emergency call is always blocked if device is in emergency satellite
+ * mode. Note if device is NOT in emergency satellite mode, emergency call is always allowed.
+ *
+ * @hide
+ */
+ public static final String KEY_SATELLITE_ROAMING_TURN_OFF_SESSION_FOR_EMERGENCY_CALL_BOOL =
+ "satellite_roaming_turn_off_session_for_emergency_call_bool";
+
/** @hide */
@IntDef({
CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC,
@@ -11276,6 +11289,7 @@
sDefaults.putBoolean(KEY_SATELLITE_ESOS_SUPPORTED_BOOL, false);
sDefaults.putBoolean(KEY_SATELLITE_ROAMING_P2P_SMS_SUPPORTED_BOOL, false);
sDefaults.putString(KEY_SATELLITE_NIDD_APN_NAME_STRING, "");
+ sDefaults.putBoolean(KEY_SATELLITE_ROAMING_TURN_OFF_SESSION_FOR_EMERGENCY_CALL_BOOL, true);
sDefaults.putInt(KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT, 0);
sDefaults.putInt(KEY_CARRIER_ROAMING_NTN_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_INT,
SatelliteManager.EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 7481daa..bc709ab 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -132,6 +132,7 @@
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.RILConstants;
import com.android.internal.telephony.flags.Flags;
+import com.android.internal.telephony.util.TelephonyUtils;
import com.android.telephony.Rlog;
import java.io.IOException;
@@ -9975,6 +9976,7 @@
ALLOWED_NETWORK_TYPES_REASON_POWER,
ALLOWED_NETWORK_TYPES_REASON_CARRIER,
ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G,
+ ALLOWED_NETWORK_TYPES_REASON_TEST,
})
@Retention(RetentionPolicy.SOURCE)
public @interface AllowedNetworkTypesReason {
@@ -10013,6 +10015,14 @@
public static final int ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G = 3;
/**
+ * To indicate allowed network type change is requested by Testing purpose, should be default to
+ * {@link #getAllNetworkTypesBitmask} when done testing.
+ *
+ * @hide
+ */
+ public static final int ALLOWED_NETWORK_TYPES_REASON_TEST = 4;
+
+ /**
* Set the allowed network types of the device and provide the reason triggering the allowed
* network change.
* <p>Requires permission: {@link android.Manifest.permission#MODIFY_PHONE_STATE} or
@@ -10118,6 +10128,8 @@
case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER:
case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G:
return true;
+ case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_TEST:
+ return TelephonyUtils.IS_DEBUGGABLE;
}
return false;
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index c5934a7..83fc0dc 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -592,7 +592,7 @@
() -> resultListener.accept(result)));
}
};
- telephony.requestSatelliteEnabled(mSubId, attributes.isEnabled(),
+ telephony.requestSatelliteEnabled(attributes.isEnabled(),
attributes.isDemoMode(), attributes.isEmergencyMode(), errorCallback);
} else {
Rlog.e(TAG, "requestEnabled() invalid telephony");
@@ -650,7 +650,7 @@
}
}
};
- telephony.requestIsSatelliteEnabled(mSubId, receiver);
+ telephony.requestIsSatelliteEnabled(receiver);
} else {
loge("requestIsEnabled() invalid telephony");
executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
@@ -707,7 +707,7 @@
}
}
};
- telephony.requestIsDemoModeEnabled(mSubId, receiver);
+ telephony.requestIsDemoModeEnabled(receiver);
} else {
loge("requestIsDemoModeEnabled() invalid telephony");
executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
@@ -764,7 +764,7 @@
}
}
};
- telephony.requestIsEmergencyModeEnabled(mSubId, receiver);
+ telephony.requestIsEmergencyModeEnabled(receiver);
} else {
executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
@@ -821,7 +821,7 @@
}
}
};
- telephony.requestIsSatelliteSupported(mSubId, receiver);
+ telephony.requestIsSatelliteSupported(receiver);
} else {
loge("requestIsSupported() invalid telephony");
executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
@@ -878,7 +878,7 @@
}
}
};
- telephony.requestSatelliteCapabilities(mSubId, receiver);
+ telephony.requestSatelliteCapabilities(receiver);
} else {
loge("requestCapabilities() invalid telephony");
executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
@@ -1204,8 +1204,7 @@
}
};
sSatelliteTransmissionUpdateCallbackMap.put(callback, internalCallback);
- telephony.startSatelliteTransmissionUpdates(mSubId, errorCallback,
- internalCallback);
+ telephony.startSatelliteTransmissionUpdates(errorCallback, internalCallback);
} else {
loge("startTransmissionUpdates() invalid telephony");
executor.execute(() -> Binder.withCleanCallingIdentity(
@@ -1255,8 +1254,7 @@
() -> resultListener.accept(result)));
}
};
- telephony.stopSatelliteTransmissionUpdates(mSubId, errorCallback,
- internalCallback);
+ telephony.stopSatelliteTransmissionUpdates(errorCallback, internalCallback);
// TODO: Notify SmsHandler that pointing UI stopped
} else {
loge("stopSatelliteTransmissionUpdates: No internal callback.");
@@ -1312,7 +1310,7 @@
() -> resultListener.accept(result)));
}
};
- cancelRemote = telephony.provisionSatelliteService(mSubId, token, provisionData,
+ cancelRemote = telephony.provisionSatelliteService(token, provisionData,
errorCallback);
} else {
loge("provisionService() invalid telephony");
@@ -1364,7 +1362,7 @@
() -> resultListener.accept(result)));
}
};
- telephony.deprovisionSatelliteService(mSubId, token, errorCallback);
+ telephony.deprovisionSatelliteService(token, errorCallback);
} else {
loge("deprovisionService() invalid telephony");
executor.execute(() -> Binder.withCleanCallingIdentity(
@@ -1419,8 +1417,7 @@
}
};
sSatelliteProvisionStateCallbackMap.put(callback, internalCallback);
- return telephony.registerForSatelliteProvisionStateChanged(
- mSubId, internalCallback);
+ return telephony.registerForSatelliteProvisionStateChanged(internalCallback);
} else {
throw new IllegalStateException("telephony service is null.");
}
@@ -1453,7 +1450,7 @@
ITelephony telephony = getITelephony();
if (telephony != null) {
if (internalCallback != null) {
- telephony.unregisterForSatelliteProvisionStateChanged(mSubId, internalCallback);
+ telephony.unregisterForSatelliteProvisionStateChanged(internalCallback);
} else {
loge("unregisterForProvisionStateChanged: No internal callback.");
}
@@ -1510,7 +1507,7 @@
}
}
};
- telephony.requestIsSatelliteProvisioned(mSubId, receiver);
+ telephony.requestIsSatelliteProvisioned(receiver);
} else {
loge("requestIsProvisioned() invalid telephony");
executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
@@ -1560,7 +1557,7 @@
}
};
sSatelliteModemStateCallbackMap.put(callback, internalCallback);
- return telephony.registerForSatelliteModemStateChanged(mSubId, internalCallback);
+ return telephony.registerForSatelliteModemStateChanged(internalCallback);
} else {
throw new IllegalStateException("telephony service is null.");
}
@@ -1593,7 +1590,7 @@
ITelephony telephony = getITelephony();
if (telephony != null) {
if (internalCallback != null) {
- telephony.unregisterForModemStateChanged(mSubId, internalCallback);
+ telephony.unregisterForModemStateChanged(internalCallback);
} else {
loge("unregisterForModemStateChanged: No internal callback.");
}
@@ -1656,7 +1653,7 @@
}
};
sSatelliteDatagramCallbackMap.put(callback, internalCallback);
- return telephony.registerForIncomingDatagram(mSubId, internalCallback);
+ return telephony.registerForIncomingDatagram(internalCallback);
} else {
throw new IllegalStateException("telephony service is null.");
}
@@ -1688,7 +1685,7 @@
ITelephony telephony = getITelephony();
if (telephony != null) {
if (internalCallback != null) {
- telephony.unregisterForIncomingDatagram(mSubId, internalCallback);
+ telephony.unregisterForIncomingDatagram(internalCallback);
} else {
loge("unregisterForIncomingDatagram: No internal callback.");
}
@@ -1732,7 +1729,7 @@
() -> resultListener.accept(result)));
}
};
- telephony.pollPendingDatagrams(mSubId, internalCallback);
+ telephony.pollPendingDatagrams(internalCallback);
} else {
loge("pollPendingDatagrams() invalid telephony");
executor.execute(() -> Binder.withCleanCallingIdentity(
@@ -1790,7 +1787,7 @@
() -> resultListener.accept(result)));
}
};
- telephony.sendDatagram(mSubId, datagramType, datagram,
+ telephony.sendDatagram(datagramType, datagram,
needFullScreenPointingUI, internalCallback);
} else {
loge("sendDatagram() invalid telephony");
@@ -1908,7 +1905,7 @@
}
}
};
- telephony.requestTimeForNextSatelliteVisibility(mSubId, receiver);
+ telephony.requestTimeForNextSatelliteVisibility(receiver);
} else {
loge("requestTimeForNextSatelliteVisibility() invalid telephony");
executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
@@ -1939,7 +1936,7 @@
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- telephony.setDeviceAlignedWithSatellite(mSubId, isAligned);
+ telephony.setDeviceAlignedWithSatellite(isAligned);
} else {
throw new IllegalStateException("telephony service is null.");
}
@@ -2203,7 +2200,7 @@
}
}
};
- telephony.requestNtnSignalStrength(mSubId, receiver);
+ telephony.requestNtnSignalStrength(receiver);
} else {
loge("requestNtnSignalStrength() invalid telephony");
executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
@@ -2254,7 +2251,7 @@
ntnSignalStrength)));
}
};
- telephony.registerForNtnSignalStrengthChanged(mSubId, internalCallback);
+ telephony.registerForNtnSignalStrengthChanged(internalCallback);
sNtnSignalStrengthCallbackMap.put(callback, internalCallback);
} else {
throw new IllegalStateException("Telephony service is null.");
@@ -2294,7 +2291,7 @@
ITelephony telephony = getITelephony();
if (telephony != null) {
if (internalCallback != null) {
- telephony.unregisterForNtnSignalStrengthChanged(mSubId, internalCallback);
+ telephony.unregisterForNtnSignalStrengthChanged(internalCallback);
} else {
loge("unregisterForNtnSignalStrengthChanged: No internal callback.");
throw new IllegalArgumentException("callback is not valid");
@@ -2339,7 +2336,7 @@
}
};
sSatelliteCapabilitiesCallbackMap.put(callback, internalCallback);
- return telephony.registerForCapabilitiesChanged(mSubId, internalCallback);
+ return telephony.registerForCapabilitiesChanged(internalCallback);
} else {
throw new IllegalStateException("Telephony service is null.");
}
@@ -2372,7 +2369,7 @@
ITelephony telephony = getITelephony();
if (telephony != null) {
if (internalCallback != null) {
- telephony.unregisterForCapabilitiesChanged(mSubId, internalCallback);
+ telephony.unregisterForCapabilitiesChanged(internalCallback);
} else {
loge("unregisterForCapabilitiesChanged: No internal callback.");
}
@@ -2448,7 +2445,7 @@
};
sSatelliteSupportedStateCallbackMap.put(callback, internalCallback);
return telephony.registerForSatelliteSupportedStateChanged(
- mSubId, internalCallback);
+ internalCallback);
} else {
throw new IllegalStateException("telephony service is null.");
}
@@ -2483,7 +2480,7 @@
ITelephony telephony = getITelephony();
if (telephony != null) {
if (internalCallback != null) {
- telephony.unregisterForSatelliteSupportedStateChanged(mSubId, internalCallback);
+ telephony.unregisterForSatelliteSupportedStateChanged(internalCallback);
} else {
loge("unregisterForSupportedStateChanged: No internal callback.");
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 0c5f30f..e852e6b 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2743,7 +2743,6 @@
/**
* Request to enable or disable the satellite modem.
*
- * @param subId The subId of the subscription to enable or disable the satellite modem for.
* @param enableSatellite True to enable the satellite modem and false to disable.
* @param enableDemoMode True if demo mode is enabled and false otherwise. When
* disabling satellite, {@code enableDemoMode} is always considered as
@@ -2755,93 +2754,83 @@
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void requestSatelliteEnabled(int subId, boolean enableSatellite, boolean enableDemoMode,
+ void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
boolean isEmergency, in IIntegerConsumer callback);
/**
* Request to get whether the satellite modem is enabled.
*
- * @param subId The subId of the subscription to request whether satellite is enabled for.
* @param receiver Result receiver to get the error code of the request and whether the
* satellite modem is enabled.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void requestIsSatelliteEnabled(int subId, in ResultReceiver receiver);
+ void requestIsSatelliteEnabled(in ResultReceiver receiver);
/**
* Request to get whether the satellite service demo mode is enabled.
*
- * @param subId The subId of the subscription to request whether the satellite demo mode is
- * enabled for.
* @param receiver Result receiver to get the error code of the request and whether the
* satellite demo mode is enabled.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void requestIsDemoModeEnabled(int subId, in ResultReceiver receiver);
+ void requestIsDemoModeEnabled(in ResultReceiver receiver);
/**
* Request to get whether the satellite service is enabled with emergency mode.
*
- * @param subId The subId of the subscription to request whether the satellite demo mode is
- * enabled for.
* @param receiver Result receiver to get the error code of the request and whether the
* satellite is enabled with emergency mode.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void requestIsEmergencyModeEnabled(int subId, in ResultReceiver receiver);
+ void requestIsEmergencyModeEnabled(in ResultReceiver receiver);
/**
* Request to get whether the satellite service is supported on the device.
*
- * @param subId The subId of the subscription to check whether satellite is supported for.
* @param receiver Result receiver to get the error code of the request and whether the
* satellite service is supported on the device.
*/
- void requestIsSatelliteSupported(int subId, in ResultReceiver receiver);
+ void requestIsSatelliteSupported(in ResultReceiver receiver);
/**
* Request to get the capabilities of the satellite service.
*
- * @param subId The subId of the subscription to get the capabilities for.
* @param receiver Result receiver to get the error code of the request and the requested
* capabilities of the satellite service.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void requestSatelliteCapabilities(int subId, in ResultReceiver receiver);
+ void requestSatelliteCapabilities(in ResultReceiver receiver);
/**
* Start receiving satellite transmission updates.
*
- * @param subId The subId of the subscription to stop satellite transmission updates for.
* @param resultCallback The callback to get the result of the request.
* @param callback The callback to handle transmission updates.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void startSatelliteTransmissionUpdates(int subId, in IIntegerConsumer resultCallback,
+ void startSatelliteTransmissionUpdates(in IIntegerConsumer resultCallback,
in ISatelliteTransmissionUpdateCallback callback);
/**
* Stop receiving satellite transmission updates.
*
- * @param subId The subId of the subscritpion to stop satellite transmission updates for.
* @param resultCallback The callback to get the result of the request.
* @param callback The callback that was passed to startSatelliteTransmissionUpdates.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void stopSatelliteTransmissionUpdates(int subId, in IIntegerConsumer resultCallback,
+ void stopSatelliteTransmissionUpdates(in IIntegerConsumer resultCallback,
in ISatelliteTransmissionUpdateCallback callback);
/**
* Register the subscription with a satellite provider.
* This is needed to register the subscription if the provider allows dynamic registration.
*
- * @param subId The subId of the subscription to be provisioned.
* @param token The token to be used as a unique identifier for provisioning with satellite
* gateway.
* @provisionData Data from the provisioning app that can be used by provisioning server
@@ -2851,7 +2840,7 @@
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- ICancellationSignal provisionSatelliteService(int subId, in String token,
+ ICancellationSignal provisionSatelliteService(in String token,
in byte[] provisionData, in IIntegerConsumer callback);
/**
@@ -2861,110 +2850,99 @@
* {@link SatelliteCallback.SatelliteProvisionStateListener#onSatelliteProvisionStateChanged}
* should report as deprovisioned.
*
- * @param subId The subId of the subscription to be deprovisioned.
* @param token The token of the device/subscription to be deprovisioned.
* @param callback The callback to get the result of the request.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void deprovisionSatelliteService(int subId, in String token, in IIntegerConsumer callback);
+ void deprovisionSatelliteService(in String token, in IIntegerConsumer callback);
/**
* Registers for provision state changed from satellite modem.
*
- * @param subId The subId of the subscription to register for provision state changed.
* @param callback The callback to handle the satellite provision state changed event.
*
* @return The {@link SatelliteError} result of the operation.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- int registerForSatelliteProvisionStateChanged(int subId,
- in ISatelliteProvisionStateCallback callback);
+ int registerForSatelliteProvisionStateChanged(in ISatelliteProvisionStateCallback callback);
/**
* Unregisters for provision state changed from satellite modem.
* If callback was not registered before, the request will be ignored.
*
- * @param subId The subId of the subscription to unregister for provision state changed.
* @param callback The callback that was passed to registerForSatelliteProvisionStateChanged.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void unregisterForSatelliteProvisionStateChanged(int subId,
+ void unregisterForSatelliteProvisionStateChanged(
in ISatelliteProvisionStateCallback callback);
/**
* Request to get whether the device is provisioned with a satellite provider.
*
- * @param subId The subId of the subscription to get whether the device is provisioned for.
* @param receiver Result receiver to get the error code of the request and whether the
* device is provisioned with a satellite provider.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void requestIsSatelliteProvisioned(int subId, in ResultReceiver receiver);
+ void requestIsSatelliteProvisioned(in ResultReceiver receiver);
/**
* Registers for modem state changed from satellite modem.
*
- * @param subId The subId of the subscription to register for satellite modem state changed.
* @param callback The callback to handle the satellite modem state changed event.
*
* @return The {@link SatelliteError} result of the operation.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- int registerForSatelliteModemStateChanged(int subId, ISatelliteModemStateCallback callback);
+ int registerForSatelliteModemStateChanged(ISatelliteModemStateCallback callback);
/**
* Unregisters for modem state changed from satellite modem.
* If callback was not registered before, the request will be ignored.
*
- * @param subId The subId of the subscription to unregister for satellite modem state changed.
* @param callback The callback that was passed to registerForSatelliteStateChanged.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void unregisterForModemStateChanged(int subId, ISatelliteModemStateCallback callback);
+ void unregisterForModemStateChanged(ISatelliteModemStateCallback callback);
/**
* Register to receive incoming datagrams over satellite.
*
- * @param subId The subId of the subscription to register for incoming satellite datagrams.
* @param callback The callback to handle the incoming datagrams.
*
* @return The {@link SatelliteError} result of the operation.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- int registerForIncomingDatagram(int subId, ISatelliteDatagramCallback callback);
+ int registerForIncomingDatagram(ISatelliteDatagramCallback callback);
/**
* Unregister to stop receiving incoming datagrams over satellite.
* If callback was not registered before, the request will be ignored.
*
- * @param subId The subId of the subscription to unregister for incoming satellite datagrams.
* @param callback The callback that was passed to registerForIncomingDatagram.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void unregisterForIncomingDatagram(int subId, ISatelliteDatagramCallback callback);
+ void unregisterForIncomingDatagram(ISatelliteDatagramCallback callback);
/**
* Poll pending satellite datagrams over satellite.
*
- * @param subId The subId of the subscription used for receiving datagrams.
* @param callback The callback to get the result of the request.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void pollPendingDatagrams(int subId, IIntegerConsumer callback);
+ void pollPendingDatagrams(IIntegerConsumer callback);
/**
* Send datagram over satellite.
*
- * @param subId The subId of the subscription to send satellite datagrams for.
* @param datagramType Type of datagram.
* @param datagram Datagram to send over satellite.
* @param needFullScreenPointingUI this is used to indicate pointingUI app to open in
@@ -2973,7 +2951,7 @@
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void sendDatagram(int subId, int datagramType, in SatelliteDatagram datagram,
+ void sendDatagram(int datagramType, in SatelliteDatagram datagram,
in boolean needFullScreenPointingUI, IIntegerConsumer callback);
/**
@@ -2991,13 +2969,12 @@
/**
* Request to get the time after which the satellite will be visible.
*
- * @param subId The subId to get the time after which the satellite will be visible for.
* @param receiver Result receiver to get the error code of the request and the requested
* time after which the satellite will be visible.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void requestTimeForNextSatelliteVisibility(int subId, in ResultReceiver receiver);
+ void requestTimeForNextSatelliteVisibility(in ResultReceiver receiver);
/**
* Inform whether the device is aligned with the satellite within in margin for demo mode.
@@ -3007,7 +2984,7 @@
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void setDeviceAlignedWithSatellite(int subId, boolean isAligned);
+ void setDeviceAlignedWithSatellite(boolean isAligned);
/**
* This API can be used by only CTS to update satellite vendor service package name.
@@ -3163,20 +3140,18 @@
/**
* Request to get the signal strength of the satellite connection.
*
- * @param subId The subId of the subscription to request for.
* @param receiver Result receiver to get the error code of the request and the current signal
* strength of the satellite connection.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void requestNtnSignalStrength(int subId, in ResultReceiver receiver);
+ void requestNtnSignalStrength(in ResultReceiver receiver);
/**
* Registers for NTN signal strength changed from satellite modem. If the registration operation
* is not successful, a {@link SatelliteException} that contains {@link SatelliteResult} will be
* thrown.
*
- * @param subId The subId of the subscription to request for.
* @param callback The callback to handle the NTN signal strength changed event. If the
* operation is successful, {@link NtnSignalStrengthCallback#onNtnSignalStrengthChanged(
* NtnSignalStrength)} will return an instance of {@link NtnSignalStrength} with a value of
@@ -3185,30 +3160,27 @@
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void registerForNtnSignalStrengthChanged(int subId,
- in INtnSignalStrengthCallback callback);
+ void registerForNtnSignalStrengthChanged(in INtnSignalStrengthCallback callback);
/**
* Unregisters for NTN signal strength changed from satellite modem.
* If callback was not registered before, the request will be ignored.
*
- * @param subId The subId of the subscription to unregister for provision state changed.
* @param callback The callback that was passed to
* {@link #registerForNtnSignalStrengthChanged(Executor, NtnSignalStrengthCallback)}.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void unregisterForNtnSignalStrengthChanged(int subId, in INtnSignalStrengthCallback callback);
+ void unregisterForNtnSignalStrengthChanged(in INtnSignalStrengthCallback callback);
/**
* Registers for satellite capabilities change event from the satellite service.
*
- * @param executor The executor on which the callback will be called.
* @param callback The callback to handle the satellite capabilities changed event.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- int registerForCapabilitiesChanged(int subId, in ISatelliteCapabilitiesCallback callback);
+ int registerForCapabilitiesChanged(in ISatelliteCapabilitiesCallback callback);
/**
* Unregisters for satellite capabilities change event from the satellite service.
@@ -3219,8 +3191,7 @@
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void unregisterForCapabilitiesChanged(int subId,
- in ISatelliteCapabilitiesCallback callback);
+ void unregisterForCapabilitiesChanged(in ISatelliteCapabilitiesCallback callback);
/**
* This API can be used by only CTS to override the cached value for the device overlay config
@@ -3329,26 +3300,24 @@
/**
* Registers for supported state changed from satellite modem.
*
- * @param subId The subId of the subscription to register for supported state changed.
* @param callback The callback to handle the satellite supported state changed event.
*
* @return The {@link SatelliteError} result of the operation.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- int registerForSatelliteSupportedStateChanged(int subId,
+ int registerForSatelliteSupportedStateChanged(
in ISatelliteSupportedStateCallback callback);
/**
* Unregisters for supported state changed from satellite modem.
* If callback was not registered before, the request will be ignored.
*
- * @param subId The subId of the subscription to unregister for supported state changed.
* @param callback The callback that was passed to registerForSatelliteSupportedStateChanged.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void unregisterForSatelliteSupportedStateChanged(int subId,
+ void unregisterForSatelliteSupportedStateChanged(
in ISatelliteSupportedStateCallback callback);
/**
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
index 379b45c..c3e1a1f 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
@@ -24,6 +24,7 @@
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
import android.tools.traces.parsers.toFlickerComponent
+import androidx.test.filters.FlakyTest
import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
import com.android.server.wm.flicker.testapp.ActivityOptions
@@ -177,6 +178,13 @@
@Ignore("Not applicable to this CUJ.")
override fun visibleLayersShownMoreThanOneConsecutiveEntry() {}
+ @FlakyTest(bugId = 342596801)
+ override fun entireScreenCovered() = super.entireScreenCovered()
+
+ @FlakyTest(bugId = 342596801)
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
companion object {
/** {@inheritDoc} */
private var startDisplayBounds = Rect()
diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
index adeba72..6e6a327 100644
--- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
+++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
@@ -196,7 +196,7 @@
super.appLayerBecomesVisible()
}
- @Presubmit
+ @FlakyTest(bugId = 338296297)
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
diff --git a/tests/Input/assets/testPointerFillStyle.png b/tests/Input/assets/testPointerFillStyle.png
index b2354f8..297244f 100644
--- a/tests/Input/assets/testPointerFillStyle.png
+++ b/tests/Input/assets/testPointerFillStyle.png
Binary files differ
diff --git a/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh b/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh
index d97dd7c..5c5421a 100755
--- a/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh
+++ b/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh
@@ -213,9 +213,9 @@
"
run_hoststubgen_for_failure "One specific class disallowed" \
- "TinyFrameworkClassAnnotations is not allowed to have Ravenwood annotations" \
+ "TinyFrameworkAnnotations is not allowed to have Ravenwood annotations" \
"
-!com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations
+!com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations
* # All other classes allowed
"
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
index 6cf2143..7dd4fdd 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
@@ -202,18 +202,6 @@
}
/**
- * Take a class name. If it's a nested class, then return the name of its direct outer class name.
- * Otherwise, return null.
- */
-fun getDirectOuterClassName(className: String): String? {
- val pos = className.lastIndexOf('$')
- if (pos < 0) {
- return null
- }
- return className.substring(0, pos)
-}
-
-/**
* Write bytecode to push all the method arguments to the stack.
* The number of arguments and their type are taken from [methodDescriptor].
*/
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt
index 47790b1..37048d9 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt
@@ -16,7 +16,6 @@
package com.android.hoststubgen.filters
import com.android.hoststubgen.asm.ClassNodes
-import com.android.hoststubgen.asm.getDirectOuterClassName
/**
* This is used as the second last fallback filter. This filter propagates the class-wide policy
@@ -24,72 +23,69 @@
*/
class ClassWidePolicyPropagatingFilter(
private val classes: ClassNodes,
- fallback: OutputFilter,
- ) : DelegatingFilter(fallback) {
+ fallback: OutputFilter
+) : DelegatingFilter(fallback) {
- private fun getClassWidePolicy(className: String, resolve: Boolean): FilterPolicyWithReason? {
+ /**
+ * We don't use ClassNode.outerClass, because it gives as the top-level
+ * outer class (A$B$C -> A), not the direct outer class (A$B$C -> A$B).
+ *
+ * Sometimes a class name includes `$`, but is not as a nested class name separator
+ * (e.g. a class name like `MyClass$$`). In this case, `MyClass$` is not actually a class.
+ *
+ * So before getting the class policy on a nonexistent class, which may cause an
+ * incorrect result, we make sure the class actually exists.
+ */
+ private fun getDirectOuterClass(className: String): String? {
var currentClass = className
-
-
- // If the class name is `a.b.c.A$B$C`, then we try to get the class wide policy
- // from a.b.c.A$B$C, then a.b.c.A$B, and then a.b.c.A.
while (true) {
- // Sometimes a class name has a `$` in it but not as a nest class name separator --
- // e.g. class name like "MyClass$$". In this case, `MyClass$` may not actually be
- // a class name.
- // So before getting the class policy on a nonexistent class, which may cause an
- // incorrect result, we make sure if className actually exists.
- if (classes.hasClass(className)) {
- outermostFilter.getPolicyForClass(className).let { policy ->
- if (policy.policy.isClassWidePolicy) {
- val p = if (resolve) {
- policy.policy.resolveClassWidePolicy()
- } else {
- policy.policy
- }
-
- return p.withReason(policy.reason)
- .wrapReason("class-wide in $currentClass")
- }
- // If the class's policy is remove, then remove it.
- if (policy.policy == FilterPolicy.Remove) {
- return FilterPolicy.Remove.withReason("class-wide in $currentClass")
- }
- }
- }
-
- // Next, look at the outer class...
- val outer = getDirectOuterClassName(currentClass)
- if (outer == null) {
+ val pos = currentClass.lastIndexOf('$')
+ if (pos < 0) {
return null
}
- currentClass = outer
+ currentClass = currentClass.substring(0, pos)
+ if (classes.hasClass(currentClass)) {
+ return currentClass
+ }
}
}
+ private fun getClassWidePolicy(className: String, resolve: Boolean): FilterPolicyWithReason? {
+ outermostFilter.getPolicyForClass(className).let { policy ->
+ if (policy.policy.isClassWidePolicy) {
+ val p = if (resolve) {
+ policy.policy.resolveClassWidePolicy()
+ } else {
+ policy.policy
+ }
+
+ return p.withReason(policy.reason)
+ .wrapReason("class-wide in $className")
+ }
+ // If the class's policy is remove, then remove it.
+ if (policy.policy == FilterPolicy.Remove) {
+ return FilterPolicy.Remove.withReason("class-wide in $className")
+ }
+ }
+ return null
+ }
+
override fun getPolicyForClass(className: String): FilterPolicyWithReason {
- // If it's a nested class, use the outer class's policy.
- getDirectOuterClassName(className)?.let { outerName ->
- getClassWidePolicy(outerName, resolve = false)?.let { policy ->
- return policy
- }
- }
-
- return super.getPolicyForClass(className)
+ // If the class name is `a.b.c.A$B$C`, then we try to get the class wide policy
+ // from a.b.c.A$B$C, then a.b.c.A$B, and then a.b.c.A, recursively
+ return getDirectOuterClass(className)?.let { getClassWidePolicy(it, resolve = false) }
+ ?: super.getPolicyForClass(className)
}
- override fun getPolicyForField(
- className: String,
- fieldName: String
- ): FilterPolicyWithReason {
+ override fun getPolicyForField(className: String, fieldName: String): FilterPolicyWithReason {
return getClassWidePolicy(className, resolve = true)
?: super.getPolicyForField(className, fieldName)
}
override fun getPolicyForMethod(
- className: String,
- methodName: String,
- descriptor: String
+ className: String,
+ methodName: String,
+ descriptor: String
): FilterPolicyWithReason {
return getClassWidePolicy(className, resolve = true)
?: super.getPolicyForMethod(className, methodName, descriptor)
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/annotation-allowed-classes-tiny-framework.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/annotation-allowed-classes-tiny-framework.txt
index bd9e85e..de4cb0c 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/annotation-allowed-classes-tiny-framework.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/annotation-allowed-classes-tiny-framework.txt
@@ -6,10 +6,10 @@
# To allow a specific class to use annotations:
-# com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations
+# com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations
# To disallow a specific class to use annotations:
-# !com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations
+# !com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations
# To allow a specific package to use annotations:
# com.android.hoststubgen.test.*
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
index c2f593c..845e1d0 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
@@ -394,6 +394,211 @@
com/android/hoststubgen/test/tinyframework/R$Nested
InnerClasses:
public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.class
+ Compiled from "TinyFrameworkAnnotations.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 3, methods: 10, attributes: 2
+ public int stub;
+ descriptor: I
+ flags: (0x0001) ACC_PUBLIC
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestStub
+
+ public int keep;
+ descriptor: I
+ flags: (0x0001) ACC_PUBLIC
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestKeep
+
+ public int remove;
+ descriptor: I
+ flags: (0x0001) ACC_PUBLIC
+
+ public com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=2, locals=1, args_size=1
+ x: aload_0
+ x: invokespecial #x // Method java/lang/Object."<init>":()V
+ x: aload_0
+ x: iconst_1
+ x: putfield #x // Field stub:I
+ x: aload_0
+ x: iconst_2
+ x: putfield #x // Field keep:I
+ x: return
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 15 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestStub
+
+ public int addOne(int);
+ descriptor: (I)I
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=2, locals=2, args_size=2
+ x: aload_0
+ x: iload_1
+ x: invokevirtual #x // Method addOneInner:(I)I
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 6 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
+ 0 6 1 value I
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestStub
+
+ public int addOneInner(int);
+ descriptor: (I)I
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=2, locals=2, args_size=2
+ x: iload_1
+ x: iconst_1
+ x: iadd
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
+ 0 4 1 value I
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestKeep
+
+ public void toBeRemoved(java.lang.String);
+ descriptor: (Ljava/lang/String;)V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=2, locals=2, args_size=2
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":()V
+ x: athrow
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 8 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
+ 0 8 1 foo Ljava/lang/String;
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestRemove
+
+ public int addTwo(int);
+ descriptor: (I)I
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=3, locals=2, args_size=2
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String not supported on host side
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 10 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
+ 0 10 1 value I
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestStub
+ x: #x(#x=s#x)
+ android.hosttest.annotation.HostSideTestSubstitute(
+ suffix="_host"
+ )
+
+ public int addTwo_host(int);
+ descriptor: (I)I
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=2, locals=2, args_size=2
+ x: iload_1
+ x: iconst_2
+ x: iadd
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
+ 0 4 1 value I
+
+ public static native int nativeAddThree(int);
+ descriptor: (I)I
+ flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestStub
+ x: #x(#x=s#x)
+ android.hosttest.annotation.HostSideTestSubstitute(
+ suffix="_host"
+ )
+
+ private static int nativeAddThree_host(int);
+ descriptor: (I)I
+ flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+ Code:
+ stack=2, locals=1, args_size=1
+ x: iload_0
+ x: iconst_3
+ x: iadd
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 4 0 value I
+
+ public java.lang.String unsupportedMethod();
+ descriptor: ()Ljava/lang/String;
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=1, locals=1, args_size=1
+ x: ldc #x // String This value shouldn\'t be seen on the host side.
+ x: areturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 3 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestThrow
+
+ public java.lang.String visibleButUsesUnsupportedMethod();
+ descriptor: ()Ljava/lang/String;
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=1, locals=1, args_size=1
+ x: aload_0
+ x: invokevirtual #x // Method unsupportedMethod:()Ljava/lang/String;
+ x: areturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestStub
+}
+SourceFile: "TinyFrameworkAnnotations.java"
+RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestStub
+ x: #x(#x=s#x)
+ android.hosttest.annotation.HostSideTestClassLoadHook(
+ value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
+ )
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
Compiled from "TinyFrameworkCallerCheck.java"
class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
@@ -492,388 +697,6 @@
com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
InnerClasses:
private static #x= #x of #x; // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.class
- Compiled from "TinyFrameworkClassAnnotations.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations
- minor version: 0
- major version: 61
- flags: (0x0021) ACC_PUBLIC, ACC_SUPER
- this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
- super_class: #x // java/lang/Object
- interfaces: 0, fields: 3, methods: 10, attributes: 2
- public int stub;
- descriptor: I
- flags: (0x0001) ACC_PUBLIC
- RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestStub
-
- public int keep;
- descriptor: I
- flags: (0x0001) ACC_PUBLIC
- RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestKeep
-
- public int remove;
- descriptor: I
- flags: (0x0001) ACC_PUBLIC
-
- public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations();
- descriptor: ()V
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=2, locals=1, args_size=1
- x: aload_0
- x: invokespecial #x // Method java/lang/Object."<init>":()V
- x: aload_0
- x: iconst_1
- x: putfield #x // Field stub:I
- x: aload_0
- x: iconst_2
- x: putfield #x // Field keep:I
- x: return
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 15 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
- RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestStub
-
- public int addOne(int);
- descriptor: (I)I
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=2, locals=2, args_size=2
- x: aload_0
- x: iload_1
- x: invokevirtual #x // Method addOneInner:(I)I
- x: ireturn
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 6 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
- 0 6 1 value I
- RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestStub
-
- public int addOneInner(int);
- descriptor: (I)I
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=2, locals=2, args_size=2
- x: iload_1
- x: iconst_1
- x: iadd
- x: ireturn
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
- 0 4 1 value I
- RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestKeep
-
- public void toBeRemoved(java.lang.String);
- descriptor: (Ljava/lang/String;)V
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=2, locals=2, args_size=2
- x: new #x // class java/lang/RuntimeException
- x: dup
- x: invokespecial #x // Method java/lang/RuntimeException."<init>":()V
- x: athrow
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 8 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
- 0 8 1 foo Ljava/lang/String;
- RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestRemove
-
- public int addTwo(int);
- descriptor: (I)I
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=3, locals=2, args_size=2
- x: new #x // class java/lang/RuntimeException
- x: dup
- x: ldc #x // String not supported on host side
- x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
- x: athrow
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 10 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
- 0 10 1 value I
- RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestStub
- x: #x(#x=s#x)
- android.hosttest.annotation.HostSideTestSubstitute(
- suffix="_host"
- )
-
- public int addTwo_host(int);
- descriptor: (I)I
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=2, locals=2, args_size=2
- x: iload_1
- x: iconst_2
- x: iadd
- x: ireturn
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
- 0 4 1 value I
-
- public static native int nativeAddThree(int);
- descriptor: (I)I
- flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
- RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestStub
- x: #x(#x=s#x)
- android.hosttest.annotation.HostSideTestSubstitute(
- suffix="_host"
- )
-
- private static int nativeAddThree_host(int);
- descriptor: (I)I
- flags: (0x000a) ACC_PRIVATE, ACC_STATIC
- Code:
- stack=2, locals=1, args_size=1
- x: iload_0
- x: iconst_3
- x: iadd
- x: ireturn
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 4 0 value I
-
- public java.lang.String unsupportedMethod();
- descriptor: ()Ljava/lang/String;
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=1, locals=1, args_size=1
- x: ldc #x // String This value shouldn\'t be seen on the host side.
- x: areturn
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 3 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
- RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestThrow
-
- public java.lang.String visibleButUsesUnsupportedMethod();
- descriptor: ()Ljava/lang/String;
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=1, locals=1, args_size=1
- x: aload_0
- x: invokevirtual #x // Method unsupportedMethod:()Ljava/lang/String;
- x: areturn
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
- RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestStub
-}
-SourceFile: "TinyFrameworkClassAnnotations.java"
-RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestStub
- x: #x(#x=s#x)
- android.hosttest.annotation.HostSideTestClassLoadHook(
- value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
- )
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations.class
- Compiled from "TinyFrameworkClassClassWideAnnotations.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassWideAnnotations
- minor version: 0
- major version: 61
- flags: (0x0021) ACC_PUBLIC, ACC_SUPER
- this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
- super_class: #x // java/lang/Object
- interfaces: 0, fields: 3, methods: 10, attributes: 2
- public int stub;
- descriptor: I
- flags: (0x0001) ACC_PUBLIC
-
- public int keep;
- descriptor: I
- flags: (0x0001) ACC_PUBLIC
-
- public int remove;
- descriptor: I
- flags: (0x0001) ACC_PUBLIC
-
- public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassWideAnnotations();
- descriptor: ()V
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=2, locals=1, args_size=1
- x: aload_0
- x: invokespecial #x // Method java/lang/Object."<init>":()V
- x: aload_0
- x: iconst_1
- x: putfield #x // Field stub:I
- x: aload_0
- x: iconst_2
- x: putfield #x // Field keep:I
- x: return
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 15 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
-
- public int addOne(int);
- descriptor: (I)I
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=2, locals=2, args_size=2
- x: aload_0
- x: iload_1
- x: invokevirtual #x // Method addOneInner:(I)I
- x: ireturn
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 6 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
- 0 6 1 value I
-
- public int addOneInner(int);
- descriptor: (I)I
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=2, locals=2, args_size=2
- x: iload_1
- x: iconst_1
- x: iadd
- x: ireturn
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
- 0 4 1 value I
-
- public void toBeRemoved(java.lang.String);
- descriptor: (Ljava/lang/String;)V
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=2, locals=2, args_size=2
- x: new #x // class java/lang/RuntimeException
- x: dup
- x: invokespecial #x // Method java/lang/RuntimeException."<init>":()V
- x: athrow
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 8 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
- 0 8 1 foo Ljava/lang/String;
-
- public int addTwo(int);
- descriptor: (I)I
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=3, locals=2, args_size=2
- x: new #x // class java/lang/RuntimeException
- x: dup
- x: ldc #x // String not supported on host side
- x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
- x: athrow
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 10 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
- 0 10 1 value I
- RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestStub
- x: #x(#x=s#x)
- android.hosttest.annotation.HostSideTestSubstitute(
- suffix="_host"
- )
-
- public int addTwo_host(int);
- descriptor: (I)I
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=2, locals=2, args_size=2
- x: iload_1
- x: iconst_2
- x: iadd
- x: ireturn
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
- 0 4 1 value I
-
- public static native int nativeAddThree(int);
- descriptor: (I)I
- flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
- RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestStub
- x: #x(#x=s#x)
- android.hosttest.annotation.HostSideTestSubstitute(
- suffix="_host"
- )
-
- public static int nativeAddThree_host(int);
- descriptor: (I)I
- flags: (0x0009) ACC_PUBLIC, ACC_STATIC
- Code:
- stack=2, locals=1, args_size=1
- x: iload_0
- x: iconst_3
- x: iadd
- x: ireturn
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 4 0 value I
-
- public java.lang.String unsupportedMethod();
- descriptor: ()Ljava/lang/String;
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=1, locals=1, args_size=1
- x: ldc #x // String This value shouldn\'t be seen on the host side.
- x: areturn
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 3 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
-
- public java.lang.String visibleButUsesUnsupportedMethod();
- descriptor: ()Ljava/lang/String;
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=1, locals=1, args_size=1
- x: aload_0
- x: invokevirtual #x // Method unsupportedMethod:()Ljava/lang/String;
- x: areturn
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
-}
-SourceFile: "TinyFrameworkClassClassWideAnnotations.java"
-RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestWholeClassStub
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.class
Compiled from "TinyFrameworkClassLoadHook.java"
public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook
@@ -936,6 +759,118 @@
RuntimeInvisibleAnnotations:
x: #x()
android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations.class
+ Compiled from "TinyFrameworkClassWideAnnotations.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWideAnnotations
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 1, methods: 6, attributes: 2
+ public int stub;
+ descriptor: I
+ flags: (0x0001) ACC_PUBLIC
+
+ public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWideAnnotations();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=2, locals=1, args_size=1
+ x: aload_0
+ x: invokespecial #x // Method java/lang/Object."<init>":()V
+ x: aload_0
+ x: iconst_1
+ x: putfield #x // Field stub:I
+ x: return
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 10 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+
+ public int addOne(int);
+ descriptor: (I)I
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=2, locals=2, args_size=2
+ x: iload_1
+ x: iconst_1
+ x: iadd
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+ 0 4 1 value I
+
+ public int addTwo(int);
+ descriptor: (I)I
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=3, locals=2, args_size=2
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String not supported on host side
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 10 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+ 0 10 1 value I
+ RuntimeInvisibleAnnotations:
+ x: #x(#x=s#x)
+ android.hosttest.annotation.HostSideTestSubstitute(
+ suffix="_host"
+ )
+
+ public int addTwo_host(int);
+ descriptor: (I)I
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=2, locals=2, args_size=2
+ x: iload_1
+ x: iconst_2
+ x: iadd
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+ 0 4 1 value I
+
+ public java.lang.String unsupportedMethod();
+ descriptor: ()Ljava/lang/String;
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=1, locals=1, args_size=1
+ x: ldc #x // String This value shouldn\'t be seen on the host side.
+ x: areturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 3 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestThrow
+
+ public java.lang.String visibleButUsesUnsupportedMethod();
+ descriptor: ()Ljava/lang/String;
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=1, locals=1, args_size=1
+ x: aload_0
+ x: invokevirtual #x // Method unsupportedMethod:()Ljava/lang/String;
+ x: areturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+}
+SourceFile: "TinyFrameworkClassWideAnnotations.java"
+RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestWholeClassStub
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault.class
Compiled from "TinyFrameworkClassWithInitializerDefault.java"
public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializerDefault
@@ -2684,7 +2619,7 @@
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
super_class: #x // java/lang/Object
- interfaces: 0, fields: 2, methods: 1, attributes: 4
+ interfaces: 0, fields: 2, methods: 1, attributes: 3
public int value;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -2717,9 +2652,6 @@
<no name> final mandated
}
SourceFile: "TinyFrameworkNestedClasses.java"
-RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestWholeClassStub
NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
InnerClasses:
public #x= #x of #x; // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
@@ -2778,6 +2710,40 @@
InnerClasses:
public static #x= #x of #x; // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
#x; // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass.class
+ Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 1, methods: 1, attributes: 3
+ public int value;
+ descriptor: I
+ flags: (0x0001) ACC_PUBLIC
+
+ public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=2, locals=1, args_size=1
+ x: aload_0
+ x: invokespecial #x // Method java/lang/Object."<init>":()V
+ x: aload_0
+ x: bipush 8
+ x: putfield #x // Field value:I
+ x: return
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 11 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass;
+}
+SourceFile: "TinyFrameworkNestedClasses.java"
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+InnerClasses:
+ public static #x= #x of #x; // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+ public static #x= #x of #x; // Double$NestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass.class
Compiled from "TinyFrameworkNestedClasses.java"
public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass
@@ -2786,7 +2752,7 @@
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
super_class: #x // java/lang/Object
- interfaces: 0, fields: 1, methods: 2, attributes: 4
+ interfaces: 0, fields: 1, methods: 2, attributes: 3
public int value;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -2820,13 +2786,11 @@
Signature: #x // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
}
SourceFile: "TinyFrameworkNestedClasses.java"
-RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestWholeClassStub
NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
InnerClasses:
public static #x= #x of #x; // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
#x; // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+ public static #x= #x of #x; // Double$NestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass.class
Compiled from "TinyFrameworkNestedClasses.java"
public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$SubClass extends com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass
@@ -2942,6 +2906,7 @@
com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+ com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
@@ -2957,6 +2922,7 @@
public static #x= #x of #x; // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
public static #x= #x of #x; // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
public #x= #x of #x; // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+ public static #x= #x of #x; // Double$NestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
#x; // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkPackageRedirect.class
Compiled from "TinyFrameworkPackageRedirect.java"
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
index 1b83d24..86a9c65 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
@@ -216,6 +216,129 @@
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
NestMembers:
com/android/hoststubgen/test/tinyframework/R$Nested
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.class
+ Compiled from "TinyFrameworkAnnotations.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 1, methods: 5, attributes: 3
+ public int stub;
+ descriptor: I
+ flags: (0x0001) ACC_PUBLIC
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestStub
+
+ public com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=3, locals=1, args_size=1
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Stub!
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestStub
+
+ public int addOne(int);
+ descriptor: (I)I
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=3, locals=2, args_size=2
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Stub!
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestStub
+
+ public int addTwo(int);
+ descriptor: (I)I
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=3, locals=2, args_size=2
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Stub!
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public static int nativeAddThree(int);
+ descriptor: (I)I
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=3, locals=1, args_size=1
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Stub!
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public java.lang.String visibleButUsesUnsupportedMethod();
+ descriptor: ()Ljava/lang/String;
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=3, locals=1, args_size=1
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Stub!
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestStub
+}
+SourceFile: "TinyFrameworkAnnotations.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestStub
+ x: #x(#x=s#x)
+ android.hosttest.annotation.HostSideTestClassLoadHook(
+ value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
+ )
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
Compiled from "TinyFrameworkCallerCheck.java"
class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
@@ -339,302 +462,6 @@
android.hosttest.annotation.HostSideTestWholeClassStub
NestMembers:
com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.class
- Compiled from "TinyFrameworkClassAnnotations.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations
- minor version: 0
- major version: 61
- flags: (0x0021) ACC_PUBLIC, ACC_SUPER
- this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
- super_class: #x // java/lang/Object
- interfaces: 0, fields: 1, methods: 5, attributes: 3
- public int stub;
- descriptor: I
- flags: (0x0001) ACC_PUBLIC
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
- RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestStub
-
- public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations();
- descriptor: ()V
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=3, locals=1, args_size=1
- x: new #x // class java/lang/RuntimeException
- x: dup
- x: ldc #x // String Stub!
- x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
- x: athrow
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
- RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestStub
-
- public int addOne(int);
- descriptor: (I)I
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=3, locals=2, args_size=2
- x: new #x // class java/lang/RuntimeException
- x: dup
- x: ldc #x // String Stub!
- x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
- x: athrow
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
- RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestStub
-
- public int addTwo(int);
- descriptor: (I)I
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=3, locals=2, args_size=2
- x: new #x // class java/lang/RuntimeException
- x: dup
- x: ldc #x // String Stub!
- x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
- x: athrow
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public static int nativeAddThree(int);
- descriptor: (I)I
- flags: (0x0009) ACC_PUBLIC, ACC_STATIC
- Code:
- stack=3, locals=1, args_size=1
- x: new #x // class java/lang/RuntimeException
- x: dup
- x: ldc #x // String Stub!
- x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
- x: athrow
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public java.lang.String visibleButUsesUnsupportedMethod();
- descriptor: ()Ljava/lang/String;
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=3, locals=1, args_size=1
- x: new #x // class java/lang/RuntimeException
- x: dup
- x: ldc #x // String Stub!
- x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
- x: athrow
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
- RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestStub
-}
-SourceFile: "TinyFrameworkClassAnnotations.java"
-RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestStub
- x: #x(#x=s#x)
- android.hosttest.annotation.HostSideTestClassLoadHook(
- value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
- )
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations.class
- Compiled from "TinyFrameworkClassClassWideAnnotations.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassWideAnnotations
- minor version: 0
- major version: 61
- flags: (0x0021) ACC_PUBLIC, ACC_SUPER
- this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
- super_class: #x // java/lang/Object
- interfaces: 0, fields: 3, methods: 8, attributes: 3
- public int stub;
- descriptor: I
- flags: (0x0001) ACC_PUBLIC
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public int keep;
- descriptor: I
- flags: (0x0001) ACC_PUBLIC
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public int remove;
- descriptor: I
- flags: (0x0001) ACC_PUBLIC
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassWideAnnotations();
- descriptor: ()V
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=3, locals=1, args_size=1
- x: new #x // class java/lang/RuntimeException
- x: dup
- x: ldc #x // String Stub!
- x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
- x: athrow
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public int addOne(int);
- descriptor: (I)I
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=3, locals=2, args_size=2
- x: new #x // class java/lang/RuntimeException
- x: dup
- x: ldc #x // String Stub!
- x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
- x: athrow
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public int addOneInner(int);
- descriptor: (I)I
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=3, locals=2, args_size=2
- x: new #x // class java/lang/RuntimeException
- x: dup
- x: ldc #x // String Stub!
- x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
- x: athrow
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public void toBeRemoved(java.lang.String);
- descriptor: (Ljava/lang/String;)V
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=3, locals=2, args_size=2
- x: new #x // class java/lang/RuntimeException
- x: dup
- x: ldc #x // String Stub!
- x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
- x: athrow
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public int addTwo(int);
- descriptor: (I)I
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=3, locals=2, args_size=2
- x: new #x // class java/lang/RuntimeException
- x: dup
- x: ldc #x // String Stub!
- x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
- x: athrow
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public static int nativeAddThree(int);
- descriptor: (I)I
- flags: (0x0009) ACC_PUBLIC, ACC_STATIC
- Code:
- stack=3, locals=1, args_size=1
- x: new #x // class java/lang/RuntimeException
- x: dup
- x: ldc #x // String Stub!
- x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
- x: athrow
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public java.lang.String unsupportedMethod();
- descriptor: ()Ljava/lang/String;
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=3, locals=1, args_size=1
- x: new #x // class java/lang/RuntimeException
- x: dup
- x: ldc #x // String Stub!
- x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
- x: athrow
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public java.lang.String visibleButUsesUnsupportedMethod();
- descriptor: ()Ljava/lang/String;
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=3, locals=1, args_size=1
- x: new #x // class java/lang/RuntimeException
- x: dup
- x: ldc #x // String Stub!
- x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
- x: athrow
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-SourceFile: "TinyFrameworkClassClassWideAnnotations.java"
-RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestWholeClassStub
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.class
Compiled from "TinyFrameworkClassLoadHook.java"
public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook
@@ -712,6 +539,97 @@
RuntimeInvisibleAnnotations:
x: #x()
android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations.class
+ Compiled from "TinyFrameworkClassWideAnnotations.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWideAnnotations
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 1, methods: 4, attributes: 3
+ public int stub;
+ descriptor: I
+ flags: (0x0001) ACC_PUBLIC
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWideAnnotations();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=3, locals=1, args_size=1
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Stub!
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public int addOne(int);
+ descriptor: (I)I
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=3, locals=2, args_size=2
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Stub!
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public int addTwo(int);
+ descriptor: (I)I
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=3, locals=2, args_size=2
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Stub!
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public java.lang.String visibleButUsesUnsupportedMethod();
+ descriptor: ()Ljava/lang/String;
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=3, locals=1, args_size=1
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Stub!
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+SourceFile: "TinyFrameworkClassWideAnnotations.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestWholeClassStub
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault.class
Compiled from "TinyFrameworkClassWithInitializerDefault.java"
public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializerDefault
@@ -2153,7 +2071,7 @@
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
super_class: #x // java/lang/Object
- interfaces: 0, fields: 2, methods: 1, attributes: 5
+ interfaces: 0, fields: 2, methods: 1, attributes: 4
public int value;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -2199,9 +2117,50 @@
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass.class
+ Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 1, methods: 1, attributes: 4
+ public int value;
+ descriptor: I
+ flags: (0x0001) ACC_PUBLIC
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=3, locals=1, args_size=1
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Stub!
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+InnerClasses:
+ public static #x= #x of #x; // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+ public static #x= #x of #x; // Double$NestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
x: #x()
- android.hosttest.annotation.HostSideTestWholeClassStub
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass.class
Compiled from "TinyFrameworkNestedClasses.java"
@@ -2211,7 +2170,7 @@
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
super_class: #x // java/lang/Object
- interfaces: 0, fields: 1, methods: 2, attributes: 5
+ interfaces: 0, fields: 1, methods: 2, attributes: 4
public int value;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -2257,15 +2216,13 @@
InnerClasses:
public static #x= #x of #x; // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
#x; // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+ public static #x= #x of #x; // Double$NestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
SourceFile: "TinyFrameworkNestedClasses.java"
RuntimeVisibleAnnotations:
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestWholeClassStub
NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass.class
Compiled from "TinyFrameworkNestedClasses.java"
@@ -2406,6 +2363,7 @@
public static #x= #x of #x; // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
public static #x= #x of #x; // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
public #x= #x of #x; // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+ public static #x= #x of #x; // Double$NestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
#x; // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
SourceFile: "TinyFrameworkNestedClasses.java"
RuntimeVisibleAnnotations:
@@ -2420,6 +2378,7 @@
com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+ com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
index d23b450..c6b9c7a 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
@@ -450,6 +450,227 @@
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
NestMembers:
com/android/hoststubgen/test/tinyframework/R$Nested
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.class
+ Compiled from "TinyFrameworkAnnotations.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 2, methods: 8, attributes: 3
+ public int stub;
+ descriptor: I
+ flags: (0x0001) ACC_PUBLIC
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestStub
+
+ public int keep;
+ descriptor: I
+ flags: (0x0001) ACC_PUBLIC
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestKeep
+
+ private static {};
+ descriptor: ()V
+ flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+ Code:
+ stack=2, locals=0, args_size=0
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
+ x: ldc #x // String com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+ x: return
+
+ public com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=2, locals=1, args_size=1
+ x: aload_0
+ x: invokespecial #x // Method java/lang/Object."<init>":()V
+ x: aload_0
+ x: iconst_1
+ x: putfield #x // Field stub:I
+ x: aload_0
+ x: iconst_2
+ x: putfield #x // Field keep:I
+ x: return
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 15 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestStub
+
+ public int addOne(int);
+ descriptor: (I)I
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=2, locals=2, args_size=2
+ x: aload_0
+ x: iload_1
+ x: invokevirtual #x // Method addOneInner:(I)I
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 6 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
+ 0 6 1 value I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestStub
+
+ public int addOneInner(int);
+ descriptor: (I)I
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=4, locals=2, args_size=2
+ x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
+ x: ldc #x // String addOneInner
+ x: ldc #x // String (I)I
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+ x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+ x: iload_1
+ x: iconst_1
+ x: iadd
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 15 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
+ 15 4 1 value I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestKeep
+
+ public int addTwo(int);
+ descriptor: (I)I
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=2, locals=2, args_size=2
+ x: iload_1
+ x: iconst_2
+ x: iadd
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
+ 0 4 1 value I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public static int nativeAddThree(int);
+ descriptor: (I)I
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=2, locals=1, args_size=1
+ x: iload_0
+ x: iconst_3
+ x: iadd
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 4 0 value I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public java.lang.String unsupportedMethod();
+ descriptor: ()Ljava/lang/String;
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=4, locals=1, args_size=1
+ x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
+ x: ldc #x // String unsupportedMethod
+ x: ldc #x // String ()Ljava/lang/String;
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+ x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Unreachable
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsThrow
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestThrow
+
+ public java.lang.String visibleButUsesUnsupportedMethod();
+ descriptor: ()Ljava/lang/String;
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=1, locals=1, args_size=1
+ x: aload_0
+ x: invokevirtual #x // Method unsupportedMethod:()Ljava/lang/String;
+ x: areturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestStub
+}
+SourceFile: "TinyFrameworkAnnotations.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestStub
+ x: #x(#x=s#x)
+ android.hosttest.annotation.HostSideTestClassLoadHook(
+ value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
+ )
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
Compiled from "TinyFrameworkCallerCheck.java"
class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
@@ -592,434 +813,6 @@
android.hosttest.annotation.HostSideTestWholeClassStub
NestMembers:
com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.class
- Compiled from "TinyFrameworkClassAnnotations.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations
- minor version: 0
- major version: 61
- flags: (0x0021) ACC_PUBLIC, ACC_SUPER
- this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
- super_class: #x // java/lang/Object
- interfaces: 0, fields: 2, methods: 8, attributes: 3
- public int stub;
- descriptor: I
- flags: (0x0001) ACC_PUBLIC
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
- RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestStub
-
- public int keep;
- descriptor: I
- flags: (0x0001) ACC_PUBLIC
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
- RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestKeep
-
- private static {};
- descriptor: ()V
- flags: (0x000a) ACC_PRIVATE, ACC_STATIC
- Code:
- stack=2, locals=0, args_size=0
- x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
- x: ldc #x // String com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
- x: return
-
- public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations();
- descriptor: ()V
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=2, locals=1, args_size=1
- x: aload_0
- x: invokespecial #x // Method java/lang/Object."<init>":()V
- x: aload_0
- x: iconst_1
- x: putfield #x // Field stub:I
- x: aload_0
- x: iconst_2
- x: putfield #x // Field keep:I
- x: return
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 15 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
- RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestStub
-
- public int addOne(int);
- descriptor: (I)I
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=2, locals=2, args_size=2
- x: aload_0
- x: iload_1
- x: invokevirtual #x // Method addOneInner:(I)I
- x: ireturn
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 6 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
- 0 6 1 value I
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
- RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestStub
-
- public int addOneInner(int);
- descriptor: (I)I
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=4, locals=2, args_size=2
- x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
- x: ldc #x // String addOneInner
- x: ldc #x // String (I)I
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
- x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
- x: iload_1
- x: iconst_1
- x: iadd
- x: ireturn
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 15 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
- 15 4 1 value I
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
- RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestKeep
-
- public int addTwo(int);
- descriptor: (I)I
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=2, locals=2, args_size=2
- x: iload_1
- x: iconst_2
- x: iadd
- x: ireturn
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
- 0 4 1 value I
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public static int nativeAddThree(int);
- descriptor: (I)I
- flags: (0x0009) ACC_PUBLIC, ACC_STATIC
- Code:
- stack=2, locals=1, args_size=1
- x: iload_0
- x: iconst_3
- x: iadd
- x: ireturn
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 4 0 value I
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public java.lang.String unsupportedMethod();
- descriptor: ()Ljava/lang/String;
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=4, locals=1, args_size=1
- x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
- x: ldc #x // String unsupportedMethod
- x: ldc #x // String ()Ljava/lang/String;
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
- x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
- x: new #x // class java/lang/RuntimeException
- x: dup
- x: ldc #x // String Unreachable
- x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
- x: athrow
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenProcessedAsThrow
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
- RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestThrow
-
- public java.lang.String visibleButUsesUnsupportedMethod();
- descriptor: ()Ljava/lang/String;
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=1, locals=1, args_size=1
- x: aload_0
- x: invokevirtual #x // Method unsupportedMethod:()Ljava/lang/String;
- x: areturn
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
- RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestStub
-}
-SourceFile: "TinyFrameworkClassAnnotations.java"
-RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestStub
- x: #x(#x=s#x)
- android.hosttest.annotation.HostSideTestClassLoadHook(
- value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
- )
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations.class
- Compiled from "TinyFrameworkClassClassWideAnnotations.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassWideAnnotations
- minor version: 0
- major version: 61
- flags: (0x0021) ACC_PUBLIC, ACC_SUPER
- this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
- super_class: #x // java/lang/Object
- interfaces: 0, fields: 3, methods: 8, attributes: 3
- public int stub;
- descriptor: I
- flags: (0x0001) ACC_PUBLIC
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public int keep;
- descriptor: I
- flags: (0x0001) ACC_PUBLIC
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public int remove;
- descriptor: I
- flags: (0x0001) ACC_PUBLIC
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassWideAnnotations();
- descriptor: ()V
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=2, locals=1, args_size=1
- x: aload_0
- x: invokespecial #x // Method java/lang/Object."<init>":()V
- x: aload_0
- x: iconst_1
- x: putfield #x // Field stub:I
- x: aload_0
- x: iconst_2
- x: putfield #x // Field keep:I
- x: return
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 15 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public int addOne(int);
- descriptor: (I)I
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=2, locals=2, args_size=2
- x: aload_0
- x: iload_1
- x: invokevirtual #x // Method addOneInner:(I)I
- x: ireturn
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 6 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
- 0 6 1 value I
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public int addOneInner(int);
- descriptor: (I)I
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=2, locals=2, args_size=2
- x: iload_1
- x: iconst_1
- x: iadd
- x: ireturn
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
- 0 4 1 value I
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public void toBeRemoved(java.lang.String);
- descriptor: (Ljava/lang/String;)V
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=2, locals=2, args_size=2
- x: new #x // class java/lang/RuntimeException
- x: dup
- x: invokespecial #x // Method java/lang/RuntimeException."<init>":()V
- x: athrow
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 8 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
- 0 8 1 foo Ljava/lang/String;
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public int addTwo(int);
- descriptor: (I)I
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=2, locals=2, args_size=2
- x: iload_1
- x: iconst_2
- x: iadd
- x: ireturn
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
- 0 4 1 value I
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public static int nativeAddThree(int);
- descriptor: (I)I
- flags: (0x0009) ACC_PUBLIC, ACC_STATIC
- Code:
- stack=2, locals=1, args_size=1
- x: iload_0
- x: iconst_3
- x: iadd
- x: ireturn
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 4 0 value I
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public java.lang.String unsupportedMethod();
- descriptor: ()Ljava/lang/String;
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=1, locals=1, args_size=1
- x: ldc #x // String This value shouldn\'t be seen on the host side.
- x: areturn
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 3 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public java.lang.String visibleButUsesUnsupportedMethod();
- descriptor: ()Ljava/lang/String;
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=1, locals=1, args_size=1
- x: aload_0
- x: invokevirtual #x // Method unsupportedMethod:()Ljava/lang/String;
- x: areturn
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-SourceFile: "TinyFrameworkClassClassWideAnnotations.java"
-RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestWholeClassStub
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.class
Compiled from "TinyFrameworkClassLoadHook.java"
public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook
@@ -1107,6 +900,140 @@
RuntimeInvisibleAnnotations:
x: #x()
android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations.class
+ Compiled from "TinyFrameworkClassWideAnnotations.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWideAnnotations
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 1, methods: 5, attributes: 3
+ public int stub;
+ descriptor: I
+ flags: (0x0001) ACC_PUBLIC
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWideAnnotations();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=2, locals=1, args_size=1
+ x: aload_0
+ x: invokespecial #x // Method java/lang/Object."<init>":()V
+ x: aload_0
+ x: iconst_1
+ x: putfield #x // Field stub:I
+ x: return
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 10 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public int addOne(int);
+ descriptor: (I)I
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=2, locals=2, args_size=2
+ x: iload_1
+ x: iconst_1
+ x: iadd
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+ 0 4 1 value I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public int addTwo(int);
+ descriptor: (I)I
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=2, locals=2, args_size=2
+ x: iload_1
+ x: iconst_2
+ x: iadd
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+ 0 4 1 value I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public java.lang.String unsupportedMethod();
+ descriptor: ()Ljava/lang/String;
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=4, locals=1, args_size=1
+ x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
+ x: ldc #x // String unsupportedMethod
+ x: ldc #x // String ()Ljava/lang/String;
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+ x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Unreachable
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsThrow
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestThrow
+
+ public java.lang.String visibleButUsesUnsupportedMethod();
+ descriptor: ()Ljava/lang/String;
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=1, locals=1, args_size=1
+ x: aload_0
+ x: invokevirtual #x // Method unsupportedMethod:()Ljava/lang/String;
+ x: areturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+SourceFile: "TinyFrameworkClassWideAnnotations.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestWholeClassStub
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault.class
Compiled from "TinyFrameworkClassWithInitializerDefault.java"
public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializerDefault
@@ -3430,7 +3357,7 @@
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
super_class: #x // java/lang/Object
- interfaces: 0, fields: 2, methods: 1, attributes: 5
+ interfaces: 0, fields: 2, methods: 1, attributes: 4
public int value;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -3485,9 +3412,6 @@
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestWholeClassStub
NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1.class
Compiled from "TinyFrameworkNestedClasses.java"
@@ -3568,6 +3492,55 @@
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass.class
+ Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 1, methods: 1, attributes: 4
+ public int value;
+ descriptor: I
+ flags: (0x0001) ACC_PUBLIC
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=2, locals=1, args_size=1
+ x: aload_0
+ x: invokespecial #x // Method java/lang/Object."<init>":()V
+ x: aload_0
+ x: bipush 8
+ x: putfield #x // Field value:I
+ x: return
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 11 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass;
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+InnerClasses:
+ public static #x= #x of #x; // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+ public static #x= #x of #x; // Double$NestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass.class
Compiled from "TinyFrameworkNestedClasses.java"
public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass
@@ -3576,7 +3549,7 @@
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
super_class: #x // java/lang/Object
- interfaces: 0, fields: 1, methods: 2, attributes: 5
+ interfaces: 0, fields: 1, methods: 2, attributes: 4
public int value;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -3627,15 +3600,13 @@
InnerClasses:
public static #x= #x of #x; // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
#x; // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+ public static #x= #x of #x; // Double$NestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
SourceFile: "TinyFrameworkNestedClasses.java"
RuntimeVisibleAnnotations:
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestWholeClassStub
NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass.class
Compiled from "TinyFrameworkNestedClasses.java"
@@ -3793,6 +3764,7 @@
public static #x= #x of #x; // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
public static #x= #x of #x; // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
public #x= #x of #x; // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+ public static #x= #x of #x; // Double$NestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
#x; // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
SourceFile: "TinyFrameworkNestedClasses.java"
RuntimeVisibleAnnotations:
@@ -3807,6 +3779,7 @@
com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+ com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
index 1b83d24..86a9c65 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
@@ -216,6 +216,129 @@
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
NestMembers:
com/android/hoststubgen/test/tinyframework/R$Nested
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.class
+ Compiled from "TinyFrameworkAnnotations.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 1, methods: 5, attributes: 3
+ public int stub;
+ descriptor: I
+ flags: (0x0001) ACC_PUBLIC
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestStub
+
+ public com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=3, locals=1, args_size=1
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Stub!
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestStub
+
+ public int addOne(int);
+ descriptor: (I)I
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=3, locals=2, args_size=2
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Stub!
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestStub
+
+ public int addTwo(int);
+ descriptor: (I)I
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=3, locals=2, args_size=2
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Stub!
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public static int nativeAddThree(int);
+ descriptor: (I)I
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=3, locals=1, args_size=1
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Stub!
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public java.lang.String visibleButUsesUnsupportedMethod();
+ descriptor: ()Ljava/lang/String;
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=3, locals=1, args_size=1
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Stub!
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestStub
+}
+SourceFile: "TinyFrameworkAnnotations.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestStub
+ x: #x(#x=s#x)
+ android.hosttest.annotation.HostSideTestClassLoadHook(
+ value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
+ )
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
Compiled from "TinyFrameworkCallerCheck.java"
class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
@@ -339,302 +462,6 @@
android.hosttest.annotation.HostSideTestWholeClassStub
NestMembers:
com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.class
- Compiled from "TinyFrameworkClassAnnotations.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations
- minor version: 0
- major version: 61
- flags: (0x0021) ACC_PUBLIC, ACC_SUPER
- this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
- super_class: #x // java/lang/Object
- interfaces: 0, fields: 1, methods: 5, attributes: 3
- public int stub;
- descriptor: I
- flags: (0x0001) ACC_PUBLIC
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
- RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestStub
-
- public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations();
- descriptor: ()V
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=3, locals=1, args_size=1
- x: new #x // class java/lang/RuntimeException
- x: dup
- x: ldc #x // String Stub!
- x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
- x: athrow
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
- RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestStub
-
- public int addOne(int);
- descriptor: (I)I
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=3, locals=2, args_size=2
- x: new #x // class java/lang/RuntimeException
- x: dup
- x: ldc #x // String Stub!
- x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
- x: athrow
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
- RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestStub
-
- public int addTwo(int);
- descriptor: (I)I
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=3, locals=2, args_size=2
- x: new #x // class java/lang/RuntimeException
- x: dup
- x: ldc #x // String Stub!
- x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
- x: athrow
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public static int nativeAddThree(int);
- descriptor: (I)I
- flags: (0x0009) ACC_PUBLIC, ACC_STATIC
- Code:
- stack=3, locals=1, args_size=1
- x: new #x // class java/lang/RuntimeException
- x: dup
- x: ldc #x // String Stub!
- x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
- x: athrow
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public java.lang.String visibleButUsesUnsupportedMethod();
- descriptor: ()Ljava/lang/String;
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=3, locals=1, args_size=1
- x: new #x // class java/lang/RuntimeException
- x: dup
- x: ldc #x // String Stub!
- x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
- x: athrow
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
- RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestStub
-}
-SourceFile: "TinyFrameworkClassAnnotations.java"
-RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestStub
- x: #x(#x=s#x)
- android.hosttest.annotation.HostSideTestClassLoadHook(
- value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
- )
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations.class
- Compiled from "TinyFrameworkClassClassWideAnnotations.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassWideAnnotations
- minor version: 0
- major version: 61
- flags: (0x0021) ACC_PUBLIC, ACC_SUPER
- this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
- super_class: #x // java/lang/Object
- interfaces: 0, fields: 3, methods: 8, attributes: 3
- public int stub;
- descriptor: I
- flags: (0x0001) ACC_PUBLIC
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public int keep;
- descriptor: I
- flags: (0x0001) ACC_PUBLIC
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public int remove;
- descriptor: I
- flags: (0x0001) ACC_PUBLIC
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassWideAnnotations();
- descriptor: ()V
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=3, locals=1, args_size=1
- x: new #x // class java/lang/RuntimeException
- x: dup
- x: ldc #x // String Stub!
- x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
- x: athrow
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public int addOne(int);
- descriptor: (I)I
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=3, locals=2, args_size=2
- x: new #x // class java/lang/RuntimeException
- x: dup
- x: ldc #x // String Stub!
- x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
- x: athrow
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public int addOneInner(int);
- descriptor: (I)I
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=3, locals=2, args_size=2
- x: new #x // class java/lang/RuntimeException
- x: dup
- x: ldc #x // String Stub!
- x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
- x: athrow
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public void toBeRemoved(java.lang.String);
- descriptor: (Ljava/lang/String;)V
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=3, locals=2, args_size=2
- x: new #x // class java/lang/RuntimeException
- x: dup
- x: ldc #x // String Stub!
- x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
- x: athrow
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public int addTwo(int);
- descriptor: (I)I
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=3, locals=2, args_size=2
- x: new #x // class java/lang/RuntimeException
- x: dup
- x: ldc #x // String Stub!
- x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
- x: athrow
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public static int nativeAddThree(int);
- descriptor: (I)I
- flags: (0x0009) ACC_PUBLIC, ACC_STATIC
- Code:
- stack=3, locals=1, args_size=1
- x: new #x // class java/lang/RuntimeException
- x: dup
- x: ldc #x // String Stub!
- x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
- x: athrow
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public java.lang.String unsupportedMethod();
- descriptor: ()Ljava/lang/String;
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=3, locals=1, args_size=1
- x: new #x // class java/lang/RuntimeException
- x: dup
- x: ldc #x // String Stub!
- x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
- x: athrow
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public java.lang.String visibleButUsesUnsupportedMethod();
- descriptor: ()Ljava/lang/String;
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=3, locals=1, args_size=1
- x: new #x // class java/lang/RuntimeException
- x: dup
- x: ldc #x // String Stub!
- x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
- x: athrow
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-SourceFile: "TinyFrameworkClassClassWideAnnotations.java"
-RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestWholeClassStub
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.class
Compiled from "TinyFrameworkClassLoadHook.java"
public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook
@@ -712,6 +539,97 @@
RuntimeInvisibleAnnotations:
x: #x()
android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations.class
+ Compiled from "TinyFrameworkClassWideAnnotations.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWideAnnotations
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 1, methods: 4, attributes: 3
+ public int stub;
+ descriptor: I
+ flags: (0x0001) ACC_PUBLIC
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWideAnnotations();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=3, locals=1, args_size=1
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Stub!
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public int addOne(int);
+ descriptor: (I)I
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=3, locals=2, args_size=2
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Stub!
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public int addTwo(int);
+ descriptor: (I)I
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=3, locals=2, args_size=2
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Stub!
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public java.lang.String visibleButUsesUnsupportedMethod();
+ descriptor: ()Ljava/lang/String;
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=3, locals=1, args_size=1
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Stub!
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+SourceFile: "TinyFrameworkClassWideAnnotations.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestWholeClassStub
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault.class
Compiled from "TinyFrameworkClassWithInitializerDefault.java"
public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializerDefault
@@ -2153,7 +2071,7 @@
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
super_class: #x // java/lang/Object
- interfaces: 0, fields: 2, methods: 1, attributes: 5
+ interfaces: 0, fields: 2, methods: 1, attributes: 4
public int value;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -2199,9 +2117,50 @@
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass.class
+ Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 1, methods: 1, attributes: 4
+ public int value;
+ descriptor: I
+ flags: (0x0001) ACC_PUBLIC
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=3, locals=1, args_size=1
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Stub!
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+InnerClasses:
+ public static #x= #x of #x; // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+ public static #x= #x of #x; // Double$NestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
x: #x()
- android.hosttest.annotation.HostSideTestWholeClassStub
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass.class
Compiled from "TinyFrameworkNestedClasses.java"
@@ -2211,7 +2170,7 @@
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
super_class: #x // java/lang/Object
- interfaces: 0, fields: 1, methods: 2, attributes: 5
+ interfaces: 0, fields: 1, methods: 2, attributes: 4
public int value;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -2257,15 +2216,13 @@
InnerClasses:
public static #x= #x of #x; // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
#x; // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+ public static #x= #x of #x; // Double$NestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
SourceFile: "TinyFrameworkNestedClasses.java"
RuntimeVisibleAnnotations:
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestWholeClassStub
NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass.class
Compiled from "TinyFrameworkNestedClasses.java"
@@ -2406,6 +2363,7 @@
public static #x= #x of #x; // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
public static #x= #x of #x; // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
public #x= #x of #x; // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+ public static #x= #x of #x; // Double$NestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
#x; // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
SourceFile: "TinyFrameworkNestedClasses.java"
RuntimeVisibleAnnotations:
@@ -2420,6 +2378,7 @@
com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+ com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
index d12a23d..da434a6 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
@@ -611,6 +611,265 @@
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
NestMembers:
com/android/hoststubgen/test/tinyframework/R$Nested
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.class
+ Compiled from "TinyFrameworkAnnotations.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 2, methods: 8, attributes: 3
+ public int stub;
+ descriptor: I
+ flags: (0x0001) ACC_PUBLIC
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestStub
+
+ public int keep;
+ descriptor: I
+ flags: (0x0001) ACC_PUBLIC
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestKeep
+
+ private static {};
+ descriptor: ()V
+ flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+ Code:
+ stack=2, locals=0, args_size=0
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
+ x: ldc #x // String com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+ x: return
+
+ public com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=4, locals=1, args_size=1
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
+ x: ldc #x // String <init>
+ x: ldc #x // String ()V
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: aload_0
+ x: invokespecial #x // Method java/lang/Object."<init>":()V
+ x: aload_0
+ x: iconst_1
+ x: putfield #x // Field stub:I
+ x: aload_0
+ x: iconst_2
+ x: putfield #x // Field keep:I
+ x: return
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 11 15 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestStub
+
+ public int addOne(int);
+ descriptor: (I)I
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=4, locals=2, args_size=2
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
+ x: ldc #x // String addOne
+ x: ldc #x // String (I)I
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: aload_0
+ x: iload_1
+ x: invokevirtual #x // Method addOneInner:(I)I
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 11 6 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
+ 11 6 1 value I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestStub
+
+ public int addOneInner(int);
+ descriptor: (I)I
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=4, locals=2, args_size=2
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
+ x: ldc #x // String addOneInner
+ x: ldc #x // String (I)I
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
+ x: ldc #x // String addOneInner
+ x: ldc #x // String (I)I
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+ x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+ x: iload_1
+ x: iconst_1
+ x: iadd
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 26 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
+ 26 4 1 value I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestKeep
+
+ public int addTwo(int);
+ descriptor: (I)I
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=4, locals=2, args_size=2
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
+ x: ldc #x // String addTwo
+ x: ldc #x // String (I)I
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: iload_1
+ x: iconst_2
+ x: iadd
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 11 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
+ 11 4 1 value I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public static int nativeAddThree(int);
+ descriptor: (I)I
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=4, locals=1, args_size=1
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
+ x: ldc #x // String nativeAddThree
+ x: ldc #x // String (I)I
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: iload_0
+ x: iconst_3
+ x: iadd
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 11 4 0 value I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public java.lang.String unsupportedMethod();
+ descriptor: ()Ljava/lang/String;
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=4, locals=1, args_size=1
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
+ x: ldc #x // String unsupportedMethod
+ x: ldc #x // String ()Ljava/lang/String;
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
+ x: ldc #x // String unsupportedMethod
+ x: ldc #x // String ()Ljava/lang/String;
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+ x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Unreachable
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsThrow
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestThrow
+
+ public java.lang.String visibleButUsesUnsupportedMethod();
+ descriptor: ()Ljava/lang/String;
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=4, locals=1, args_size=1
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
+ x: ldc #x // String visibleButUsesUnsupportedMethod
+ x: ldc #x // String ()Ljava/lang/String;
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: aload_0
+ x: invokevirtual #x // Method unsupportedMethod:()Ljava/lang/String;
+ x: areturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestStub
+}
+SourceFile: "TinyFrameworkAnnotations.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestStub
+ x: #x(#x=s#x)
+ android.hosttest.annotation.HostSideTestClassLoadHook(
+ value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
+ )
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
Compiled from "TinyFrameworkCallerCheck.java"
class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
@@ -803,522 +1062,6 @@
android.hosttest.annotation.HostSideTestWholeClassStub
NestMembers:
com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.class
- Compiled from "TinyFrameworkClassAnnotations.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations
- minor version: 0
- major version: 61
- flags: (0x0021) ACC_PUBLIC, ACC_SUPER
- this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
- super_class: #x // java/lang/Object
- interfaces: 0, fields: 2, methods: 8, attributes: 3
- public int stub;
- descriptor: I
- flags: (0x0001) ACC_PUBLIC
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
- RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestStub
-
- public int keep;
- descriptor: I
- flags: (0x0001) ACC_PUBLIC
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
- RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestKeep
-
- private static {};
- descriptor: ()V
- flags: (0x000a) ACC_PRIVATE, ACC_STATIC
- Code:
- stack=2, locals=0, args_size=0
- x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
- x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
- x: ldc #x // String com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
- x: return
-
- public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations();
- descriptor: ()V
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=4, locals=1, args_size=1
- x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
- x: ldc #x // String <init>
- x: ldc #x // String ()V
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
- x: aload_0
- x: invokespecial #x // Method java/lang/Object."<init>":()V
- x: aload_0
- x: iconst_1
- x: putfield #x // Field stub:I
- x: aload_0
- x: iconst_2
- x: putfield #x // Field keep:I
- x: return
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 11 15 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
- RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestStub
-
- public int addOne(int);
- descriptor: (I)I
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=4, locals=2, args_size=2
- x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
- x: ldc #x // String addOne
- x: ldc #x // String (I)I
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
- x: aload_0
- x: iload_1
- x: invokevirtual #x // Method addOneInner:(I)I
- x: ireturn
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 11 6 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
- 11 6 1 value I
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
- RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestStub
-
- public int addOneInner(int);
- descriptor: (I)I
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=4, locals=2, args_size=2
- x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
- x: ldc #x // String addOneInner
- x: ldc #x // String (I)I
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
- x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
- x: ldc #x // String addOneInner
- x: ldc #x // String (I)I
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
- x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
- x: iload_1
- x: iconst_1
- x: iadd
- x: ireturn
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 26 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
- 26 4 1 value I
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
- RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestKeep
-
- public int addTwo(int);
- descriptor: (I)I
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=4, locals=2, args_size=2
- x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
- x: ldc #x // String addTwo
- x: ldc #x // String (I)I
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
- x: iload_1
- x: iconst_2
- x: iadd
- x: ireturn
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 11 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
- 11 4 1 value I
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public static int nativeAddThree(int);
- descriptor: (I)I
- flags: (0x0009) ACC_PUBLIC, ACC_STATIC
- Code:
- stack=4, locals=1, args_size=1
- x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
- x: ldc #x // String nativeAddThree
- x: ldc #x // String (I)I
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
- x: iload_0
- x: iconst_3
- x: iadd
- x: ireturn
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 11 4 0 value I
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public java.lang.String unsupportedMethod();
- descriptor: ()Ljava/lang/String;
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=4, locals=1, args_size=1
- x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
- x: ldc #x // String unsupportedMethod
- x: ldc #x // String ()Ljava/lang/String;
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
- x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
- x: ldc #x // String unsupportedMethod
- x: ldc #x // String ()Ljava/lang/String;
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
- x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
- x: new #x // class java/lang/RuntimeException
- x: dup
- x: ldc #x // String Unreachable
- x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
- x: athrow
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenProcessedAsThrow
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
- RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestThrow
-
- public java.lang.String visibleButUsesUnsupportedMethod();
- descriptor: ()Ljava/lang/String;
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=4, locals=1, args_size=1
- x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
- x: ldc #x // String visibleButUsesUnsupportedMethod
- x: ldc #x // String ()Ljava/lang/String;
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
- x: aload_0
- x: invokevirtual #x // Method unsupportedMethod:()Ljava/lang/String;
- x: areturn
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
- RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestStub
-}
-SourceFile: "TinyFrameworkClassAnnotations.java"
-RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestStub
- x: #x(#x=s#x)
- android.hosttest.annotation.HostSideTestClassLoadHook(
- value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
- )
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations.class
- Compiled from "TinyFrameworkClassClassWideAnnotations.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassWideAnnotations
- minor version: 0
- major version: 61
- flags: (0x0021) ACC_PUBLIC, ACC_SUPER
- this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
- super_class: #x // java/lang/Object
- interfaces: 0, fields: 3, methods: 9, attributes: 3
- public int stub;
- descriptor: I
- flags: (0x0001) ACC_PUBLIC
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public int keep;
- descriptor: I
- flags: (0x0001) ACC_PUBLIC
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public int remove;
- descriptor: I
- flags: (0x0001) ACC_PUBLIC
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- private static {};
- descriptor: ()V
- flags: (0x000a) ACC_PRIVATE, ACC_STATIC
- Code:
- stack=2, locals=0, args_size=0
- x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
- x: return
-
- public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassWideAnnotations();
- descriptor: ()V
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=4, locals=1, args_size=1
- x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
- x: ldc #x // String <init>
- x: ldc #x // String ()V
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
- x: aload_0
- x: invokespecial #x // Method java/lang/Object."<init>":()V
- x: aload_0
- x: iconst_1
- x: putfield #x // Field stub:I
- x: aload_0
- x: iconst_2
- x: putfield #x // Field keep:I
- x: return
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 11 15 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public int addOne(int);
- descriptor: (I)I
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=4, locals=2, args_size=2
- x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
- x: ldc #x // String addOne
- x: ldc #x // String (I)I
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
- x: aload_0
- x: iload_1
- x: invokevirtual #x // Method addOneInner:(I)I
- x: ireturn
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 11 6 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
- 11 6 1 value I
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public int addOneInner(int);
- descriptor: (I)I
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=4, locals=2, args_size=2
- x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
- x: ldc #x // String addOneInner
- x: ldc #x // String (I)I
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
- x: iload_1
- x: iconst_1
- x: iadd
- x: ireturn
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 11 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
- 11 4 1 value I
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public void toBeRemoved(java.lang.String);
- descriptor: (Ljava/lang/String;)V
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=4, locals=2, args_size=2
- x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
- x: ldc #x // String toBeRemoved
- x: ldc #x // String (Ljava/lang/String;)V
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
- x: new #x // class java/lang/RuntimeException
- x: dup
- x: invokespecial #x // Method java/lang/RuntimeException."<init>":()V
- x: athrow
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 11 8 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
- 11 8 1 foo Ljava/lang/String;
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public int addTwo(int);
- descriptor: (I)I
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=4, locals=2, args_size=2
- x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
- x: ldc #x // String addTwo
- x: ldc #x // String (I)I
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
- x: iload_1
- x: iconst_2
- x: iadd
- x: ireturn
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 11 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
- 11 4 1 value I
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public static int nativeAddThree(int);
- descriptor: (I)I
- flags: (0x0009) ACC_PUBLIC, ACC_STATIC
- Code:
- stack=4, locals=1, args_size=1
- x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
- x: ldc #x // String nativeAddThree
- x: ldc #x // String (I)I
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
- x: iload_0
- x: iconst_3
- x: iadd
- x: ireturn
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 11 4 0 value I
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public java.lang.String unsupportedMethod();
- descriptor: ()Ljava/lang/String;
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=4, locals=1, args_size=1
- x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
- x: ldc #x // String unsupportedMethod
- x: ldc #x // String ()Ljava/lang/String;
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
- x: ldc #x // String This value shouldn\'t be seen on the host side.
- x: areturn
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 11 3 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
- public java.lang.String visibleButUsesUnsupportedMethod();
- descriptor: ()Ljava/lang/String;
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=4, locals=1, args_size=1
- x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
- x: ldc #x // String visibleButUsesUnsupportedMethod
- x: ldc #x // String ()Ljava/lang/String;
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
- x: aload_0
- x: invokevirtual #x // Method unsupportedMethod:()Ljava/lang/String;
- x: areturn
- LineNumberTable:
- LocalVariableTable:
- Start Length Slot Name Signature
- 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
- RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-SourceFile: "TinyFrameworkClassClassWideAnnotations.java"
-RuntimeVisibleAnnotations:
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
- x: #x()
- com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestWholeClassStub
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.class
Compiled from "TinyFrameworkClassLoadHook.java"
public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook
@@ -1424,6 +1167,175 @@
RuntimeInvisibleAnnotations:
x: #x()
android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations.class
+ Compiled from "TinyFrameworkClassWideAnnotations.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWideAnnotations
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 1, methods: 6, attributes: 3
+ public int stub;
+ descriptor: I
+ flags: (0x0001) ACC_PUBLIC
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ private static {};
+ descriptor: ()V
+ flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+ Code:
+ stack=2, locals=0, args_size=0
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+ x: return
+
+ public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWideAnnotations();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=4, locals=1, args_size=1
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
+ x: ldc #x // String <init>
+ x: ldc #x // String ()V
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: aload_0
+ x: invokespecial #x // Method java/lang/Object."<init>":()V
+ x: aload_0
+ x: iconst_1
+ x: putfield #x // Field stub:I
+ x: return
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 11 10 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public int addOne(int);
+ descriptor: (I)I
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=4, locals=2, args_size=2
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
+ x: ldc #x // String addOne
+ x: ldc #x // String (I)I
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: iload_1
+ x: iconst_1
+ x: iadd
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 11 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+ 11 4 1 value I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public int addTwo(int);
+ descriptor: (I)I
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=4, locals=2, args_size=2
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
+ x: ldc #x // String addTwo
+ x: ldc #x // String (I)I
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: iload_1
+ x: iconst_2
+ x: iadd
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 11 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+ 11 4 1 value I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public java.lang.String unsupportedMethod();
+ descriptor: ()Ljava/lang/String;
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=4, locals=1, args_size=1
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
+ x: ldc #x // String unsupportedMethod
+ x: ldc #x // String ()Ljava/lang/String;
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
+ x: ldc #x // String unsupportedMethod
+ x: ldc #x // String ()Ljava/lang/String;
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+ x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Unreachable
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsThrow
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestThrow
+
+ public java.lang.String visibleButUsesUnsupportedMethod();
+ descriptor: ()Ljava/lang/String;
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=4, locals=1, args_size=1
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
+ x: ldc #x // String visibleButUsesUnsupportedMethod
+ x: ldc #x // String ()Ljava/lang/String;
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: aload_0
+ x: invokevirtual #x // Method unsupportedMethod:()Ljava/lang/String;
+ x: areturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+SourceFile: "TinyFrameworkClassWideAnnotations.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestWholeClassStub
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault.class
Compiled from "TinyFrameworkClassWithInitializerDefault.java"
public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializerDefault
@@ -4280,7 +4192,7 @@
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
super_class: #x // java/lang/Object
- interfaces: 0, fields: 2, methods: 2, attributes: 5
+ interfaces: 0, fields: 2, methods: 2, attributes: 4
public int value;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -4350,9 +4262,6 @@
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestWholeClassStub
NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1.class
Compiled from "TinyFrameworkNestedClasses.java"
@@ -4458,6 +4367,70 @@
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass.class
+ Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 1, methods: 2, attributes: 4
+ public int value;
+ descriptor: I
+ flags: (0x0001) ACC_PUBLIC
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ private static {};
+ descriptor: ()V
+ flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+ Code:
+ stack=2, locals=0, args_size=0
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+ x: return
+
+ public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=4, locals=1, args_size=1
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
+ x: ldc #x // String <init>
+ x: ldc #x // String ()V
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: aload_0
+ x: invokespecial #x // Method java/lang/Object."<init>":()V
+ x: aload_0
+ x: bipush 8
+ x: putfield #x // Field value:I
+ x: return
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 11 11 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass;
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+InnerClasses:
+ public static #x= #x of #x; // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+ public static #x= #x of #x; // Double$NestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass.class
Compiled from "TinyFrameworkNestedClasses.java"
public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass
@@ -4466,7 +4439,7 @@
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
super_class: #x // java/lang/Object
- interfaces: 0, fields: 1, methods: 3, attributes: 5
+ interfaces: 0, fields: 1, methods: 3, attributes: 4
public int value;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -4537,15 +4510,13 @@
InnerClasses:
public static #x= #x of #x; // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
#x; // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+ public static #x= #x of #x; // Double$NestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
SourceFile: "TinyFrameworkNestedClasses.java"
RuntimeVisibleAnnotations:
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
- x: #x()
- android.hosttest.annotation.HostSideTestWholeClassStub
NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass.class
Compiled from "TinyFrameworkNestedClasses.java"
@@ -4741,6 +4712,7 @@
public static #x= #x of #x; // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
public static #x= #x of #x; // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
public #x= #x of #x; // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+ public static #x= #x of #x; // Double$NestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
#x; // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
SourceFile: "TinyFrameworkNestedClasses.java"
RuntimeVisibleAnnotations:
@@ -4755,6 +4727,7 @@
com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+ com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.java
similarity index 96%
rename from tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.java
rename to tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.java
index 6d8a48a..30dfc80 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.java
@@ -28,9 +28,9 @@
@HostSideTestStub
@HostSideTestClassLoadHook(
"com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded")
-public class TinyFrameworkClassAnnotations {
+public class TinyFrameworkAnnotations {
@HostSideTestStub
- public TinyFrameworkClassAnnotations() {
+ public TinyFrameworkAnnotations() {
}
@HostSideTestStub
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations.java
similarity index 64%
rename from tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations.java
rename to tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations.java
index 145b65a..a626bc9 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations.java
@@ -15,38 +15,21 @@
*/
package com.android.hoststubgen.test.tinyframework;
-import android.hosttest.annotation.HostSideTestStub;
import android.hosttest.annotation.HostSideTestSubstitute;
+import android.hosttest.annotation.HostSideTestThrow;
import android.hosttest.annotation.HostSideTestWholeClassStub;
@HostSideTestWholeClassStub
-public class TinyFrameworkClassClassWideAnnotations {
- public TinyFrameworkClassClassWideAnnotations() {
+public class TinyFrameworkClassWideAnnotations {
+ public TinyFrameworkClassWideAnnotations() {
}
public int stub = 1;
- public int keep = 2;
-
- // Cannot have an initial value, because otherwise .ctor will fail to set it at runtime.
- public int remove;
-
- // @Stub
public int addOne(int value) {
- return addOneInner(value);
- }
-
- // @Keep
- public int addOneInner(int value) {
return value + 1;
}
- // @Remove
- public void toBeRemoved(String foo) {
- throw new RuntimeException();
- }
-
- @HostSideTestStub
@HostSideTestSubstitute(suffix = "_host")
public int addTwo(int value) {
throw new RuntimeException("not supported on host side");
@@ -56,14 +39,7 @@
return value + 2;
}
- @HostSideTestStub
- @HostSideTestSubstitute(suffix = "_host")
- public static native int nativeAddThree(int value);
-
- public static int nativeAddThree_host(int value) {
- return value + 3;
- }
-
+ @HostSideTestThrow
public String unsupportedMethod() {
return "This value shouldn't be seen on the host side.";
}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses.java
index e1c48bb..fec307a 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses.java
@@ -34,6 +34,7 @@
return 2;
}
};
+
public Supplier<Integer> getSupplier() {
return new Supplier<Integer>() {
@Override
@@ -52,12 +53,10 @@
};
}
- @HostSideTestWholeClassStub
public class InnerClass {
public int value = 5;
}
- @HostSideTestWholeClassStub
public static class StaticNestedClass {
public int value = 6;
@@ -70,6 +69,10 @@
}
};
}
+
+ public static class Double$NestedClass {
+ public int value = 8;
+ }
}
public static class BaseClass {
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithAnnotTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotationsTest.java
similarity index 79%
rename from tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithAnnotTest.java
rename to tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotationsTest.java
index 288c716..181902a 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithAnnotTest.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotationsTest.java
@@ -21,20 +21,20 @@
import org.junit.Test;
import org.junit.rules.ExpectedException;
-public class TinyFrameworkClassWithAnnotTest {
+public class TinyFrameworkAnnotationsTest {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void testSimple() {
- TinyFrameworkClassAnnotations tfc = new TinyFrameworkClassAnnotations();
+ TinyFrameworkAnnotations tfc = new TinyFrameworkAnnotations();
assertThat(tfc.addOne(1)).isEqualTo(2);
assertThat(tfc.stub).isEqualTo(1);
}
// @Test
// public void testDoesntCompile() {
-// TinyFrameworkClassWithAnnot tfc = new TinyFrameworkClassWithAnnot();
+// TinyFrameworkAnnotations tfc = new TinyFrameworkAnnotations();
//
// tfc.addOneInner(1); // Shouldn't compile.
// tfc.toBeRemoved("abc"); // Shouldn't compile.
@@ -45,19 +45,19 @@
@Test
public void testSubstitute() {
- TinyFrameworkClassAnnotations tfc = new TinyFrameworkClassAnnotations();
+ TinyFrameworkAnnotations tfc = new TinyFrameworkAnnotations();
assertThat(tfc.addTwo(1)).isEqualTo(3);
}
@Test
public void testSubstituteNative() {
- TinyFrameworkClassAnnotations tfc = new TinyFrameworkClassAnnotations();
+ TinyFrameworkAnnotations tfc = new TinyFrameworkAnnotations();
assertThat(tfc.nativeAddThree(1)).isEqualTo(4);
}
@Test
public void testVisibleButUsesUnsupportedMethod() {
- TinyFrameworkClassAnnotations tfc = new TinyFrameworkClassAnnotations();
+ TinyFrameworkAnnotations tfc = new TinyFrameworkAnnotations();
thrown.expect(RuntimeException.class);
thrown.expectMessage("not yet supported");
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
index 1692c6e89..dda5a05 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
@@ -20,7 +20,6 @@
import static org.junit.Assert.fail;
import com.android.hoststubgen.test.tinyframework.R.Nested;
-import com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses.SubClass;
import org.junit.Rule;
import org.junit.Test;
@@ -88,42 +87,6 @@
}
@Test
- public void testNestedClass1() {
- assertThat(new TinyFrameworkNestedClasses().mSupplier.get()).isEqualTo(1);
- }
-
- @Test
- public void testNestedClass2() {
- assertThat(TinyFrameworkNestedClasses.sSupplier.get()).isEqualTo(2);
- }
-
- @Test
- public void testNestedClass3() {
- assertThat(new TinyFrameworkNestedClasses().getSupplier().get()).isEqualTo(3);
- }
-
- @Test
- public void testNestedClass4() {
- assertThat(TinyFrameworkNestedClasses.getSupplier_static().get()).isEqualTo(4);
- }
-
- @Test
- public void testNestedClass5() {
- assertThat((new TinyFrameworkNestedClasses()).new InnerClass().value).isEqualTo(5);
- }
-
- @Test
- public void testNestedClass6() {
- assertThat(new TinyFrameworkNestedClasses.StaticNestedClass().value).isEqualTo(6);
- }
-
- @Test
- public void testNestedClass7() {
- assertThat(TinyFrameworkNestedClasses.StaticNestedClass.getSupplier_static().get())
- .isEqualTo(7);
- }
-
- @Test
public void testLambda1() {
assertThat(new TinyFrameworkLambdas().mSupplier.get()).isEqualTo(1);
}
@@ -220,18 +183,13 @@
}
@Test
- public void testMethodCallBeforeSuperCall() {
- assertThat(new SubClass(3).value).isEqualTo(3);
- }
-
- @Test
public void testClassLoadHook() {
assertThat(TinyFrameworkClassWithInitializerStub.sInitialized).isTrue();
// Having this line before assertThat() will ensure these class are already loaded.
var classes = new Class[]{
TinyFrameworkClassWithInitializerStub.class,
- TinyFrameworkClassAnnotations.class,
+ TinyFrameworkAnnotations.class,
TinyFrameworkForTextPolicy.class,
};
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotationsTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotationsTest.java
new file mode 100644
index 0000000..83753b5
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotationsTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.hoststubgen.test.tinyframework;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class TinyFrameworkClassWideAnnotationsTest {
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void testSimple() {
+ var tfc = new TinyFrameworkClassWideAnnotations();
+ assertThat(tfc.addOne(1)).isEqualTo(2);
+ assertThat(tfc.stub).isEqualTo(1);
+ }
+
+ @Test
+ public void testSubstitute() {
+ var tfc = new TinyFrameworkClassWideAnnotations();
+ assertThat(tfc.addTwo(1)).isEqualTo(3);
+ }
+
+ @Test
+ public void testVisibleButUsesUnsupportedMethod() {
+ var tfc = new TinyFrameworkClassWideAnnotations();
+
+ thrown.expect(RuntimeException.class);
+ thrown.expectMessage("not yet supported");
+ tfc.visibleButUsesUnsupportedMethod();
+ }
+
+ @Test
+ public void testMethodCallBeforeSuperCall() {
+ assertThat(new TinyFrameworkNestedClasses.SubClass(3).value).isEqualTo(3);
+ }
+
+ @Test
+ public void testNestedClass1() {
+ assertThat(new TinyFrameworkNestedClasses().mSupplier.get()).isEqualTo(1);
+ }
+
+ @Test
+ public void testNestedClass2() {
+ assertThat(TinyFrameworkNestedClasses.sSupplier.get()).isEqualTo(2);
+ }
+
+ @Test
+ public void testNestedClass3() {
+ assertThat(new TinyFrameworkNestedClasses().getSupplier().get()).isEqualTo(3);
+ }
+
+ @Test
+ public void testNestedClass4() {
+ assertThat(TinyFrameworkNestedClasses.getSupplier_static().get()).isEqualTo(4);
+ }
+
+ @Test
+ public void testNestedClass5() {
+ assertThat((new TinyFrameworkNestedClasses()).new InnerClass().value).isEqualTo(5);
+ }
+
+ @Test
+ public void testNestedClass6() {
+ assertThat(new TinyFrameworkNestedClasses.StaticNestedClass().value).isEqualTo(6);
+ }
+
+ @Test
+ public void testNestedClass7() {
+ assertThat(TinyFrameworkNestedClasses.StaticNestedClass.getSupplier_static().get())
+ .isEqualTo(7);
+ }
+
+ @Test
+ public void testNestedClass8() {
+ assertThat(new TinyFrameworkNestedClasses.StaticNestedClass.Double$NestedClass().value)
+ .isEqualTo(8);
+ }
+}
diff --git a/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/asm/AsmUtilsTest.kt b/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/asm/AsmUtilsTest.kt
index 6b46c84..5b2795c 100644
--- a/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/asm/AsmUtilsTest.kt
+++ b/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/asm/AsmUtilsTest.kt
@@ -23,18 +23,6 @@
import org.objectweb.asm.Opcodes.ACC_STATIC
class AsmUtilsTest {
- private fun checkGetDirectOuterClassName(input: String, expected: String?) {
- assertThat(getDirectOuterClassName(input)).isEqualTo(expected)
- }
-
- @Test
- fun testGetDirectOuterClassName() {
- checkGetDirectOuterClassName("a", null)
- checkGetDirectOuterClassName("a\$x", "a")
- checkGetDirectOuterClassName("a.b.c\$x", "a.b.c")
- checkGetDirectOuterClassName("a.b.c\$x\$y", "a.b.c\$x")
- }
-
@Test
fun testVisibility() {
fun test(access: Int, expected: Visibility) {